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.