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);