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 8499f7801d..c259692e69 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -420,6 +420,9 @@ Microsoft\Data\SqlClient\SqlCollation.cs + + Microsoft\Data\SqlClient\SqlColumnEncryptionEnclaveProvider.cs + Microsoft\Data\SqlClient\SqlCommandBuilder.cs @@ -673,8 +676,6 @@ - - @@ -855,6 +856,12 @@ Microsoft\Data\Sql\SqlDataSourceEnumerator.Windows.cs + + Microsoft\Data\SqlClient\SqlColumnEncryptionCngProvider.Windows.cs + + + Microsoft\Data\SqlClient\SqlColumnEncryptionCspProvider.Windows.cs + Microsoft\Data\SqlClient\SSPI\NativeSSPIContextProvider.cs @@ -871,8 +878,6 @@ - - diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.NetCoreApp.cs deleted file mode 100644 index fd81db557d..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.NetCoreApp.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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.Security.Cryptography; - -namespace Microsoft.Data.SqlClient -{ - /// - /// The base class that defines the interface for enclave providers for Always Encrypted. An enclave is a protected region of memory inside SQL Server, used for computations on encrypted columns. An enclave provider encapsulates the client-side implementation details of the enclave attestation protocol as well as the logic for creating and caching enclave sessions. - /// - internal abstract partial class SqlColumnEncryptionEnclaveProvider - { - /// Performs enclave attestation, generates a symmetric key for the session, creates a an enclave session and stores the session information in the cache. - /// The information the provider uses to attest the enclave and generate a symmetric key for the session. The format of this information is specific to the enclave attestation protocol. - /// A Diffie-Hellman algorithm object encapsulating a client-side key pair. - /// The set of parameters required for enclave session. - /// The set of extra data needed for attesting the enclave. - /// The length of the extra data needed for attesting the enclave. - /// The requested enclave session or null if the provider does not implement session caching. - /// A counter that the enclave provider is expected to increment each time SqlClient retrieves the session from the cache. The purpose of this field is to prevent replay attacks. - internal abstract void CreateEnclaveSession(byte[] enclaveAttestationInfo, ECDiffieHellman clientDiffieHellmanKey, EnclaveSessionParameters enclaveSessionParameters, byte[] customData, int customDataLength, - out SqlEnclaveSession sqlEnclaveSession, out long counter); - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs deleted file mode 100644 index 9223702509..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -// 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. - -namespace Microsoft.Data.SqlClient -{ - /// - internal abstract partial class SqlColumnEncryptionEnclaveProvider - { - /// - internal abstract void GetEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, bool isRetry, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength); - - /// - internal abstract SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength); - - /// - internal abstract void InvalidateEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, SqlEnclaveSession enclaveSession); - } -} 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 9b161fa048..02ac8c7449 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -469,6 +469,15 @@ Microsoft\Data\SqlClient\SqlColumnEncryptionCertificateStoreProvider.cs + + Microsoft\Data\SqlClient\SqlColumnEncryptionCngProvider.cs + + + Microsoft\Data\SqlClient\SqlColumnEncryptionCspProvider.cs + + + Microsoft\Data\SqlClient\SqlColumnEncryptionEnclaveProvider.cs + Microsoft\Data\SqlClient\SqlColumnEncryptionKeyStoreProvider.cs @@ -707,9 +716,6 @@ - - - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.cs deleted file mode 100644 index 9ea5374c9f..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.cs +++ /dev/null @@ -1,392 +0,0 @@ -// 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.Security.Cryptography; -using System.Text; - -namespace Microsoft.Data.SqlClient -{ - /// - public class SqlColumnEncryptionCngProvider : SqlColumnEncryptionKeyStoreProvider - { - /// - public const string ProviderName = @"MSSQL_CNG_STORE"; - - /// - /// 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. - /// - private const string RSAEncryptionAlgorithmWithOAEP = @"RSA_OAEP"; - - /// - /// Algorithm version - /// - private readonly byte[] _version = new byte[] { 0x01 }; - - /// - public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey) - { - // Validate the input parameters - ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: true); - - if (encryptedColumnEncryptionKey == null) - { - throw SQL.NullEncryptedColumnEncryptionKey(); - } - - if (0 == encryptedColumnEncryptionKey.Length) - { - throw SQL.EmptyEncryptedColumnEncryptionKey(); - } - - // Validate encryptionAlgorithm - ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true); - - // Create RSA Provider with the given CNG name and key name - RSACng rsaCngProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: true); - - // Validate whether the key is RSA one or not and then get the key size - int keySizeInBytes = GetKeySize(rsaCngProvider); - - // Validate and decrypt the EncryptedColumnEncryptionKey - // Format is - // version + keyPathLength + ciphertextLength + keyPath + ciphervtext + signature - // - // keyPath is present in the encrypted column encryption key for identifying the original source of the asymmetric key pair and - // we will not validate it against the data contained in the CMK metadata (masterKeyPath). - - // Validate the version byte - if (encryptedColumnEncryptionKey[0] != _version[0]) - { - throw SQL.InvalidAlgorithmVersionInEncryptedCEK(encryptedColumnEncryptionKey[0], _version[0]); - } - - // Get key path length - int currentIndex = _version.Length; - UInt16 keyPathLength = BitConverter.ToUInt16(encryptedColumnEncryptionKey, currentIndex); - currentIndex += sizeof(UInt16); - - // Get ciphertext length - UInt16 cipherTextLength = BitConverter.ToUInt16(encryptedColumnEncryptionKey, currentIndex); - currentIndex += sizeof(UInt16); - - // Skip KeyPath - // KeyPath exists only for troubleshooting purposes and doesnt need validation. - currentIndex += keyPathLength; - - // validate the ciphertext length - if (cipherTextLength != keySizeInBytes) - { - throw SQL.InvalidCiphertextLengthInEncryptedCEKCng(cipherTextLength, keySizeInBytes, masterKeyPath); - } - - // Validate the signature length - // Signature length should be same as the key side for RSA PKCSv1.5 - int signatureLength = encryptedColumnEncryptionKey.Length - currentIndex - cipherTextLength; - if (signatureLength != keySizeInBytes) - { - throw SQL.InvalidSignatureInEncryptedCEKCng(signatureLength, keySizeInBytes, masterKeyPath); - } - - // Get ciphertext - byte[] cipherText = new byte[cipherTextLength]; - Buffer.BlockCopy(encryptedColumnEncryptionKey, currentIndex, cipherText, 0, cipherText.Length); - currentIndex += cipherTextLength; - - // Get signature - byte[] signature = new byte[signatureLength]; - Buffer.BlockCopy(encryptedColumnEncryptionKey, currentIndex, signature, 0, signature.Length); - - // Compute the hash to validate the signature - byte[] hash; - using (SHA256Cng sha256 = new SHA256Cng()) - { - sha256.TransformFinalBlock(encryptedColumnEncryptionKey, 0, encryptedColumnEncryptionKey.Length - signature.Length); - hash = sha256.Hash; - } - - Debug.Assert(hash != null, @"hash should not be null while decrypting encrypted column encryption key."); - - // Validate the signature - if (!RSAVerifySignature(hash, signature, rsaCngProvider)) - { - throw SQL.InvalidSignature(masterKeyPath); - } - - // Decrypt the CEK - return RSADecrypt(rsaCngProvider, cipherText); - } - - /// - public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey) - { - // Validate the input parameters - ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: false); - - if (columnEncryptionKey == null) - { - throw SQL.NullColumnEncryptionKey(); - } - else if (0 == columnEncryptionKey.Length) - { - throw SQL.EmptyColumnEncryptionKey(); - } - - // Validate encryptionAlgorithm - ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: false); - - // CreateCNGProviderWithKey - RSACng rsaCngProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: false); - - // Validate whether the key is RSA one or not and then get the key size - int keySizeInBytes = GetKeySize(rsaCngProvider); - - // Construct the encryptedColumnEncryptionKey - // Format is - // version + keyPathLength + ciphertextLength + ciphertext + keyPath + signature - // - // We currently only support one version - byte[] version = new byte[] { _version[0] }; - - // Get the Unicode encoded bytes of cultureinvariant lower case masterKeyPath - byte[] masterKeyPathBytes = Encoding.Unicode.GetBytes(masterKeyPath.ToLowerInvariant()); - byte[] keyPathLength = BitConverter.GetBytes((Int16)masterKeyPathBytes.Length); - - // Encrypt the plain text - byte[] cipherText = RSAEncrypt(rsaCngProvider, columnEncryptionKey); - byte[] cipherTextLength = BitConverter.GetBytes((Int16)cipherText.Length); - Debug.Assert(cipherText.Length == keySizeInBytes, @"cipherText length does not match the RSA key size"); - - // Compute hash - // SHA-2-256(version + keyPathLength + ciphertextLength + keyPath + ciphertext) - byte[] hash; - using (SHA256Cng sha256 = new SHA256Cng()) - { - sha256.TransformBlock(version, 0, version.Length, version, 0); - sha256.TransformBlock(keyPathLength, 0, keyPathLength.Length, keyPathLength, 0); - sha256.TransformBlock(cipherTextLength, 0, cipherTextLength.Length, cipherTextLength, 0); - sha256.TransformBlock(masterKeyPathBytes, 0, masterKeyPathBytes.Length, masterKeyPathBytes, 0); - sha256.TransformFinalBlock(cipherText, 0, cipherText.Length); - hash = sha256.Hash; - } - - // Sign the hash - byte[] signedHash = RSASignHashedData(hash, rsaCngProvider); - Debug.Assert(signedHash.Length == keySizeInBytes, @"signed hash length does not match the RSA key size"); - Debug.Assert(RSAVerifySignature(hash, signedHash, rsaCngProvider), @"Invalid signature of the encrypted column encryption key computed."); - - // Construct the encrypted column encryption key - // EncryptedColumnEncryptionKey = version + keyPathLength + ciphertextLength + keyPath + ciphertext + signature - int encryptedColumnEncryptionKeyLength = version.Length + cipherTextLength.Length + keyPathLength.Length + cipherText.Length + masterKeyPathBytes.Length + signedHash.Length; - byte[] encryptedColumnEncryptionKey = new byte[encryptedColumnEncryptionKeyLength]; - - // Copy version byte - int currentIndex = 0; - Buffer.BlockCopy(version, 0, encryptedColumnEncryptionKey, currentIndex, version.Length); - currentIndex += version.Length; - - // Copy key path length - Buffer.BlockCopy(keyPathLength, 0, encryptedColumnEncryptionKey, currentIndex, keyPathLength.Length); - currentIndex += keyPathLength.Length; - - // Copy ciphertext length - Buffer.BlockCopy(cipherTextLength, 0, encryptedColumnEncryptionKey, currentIndex, cipherTextLength.Length); - currentIndex += cipherTextLength.Length; - - // Copy key path - Buffer.BlockCopy(masterKeyPathBytes, 0, encryptedColumnEncryptionKey, currentIndex, masterKeyPathBytes.Length); - currentIndex += masterKeyPathBytes.Length; - - // Copy ciphertext - Buffer.BlockCopy(cipherText, 0, encryptedColumnEncryptionKey, currentIndex, cipherText.Length); - currentIndex += cipherText.Length; - - // copy the signature - Buffer.BlockCopy(signedHash, 0, encryptedColumnEncryptionKey, currentIndex, signedHash.Length); - - return encryptedColumnEncryptionKey; - } - - /// - public override byte[] SignColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations) - { - throw new NotSupportedException(); - } - - /// - public override bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations, byte[] signature) - { - throw new NotSupportedException(); - } - - /// - /// This function validates that the encryption algorithm is RSA_OAEP and if it is not, - /// then throws an exception - /// - /// Asymmetric key encryptio algorithm - /// Indicates if ADO.NET calls or the customer calls the API - private void ValidateEncryptionAlgorithm(string encryptionAlgorithm, bool isSystemOp) - { - // This validates that the encryption algorithm is RSA_OAEP - if (encryptionAlgorithm == null) - { - throw SQL.NullKeyEncryptionAlgorithm(isSystemOp); - } - - if (!string.Equals(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, StringComparison.OrdinalIgnoreCase)) - { - throw SQL.InvalidKeyEncryptionAlgorithm(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, isSystemOp); - } - } - - /// - /// Checks if the CNG key path is Empty or Null (and raises exception if they are). - /// - /// keypath containing the CNG provider name and key name - /// Indicates if ADO.NET calls or the customer calls the API - private void ValidateNonEmptyKeyPath(string masterKeyPath, bool isSystemOp) - { - if (string.IsNullOrWhiteSpace(masterKeyPath)) - { - if (masterKeyPath == null) - { - throw SQL.NullCngKeyPath(isSystemOp); - } - else - { - throw SQL.InvalidCngPath(masterKeyPath, isSystemOp); - } - } - } - - /// - /// Encrypt the text using specified CNG key. - /// - /// RSA CNG Provider. - /// Plain text Column Encryption Key. - /// Returns an encrypted blob or throws an exception if there are any errors. - private byte[] RSAEncrypt(RSACng rsaCngProvider, byte[] columnEncryptionKey) - { - Debug.Assert(columnEncryptionKey != null); - Debug.Assert(rsaCngProvider != null); - - return rsaCngProvider.Encrypt(columnEncryptionKey, RSAEncryptionPadding.OaepSHA1); - } - - /// - /// Decrypt the text using the specified CNG key. - /// - /// RSA CNG Provider. - /// Encrypted Column Encryption Key. - /// Returns the decrypted plaintext Column Encryption Key or throws an exception if there are any errors. - private byte[] RSADecrypt(RSACng rsaCngProvider, byte[] encryptedColumnEncryptionKey) - { - Debug.Assert((encryptedColumnEncryptionKey != null) && (encryptedColumnEncryptionKey.Length != 0)); - Debug.Assert(rsaCngProvider != null); - - return rsaCngProvider.Decrypt(encryptedColumnEncryptionKey, RSAEncryptionPadding.OaepSHA1); - } - - /// - /// Generates signature based on RSA PKCS#v1.5 scheme using a specified CNG Key. - /// - /// Text to sign. - /// RSA CNG Provider. - /// Signature - private byte[] RSASignHashedData(byte[] dataToSign, RSACng rsaCngProvider) - { - Debug.Assert((dataToSign != null) && (dataToSign.Length != 0)); - Debug.Assert(rsaCngProvider != null); - - return rsaCngProvider.SignData(dataToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - } - - /// - /// Verifies the given RSA PKCSv1.5 signature. - /// - /// - /// - /// RSA CNG Provider. - /// true if signature is valid, false if it is not valid - private bool RSAVerifySignature(byte[] dataToVerify, byte[] signature, RSACng rsaCngProvider) - { - Debug.Assert((dataToVerify != null) && (dataToVerify.Length != 0)); - Debug.Assert((signature != null) && (signature.Length != 0)); - Debug.Assert(rsaCngProvider != null); - - return rsaCngProvider.VerifyData(dataToVerify, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - } - - /// - /// Gets the public Key size in bytes - /// - /// RSA CNG Provider. - /// Key size in bytes - private int GetKeySize(RSACng rsaCngProvider) - { - Debug.Assert(rsaCngProvider != null); - - return rsaCngProvider.KeySize / 8; // Convert from bits to byte - } - - /// - /// Creates a RSACng object from the given keyName - /// - /// - /// Indicates if ADO.NET calls or the customer calls the API - /// - private RSACng CreateRSACngProvider(string keyPath, bool isSystemOp) - { - // Get CNGProvider and the KeyID - string cngProviderName; - string keyIdentifier; - GetCngProviderAndKeyId(keyPath, isSystemOp, out cngProviderName, out keyIdentifier); - - CngProvider cngProvider = new CngProvider(cngProviderName); - CngKey cngKey; - - try - { - cngKey = CngKey.Open(keyIdentifier, cngProvider); - } - catch (CryptographicException) - { - throw SQL.InvalidCngKey(keyPath, cngProviderName, keyIdentifier, isSystemOp); - } - - return new RSACng(cngKey); - } - - /// - /// Extracts the CNG provider and key name from the key path - /// - /// keypath in the format [CNG Provider]\[KeyName] - /// Indicates if ADO.NET calls or the customer calls the API - /// CNG Provider - /// Key identifier inside the CNG provider - private void GetCngProviderAndKeyId(string keyPath, bool isSystemOp, out string cngProvider, out string keyIdentifier) - { - int indexOfSlash = keyPath.IndexOf(@"/"); - if (indexOfSlash == -1) - { - throw SQL.InvalidCngPath(keyPath, isSystemOp); - } - - cngProvider = keyPath.Substring(0, indexOfSlash); - keyIdentifier = keyPath.Substring(indexOfSlash + 1, keyPath.Length - (indexOfSlash + 1)); - - if (cngProvider.Length == 0) - { - throw SQL.EmptyCngName(keyPath, isSystemOp); - } - - if (keyIdentifier.Length == 0) - { - throw SQL.EmptyCngKeyId(keyPath, isSystemOp); - } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.cs deleted file mode 100644 index 9489f1b5f4..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.cs +++ /dev/null @@ -1,436 +0,0 @@ -// 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.Security.Cryptography; -using System.Text; -using Microsoft.Win32; - -namespace Microsoft.Data.SqlClient -{ - /// - public class SqlColumnEncryptionCspProvider : SqlColumnEncryptionKeyStoreProvider - { - /// - public const string ProviderName = @"MSSQL_CSP_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. - /// - private const string RSAEncryptionAlgorithmWithOAEP = @"RSA_OAEP"; - - - private const string HashingAlgorithm = @"SHA256"; - - /// - /// Algorithm version - /// - private readonly byte[] _version = new byte[] { 0x01 }; - - /// - public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey) - { - // Validate the input parameters - ValidateNonEmptyCSPKeyPath(masterKeyPath, isSystemOp: true); - - if (encryptedColumnEncryptionKey == null) - { - throw SQL.NullEncryptedColumnEncryptionKey(); - } - - if (0 == encryptedColumnEncryptionKey.Length) - { - throw SQL.EmptyEncryptedColumnEncryptionKey(); - } - - // Validate encryptionAlgorithm - ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true); - - // Create RSA Provider with the given CSP name and key name - RSACryptoServiceProvider rsaProvider = CreateRSACryptoProvider(masterKeyPath, isSystemOp: true); - - // Validate whether the key is RSA one or not and then get the key size - int keySizeInBytes = GetKeySize(rsaProvider); - - // Validate and decrypt the EncryptedColumnEncryptionKey - // Format is - // version + keyPathLength + ciphertextLength + keyPath + ciphervtext + signature - // - // keyPath is present in the encrypted column encryption key for identifying the original source of the asymmetric key pair and - // we will not validate it against the data contained in the CMK metadata (masterKeyPath). - - // Validate the version byte - if (encryptedColumnEncryptionKey[0] != _version[0]) - { - throw SQL.InvalidAlgorithmVersionInEncryptedCEK(encryptedColumnEncryptionKey[0], _version[0]); - } - - // Get key path length - int currentIndex = _version.Length; - UInt16 keyPathLength = BitConverter.ToUInt16(encryptedColumnEncryptionKey, currentIndex); - currentIndex += sizeof(UInt16); - - // Get ciphertext length - UInt16 cipherTextLength = BitConverter.ToUInt16(encryptedColumnEncryptionKey, currentIndex); - currentIndex += sizeof(UInt16); - - // Skip KeyPath - // KeyPath exists only for troubleshooting purposes and doesnt need validation. - currentIndex += keyPathLength; - - // validate the ciphertext length - if (cipherTextLength != keySizeInBytes) - { - throw SQL.InvalidCiphertextLengthInEncryptedCEKCsp(cipherTextLength, keySizeInBytes, masterKeyPath); - } - - // Validate the signature length - // Signature length should be same as the key side for RSA PKCSv1.5 - int signatureLength = encryptedColumnEncryptionKey.Length - currentIndex - cipherTextLength; - if (signatureLength != keySizeInBytes) - { - throw SQL.InvalidSignatureInEncryptedCEKCsp(signatureLength, keySizeInBytes, masterKeyPath); - } - - // Get ciphertext - byte[] cipherText = new byte[cipherTextLength]; - Buffer.BlockCopy(encryptedColumnEncryptionKey, currentIndex, cipherText, 0, cipherText.Length); - currentIndex += cipherTextLength; - - // Get signature - byte[] signature = new byte[signatureLength]; - Buffer.BlockCopy(encryptedColumnEncryptionKey, currentIndex, signature, 0, signature.Length); - - // Compute the hash to validate the signature - byte[] hash; - using (SHA256Cng sha256 = new SHA256Cng()) - { - sha256.TransformFinalBlock(encryptedColumnEncryptionKey, 0, encryptedColumnEncryptionKey.Length - signature.Length); - hash = sha256.Hash; - } - - Debug.Assert(hash != null, @"hash should not be null while decrypting encrypted column encryption key."); - - // Validate the signature - if (!RSAVerifySignature(hash, signature, rsaProvider)) - { - throw SQL.InvalidSignature(masterKeyPath); - } - - // Decrypt the CEK - return RSADecrypt(rsaProvider, cipherText); - } - - /// - public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey) - { - // Validate the input parameters - ValidateNonEmptyCSPKeyPath(masterKeyPath, isSystemOp: false); - - if (columnEncryptionKey == null) - { - throw SQL.NullColumnEncryptionKey(); - } - else if (0 == columnEncryptionKey.Length) - { - throw SQL.EmptyColumnEncryptionKey(); - } - - // Validate encryptionAlgorithm - ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: false); - - // Create RSA Provider with the given CSP name and key name - RSACryptoServiceProvider rsaProvider = CreateRSACryptoProvider(masterKeyPath, isSystemOp: false); - - // Validate whether the key is RSA one or not and then get the key size - int keySizeInBytes = GetKeySize(rsaProvider); - - // Construct the encryptedColumnEncryptionKey - // Format is - // version + keyPathLength + ciphertextLength + ciphertext + keyPath + signature - // - // We currently only support one version - byte[] version = new byte[] { _version[0] }; - - // Get the Unicode encoded bytes of cultureinvariant lower case masterKeyPath - byte[] masterKeyPathBytes = Encoding.Unicode.GetBytes(masterKeyPath.ToLowerInvariant()); - byte[] keyPathLength = BitConverter.GetBytes((Int16)masterKeyPathBytes.Length); - - // Encrypt the plain text - byte[] cipherText = RSAEncrypt(rsaProvider, columnEncryptionKey); - byte[] cipherTextLength = BitConverter.GetBytes((Int16)cipherText.Length); - Debug.Assert(cipherText.Length == keySizeInBytes, @"cipherText length does not match the RSA key size"); - - // Compute hash - // SHA-2-256(version + keyPathLength + ciphertextLength + keyPath + ciphertext) - byte[] hash; - using (SHA256Cng sha256 = new SHA256Cng()) - { - sha256.TransformBlock(version, 0, version.Length, version, 0); - sha256.TransformBlock(keyPathLength, 0, keyPathLength.Length, keyPathLength, 0); - sha256.TransformBlock(cipherTextLength, 0, cipherTextLength.Length, cipherTextLength, 0); - sha256.TransformBlock(masterKeyPathBytes, 0, masterKeyPathBytes.Length, masterKeyPathBytes, 0); - sha256.TransformFinalBlock(cipherText, 0, cipherText.Length); - hash = sha256.Hash; - } - - // Sign the hash - byte[] signedHash = RSASignHashedData(hash, rsaProvider); - Debug.Assert(signedHash.Length == keySizeInBytes, @"signed hash length does not match the RSA key size"); - Debug.Assert(RSAVerifySignature(hash, signedHash, rsaProvider), @"Invalid signature of the encrypted column encryption key computed."); - - // Construct the encrypted column encryption key - // EncryptedColumnEncryptionKey = version + keyPathLength + ciphertextLength + keyPath + ciphertext + signature - int encryptedColumnEncryptionKeyLength = version.Length + cipherTextLength.Length + keyPathLength.Length + cipherText.Length + masterKeyPathBytes.Length + signedHash.Length; - byte[] encryptedColumnEncryptionKey = new byte[encryptedColumnEncryptionKeyLength]; - - // Copy version byte - int currentIndex = 0; - Buffer.BlockCopy(version, 0, encryptedColumnEncryptionKey, currentIndex, version.Length); - currentIndex += version.Length; - - // Copy key path length - Buffer.BlockCopy(keyPathLength, 0, encryptedColumnEncryptionKey, currentIndex, keyPathLength.Length); - currentIndex += keyPathLength.Length; - - // Copy ciphertext length - Buffer.BlockCopy(cipherTextLength, 0, encryptedColumnEncryptionKey, currentIndex, cipherTextLength.Length); - currentIndex += cipherTextLength.Length; - - // Copy key path - Buffer.BlockCopy(masterKeyPathBytes, 0, encryptedColumnEncryptionKey, currentIndex, masterKeyPathBytes.Length); - currentIndex += masterKeyPathBytes.Length; - - // Copy ciphertext - Buffer.BlockCopy(cipherText, 0, encryptedColumnEncryptionKey, currentIndex, cipherText.Length); - currentIndex += cipherText.Length; - - // copy the signature - Buffer.BlockCopy(signedHash, 0, encryptedColumnEncryptionKey, currentIndex, signedHash.Length); - - return encryptedColumnEncryptionKey; - } - - /// - public override byte[] SignColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations) - { - throw new NotSupportedException(); - } - - /// - public override bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations, byte[] signature) - { - throw new NotSupportedException(); - } - - /// - /// This function validates that the encryption algorithm is RSA_OAEP and if it is not, - /// then throws an exception - /// - /// Asymmetric key encryptio algorithm - /// Indicates if ADO.NET calls or the customer calls the API - private void ValidateEncryptionAlgorithm(string encryptionAlgorithm, bool isSystemOp) - { - // This validates that the encryption algorithm is RSA_OAEP - if (encryptionAlgorithm == null) - { - throw SQL.NullKeyEncryptionAlgorithm(isSystemOp); - } - - if (string.Equals(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, StringComparison.OrdinalIgnoreCase) != true) - { - throw SQL.InvalidKeyEncryptionAlgorithm(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, isSystemOp); - } - } - - - /// - /// Checks if the CSP key path is Empty or Null (and raises exception if they are). - /// - /// CSP key path. - /// Indicates if ADO.NET calls or the customer calls the API - private void ValidateNonEmptyCSPKeyPath(string masterKeyPath, bool isSystemOp) - { - if (string.IsNullOrWhiteSpace(masterKeyPath)) - { - if (masterKeyPath == null) - { - throw SQL.NullCspKeyPath(isSystemOp); - } - else - { - throw SQL.InvalidCspPath(masterKeyPath, isSystemOp); - } - } - } - - /// - /// Encrypt the text using specified CSP key. - /// - /// RSACryptoServiceProvider - /// Plain text Column Encryption Key. - /// Returns an encrypted blob or throws an exception if there are any errors. - private byte[] RSAEncrypt(RSACryptoServiceProvider rscp, byte[] columnEncryptionKey) - { - Debug.Assert(columnEncryptionKey != null); - Debug.Assert(rscp != null); - - return rscp.Encrypt(columnEncryptionKey, fOAEP: true); - } - - /// - /// Decrypt the text using specified CSP key. - /// - /// RSACryptoServiceProvider - /// Encrypted Column Encryption Key. - /// Returns the decrypted plaintext Column Encryption Key or throws an exception if there are any errors. - private byte[] RSADecrypt(RSACryptoServiceProvider rscp, byte[] encryptedColumnEncryptionKey) - { - Debug.Assert((encryptedColumnEncryptionKey != null) && (encryptedColumnEncryptionKey.Length != 0)); - Debug.Assert(rscp != null); - - return rscp.Decrypt(encryptedColumnEncryptionKey, fOAEP: true); - } - - /// - /// Generates signature based on RSA PKCS#v1.5 scheme using a specified CSP Key URL. - /// - /// Text to sign. - /// RSA Provider with a given key - /// Signature - private byte[] RSASignHashedData(byte[] dataToSign, RSACryptoServiceProvider rscp) - { - Debug.Assert((dataToSign != null) && (dataToSign.Length != 0)); - Debug.Assert(rscp != null); - - return rscp.SignData(dataToSign, HashingAlgorithm); - } - - /// - /// Verifies the given RSA PKCSv1.5 signature. - /// - /// - /// - /// RSA Provider with a given key - /// true if signature is valid, false if it is not valid - private bool RSAVerifySignature(byte[] dataToVerify, byte[] signature, RSACryptoServiceProvider rscp) - { - Debug.Assert((dataToVerify != null) && (dataToVerify.Length != 0)); - Debug.Assert((signature != null) && (signature.Length != 0)); - Debug.Assert(rscp != null); - - return rscp.VerifyData(dataToVerify, HashingAlgorithm, signature); - } - - /// - /// Gets the public Key size in bytes - /// - /// RSA Provider with a given key - /// Key size in bytes - private int GetKeySize(RSACryptoServiceProvider rscp) - { - Debug.Assert(rscp != null); - - return rscp.KeySize / 8; - } - - /// - /// Creates a RSACryptoServiceProvider from the given key path which contains both CSP name and key name - /// - /// key path in the format of [CAPI provider name]\[key name] - /// Indicates if ADO.NET calls or the customer calls the API - /// - private RSACryptoServiceProvider CreateRSACryptoProvider(string keyPath, bool isSystemOp) - { - // Get CNGProvider and the KeyID - string cspProviderName; - string keyName; - GetCspProviderAndKeyName(keyPath, isSystemOp, out cspProviderName, out keyName); - - // Verify the existence of CSP and then get the provider type - int providerType = GetProviderType(cspProviderName, keyPath, isSystemOp); - - // Create a new instance of CspParameters for an RSA container. - CspParameters cspParams = new CspParameters(providerType, cspProviderName, keyName); - cspParams.Flags = CspProviderFlags.UseExistingKey; - - RSACryptoServiceProvider rscp = null; - - try - { - //Create a new instance of RSACryptoServiceProvider - rscp = new RSACryptoServiceProvider(cspParams); - } - catch (CryptographicException e) - { - const int KEYSETDOESNOTEXIST = -2146893802; - if (e.HResult == KEYSETDOESNOTEXIST) - { - // Key does not exist - throw SQL.InvalidCspKeyIdentifier(keyName, keyPath, isSystemOp); - } - else - { - // bubble up the exception - throw; - } - } - - return rscp; - } - - /// - /// Extracts the CSP provider name and key name from the given key path - /// - /// key path in the format of [CSP provider name]\[key name] - /// Indicates if ADO.NET calls or the customer calls the API - /// output containing the CSP provider name - /// output containing the key name - private void GetCspProviderAndKeyName(string keyPath, bool isSystemOp, out string cspProviderName, out string keyIdentifier) - { - int indexOfSlash = keyPath.IndexOf(@"/"); - if (indexOfSlash == -1) - { - throw SQL.InvalidCspPath(keyPath, isSystemOp); - } - - cspProviderName = keyPath.Substring(0, indexOfSlash); - keyIdentifier = keyPath.Substring(indexOfSlash + 1, keyPath.Length - (indexOfSlash + 1)); - - if (cspProviderName.Length == 0) - { - throw SQL.EmptyCspName(keyPath, isSystemOp); - } - - if (keyIdentifier.Length == 0) - { - throw SQL.EmptyCspKeyId(keyPath, isSystemOp); - } - } - - /// - /// Gets the provider type from a given CAPI provider name - /// - /// CAPI provider name - /// key path in the format of [CSP provider name]\[key name] - /// Indicates if ADO.NET calls or the customer calls the API - /// - private int GetProviderType(string providerName, string keyPath, bool isSystemOp) - { - string keyName = String.Format(@"SOFTWARE\Microsoft\Cryptography\Defaults\Provider\{0}", providerName); - RegistryKey key = Registry.LocalMachine.OpenSubKey(keyName); - if (key == null) - { - throw SQL.InvalidCspName(providerName, keyPath, isSystemOp); - } - - int providerType = (int)key.GetValue(@"Type"); - key.Close(); - - return providerType; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs similarity index 93% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs index cb09f49d91..bb28a12d60 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs @@ -9,10 +9,10 @@ namespace Microsoft.Data.SqlClient { - /// + /// public class SqlColumnEncryptionCngProvider : SqlColumnEncryptionKeyStoreProvider { - /// + /// public const string ProviderName = @"MSSQL_CNG_STORE"; /// @@ -26,7 +26,7 @@ public class SqlColumnEncryptionCngProvider : SqlColumnEncryptionKeyStoreProvide /// private readonly byte[] _version = new byte[] { 0x01 }; - /// + /// public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey) { // Validate the input parameters @@ -120,7 +120,7 @@ public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string e return RSADecrypt(rsaCngProvider, cipherText); } - /// + /// public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey) { // Validate the input parameters @@ -210,13 +210,13 @@ public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string e return encryptedColumnEncryptionKey; } - /// + /// public override byte[] SignColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations) { throw new NotSupportedException(); } - /// + /// public override bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations, byte[] signature) { throw new NotSupportedException(); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs similarity index 93% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs index c356e73527..6a5227d039 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs @@ -11,11 +11,11 @@ namespace Microsoft.Data.SqlClient { - /// + /// public class SqlColumnEncryptionCspProvider : SqlColumnEncryptionKeyStoreProvider { - /// + /// public const string ProviderName = @"MSSQL_CSP_PROVIDER"; /// @@ -34,7 +34,7 @@ public class SqlColumnEncryptionCspProvider : SqlColumnEncryptionKeyStoreProvide /// private readonly byte[] _version = new byte[] { 0x01 }; - /// + /// public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey) { // Validate the input parameters @@ -128,7 +128,7 @@ public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string e return RSADecrypt(rsaProvider, cipherText); } - /// + /// public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey) { // Validate the input parameters @@ -218,13 +218,13 @@ public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string e return encryptedColumnEncryptionKey; } - /// + /// public override byte[] SignColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations) { throw new NotSupportedException(); } - /// + /// public override bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations, byte[] signature) { throw new NotSupportedException(); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs similarity index 52% rename from src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs index de26b79232..21a62a80c3 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs @@ -6,20 +6,19 @@ namespace Microsoft.Data.SqlClient { - /// + /// internal abstract class SqlColumnEncryptionEnclaveProvider { - - /// + /// internal abstract void GetEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, bool isRetry, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength); - /// + /// internal abstract SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength); - /// + /// internal abstract void CreateEnclaveSession(byte[] enclaveAttestationInfo, ECDiffieHellman clientDiffieHellmanKey, EnclaveSessionParameters enclaveSessionParameters, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter); - /// + /// internal abstract void InvalidateEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, SqlEnclaveSession enclaveSession); } }