From 2f2c2d0843cc28cf595151ae8cef32cd23e692eb Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 18 May 2024 08:10:13 +0100 Subject: [PATCH 1/2] Merged SqlColumnEncryptionCertificateStoreProvider This also ports support for CNG keys from .NET to .NET Framework. --- .../src/Microsoft.Data.SqlClient.csproj | 4 +- .../netfx/src/Microsoft.Data.SqlClient.csproj | 4 +- ...olumnEncryptionCertificateStoreProvider.cs | 581 ------------------ ...ryptionCertificateStoreProvider.Windows.cs | 12 +- 4 files changed, 12 insertions(+), 589 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs rename src/Microsoft.Data.SqlClient/{netcore => }/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.Windows.cs (94%) 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 2cd3537ea7..0d8b4a84a1 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -833,6 +833,9 @@ Microsoft\Data\SqlClient\SSPI\NativeSSPIContextProvider.cs + + Microsoft\Data\SqlClient\SqlColumnEncryptionCertificateStoreProvider.Windows.cs + Microsoft\Data\SqlClient\TdsParserSafeHandles.Windows.cs @@ -844,7 +847,6 @@ - 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 e291206a4a..78edddbca1 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -438,6 +438,9 @@ Microsoft\Data\SqlClient\SqlCollation.cs + + Microsoft\Data\SqlClient\SqlColumnEncryptionCertificateStoreProvider.cs + Microsoft\Data\SqlClient\SqlColumnEncryptionKeyStoreProvider.cs @@ -675,7 +678,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs deleted file mode 100644 index e8dca4c858..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs +++ /dev/null @@ -1,581 +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.Security.Cryptography.X509Certificates; -using System.Text; - -namespace Microsoft.Data.SqlClient -{ - /// - public class SqlColumnEncryptionCertificateStoreProvider : SqlColumnEncryptionKeyStoreProvider - { - // Constants - // - // Assumption: Certificate Locations (LocalMachine & CurrentUser), Certificate Store name "My" - // Certificate provider name (CertificateStore) dont need to be localized. - - /// - public const string ProviderName = @"MSSQL_CERTIFICATE_STORE"; - - /// - /// RSA_OAEP is the only algorithm supported for encrypting/decrypting column encryption keys. - /// - internal const string RSAEncryptionAlgorithmWithOAEP = @"RSA_OAEP"; - - /// - /// LocalMachine certificate store location. Valid certificate locations are LocalMachine and CurrentUser. - /// - private const string _certLocationLocalMachine = @"LocalMachine"; - - /// - /// CurrentUser certificate store location. Valid certificate locations are LocalMachine and CurrentUser. - /// - private const string _certLocationCurrentUser = @"CurrentUser"; - - /// - /// Valid certificate store - /// - private const string _myCertificateStore = @"My"; - - /// - /// Certificate path format. This is a custom format. - /// - private const string _certificatePathFormat = @"[LocalMachine|CurrentUser]/My/[Thumbprint]"; - - /// - /// Hashing algorithm used for signing - /// - 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 - ValidateNonEmptyCertificatePath(masterKeyPath, isSystemOp: true); - - if (null == encryptedColumnEncryptionKey) - { - throw SQL.NullEncryptedColumnEncryptionKey(); - } - else if (0 == encryptedColumnEncryptionKey.Length) - { - throw SQL.EmptyEncryptedColumnEncryptionKey(); - } - - // Validate encryptionAlgorithm - ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true); - - // Validate key path length - ValidateCertificatePathLength(masterKeyPath, isSystemOp: true); - - // Parse the path and get the X509 cert - X509Certificate2 certificate = GetCertificateByPath(masterKeyPath, isSystemOp: true); - int keySizeInBytes = certificate.PublicKey.Key.KeySize / 8; - - // Validate and decrypt the EncryptedColumnEncryptionKey - // Format is - // version + keyPathLength + ciphertextLength + keyPath + ciphertext + 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; - Int16 keyPathLength = BitConverter.ToInt16(encryptedColumnEncryptionKey, currentIndex); - currentIndex += sizeof(Int16); - - // Get ciphertext length - int cipherTextLength = BitConverter.ToInt16(encryptedColumnEncryptionKey, currentIndex); - currentIndex += sizeof(Int16); - - // Skip KeyPath - // KeyPath exists only for troubleshooting purposes and doesnt need validation. - currentIndex += keyPathLength; - - // validate the ciphertext length - if (cipherTextLength != keySizeInBytes) - { - throw SQL.InvalidCiphertextLengthInEncryptedCEK(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.InvalidSignatureInEncryptedCEK(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, certificate)) - { - throw SQL.InvalidCertificateSignature(masterKeyPath); - } - - // Decrypt the CEK - return RSADecrypt(cipherText, certificate); - } - - /// - public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey) - { - // Validate the input parameters - ValidateNonEmptyCertificatePath(masterKeyPath, isSystemOp: false); - if (null == columnEncryptionKey) - { - throw SQL.NullColumnEncryptionKey(); - } - else if (0 == columnEncryptionKey.Length) - { - throw SQL.EmptyColumnEncryptionKey(); - } - - // Validate encryptionAlgorithm - ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: false); - - // Validate masterKeyPath Length - ValidateCertificatePathLength(masterKeyPath, isSystemOp: false); - - // Parse the certificate path and get the X509 cert - X509Certificate2 certificate = GetCertificateByPath(masterKeyPath, isSystemOp: false); - int keySizeInBytes = certificate.PublicKey.Key.KeySize / 8; - - // 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(columnEncryptionKey, certificate); - 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, certificate); - Debug.Assert(signedHash.Length == keySizeInBytes, @"signed hash length does not match the RSA key size"); - Debug.Assert(RSAVerifySignature(hash, signedHash, certificate), @"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) - { - var hash = ComputeMasterKeyMetadataHash(masterKeyPath, allowEnclaveComputations, isSystemOp: false); - - // Parse the certificate path and get the X509 cert - X509Certificate2 certificate = GetCertificateByPath(masterKeyPath, isSystemOp: false); - - byte[] signature = RSASignHashedData(hash, certificate); - - return signature; - } - - /// - public override bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations, byte[] signature) - { - var hash = ComputeMasterKeyMetadataHash(masterKeyPath, allowEnclaveComputations, isSystemOp: true); - - // Parse the certificate path and get the X509 cert - X509Certificate2 certificate = GetCertificateByPath(masterKeyPath, isSystemOp: true); - - // Validate the signature - return RSAVerifySignature(hash, signature, certificate); - } - - private byte[] ComputeMasterKeyMetadataHash(string masterKeyPath, bool allowEnclaveComputations, bool isSystemOp) - { - // Validate the input parameters - ValidateNonEmptyCertificatePath(masterKeyPath, isSystemOp); - - // Validate masterKeyPath Length - ValidateCertificatePathLength(masterKeyPath, isSystemOp); - - string masterkeyMetadata = ProviderName + masterKeyPath + allowEnclaveComputations; - masterkeyMetadata = masterkeyMetadata.ToLowerInvariant(); - byte[] masterkeyMetadataBytes = Encoding.Unicode.GetBytes(masterkeyMetadata.ToLowerInvariant()); - - // Compute hash - byte[] hash; - using (SHA256Cng sha256 = new SHA256Cng()) - { - sha256.TransformFinalBlock(masterkeyMetadataBytes, 0, masterkeyMetadataBytes.Length); - hash = sha256.Hash; - } - return hash; - } - - /// - /// This function validates that the encryption algorithm is RSA_OAEP and if it is not, - /// then throws an exception - /// - /// Asymmetric key encryptio algorithm - /// - private void ValidateEncryptionAlgorithm(string encryptionAlgorithm, bool isSystemOp) - { - // This validates that the encryption algorithm is RSA_OAEP - if (null == encryptionAlgorithm) - { - throw SQL.NullKeyEncryptionAlgorithm(isSystemOp); - } - - if (string.Equals(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, StringComparison.OrdinalIgnoreCase) != true) - { - throw SQL.InvalidKeyEncryptionAlgorithm(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, isSystemOp); - } - } - - /// - /// Certificate path length has to fit in two bytes, so check its value against Int16.MaxValue - /// - /// - /// - private void ValidateCertificatePathLength(string masterKeyPath, bool isSystemOp) - { - if (masterKeyPath.Length >= Int16.MaxValue) - { - throw SQL.LargeCertificatePathLength(masterKeyPath.Length, Int16.MaxValue, isSystemOp); - } - } - - /// - /// Gets a string array containing Valid certificate locations. - /// - private string[] GetValidCertificateLocations() - { - return new string[2] { _certLocationLocalMachine, _certLocationCurrentUser }; - } - - /// - /// Checks if the certificate path is Empty or Null (and raises exception if they are). - /// - private void ValidateNonEmptyCertificatePath(string masterKeyPath, bool isSystemOp) - { - if (string.IsNullOrWhiteSpace(masterKeyPath)) - { - if (null == masterKeyPath) - { - throw SQL.NullCertificatePath(GetValidCertificateLocations(), isSystemOp); - } - else - { - throw SQL.InvalidCertificatePath(masterKeyPath, GetValidCertificateLocations(), isSystemOp); - } - } - } - - /// - /// Parses the given certificate path, searches in certificate store and returns a matching certificate - /// - /// - /// Certificate key path. Format of the path is [LocalMachine|CurrentUser]/[storename]/thumbprint - /// - /// - /// Returns the certificate identified by the certificate path - private X509Certificate2 GetCertificateByPath(string keyPath, bool isSystemOp) - { - Debug.Assert(!string.IsNullOrEmpty(keyPath)); - - // Assign default values for omitted fields - StoreLocation storeLocation = StoreLocation.LocalMachine; // Default to Local Machine - StoreName storeName = StoreName.My; - string[] certParts = keyPath.Split('/'); - - // Validate certificate path - // Certificate path should only contain 3 parts (Certificate Location, Certificate Store Name and Thumbprint) - if (certParts.Length > 3) - { - throw SQL.InvalidCertificatePath(keyPath, GetValidCertificateLocations(), isSystemOp); - } - - // Extract the store location where the cert is stored - if (certParts.Length > 2) - { - if (string.Equals(certParts[0], _certLocationLocalMachine, StringComparison.OrdinalIgnoreCase) == true) - { - storeLocation = StoreLocation.LocalMachine; - } - else if (string.Equals(certParts[0], _certLocationCurrentUser, StringComparison.OrdinalIgnoreCase) == true) - { - storeLocation = StoreLocation.CurrentUser; - } - else - { - // throw an invalid certificate location exception - throw SQL.InvalidCertificateLocation(certParts[0], keyPath, GetValidCertificateLocations(), isSystemOp); - } - } - - // Parse the certificate store name - if (certParts.Length > 1) - { - if (string.Equals(certParts[certParts.Length - 2], _myCertificateStore, StringComparison.OrdinalIgnoreCase) == true) - { - storeName = StoreName.My; - } - else - { - // We only support storing them in My certificate store - throw SQL.InvalidCertificateStore(certParts[certParts.Length - 2], keyPath, _myCertificateStore, isSystemOp); - } - } - - // Get thumpbrint - string thumbprint = certParts[certParts.Length - 1]; - if (string.IsNullOrEmpty(thumbprint)) - { - // An empty thumbprint specified - throw SQL.EmptyCertificateThumbprint(keyPath, isSystemOp); - } - - // Find the certificate and return - return GetCertificate(storeLocation, storeName, keyPath, thumbprint, isSystemOp); - } - - /// - /// Searches for a certificate in certificate store and returns the matching certificate - /// - /// Store Location: This can be one of LocalMachine or UserName - /// Store Location: Currently this can only be My store. - /// - /// Certificate thumbprint - /// - /// Matching certificate - private X509Certificate2 GetCertificate(StoreLocation storeLocation, StoreName storeName, string masterKeyPath, string thumbprint, bool isSystemOp) - { - // Open specified certificate store - X509Store certificateStore = null; - - try - { - certificateStore = new X509Store(storeName, storeLocation); - certificateStore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); - - // Search for the specified certificate - X509Certificate2Collection matchingCertificates = - certificateStore.Certificates.Find(X509FindType.FindByThumbprint, - thumbprint, - false); - - // Throw an exception if a cert with the specified thumbprint is not found - if (matchingCertificates == null || matchingCertificates.Count == 0) - { - throw SQL.CertificateNotFound(thumbprint, storeName.ToString(), storeLocation.ToString(), isSystemOp); - } - - X509Certificate2 certificate = matchingCertificates[0]; - if (!certificate.HasPrivateKey) - { - // ensure the certificate has private key - throw SQL.CertificateWithNoPrivateKey(masterKeyPath, isSystemOp); - } - - // return the matching certificate - return certificate; - } - finally - { - // Close the certificate store - if (certificateStore != null) - { - certificateStore.Close(); - } - } - } - - /// - /// Encrypt the text using specified certificate. - /// - /// Text to encrypt. - /// Certificate object. - /// Returns an encrypted blob or throws an exception if there are any errors. - private byte[] RSAEncrypt(byte[] plainText, X509Certificate2 certificate) - { - Debug.Assert(plainText != null); - Debug.Assert(certificate != null); - Debug.Assert(certificate.HasPrivateKey, "Attempting to encrypt with cert without privatekey"); - - RSACryptoServiceProvider rscp = (RSACryptoServiceProvider)certificate.PublicKey.Key; - return rscp.Encrypt(plainText, fOAEP: true); - } - - /// - /// Encrypt the text using specified certificate. - /// - /// Text to decrypt. - /// Certificate object. - private byte[] RSADecrypt(byte[] cipherText, X509Certificate2 certificate) - { - Debug.Assert((cipherText != null) && (cipherText.Length != 0)); - Debug.Assert(certificate != null); - Debug.Assert(certificate.HasPrivateKey, "Attempting to decrypt with cert without privatekey"); - - RSACryptoServiceProvider rscp = (RSACryptoServiceProvider)certificate.PrivateKey; - return rscp.Decrypt(cipherText, fOAEP: true); - } - - /// - /// Generates signature based on RSA PKCS#v1.5 scheme using a specified certificate. - /// - /// Text to sign. - /// Certificate object. - /// Signature - private byte[] RSASignHashedData(byte[] dataToSign, X509Certificate2 certificate) - { - Debug.Assert((dataToSign != null) && (dataToSign.Length != 0)); - Debug.Assert(certificate != null); - Debug.Assert(certificate.HasPrivateKey, "Attempting to sign with cert without privatekey"); - - // Prepare RSACryptoServiceProvider from certificate's private key - RSACryptoServiceProvider rscp = GetCSPFromCertificatePrivateKey(certificate); - - // Prepare RSAPKCS1SignatureFormatter for signing the passed in hash - RSAPKCS1SignatureFormatter rsaFormatter = new RSAPKCS1SignatureFormatter(rscp); - - //Set the hash algorithm to SHA256. - rsaFormatter.SetHashAlgorithm(_hashingAlgorithm); - - //Create a signature for HashValue and return it. - return rsaFormatter.CreateSignature(dataToSign); - } - - /// - /// Verifies the given RSA PKCSv1.5 signature. - /// - /// - /// - /// - /// true if signature is valid, false if it is not valid - private bool RSAVerifySignature(byte[] dataToVerify, byte[] signature, X509Certificate2 certificate) - { - Debug.Assert((dataToVerify != null) && (dataToVerify.Length != 0)); - Debug.Assert((signature != null) && (signature.Length != 0)); - Debug.Assert(certificate != null); - Debug.Assert(certificate.HasPrivateKey, "Attempting to sign with cert without privatekey"); - - // Prepare RSACryptoServiceProvider from certificate's private key - RSACryptoServiceProvider rscp = GetCSPFromCertificatePrivateKey(certificate); - - // Prepare RSAPKCS1SignatureFormatter for signing the passed in hash - RSAPKCS1SignatureDeformatter rsaDeFormatter = new RSAPKCS1SignatureDeformatter(rscp); - - //Set the hash algorithm to SHA256. - rsaDeFormatter.SetHashAlgorithm(_hashingAlgorithm); - - //Create a signature for HashValue and return it. - return rsaDeFormatter.VerifySignature(dataToVerify, signature); - } - - /// - /// Prepares RSACryptoServiceProvider from a given certificate's private key - /// - /// - /// - private RSACryptoServiceProvider GetCSPFromCertificatePrivateKey(X509Certificate2 certificate) - { - const int rsaAesProviderType = 24; - - CspParameters privateKeyParams = new CspParameters(); - privateKeyParams = new CspParameters(); - privateKeyParams.KeyContainerName = ((RSACryptoServiceProvider)certificate.PrivateKey).CspKeyContainerInfo.KeyContainerName; - privateKeyParams.ProviderType = rsaAesProviderType /*PROV_RSA_AES*/; - privateKeyParams.KeyNumber = (int)((RSACryptoServiceProvider)certificate.PrivateKey).CspKeyContainerInfo.KeyNumber; - - // For CurrentUser store, use UseExistingKey - // For LocalMachine store, use UseMachineKeyStore - // CspKeyContainerInfo.MachineKeyStore already contains the appropriate information so just use it. - if (((RSACryptoServiceProvider)certificate.PrivateKey).CspKeyContainerInfo.MachineKeyStore) - { - privateKeyParams.Flags = CspProviderFlags.UseMachineKeyStore; - } - else - { - privateKeyParams.Flags = CspProviderFlags.UseExistingKey; - } - - return new RSACryptoServiceProvider(privateKeyParams); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.Windows.cs similarity index 94% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.Windows.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.Windows.cs index 0036fca41e..0e01b53b33 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.Windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.Windows.cs @@ -10,7 +10,7 @@ namespace Microsoft.Data.SqlClient { - /// + /// public class SqlColumnEncryptionCertificateStoreProvider : SqlColumnEncryptionKeyStoreProvider { // Constants @@ -18,7 +18,7 @@ public class SqlColumnEncryptionCertificateStoreProvider : SqlColumnEncryptionKe // Assumption: Certificate Locations (LocalMachine & CurrentUser), Certificate Store name "My" // Certificate provider name (CertificateStore) dont need to be localized. - /// + /// public const string ProviderName = @"MSSQL_CERTIFICATE_STORE"; /// @@ -56,7 +56,7 @@ public class SqlColumnEncryptionCertificateStoreProvider : SqlColumnEncryptionKe /// private readonly byte[] _version = new byte[] { 0x01 }; - /// + /// public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey) { // Validate the input parameters @@ -152,7 +152,7 @@ public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string e return RSADecrypt(cipherText, certificate); } - /// + /// public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey) { // Validate the input parameters @@ -244,7 +244,7 @@ public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string e return encryptedColumnEncryptionKey; } - /// + /// public override byte[] SignColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations) { var hash = ComputeMasterKeyMetadataHash(masterKeyPath, allowEnclaveComputations, isSystemOp: false); @@ -257,7 +257,7 @@ public override byte[] SignColumnMasterKeyMetadata(string masterKeyPath, bool al return signature; } - /// + /// public override bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations, byte[] signature) { var hash = ComputeMasterKeyMetadataHash(masterKeyPath, allowEnclaveComputations, isSystemOp: true); From 5dfaecfbfc95fac056751cc8987f582d0cd75470 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Mon, 20 May 2024 20:56:30 +0100 Subject: [PATCH 2/2] Updated to fit SqlClient coding style --- ...ryptionCertificateStoreProvider.Windows.cs | 54 ++++++++----------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.Windows.cs index 0e01b53b33..f850259311 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.Windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.Windows.cs @@ -29,27 +29,22 @@ public class SqlColumnEncryptionCertificateStoreProvider : SqlColumnEncryptionKe /// /// LocalMachine certificate store location. Valid certificate locations are LocalMachine and CurrentUser. /// - private const string _certLocationLocalMachine = @"LocalMachine"; + private const string CertLocationLocalMachine = @"LocalMachine"; /// /// CurrentUser certificate store location. Valid certificate locations are LocalMachine and CurrentUser. /// - private const string _certLocationCurrentUser = @"CurrentUser"; + private const string CertLocationCurrentUser = @"CurrentUser"; /// /// Valid certificate store /// - private const string _myCertificateStore = @"My"; - - /// - /// Certificate path format. This is a custom format. - /// - private const string _certificatePathFormat = @"[LocalMachine|CurrentUser]/My/[Thumbprint]"; + private const string MyCertificateStore = @"My"; /// /// Hashing algorithm used for signing /// - private const string _hashingAlgorithm = @"SHA256"; + private const string HashingAlgorithm = @"SHA256"; /// /// Algorithm version @@ -98,12 +93,12 @@ public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string e // Get key path length int currentIndex = _version.Length; - Int16 keyPathLength = BitConverter.ToInt16(encryptedColumnEncryptionKey, currentIndex); - currentIndex += sizeof(Int16); + short keyPathLength = BitConverter.ToInt16(encryptedColumnEncryptionKey, currentIndex); + currentIndex += sizeof(short); // Get ciphertext length int cipherTextLength = BitConverter.ToInt16(encryptedColumnEncryptionKey, currentIndex); - currentIndex += sizeof(Int16); + currentIndex += sizeof(short); // Skip KeyPath // KeyPath exists only for troubleshooting purposes and doesnt need validation. @@ -176,7 +171,7 @@ public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string e X509Certificate2 certificate = GetCertificateByPath(masterKeyPath, isSystemOp: false); RSA RSAPublicKey = certificate.GetRSAPublicKey(); - int keySizeInBytes= RSAPublicKey.KeySize / 8; + int keySizeInBytes = RSAPublicKey.KeySize / 8; // Construct the encryptedColumnEncryptionKey // Format is @@ -185,13 +180,13 @@ public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string e // We currently only support one version byte[] version = new byte[] { _version[0] }; - // Get the Unicode encoded bytes of cultureinvariant lower case masterKeyPath + // Get the Unicode encoded bytes of culture invariant lower case masterKeyPath byte[] masterKeyPathBytes = Encoding.Unicode.GetBytes(masterKeyPath.ToLowerInvariant()); - byte[] keyPathLength = BitConverter.GetBytes((Int16)masterKeyPathBytes.Length); + byte[] keyPathLength = BitConverter.GetBytes((short)masterKeyPathBytes.Length); // Encrypt the plain text byte[] cipherText = RSAEncrypt(columnEncryptionKey, certificate); - byte[] cipherTextLength = BitConverter.GetBytes((Int16)cipherText.Length); + byte[] cipherTextLength = BitConverter.GetBytes((short)cipherText.Length); Debug.Assert(cipherText.Length == keySizeInBytes, @"cipherText length does not match the RSA key size"); // Compute hash @@ -247,7 +242,7 @@ public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string e /// public override byte[] SignColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations) { - var hash = ComputeMasterKeyMetadataHash(masterKeyPath, allowEnclaveComputations, isSystemOp: false); + byte[] hash = ComputeMasterKeyMetadataHash(masterKeyPath, allowEnclaveComputations, isSystemOp: false); // Parse the certificate path and get the X509 cert X509Certificate2 certificate = GetCertificateByPath(masterKeyPath, isSystemOp: false); @@ -260,7 +255,7 @@ public override byte[] SignColumnMasterKeyMetadata(string masterKeyPath, bool al /// public override bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations, byte[] signature) { - var hash = ComputeMasterKeyMetadataHash(masterKeyPath, allowEnclaveComputations, isSystemOp: true); + byte[] hash = ComputeMasterKeyMetadataHash(masterKeyPath, allowEnclaveComputations, isSystemOp: true); // Parse the certificate path and get the X509 cert X509Certificate2 certificate = GetCertificateByPath(masterKeyPath, isSystemOp: true); @@ -318,9 +313,9 @@ private void ValidateEncryptionAlgorithm(string encryptionAlgorithm, bool isSyst /// private void ValidateCertificatePathLength(string masterKeyPath, bool isSystemOp) { - if (masterKeyPath.Length >= Int16.MaxValue) + if (masterKeyPath.Length >= short.MaxValue) { - throw SQL.LargeCertificatePathLength(masterKeyPath.Length, Int16.MaxValue, isSystemOp); + throw SQL.LargeCertificatePathLength(masterKeyPath.Length, short.MaxValue, isSystemOp); } } @@ -329,7 +324,7 @@ private void ValidateCertificatePathLength(string masterKeyPath, bool isSystemOp /// private string[] GetValidCertificateLocations() { - return new string[2] { _certLocationLocalMachine, _certLocationCurrentUser }; + return new string[2] { CertLocationLocalMachine, CertLocationCurrentUser }; } /// @@ -377,11 +372,11 @@ private X509Certificate2 GetCertificateByPath(string keyPath, bool isSystemOp) // Extract the store location where the cert is stored if (certParts.Length > 2) { - if (string.Equals(certParts[0], _certLocationLocalMachine, StringComparison.OrdinalIgnoreCase) == true) + if (string.Equals(certParts[0], CertLocationLocalMachine, StringComparison.OrdinalIgnoreCase) == true) { storeLocation = StoreLocation.LocalMachine; } - else if (string.Equals(certParts[0], _certLocationCurrentUser, StringComparison.OrdinalIgnoreCase) == true) + else if (string.Equals(certParts[0], CertLocationCurrentUser, StringComparison.OrdinalIgnoreCase) == true) { storeLocation = StoreLocation.CurrentUser; } @@ -395,14 +390,14 @@ private X509Certificate2 GetCertificateByPath(string keyPath, bool isSystemOp) // Parse the certificate store name if (certParts.Length > 1) { - if (string.Equals(certParts[certParts.Length - 2], _myCertificateStore, StringComparison.OrdinalIgnoreCase) == true) + if (string.Equals(certParts[certParts.Length - 2], MyCertificateStore, StringComparison.OrdinalIgnoreCase) == true) { storeName = StoreName.My; } else { // We only support storing them in My certificate store - throw SQL.InvalidCertificateStore(certParts[certParts.Length - 2], keyPath, _myCertificateStore, isSystemOp); + throw SQL.InvalidCertificateStore(certParts[certParts.Length - 2], keyPath, MyCertificateStore, isSystemOp); } } @@ -462,10 +457,7 @@ private X509Certificate2 GetCertificate(StoreLocation storeLocation, StoreName s finally { // Close the certificate store - if (certificateStore != null) - { - certificateStore.Close(); - } + certificateStore?.Close(); } } @@ -518,7 +510,7 @@ private byte[] RSASignHashedData(byte[] dataToSign, X509Certificate2 certificate // Prepare RSAPKCS1SignatureFormatter for signing the passed in hash RSAPKCS1SignatureFormatter rsaFormatter = new RSAPKCS1SignatureFormatter(rsa); //Set the hash algorithm to SHA256. - rsaFormatter.SetHashAlgorithm(_hashingAlgorithm); + rsaFormatter.SetHashAlgorithm(HashingAlgorithm); //Create a signature for HashValue and return it. return rsaFormatter.CreateSignature(dataToSign); @@ -544,7 +536,7 @@ private bool RSAVerifySignature(byte[] dataToVerify, byte[] signature, X509Certi RSAPKCS1SignatureDeformatter rsaDeFormatter = new RSAPKCS1SignatureDeformatter(rsa); //Set the hash algorithm to SHA256. - rsaDeFormatter.SetHashAlgorithm(_hashingAlgorithm); + rsaDeFormatter.SetHashAlgorithm(HashingAlgorithm); //Create a signature for HashValue and return it. return rsaDeFormatter.VerifySignature(dataToVerify, signature);