diff --git a/.editorconfig b/.editorconfig
index ff6d9f3bd7..e07baba61b 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -46,6 +46,9 @@ csharp_style_var_for_built_in_types = false:none
csharp_style_var_when_type_is_apparent = false:none
csharp_style_var_elsewhere = false:suggestion
+# don't prefer the range operator, netfx doesn't have these types
+csharp_style_prefer_range_operator = false
+
# use language keywords instead of BCL types
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
index 000941d00d..beeb753867 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
@@ -99,6 +99,12 @@
Microsoft\Data\ProviderBase\DbConnectionClosed.cs
+
+ Microsoft\Data\SqlClient\AlwaysEncrypted\ColumnMasterKeyMetadata.cs
+
+
+ Microsoft\Data\SqlClient\AlwaysEncrypted\EncryptedColumnEncryptionKeyParameters.cs
+
Microsoft\Data\SqlClient\ConnectionPool\ChannelDbConnectionPool.cs
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
index 91b8345215..25fb57b113 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
@@ -288,6 +288,12 @@
Microsoft\Data\ProviderBase\DbConnectionInternal.cs
+
+ Microsoft\Data\SqlClient\AlwaysEncrypted\ColumnMasterKeyMetadata.cs
+
+
+ Microsoft\Data\SqlClient\AlwaysEncrypted\EncryptedColumnEncryptionKeyParameters.cs
+
Microsoft\Data\SqlClient\ConnectionPool\ChannelDbConnectionPool.cs
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/ColumnMasterKeyMetadata.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/ColumnMasterKeyMetadata.cs
new file mode 100644
index 0000000000..32663eb1a1
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/ColumnMasterKeyMetadata.cs
@@ -0,0 +1,162 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography;
+using System.Text;
+
+#nullable enable
+
+namespace Microsoft.Data.SqlClient.AlwaysEncrypted;
+
+///
+/// Represents metadata about the column master key, to be signed or verified by an enclave.
+///
+///
+/// This metadata is a lower-case string which is laid out in the following format:
+///
+///
+/// Provider name. This is always .
+///
+///
+/// Master key path. This will be in the format [LocalMachine|CurrentUser]/My/[SHA1 thumbprint].
+///
+///
+/// Boolean to indicate whether the CMK supports enclave computations. This is either true or false.
+///
+///
+///
+/// This takes ownership of the RSA instance supplied to it, disposing of it when Dispose is called.
+///
+///
+internal readonly ref struct ColumnMasterKeyMetadata // : IDisposable
+{
+ private static readonly HashAlgorithmName s_hashAlgorithm = HashAlgorithmName.SHA256;
+
+#if NET
+ [InlineArray(SHA256.HashSizeInBytes)]
+ private struct Sha256Hash
+ {
+ private byte _elementTemplate;
+ }
+
+ private readonly Sha256Hash _hash;
+#else
+ private readonly byte[] _hash;
+#endif
+ private readonly RSA _rsa;
+
+ // @TODO: SqlColumnEncryptionCertificateStoreProvider.SignMasterKeyMetadata and .VerifyMasterKeyMetadata should use this type.
+ ///
+ /// Represents metadata associated with a column master key, including its cryptographic hash, path, provider name,
+ /// and enclave computation settings.
+ ///
+ ///
+ /// This struct is used to encapsulate the metadata required for signing or verifying a column master key. The metadata includes
+ /// the provider name, the master key path, and whether enclave computations are allowed. The metadata is hashed using SHA-256
+ /// to ensure integrity.
+ ///
+ /// The RSA cryptographic provider used for signing or verifying the metadata.
+ /// The path to the column master key. This must be a valid path in one of the following formats:
+ ///
+ /// [LocalMachine|CurrentUser]/My/[40-character SHA1 thumbprint]
+ /// My/[40-character SHA1 thumbprint]
+ /// [40-character SHA1 thumbprint]
+ ///
+ /// The path is case-insensitive and will be converted to lowercase for processing.
+ /// The name of the provider associated with the column master key.
+ /// A value indicating whether enclave computations are allowed for this column master key.
+ public ColumnMasterKeyMetadata(RSA rsa, string masterKeyPath, string providerName, bool allowEnclaveComputations)
+ {
+ // Lay the column master key metadata out in memory. Then, calculate the hash of this metadata ready for signature or verification.
+ // .NET Core supports Spans in more places, allowing us to allocate on the stack for better performance. It also supports the
+ // SHA256.HashData method, which saves allocations compared to instantiating a SHA256 object and calling TransformFinalBlock.
+
+ // By this point, we know that we have a valid certificate, so the path is valid. The longest valid masterKeyPath is in one of the formats:
+ // * [LocalMachine|CurrentUser]/My/[40 character SHA1 thumbprint]
+ // * My/[40 character SHA1 thumbprint]
+ // * [40 character SHA1 thumbprint]
+ // ProviderName is a constant string of length 23 characters, and allowEnclaveComputations' longest value is 5 characters long. This
+ // implies a maximum length of 84 characters for the masterKeyMetadata string - and by extension, 168 bytes for the Unicode-encoded
+ // byte array. This is small enough to allocate on the stack, but we fall back to allocating a new char/byte array in case those assumptions fail.
+ // It also implies that when masterKeyPath is converted to its invariant lowercase value, it will be the same length (because it's
+ // an ASCII string.)
+ Debug.Assert(masterKeyPath.Length == masterKeyPath.ToLowerInvariant().Length);
+
+ ReadOnlySpan enclaveComputationSpan = (allowEnclaveComputations ? bool.TrueString : bool.FalseString).AsSpan();
+ int masterKeyMetadataLength = providerName.Length + masterKeyPath.Length + enclaveComputationSpan.Length;
+ int byteCount;
+
+#if NET
+ const int CharStackAllocationThreshold = 128;
+ const int ByteStackAllocationThreshold = CharStackAllocationThreshold * sizeof(char);
+
+ Span masterKeyMetadata = masterKeyMetadataLength <= CharStackAllocationThreshold
+ ? stackalloc char[CharStackAllocationThreshold].Slice(0, masterKeyMetadataLength)
+ : new char[masterKeyMetadataLength];
+ Span masterKeyMetadataSpan = masterKeyMetadata;
+#else
+ char[] masterKeyMetadata = new char[masterKeyMetadataLength];
+ Span masterKeyMetadataSpan = masterKeyMetadata.AsSpan();
+#endif
+
+ providerName.AsSpan().ToLowerInvariant(masterKeyMetadataSpan);
+ masterKeyPath.AsSpan().ToLowerInvariant(masterKeyMetadataSpan.Slice(providerName.Length));
+ enclaveComputationSpan.ToLowerInvariant(masterKeyMetadataSpan.Slice(providerName.Length + masterKeyPath.Length));
+ byteCount = Encoding.Unicode.GetByteCount(masterKeyMetadata);
+
+#if NET
+ Span masterKeyMetadataBytes = byteCount <= ByteStackAllocationThreshold
+ ? stackalloc byte[ByteStackAllocationThreshold].Slice(0, byteCount)
+ : new byte[byteCount];
+
+ Encoding.Unicode.GetBytes(masterKeyMetadata, masterKeyMetadataBytes);
+
+ // Compute hash
+ SHA256.HashData(masterKeyMetadataBytes, _hash);
+#else
+ byte[] masterKeyMetadataBytes = Encoding.Unicode.GetBytes(masterKeyMetadata);
+ using (SHA256 sha256 = SHA256.Create())
+ {
+ // Compute hash
+ sha256.TransformFinalBlock(masterKeyMetadataBytes, 0, masterKeyMetadataBytes.Length);
+ _hash = sha256.Hash;
+ }
+#endif
+
+ _rsa = rsa;
+ }
+
+ ///
+ /// Signs the current master key metadata using the RSA key associated with this instance.
+ ///
+ ///
+ /// A byte array containing the digital signature of the master key metadata.
+ ///
+ /// Thrown when the signing operation fails.
+ public byte[] Sign() =>
+ _rsa.SignHash(_hash, s_hashAlgorithm, RSASignaturePadding.Pkcs1);
+
+ ///
+ /// Verifies the specified master key metadata signature against the computed hash using the RSA key associated with this instance.
+ ///
+ /// The digital signature to verify. This must be a valid signature generated by .
+ ///
+ /// if the signature is valid and matches the computed hash; otherwise, .
+ ///
+ /// Thrown when is ."
+ public bool Verify(byte[] signature) =>
+ _rsa.VerifyHash(_hash, signature, s_hashAlgorithm, RSASignaturePadding.Pkcs1);
+
+ ///
+ /// Releases all resources used by this .
+ ///
+ ///
+ /// This method disposes the instance used to construct this instance.
+ ///
+ public void Dispose() =>
+ _rsa.Dispose();
+}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptedColumnEncryptionKeyParameters.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptedColumnEncryptionKeyParameters.cs
new file mode 100644
index 0000000000..4abfa62abd
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptedColumnEncryptionKeyParameters.cs
@@ -0,0 +1,272 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Buffers.Binary;
+using System.Diagnostics;
+using System.Security.Cryptography;
+using System.Text;
+
+#nullable enable
+
+namespace Microsoft.Data.SqlClient.AlwaysEncrypted;
+
+///
+/// Represents the parameters used to construct an encrypted column encryption key, used to encrypt and decrypt data in SQL Server Always Encrypted columns.
+///
+///
+/// An encrypted CEK is a byte array that contains the following structure:
+///
+///
+/// Version: 1 byte, always 0x01
+///
+///
+/// Key path length: 2 bytes, length of the key path in bytes. Written in little-endian byte order.
+///
+///
+/// Ciphertext length: 2 bytes, length of the ciphertext in bytes. Written in little-endian byte order.
+///
+///
+/// Key path: variable length, string representing the key path. Encoded with UTF-16.
+///
+///
+/// Ciphertext: variable length, encrypted data. Length determined by size of the RSA key used for encryption
+///
+///
+/// Signature: variable length, digital signature of the encrypted CEK's SHA256 hash. Length determined by the size of the RSA key used for signing
+///
+///
+///
+/// This takes ownership of the RSA instance supplied to it, disposing of it when Dispose is called.
+///
+///
+internal readonly ref struct EncryptedColumnEncryptionKeyParameters // : IDisposable
+{
+ private const byte AlgorithmVersion = 0x01;
+
+ private const int AlgorithmOffset = 0;
+ private const int KeyPathLengthOffset = AlgorithmOffset + sizeof(byte);
+ private const int CiphertextLengthOffset = KeyPathLengthOffset + sizeof(ushort);
+ private const int KeyPathOffset = CiphertextLengthOffset + sizeof(ushort);
+
+#if NET
+ private const int HashSize = SHA256.HashSizeInBytes;
+#else
+ private const int HashSize = 32;
+#endif
+
+ private static readonly HashAlgorithmName s_hashAlgorithm = HashAlgorithmName.SHA256;
+
+ private readonly RSA _rsa;
+ private readonly int _rsaKeySize;
+ private readonly string _keyPath;
+ private readonly string _keyType;
+ private readonly string _keyPathReference;
+
+ // @TODO: SqlColumnEncryptionCertificateStoreProvider, SqlColumnEncryptionCngProvider and SqlColumnEncryptionCspProvider should use this type.
+ ///
+ /// Initializes a new instance of the struct with the specified
+ /// RSA key, key path, key type, and key path reference.
+ ///
+ ///
+ /// This constructor is used to initialize the parameters required for encrypting a column encryption key. The
+ /// and must correspond to the supported types and
+ /// references defined by the specific encryption provider being used.
+ ///
+ /// The object representing the RSA key used for encryption.
+ /// The path initially used to locate .
+ /// The type of the encryption key. This must be one of the supported key types, such
+ /// as , ,
+ /// or .
+ /// The type of object which contains the RSA key referenced by the
+ /// parameter. This must be one of the supported object types, such as ,
+ /// , or .
+ public EncryptedColumnEncryptionKeyParameters(RSA rsa, string keyPath, string keyType, string keyPathReference)
+ {
+ _rsa = rsa;
+ _rsaKeySize = rsa.KeySize / 8;
+ _keyPath = keyPath;
+
+ Debug.Assert(keyType is SqlColumnEncryptionCertificateStoreProvider.MasterKeyType
+ or SqlColumnEncryptionCngProvider.MasterKeyType or SqlColumnEncryptionCspProvider.MasterKeyType);
+ Debug.Assert(keyPathReference is SqlColumnEncryptionCertificateStoreProvider.KeyPathReference
+ or SqlColumnEncryptionCngProvider.KeyPathReference or SqlColumnEncryptionCspProvider.KeyPathReference);
+ _keyType = keyType;
+ _keyPathReference = keyPathReference;
+ }
+
+ ///
+ /// Encrypts the specified column encryption key using the RSA key associated with this instance.
+ ///
+ /// The plaintext column encryption key to encrypt.
+ ///
+ /// The encrypted column encryption key, including metadata such as the key path, ciphertext, and a digital signature
+ /// for integrity verification.
+ ///
+ /// Thrown when is null.
+ /// Thrown when is longer than the RSA
+ /// key size, when an error occurs encrypting the column encryption key, or when signing the encrypted column
+ /// encryption key fails.
+ public byte[] Encrypt(byte[] columnEncryptionKey)
+ {
+ ushort keyPathSize = (ushort)Encoding.Unicode.GetByteCount(_keyPath);
+ // The signature size is always the same as the RSA key size
+ int signatureSize = _rsaKeySize;
+ int cekSize = sizeof(byte) + sizeof(ushort) + sizeof(ushort) + keyPathSize + _rsaKeySize + signatureSize;
+ byte[] encryptedColumnEncryptionKey = new byte[cekSize];
+ int bytesWritten;
+ int cipherTextOffset = KeyPathOffset + keyPathSize;
+ int signatureOffset = cipherTextOffset + _rsaKeySize;
+
+ // We currently only support one version
+ encryptedColumnEncryptionKey[AlgorithmOffset] = AlgorithmVersion;
+
+ // Write the key path length and the ciphertext length
+ BinaryPrimitives.WriteUInt16LittleEndian(encryptedColumnEncryptionKey.AsSpan(KeyPathLengthOffset), keyPathSize);
+ BinaryPrimitives.WriteUInt16LittleEndian(encryptedColumnEncryptionKey.AsSpan(CiphertextLengthOffset), (ushort)_rsaKeySize);
+
+ // Write the unicode encoded bytes of the key path
+ bytesWritten = Encoding.Unicode.GetBytes(_keyPath, 0, _keyPath.Length, encryptedColumnEncryptionKey, KeyPathOffset);
+ Debug.Assert(bytesWritten == keyPathSize, @"Key path length does not match the expected length.");
+
+ // Encrypt the column encryption key using RSA with OAEP padding.
+ // In .NET Core, we can encrypt directly into the byte array, while in .NET Framework we need to allocate an intermediary and copy.
+#if NET
+ // CodeQL [SM03796] Required for an external standard: Always Encrypted only supports encrypting column encryption keys with RSA_OAEP(SHA1) (https://learn.microsoft.com/en-us/sql/t-sql/statements/create-column-encryption-key-transact-sql?view=sql-server-ver16)
+ bytesWritten = _rsa.Encrypt(columnEncryptionKey, encryptedColumnEncryptionKey.AsSpan(cipherTextOffset), RSAEncryptionPadding.OaepSHA1);
+#else
+ // CodeQL [SM03796] Required for an external standard: Always Encrypted only supports encrypting column encryption keys with RSA_OAEP(SHA1) (https://learn.microsoft.com/en-us/sql/t-sql/statements/create-column-encryption-key-transact-sql?view=sql-server-ver16)
+ byte[] cipherText = _rsa.Encrypt(columnEncryptionKey, RSAEncryptionPadding.OaepSHA1);
+ bytesWritten = cipherText.Length;
+
+ Buffer.BlockCopy(cipherText, 0, encryptedColumnEncryptionKey, cipherTextOffset, bytesWritten);
+#endif
+ Debug.Assert(bytesWritten == _rsaKeySize, @"Ciphertext length does not match the RSA key size.");
+
+ // Compute the SHA256 hash of the encrypted CEK, (up to this point) then sign it and write the signature
+ // In .NET Core, we can use a stack-allocated span for the hash, while in .NET Framework we need to allocate a byte array.
+#if NET
+ Span hash = stackalloc byte[HashSize];
+ bytesWritten = SHA256.HashData(encryptedColumnEncryptionKey.AsSpan(0, signatureOffset), hash);
+ Debug.Assert(bytesWritten == HashSize, @"Hash size does not match the expected size.");
+
+ bytesWritten = _keyType == SqlColumnEncryptionCertificateStoreProvider.MasterKeyType
+ ? _rsa.SignHash(hash, encryptedColumnEncryptionKey.AsSpan(signatureOffset), s_hashAlgorithm, RSASignaturePadding.Pkcs1)
+ : _rsa.SignData(hash, encryptedColumnEncryptionKey.AsSpan(signatureOffset), s_hashAlgorithm, RSASignaturePadding.Pkcs1);
+ Debug.Assert(bytesWritten == _rsaKeySize, @"Signature length does not match the RSA key size.");
+
+#else
+ byte[] hash;
+ using (SHA256 sha256 = SHA256.Create())
+ {
+ sha256.TransformFinalBlock(encryptedColumnEncryptionKey, 0, signatureOffset);
+ hash = sha256.Hash;
+ }
+ bytesWritten = hash.Length;
+ Debug.Assert(bytesWritten == HashSize, @"Hash size does not match the expected size.");
+
+ byte[] signedHash = _keyType == SqlColumnEncryptionCertificateStoreProvider.MasterKeyType
+ ? _rsa.SignHash(hash, s_hashAlgorithm, RSASignaturePadding.Pkcs1)
+ : _rsa.SignData(hash, s_hashAlgorithm, RSASignaturePadding.Pkcs1);
+ bytesWritten = signedHash.Length;
+ Debug.Assert(bytesWritten == _rsaKeySize, @"Signature length does not match the RSA key size.");
+
+ Buffer.BlockCopy(signedHash, 0, encryptedColumnEncryptionKey, signatureOffset, bytesWritten);
+#endif
+
+ return encryptedColumnEncryptionKey;
+ }
+
+ ///
+ /// Decrypts an encrypted column encryption key (CEK) using the RSA key associated with this instance.
+ ///
+ ///
+ /// This method validates the algorithm version, ciphertext length, and signature length before
+ /// decrypting the CEK. It also verifies the integrity of the encrypted CEK using the provided signature.
+ ///
+ /// A byte array containing the encrypted column encryption key. The array must include
+ /// the algorithm version, key path length, ciphertext, and signature.
+ ///
+ /// The decrypted column encryption key.
+ ///
+ /// Thrown when is null.
+ /// Thrown when the contents of are malformed or its signature fails to verify.
+ /// Thrown when decryption fails.
+ public byte[] Decrypt(byte[] encryptedCek)
+ {
+ // Validate the version byte
+ if (encryptedCek[AlgorithmOffset] != AlgorithmVersion)
+ {
+ throw SQL.InvalidAlgorithmVersionInEncryptedCEK(encryptedCek[AlgorithmOffset], AlgorithmVersion);
+ }
+
+ // Get key path length, but skip reading it. It exists only for troubleshooting purposes and doesn't need validation.
+ ushort keyPathLength = BinaryPrimitives.ReadUInt16LittleEndian(encryptedCek.AsSpan(KeyPathLengthOffset));
+
+ // Get ciphertext length, then validate it against the RSA key size
+ ushort cipherTextLength = BinaryPrimitives.ReadUInt16LittleEndian(encryptedCek.AsSpan(CiphertextLengthOffset));
+
+ if (cipherTextLength != _rsaKeySize)
+ {
+ throw SQL.InvalidCiphertextLengthInEncryptedCEK(_keyType, _keyPathReference, cipherTextLength, _rsaKeySize, _keyPath);
+ }
+
+ // Validate the signature length
+ int cipherTextOffset = KeyPathOffset + keyPathLength;
+ int signatureOffset = cipherTextOffset + cipherTextLength;
+ int signatureLength = encryptedCek.Length - signatureOffset;
+
+ if (signatureLength != _rsaKeySize)
+ {
+ throw SQL.InvalidSignatureInEncryptedCEK(_keyType, _keyPathReference, signatureLength, _rsaKeySize, _keyPath);
+ }
+
+ // Get the ciphertext and signature, then calculate the hash of the encrypted CEK.
+ // In .NET Core most of these operations can be done with spans, while in .NET Framework we need to allocate byte arrays.
+#if NET
+ Span cipherText = encryptedCek.AsSpan(cipherTextOffset, cipherTextLength);
+ Span signature = encryptedCek.AsSpan(signatureOffset);
+
+ Span hash = stackalloc byte[HashSize];
+ SHA256.HashData(encryptedCek.AsSpan(0, signatureOffset), hash);
+#else
+ byte[] cipherText = new byte[cipherTextLength];
+ Buffer.BlockCopy(encryptedCek, cipherTextOffset, cipherText, 0, cipherText.Length);
+
+ byte[] signature = new byte[signatureLength];
+ Buffer.BlockCopy(encryptedCek, signatureOffset, signature, 0, signature.Length);
+
+ byte[] hash;
+ using (SHA256 sha256 = SHA256.Create())
+ {
+ sha256.TransformFinalBlock(encryptedCek, 0, signatureOffset);
+ hash = sha256.Hash;
+ }
+ Debug.Assert(hash.Length == HashSize, @"hash length should be same as the signature length while decrypting encrypted column encryption key.");
+#endif
+
+ bool dataVerified = _keyType == SqlColumnEncryptionCertificateStoreProvider.MasterKeyType
+ ? _rsa.VerifyHash(hash, signature, s_hashAlgorithm, RSASignaturePadding.Pkcs1)
+ : _rsa.VerifyData(hash, signature, s_hashAlgorithm, RSASignaturePadding.Pkcs1);
+
+ // Validate the signature
+ if (!dataVerified)
+ {
+ throw SQL.InvalidSignature(_keyPath, _keyType);
+ }
+
+ // Decrypt the CEK
+ // CodeQL [SM03796] Required for an external standard: Always Encrypted only supports encrypting column encryption keys with RSA_OAEP(SHA1) (https://learn.microsoft.com/en-us/sql/t-sql/statements/create-column-encryption-key-transact-sql?view=sql-server-ver16)
+ return _rsa.Decrypt(cipherText, RSAEncryptionPadding.OaepSHA1);
+ }
+
+ ///
+ /// Releases all resources used by this .
+ ///
+ ///
+ /// This method disposes the instance used to construct this instance.
+ ///
+ public void Dispose() =>
+ _rsa.Dispose();
+}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs
index 73e8584b20..09bcd852dd 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs
@@ -21,6 +21,16 @@ public class SqlColumnEncryptionCertificateStoreProvider : SqlColumnEncryptionKe
///
public const string ProviderName = @"MSSQL_CERTIFICATE_STORE";
+ ///
+ /// This encryption keystore uses a certificate as the column master key.
+ ///
+ internal const string MasterKeyType = @"certificate";
+
+ ///
+ /// This encryption keystore uses the master key path to reference a specific certificate.
+ ///
+ internal const string KeyPathReference = @"certificate";
+
///
/// RSA_OAEP is the only algorithm supported for encrypting/decrypting column encryption keys.
///
@@ -107,7 +117,7 @@ public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string e
// validate the ciphertext length
if (cipherTextLength != keySizeInBytes)
{
- throw SQL.InvalidCiphertextLengthInEncryptedCEK(cipherTextLength, keySizeInBytes, masterKeyPath);
+ throw SQL.InvalidCiphertextLengthInEncryptedCEKCertificate(cipherTextLength, keySizeInBytes, masterKeyPath);
}
// Validate the signature length
@@ -115,7 +125,7 @@ public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string e
int signatureLength = encryptedColumnEncryptionKey.Length - currentIndex - cipherTextLength;
if (signatureLength != keySizeInBytes)
{
- throw SQL.InvalidSignatureInEncryptedCEK(signatureLength, keySizeInBytes, masterKeyPath);
+ throw SQL.InvalidSignatureInEncryptedCEKCertificate(signatureLength, keySizeInBytes, masterKeyPath);
}
// Get ciphertext
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs
index c47173c33c..2a6c86357c 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs
@@ -15,6 +15,16 @@ public class SqlColumnEncryptionCngProvider : SqlColumnEncryptionKeyStoreProvide
///
public const string ProviderName = @"MSSQL_CNG_STORE";
+ ///
+ /// This encryption keystore uses an asymmetric key as the column master key.
+ ///
+ internal const string MasterKeyType = @"asymmetric key";
+
+ ///
+ /// This encryption keystore uses the master key path to reference a CNG provider.
+ ///
+ internal const string KeyPathReference = @"Microsoft Cryptography API: Next Generation (CNG) provider";
+
///
/// RSA_OAEP is the only algorithm supported for encrypting/decrypting column encryption keys using this provider.
/// For now, we are keeping all the providers in sync.
@@ -113,7 +123,7 @@ public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string e
// Validate the signature
if (!RSAVerifySignature(hash, signature, rsaCngProvider))
{
- throw SQL.InvalidSignature(masterKeyPath);
+ throw SQL.InvalidAsymmetricKeySignature(masterKeyPath);
}
// Decrypt the CEK
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.netcore.Unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.netcore.Unix.cs
index 038b43df2d..5f75192562 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.netcore.Unix.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.netcore.Unix.cs
@@ -28,6 +28,16 @@ public class SqlColumnEncryptionCngProvider : SqlColumnEncryptionKeyStoreProvide
///
public const string ProviderName = @"MSSQL_CNG_STORE";
+ ///
+ /// This encryption keystore uses an asymmetric key as the column master key.
+ ///
+ internal const string MasterKeyType = @"asymmetric key";
+
+ ///
+ /// This encryption keystore uses the master key path to reference a CNG provider.
+ ///
+ internal const string KeyPathReference = @"Microsoft Cryptography API: Next Generation (CNG) provider";
+
///
/// This function uses the asymmetric key specified by the key path
/// and decrypts an encrypted CEK with RSA encryption algorithm.
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs
index 6a5227d039..db659a1784 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs
@@ -18,6 +18,16 @@ public class SqlColumnEncryptionCspProvider : SqlColumnEncryptionKeyStoreProvide
///
public const string ProviderName = @"MSSQL_CSP_PROVIDER";
+ ///
+ /// This encryption keystore uses an asymmetric key as the column master key.
+ ///
+ internal const string MasterKeyType = @"asymmetric key";
+
+ ///
+ /// This encryption keystore uses the master key path to reference a CSP.
+ ///
+ internal const string KeyPathReference = @"Microsoft Cryptographic Service Provider (CSP)";
+
///
/// RSA_OAEP is the only algorithm supported for encrypting/decrypting column encryption keys using this provider.
/// For now, we are keeping all the providers in sync.
@@ -121,7 +131,7 @@ public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string e
// Validate the signature
if (!RSAVerifySignature(hash, signature, rsaProvider))
{
- throw SQL.InvalidSignature(masterKeyPath);
+ throw SQL.InvalidAsymmetricKeySignature(masterKeyPath);
}
// Decrypt the CEK
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.netcore.Unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.netcore.Unix.cs
index 5fe9980788..4e2836c020 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.netcore.Unix.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.netcore.Unix.cs
@@ -28,6 +28,16 @@ public class SqlColumnEncryptionCspProvider : SqlColumnEncryptionKeyStoreProvide
///
public const string ProviderName = @"MSSQL_CSP_PROVIDER";
+ ///
+ /// This encryption keystore uses an asymmetric key as the column master key.
+ ///
+ internal const string MasterKeyType = @"asymmetric key";
+
+ ///
+ /// This encryption keystore uses the master key path to reference a CSP.
+ ///
+ internal const string KeyPathReference = @"Microsoft Cryptographic Service Provider (CSP)";
+
///
/// This function uses the asymmetric key specified by the key path
/// and decrypts an encrypted CEK with RSA encryption algorithm.
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs
index 1633f53c81..55e41147b6 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs
@@ -1778,9 +1778,14 @@ internal static Exception InvalidAlgorithmVersionInEncryptedCEK(byte actual, byt
return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidAlgorithmVersionInEncryptedCEK, actual.ToString(@"X2"), expected.ToString(@"X2")), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
}
- internal static Exception InvalidCiphertextLengthInEncryptedCEK(int actual, int expected, string certificateName)
+ internal static Exception InvalidCiphertextLengthInEncryptedCEK(string keyType, string keyPathReference, int actual, int expected, string masterKeyPath)
{
- return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidCiphertextLengthInEncryptedCEK, actual, expected, certificateName), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
+ return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidCiphertextLengthInEncryptedCEK, actual, expected, keyType, masterKeyPath, keyPathReference), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
+ }
+
+ internal static Exception InvalidCiphertextLengthInEncryptedCEKCertificate(int actual, int expected, string certificateName)
+ {
+ return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidCiphertextLengthInEncryptedCEKCertificate, actual, expected, certificateName), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
}
internal static Exception InvalidCiphertextLengthInEncryptedCEKCsp(int actual, int expected, string masterKeyPath)
@@ -1793,9 +1798,14 @@ internal static Exception InvalidCiphertextLengthInEncryptedCEKCng(int actual, i
return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidCiphertextLengthInEncryptedCEKCng, actual, expected, masterKeyPath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
}
- internal static Exception InvalidSignatureInEncryptedCEK(int actual, int expected, string masterKeyPath)
+ internal static Exception InvalidSignatureInEncryptedCEK(string keyType, string keyPathReference, int actual, int expected, string masterKeyPath)
{
- return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidSignatureInEncryptedCEK, actual, expected, masterKeyPath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
+ return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidSignatureInEncryptedCEK, actual, expected, keyType, masterKeyPath, keyPathReference), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
+ }
+
+ internal static Exception InvalidSignatureInEncryptedCEKCertificate(int actual, int expected, string masterKeyPath)
+ {
+ return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidSignatureInEncryptedCEKCertificate, actual, expected, masterKeyPath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
}
internal static Exception InvalidSignatureInEncryptedCEKCsp(int actual, int expected, string masterKeyPath)
@@ -1808,14 +1818,19 @@ internal static Exception InvalidSignatureInEncryptedCEKCng(int actual, int expe
return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidSignatureInEncryptedCEKCng, actual, expected, masterKeyPath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
}
+ internal static Exception InvalidSignature(string masterKeyPath, string keyType)
+ {
+ return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidSignature, keyType, masterKeyPath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
+ }
+
internal static Exception InvalidCertificateSignature(string certificatePath)
{
return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidCertificateSignature, certificatePath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
}
- internal static Exception InvalidSignature(string masterKeyPath)
+ internal static Exception InvalidAsymmetricKeySignature(string masterKeyPath)
{
- return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidSignature, masterKeyPath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
+ return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidAsymmetricKeySignature, masterKeyPath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
}
internal static Exception CertificateWithNoPrivateKey(string keyPath, bool isSystemOp)
diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs
index fc5464678f..16cf52c86d 100644
--- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs
+++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs
@@ -12683,15 +12683,24 @@ internal static string TCE_InvalidCertificateStoreSysErr {
return ResourceManager.GetString("TCE_InvalidCertificateStoreSysErr", resourceCulture);
}
}
-
+
///
- /// Looks up a localized string similar to The specified encrypted column encryption key's ciphertext length: {0} does not match the ciphertext length: {1} when using column master key (certificate) in '{2}'. The encrypted column encryption key may be corrupt, or the specified certificate path may be incorrect..
+ /// Looks up a localized string similar to The specified encrypted column encryption key's ciphertext length: {0} does not match the ciphertext length: {1} when using column master key ({2}) in '{3}'. The encrypted column encryption key may be corrupt, or the specified {4} path may be incorrect..
///
internal static string TCE_InvalidCiphertextLengthInEncryptedCEK {
get {
return ResourceManager.GetString("TCE_InvalidCiphertextLengthInEncryptedCEK", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to The specified encrypted column encryption key's ciphertext length: {0} does not match the ciphertext length: {1} when using column master key (certificate) in '{2}'. The encrypted column encryption key may be corrupt, or the specified certificate path may be incorrect..
+ ///
+ internal static string TCE_InvalidCiphertextLengthInEncryptedCEKCertificate {
+ get {
+ return ResourceManager.GetString("TCE_InvalidCiphertextLengthInEncryptedCEKCertificate", resourceCulture);
+ }
+ }
///
/// Looks up a localized string similar to The specified encrypted column encryption key's ciphertext length: {0} does not match the ciphertext length: {1} when using column master key (asymmetric key) in '{2}'. The encrypted column encryption key may be corrupt, or the specified Microsoft Cryptography API: Next Generation (CNG) provider path may be incorrect..
@@ -12899,24 +12908,42 @@ internal static string TCE_InvalidKeyStoreProviderName {
return ResourceManager.GetString("TCE_InvalidKeyStoreProviderName", resourceCulture);
}
}
-
+
///
- /// Looks up a localized string similar to The specified encrypted column encryption key signature does not match the signature computed with the column master key (asymmetric key) in '{0}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect..
+ /// Looks up a localized string similar to The specified encrypted column encryption key signature does not match the signature computed with the column master key ({0}) in '{1}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect..
///
internal static string TCE_InvalidSignature {
get {
return ResourceManager.GetString("TCE_InvalidSignature", resourceCulture);
}
}
-
+
///
- /// Looks up a localized string similar to The specified encrypted column encryption key's signature length: {0} does not match the signature length: {1} when using column master key (certificate) in '{2}'. The encrypted column encryption key may be corrupt, or the specified certificate path may be incorrect..
+ /// Looks up a localized string similar to The specified encrypted column encryption key signature does not match the signature computed with the column master key (asymmetric key) in '{0}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect..
+ ///
+ internal static string TCE_InvalidAsymmetricKeySignature {
+ get {
+ return ResourceManager.GetString("TCE_InvalidAsymmetricKeySignature", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The specified encrypted column encryption key's signature length: {0} does not match the signature length: {1} when using column master key ({2}) in '{3}'. The encrypted column encryption key may be corrupt, or the specified {4} path may be incorrect..
///
internal static string TCE_InvalidSignatureInEncryptedCEK {
get {
return ResourceManager.GetString("TCE_InvalidSignatureInEncryptedCEK", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to The specified encrypted column encryption key's signature length: {0} does not match the signature length: {1} when using column master key (certificate) in '{2}'. The encrypted column encryption key may be corrupt, or the specified certificate path may be incorrect..
+ ///
+ internal static string TCE_InvalidSignatureInEncryptedCEKCertificate {
+ get {
+ return ResourceManager.GetString("TCE_InvalidSignatureInEncryptedCEKCertificate", resourceCulture);
+ }
+ }
///
/// Looks up a localized string similar to The specified encrypted column encryption key's signature length: {0} does not match the signature length: {1} when using column master key (asymmetric key) in '{2}'. The encrypted column encryption key may be corrupt, or the specified Microsoft Cryptography API: Next Generation (CNG) provider path may be incorrect..
diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx
index 15b2c3dc7f..2eb501cec8 100644
--- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx
+++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx
@@ -4075,6 +4075,9 @@
Specified encrypted column encryption key contains an invalid encryption algorithm version '{0}'. Expected version is '{1}'.
+ The specified encrypted column encryption key's ciphertext length: {0} does not match the ciphertext length: {1} when using column master key ({2}) in '{3}'. The encrypted column encryption key may be corrupt, or the specified {4} path may be incorrect.
+
+
The specified encrypted column encryption key's ciphertext length: {0} does not match the ciphertext length: {1} when using column master key (certificate) in '{2}'. The encrypted column encryption key may be corrupt, or the specified certificate path may be incorrect.
@@ -4084,6 +4087,9 @@
The specified encrypted column encryption key's ciphertext length: {0} does not match the ciphertext length: {1} when using column master key (asymmetric key) in '{2}'. The encrypted column encryption key may be corrupt, or the specified Microsoft Cryptography API: Next Generation (CNG) provider path may be incorrect.
+ The specified encrypted column encryption key's signature length: {0} does not match the signature length: {1} when using column master key ({2}) in '{3}'. The encrypted column encryption key may be corrupt, or the specified {4} path may be incorrect.
+
+
The specified encrypted column encryption key's signature length: {0} does not match the signature length: {1} when using column master key (certificate) in '{2}'. The encrypted column encryption key may be corrupt, or the specified certificate path may be incorrect.
@@ -4092,10 +4098,13 @@
The specified encrypted column encryption key's signature length: {0} does not match the signature length: {1} when using column master key (asymmetric key) in '{2}'. The encrypted column encryption key may be corrupt, or the specified Microsoft Cryptography API: Next Generation (CNG) provider path may be incorrect.
+
+ The specified encrypted column encryption key signature does not match the signature computed with the column master key ({0}) in '{1}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.
+
The specified encrypted column encryption key signature does not match the signature computed with the column master key (certificate) in '{0}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.
-
+
The specified encrypted column encryption key signature does not match the signature computed with the column master key (asymmetric key) in '{0}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.