diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/ColumnMasterKeyMetadata.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/ColumnMasterKeyMetadata.cs index 32663eb1a1..3f9e7fad2d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/ColumnMasterKeyMetadata.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/ColumnMasterKeyMetadata.cs @@ -32,7 +32,7 @@ namespace Microsoft.Data.SqlClient.AlwaysEncrypted; /// This takes ownership of the RSA instance supplied to it, disposing of it when Dispose is called. /// /// -internal readonly ref struct ColumnMasterKeyMetadata // : IDisposable +internal readonly ref struct ColumnMasterKeyMetadata : IDisposable { private static readonly HashAlgorithmName s_hashAlgorithm = HashAlgorithmName.SHA256; @@ -49,7 +49,6 @@ private struct Sha256Hash #endif private readonly RSA _rsa; - // @TODO: SqlColumnEncryptionCertificateStoreProvider.SignMasterKeyMetadata and .VerifyMasterKeyMetadata should use this type. /// /// Represents metadata associated with a column master key, including its cryptographic hash, path, provider name, /// and enclave computation settings. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptedColumnEncryptionKeyParameters.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptedColumnEncryptionKeyParameters.cs index 4abfa62abd..d1310e7b07 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptedColumnEncryptionKeyParameters.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptedColumnEncryptionKeyParameters.cs @@ -41,7 +41,7 @@ namespace Microsoft.Data.SqlClient.AlwaysEncrypted; /// This takes ownership of the RSA instance supplied to it, disposing of it when Dispose is called. /// /// -internal readonly ref struct EncryptedColumnEncryptionKeyParameters // : IDisposable +internal readonly ref struct EncryptedColumnEncryptionKeyParameters : IDisposable { private const byte AlgorithmVersion = 0x01; @@ -64,7 +64,6 @@ namespace Microsoft.Data.SqlClient.AlwaysEncrypted; private readonly string _keyType; private readonly string _keyPathReference; - // @TODO: SqlColumnEncryptionCertificateStoreProvider, SqlColumnEncryptionCngProvider and SqlColumnEncryptionCspProvider should use this type. /// /// Initializes a new instance of the struct with the specified /// RSA key, key path, key type, and key path reference. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs index 09bcd852dd..b02dd24af1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs @@ -3,10 +3,12 @@ // See the LICENSE file in the project root for more information. using System; -using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; -using System.Text; +using Microsoft.Data.SqlClient.AlwaysEncrypted; + +#nullable enable namespace Microsoft.Data.SqlClient { @@ -16,7 +18,7 @@ public class SqlColumnEncryptionCertificateStoreProvider : SqlColumnEncryptionKe // Constants // // Assumption: Certificate Locations (LocalMachine & CurrentUser), Certificate Store name "My" - // Certificate provider name (CertificateStore) dont need to be localized. + // Certificate provider name (CertificateStore) don't need to be localized. /// public const string ProviderName = @"MSSQL_CERTIFICATE_STORE"; @@ -37,12 +39,12 @@ public class SqlColumnEncryptionCertificateStoreProvider : SqlColumnEncryptionKe internal const string RSAEncryptionAlgorithmWithOAEP = @"RSA_OAEP"; /// - /// LocalMachine certificate store location. Valid certificate locations are LocalMachine and CurrentUser. + /// LocalMachine certificate store location. Valid certificate locations are LocalMachine (on Windows) and CurrentUser. /// private const string CertLocationLocalMachine = @"LocalMachine"; /// - /// CurrentUser certificate store location. Valid certificate locations are LocalMachine and CurrentUser. + /// CurrentUser certificate store location. Valid certificate locations are LocalMachine (on Windows) and CurrentUser. /// private const string CertLocationCurrentUser = @"CurrentUser"; @@ -52,249 +54,12 @@ public class SqlColumnEncryptionCertificateStoreProvider : SqlColumnEncryptionKe private const string MyCertificateStore = @"My"; /// - /// Hashing algorithm used for signing - /// - private const string HashingAlgorithm = @"SHA256"; - - /// - /// Algorithm version + /// Gets a string array containing valid certificate locations. /// - 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 (encryptedColumnEncryptionKey == null) - { - 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); - - RSA RSAPublicKey = certificate.GetRSAPublicKey(); - int keySizeInBytes= RSAPublicKey.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; - short keyPathLength = BitConverter.ToInt16(encryptedColumnEncryptionKey, currentIndex); - currentIndex += sizeof(short); - - // Get ciphertext length - int cipherTextLength = BitConverter.ToInt16(encryptedColumnEncryptionKey, currentIndex); - currentIndex += sizeof(short); - - // Skip KeyPath - // KeyPath exists only for troubleshooting purposes and doesnt need validation. - currentIndex += keyPathLength; - - // validate the ciphertext length - if (cipherTextLength != keySizeInBytes) - { - throw SQL.InvalidCiphertextLengthInEncryptedCEKCertificate(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.InvalidSignatureInEncryptedCEKCertificate(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 (SHA256 sha256 = SHA256.Create()) - { - 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 (columnEncryptionKey == null) - { - 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); - - RSA RSAPublicKey = certificate.GetRSAPublicKey(); - int keySizeInBytes = RSAPublicKey.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 culture invariant lower case masterKeyPath - byte[] masterKeyPathBytes = Encoding.Unicode.GetBytes(masterKeyPath.ToLowerInvariant()); - byte[] keyPathLength = BitConverter.GetBytes((short)masterKeyPathBytes.Length); - - // Encrypt the plain text - byte[] cipherText = RSAEncrypt(columnEncryptionKey, certificate); - byte[] cipherTextLength = BitConverter.GetBytes((short)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 (SHA256 sha256 = SHA256.Create()) - { - 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) - { - byte[] 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) - { - byte[] 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 (SHA256 sha256 = SHA256.Create()) - { - sha256.TransformFinalBlock(masterkeyMetadataBytes, 0, masterkeyMetadataBytes.Length); - hash = sha256.Hash; - } - return hash; - } + private static string[] ValidCertificateLocations => + Environment.OSVersion.Platform == PlatformID.Win32NT + ? [CertLocationLocalMachine, CertLocationCurrentUser] + : [CertLocationCurrentUser]; /// /// This function validates that the encryption algorithm is RSA_OAEP and if it is not, @@ -302,261 +67,260 @@ private byte[] ComputeMasterKeyMetadataHash(string masterKeyPath, bool allowEncl /// /// Asymmetric key encryption algorithm /// - private void ValidateEncryptionAlgorithm(string encryptionAlgorithm, bool isSystemOp) + private static void ValidateEncryptionAlgorithm([NotNull] string? encryptionAlgorithm, bool isSystemOp) { // This validates that the encryption algorithm is RSA_OAEP - if (encryptionAlgorithm == null) + if (encryptionAlgorithm is null) { throw SQL.NullKeyEncryptionAlgorithm(isSystemOp); } - if (string.Equals(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, StringComparison.OrdinalIgnoreCase) != true) + if (!string.Equals(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, StringComparison.OrdinalIgnoreCase)) { throw SQL.InvalidKeyEncryptionAlgorithm(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, isSystemOp); } } /// - /// Certificate path length has to fit in two bytes, so check its value against Int16.MaxValue + /// Checks if the certificate path is Empty, Null or larger than short.MaxValue characters (and raises exception if they are). /// - /// - /// - private void ValidateCertificatePathLength(string masterKeyPath, bool isSystemOp) + private static void ValidateCertificatePathLength([NotNull] string? masterKeyPath, bool isSystemOp) { - if (masterKeyPath.Length >= short.MaxValue) + if (masterKeyPath is null) { - throw SQL.LargeCertificatePathLength(masterKeyPath.Length, short.MaxValue, isSystemOp); + throw SQL.NullCertificatePath(ValidCertificateLocations, isSystemOp); } - } - - /// - /// Gets a string array containing Valid certificate locations. - /// - private string[] GetValidCertificateLocations() - { - return Environment.OSVersion.Platform == PlatformID.Win32NT - ? new string[2] { CertLocationLocalMachine, CertLocationCurrentUser } - : new string[1] { 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 (masterKeyPath == null) - { - throw SQL.NullCertificatePath(GetValidCertificateLocations(), isSystemOp); - } - else - { - throw SQL.InvalidCertificatePath(masterKeyPath, GetValidCertificateLocations(), isSystemOp); - } + throw SQL.InvalidCertificatePath(masterKeyPath, ValidCertificateLocations, isSystemOp); + } + + if (masterKeyPath.Length >= short.MaxValue) + { + throw SQL.LargeCertificatePathLength(masterKeyPath.Length, short.MaxValue, isSystemOp); } } /// - /// Parses the given certificate path, searches in certificate store and returns a matching certificate + /// Parses the given certificate path, searches in certificate store and returns a matching certificate's private key /// /// - /// Certificate key path. Format of the path is [LocalMachine|CurrentUser]/[storename]/thumbprint + /// 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) + /// Returns the private key of the certificate identified by the certificate path + private static RSA GetCertificatePrivateKeyByPath(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('/'); + StoreLocation storeLocation; + StoreName storeName; + // Convert keyPath to a span and slice based on the existence/position of separators. While string.Split('/') would also + // suffice, using Span.Slice avoids allocating a string[] and its contents - the first two components will be mapped to + // an enum value anyway. + ReadOnlySpan keyPathSpan = keyPath.AsSpan(); + int firstSeparator = keyPathSpan.IndexOf('/'); + ReadOnlySpan trailingFirstSeparator = firstSeparator == -1 ? default : keyPathSpan.Slice(firstSeparator + 1); + int secondSeparator = firstSeparator == -1 ? -1 : trailingFirstSeparator.IndexOf('/'); + ReadOnlySpan trailingSecondSeparator = secondSeparator == -1 ? default : trailingFirstSeparator.Slice(secondSeparator + 1); + int subsequentSeparators = secondSeparator == -1 ? -1 : trailingSecondSeparator.IndexOf('/'); // Validate certificate path // Certificate path should only contain 3 parts (Certificate Location, Certificate Store Name and Thumbprint) - if (certParts.Length > 3) + // We know there are more than three parts if there are three or more separators + if (subsequentSeparators != -1) { - throw SQL.InvalidCertificatePath(keyPath, GetValidCertificateLocations(), isSystemOp); + throw SQL.InvalidCertificatePath(keyPath, ValidCertificateLocations, isSystemOp); + } + + ReadOnlySpan storeLocationSpan = default; + ReadOnlySpan storeNameSpan = default; + ReadOnlySpan thumbprintSpan; + + // Extract the store location where the cert is stored. This can be in one of the following formats: + // * [Location]/[StoreName]/[Thumbprint] (firstSeparator and secondSeparator are not -1) + // * [StoreName]/[Thumbprint] (firstSeparator is not -1, secondSeparator is -1) + // * [Thumbprint] (general case) + if (secondSeparator != -1) + { + // There are two separators (and thus, three parts) + storeLocationSpan = keyPathSpan.Slice(0, firstSeparator); + storeNameSpan = trailingFirstSeparator.Slice(0, secondSeparator); + thumbprintSpan = trailingSecondSeparator; + } + else if (firstSeparator != -1) + { + storeNameSpan = keyPathSpan.Slice(0, firstSeparator); + thumbprintSpan = trailingFirstSeparator; + } + else + { + thumbprintSpan = keyPathSpan; } // Extract the store location where the cert is stored - if (certParts.Length > 2) + if (storeLocationSpan.IsEmpty + && Environment.OSVersion.Platform == PlatformID.Win32NT) + { + // Default to Local Machine on Windows. Non-Windows platforms only support CurrentUser + storeLocation = StoreLocation.LocalMachine; + } + else if (storeLocationSpan.Equals(CertLocationLocalMachine.AsSpan(), StringComparison.OrdinalIgnoreCase) + && Environment.OSVersion.Platform == PlatformID.Win32NT) { - if (string.Equals(certParts[0], CertLocationLocalMachine, StringComparison.OrdinalIgnoreCase) == true - && Environment.OSVersion.Platform == PlatformID.Win32NT) - { - 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); - } + storeLocation = StoreLocation.LocalMachine; + } + else if (storeLocationSpan.Equals(CertLocationCurrentUser.AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + storeLocation = StoreLocation.CurrentUser; + } + else + { + // Throw an invalid certificate location exception + throw SQL.InvalidCertificateLocation(storeLocationSpan.ToString(), keyPath, ValidCertificateLocations, isSystemOp); } // Parse the certificate store name - if (certParts.Length > 1) + if (storeNameSpan.IsEmpty) + { + // Default to My certificate store + storeName = StoreName.My; + } + else if (storeNameSpan.Equals(MyCertificateStore.AsSpan(), StringComparison.OrdinalIgnoreCase)) { - 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); - } + storeName = StoreName.My; + } + else + { + // We only support storing them in My certificate store + throw SQL.InvalidCertificateStore(storeNameSpan.ToString(), keyPath, MyCertificateStore, isSystemOp); } - // Get thumpbrint - string thumbprint = certParts[certParts.Length - 1]; - if (string.IsNullOrEmpty(thumbprint)) + // Get thumbprint + if (thumbprintSpan.IsEmpty) { // An empty thumbprint specified throw SQL.EmptyCertificateThumbprint(keyPath, isSystemOp); } // Find the certificate and return - return GetCertificate(storeLocation, storeName, keyPath, thumbprint, isSystemOp); + return GetCertificatePrivateKey(storeLocation, storeName, keyPath, thumbprintSpan, isSystemOp); } /// - /// Searches for a certificate in certificate store and returns the matching certificate + /// Searches for a certificate in certificate store and returns the matching certificate's private key /// /// 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) + /// Matching certificate's private key + private static RSA GetCertificatePrivateKey(StoreLocation storeLocation, StoreName storeName, string masterKeyPath, ReadOnlySpan thumbprint, bool isSystemOp) { // Open specified certificate store - X509Store certificateStore = null; + using X509Store certificateStore = new(storeName, storeLocation); + certificateStore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); + + // Search for the specified certificate + X509Certificate2Collection matchingCertificates = + certificateStore.Certificates.Find(X509FindType.FindByThumbprint, + thumbprint.ToString(), + false); - try + // Throw an exception if a cert with the specified thumbprint is not found + if (matchingCertificates == null || matchingCertificates.Count == 0) { - 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; + throw SQL.CertificateNotFound(thumbprint.ToString(), storeName.ToString(), storeLocation.ToString(), isSystemOp); } - finally + + using X509Certificate2 certificate = matchingCertificates[0]; + if (!certificate.HasPrivateKey) { - // Close the certificate store - certificateStore?.Close(); + // Ensure the certificate has private key + throw SQL.CertificateWithNoPrivateKey(masterKeyPath, isSystemOp); } + + // Return the matching certificate's private key. Throw an exception if the private key is not RSA + return certificate.GetRSAPrivateKey() + ?? throw SQL.CertificateWithNoPrivateKey(masterKeyPath, isSystemOp); } - /// - /// 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) + /// + public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey) { - Debug.Assert(plainText != null); - Debug.Assert(certificate != null); - Debug.Assert(certificate.HasPrivateKey, "Attempting to encrypt with cert without privatekey"); - - RSA rsa = certificate.GetRSAPublicKey(); - - // CodeQL [SM03796] Required for an external standard: Always Encrypted only supports encrypting column encryption keys with RSA_OAEP(SHA1) (https://learn.microsoft.com/en-us/sql/t-sql/statements/create-column-encryption-key-transact-sql?view=sql-server-ver16) - return rsa.Encrypt(plainText, RSAEncryptionPadding.OaepSHA1); + // Validate the input parameters + ValidateCertificatePathLength(masterKeyPath, isSystemOp: true); + + if (encryptedColumnEncryptionKey is null) + { + throw SQL.NullEncryptedColumnEncryptionKey(); + } + else if (encryptedColumnEncryptionKey.Length == 0) + { + throw SQL.EmptyEncryptedColumnEncryptionKey(); + } + + // Validate encryptionAlgorithm + ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true); + + // Parse the path and get the X509 cert + RSA rsaPrivateKey = GetCertificatePrivateKeyByPath(masterKeyPath, isSystemOp: true); + using EncryptedColumnEncryptionKeyParameters cekHandler = new(rsaPrivateKey, masterKeyPath, MasterKeyType, KeyPathReference); + + return cekHandler.Decrypt(encryptedColumnEncryptionKey); } - /// - /// Decrypt the data using specified certificate. - /// - /// Text to decrypt. - /// Certificate object. - /// Returns a decrypted blob or throws an exception if there are any errors. - private byte[] RSADecrypt(byte[] cipherText, X509Certificate2 certificate) + /// + public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey) { - Debug.Assert((cipherText != null) && (cipherText.Length != 0)); - Debug.Assert(certificate != null); - Debug.Assert(certificate.HasPrivateKey, "Attempting to decrypt with cert without privatekey"); - - RSA rsa = certificate.GetRSAPrivateKey(); - - // CodeQL [SM03796] Required for an external standard: Always Encrypted only supports encrypting column encryption keys with RSA_OAEP(SHA1) (https://learn.microsoft.com/en-us/sql/t-sql/statements/create-column-encryption-key-transact-sql?view=sql-server-ver16) - return rsa.Decrypt(cipherText, RSAEncryptionPadding.OaepSHA1); + // Validate the input parameters + ValidateCertificatePathLength(masterKeyPath, isSystemOp: false); + if (columnEncryptionKey is null) + { + throw SQL.NullColumnEncryptionKey(); + } + else if (columnEncryptionKey.Length == 0) + { + throw SQL.EmptyColumnEncryptionKey(); + } + + // Validate encryptionAlgorithm + ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: false); + + // Parse the certificate path and get the X509 cert + RSA rsaPrivateKey = GetCertificatePrivateKeyByPath(masterKeyPath, isSystemOp: false); + + using EncryptedColumnEncryptionKeyParameters cekBuilder = new(rsaPrivateKey, masterKeyPath, MasterKeyType, KeyPathReference); + + return cekBuilder.Encrypt(columnEncryptionKey); } - /// - /// 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) + /// + public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations) { - Debug.Assert((dataToSign != null) && (dataToSign.Length != 0)); - Debug.Assert(certificate != null); - Debug.Assert(certificate.HasPrivateKey, "Attempting to sign with cert without privatekey"); + // Validate the input parameters + ValidateCertificatePathLength(masterKeyPath, isSystemOp: false); - RSA rsa = certificate.GetRSAPrivateKey(); + // Parse the certificate path and get the X509 cert + RSA rsaPrivateKey = GetCertificatePrivateKeyByPath(masterKeyPath, isSystemOp: false); - // Prepare RSAPKCS1SignatureFormatter for signing the passed in hash - RSAPKCS1SignatureFormatter rsaFormatter = new RSAPKCS1SignatureFormatter(rsa); - //Set the hash algorithm to SHA256. - rsaFormatter.SetHashAlgorithm(HashingAlgorithm); + using ColumnMasterKeyMetadata cmkSigner = new(rsaPrivateKey, masterKeyPath, ProviderName, allowEnclaveComputations); - //Create a signature for HashValue and return it. - return rsaFormatter.CreateSignature(dataToSign); + return cmkSigner.Sign(); } - /// - /// 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) + /// + public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[] signature) { - 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"); - - RSA rsa = certificate.GetRSAPrivateKey(); + // Validate the input parameters + ValidateCertificatePathLength(masterKeyPath, isSystemOp: false); - // Prepare RSAPKCS1SignatureFormatter for signing the passed in hash - RSAPKCS1SignatureDeformatter rsaDeFormatter = new RSAPKCS1SignatureDeformatter(rsa); + // Parse the certificate path and get the X509 cert + RSA rsaPrivateKey = GetCertificatePrivateKeyByPath(masterKeyPath, isSystemOp: true); - //Set the hash algorithm to SHA256. - rsaDeFormatter.SetHashAlgorithm(HashingAlgorithm); + using ColumnMasterKeyMetadata cmkVerifier = new(rsaPrivateKey, masterKeyPath, ProviderName, allowEnclaveComputations); - //Create a signature for HashValue and return it. - return rsaDeFormatter.VerifySignature(dataToVerify, signature); + return cmkVerifier.Verify(signature); } } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs index 96232de279..a09a8a644c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -1783,31 +1783,16 @@ internal static Exception InvalidCiphertextLengthInEncryptedCEK(string keyType, return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidCiphertextLengthInEncryptedCEK, actual, expected, keyType, masterKeyPath, keyPathReference), TdsEnums.TCE_PARAM_ENCRYPTED_CEK); } - internal static Exception InvalidCiphertextLengthInEncryptedCEKCertificate(int actual, int expected, string certificateName) - { - return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidCiphertextLengthInEncryptedCEKCertificate, actual, expected, certificateName), TdsEnums.TCE_PARAM_ENCRYPTED_CEK); - } - internal static Exception InvalidSignatureInEncryptedCEK(string keyType, string keyPathReference, int actual, int expected, string masterKeyPath) { return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidSignatureInEncryptedCEK, actual, expected, keyType, masterKeyPath, keyPathReference), TdsEnums.TCE_PARAM_ENCRYPTED_CEK); } - internal static Exception InvalidSignatureInEncryptedCEKCertificate(int actual, int expected, string masterKeyPath) - { - return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidSignatureInEncryptedCEKCertificate, actual, expected, masterKeyPath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK); - } - internal static Exception InvalidSignature(string masterKeyPath, string keyType) { return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidSignature, keyType, masterKeyPath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK); } - internal static Exception InvalidCertificateSignature(string certificatePath) - { - return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidCertificateSignature, certificatePath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK); - } - internal static Exception CertificateWithNoPrivateKey(string keyPath, bool isSystemOp) { if (isSystemOp) diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs index 50132bed20..10dbae761d 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs @@ -12656,15 +12656,6 @@ internal static string TCE_InvalidCertificatePathSysErr_Unix return ResourceManager.GetString("TCE_InvalidCertificatePathSysErr_Unix", resourceCulture); } } - - /// - /// Looks up a localized string similar to The specified encrypted column encryption key signature does not match the signature computed with the column master key (certificate) in '{0}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.. - /// - internal static string TCE_InvalidCertificateSignature { - get { - return ResourceManager.GetString("TCE_InvalidCertificateSignature", resourceCulture); - } - } /// /// Looks up a localized string similar to Invalid certificate store '{0}' specified in certificate path '{1}'. Expected value: '{2}'.. @@ -12692,15 +12683,6 @@ internal static string TCE_InvalidCiphertextLengthInEncryptedCEK { return ResourceManager.GetString("TCE_InvalidCiphertextLengthInEncryptedCEK", resourceCulture); } } - - /// - /// Looks up a localized string similar to The specified encrypted column encryption key's ciphertext length: {0} does not match the ciphertext length: {1} when using column master key (certificate) in '{2}'. The encrypted column encryption key may be corrupt, or the specified certificate path may be incorrect.. - /// - internal static string TCE_InvalidCiphertextLengthInEncryptedCEKCertificate { - get { - return ResourceManager.GetString("TCE_InvalidCiphertextLengthInEncryptedCEKCertificate", resourceCulture); - } - } /// /// Looks up a localized string similar to Specified ciphertext has an invalid size of {0} bytes, which is below the minimum {1} bytes required for decryption.. @@ -12908,15 +12890,6 @@ internal static string TCE_InvalidSignatureInEncryptedCEK { return ResourceManager.GetString("TCE_InvalidSignatureInEncryptedCEK", resourceCulture); } } - - /// - /// Looks up a localized string similar to The specified encrypted column encryption key's signature length: {0} does not match the signature length: {1} when using column master key (certificate) in '{2}'. The encrypted column encryption key may be corrupt, or the specified certificate path may be incorrect.. - /// - internal static string TCE_InvalidSignatureInEncryptedCEKCertificate { - get { - return ResourceManager.GetString("TCE_InvalidSignatureInEncryptedCEKCertificate", resourceCulture); - } - } /// /// Looks up a localized string similar to Failed to decrypt a column encryption key using key store provider: '{0}'. Verify the properties of the column encryption key and its column master key in your database. The last 10 bytes of the encrypted column encryption key are: '{1}'.. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx index 75f3c52874..bb81f1c357 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx @@ -4077,21 +4077,12 @@ The specified encrypted column encryption key's ciphertext length: {0} does not match the ciphertext length: {1} when using column master key ({2}) in '{3}'. The encrypted column encryption key may be corrupt, or the specified {4} path may be incorrect. - - The specified encrypted column encryption key's ciphertext length: {0} does not match the ciphertext length: {1} when using column master key (certificate) in '{2}'. The encrypted column encryption key may be corrupt, or the specified certificate path may be incorrect. - The specified encrypted column encryption key's signature length: {0} does not match the signature length: {1} when using column master key ({2}) in '{3}'. The encrypted column encryption key may be corrupt, or the specified {4} path may be incorrect. - - The specified encrypted column encryption key's signature length: {0} does not match the signature length: {1} when using column master key (certificate) in '{2}'. The encrypted column encryption key may be corrupt, or the specified certificate path may be incorrect. - The specified encrypted column encryption key signature does not match the signature computed with the column master key ({0}) in '{1}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect. - - The specified encrypted column encryption key signature does not match the signature computed with the column master key (certificate) in '{0}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect. - Certificate specified in key path '{0}' does not have a private key to encrypt a column encryption key. Verify the certificate is imported correctly. diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CoreCryptoTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CoreCryptoTests.cs index 245e1cbd67..fafd721522 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CoreCryptoTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CoreCryptoTests.cs @@ -1,10 +1,11 @@ -using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; -using System; +using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using System.Security.Cryptography.X509Certificates; using System.Text; using Microsoft.Data.SqlClient; -using System.Diagnostics; +using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted @@ -49,6 +50,8 @@ public void TestAeadCryptoWithNativeBaseline() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public void TestRsaCryptoWithNativeBaseline() { + SqlColumnEncryptionCertificateStoreProvider rsaProvider = new(); + // Initialize the reader for resource text file which has the native code generated baseline. CryptoNativeBaselineReader cryptoNativeBaselineReader = new CryptoNativeBaselineReader(); @@ -64,23 +67,50 @@ public void TestRsaCryptoWithNativeBaseline() byte[] rsaKeyPair = cryptoParametersListForTest[0].RsaKeyPair; byte[] rsaPfx = cryptoParametersListForTest[1].RsaKeyPair; - // For each crypto vector, run the test to compare the output generated through sqlclient's code and the native code. - foreach (CryptoVector cryptoParameter in cryptoParametersListForTest) + // Convert the PFX into a certificate and install it into the local user's certificate store. + // We can only do this cross-platform on the CurrentUser store, which matches the baseline data we have. + Assert.NotNull(rsaPfx); + Assert.NotEmpty(rsaPfx); + + X509Store store = null; + bool addedToStore = false; +#if NET9_0_OR_GREATER + using X509Certificate2 x509 = X509CertificateLoader.LoadPkcs12(rsaPfx, @"P@zzw0rD!SqlvN3x+"); +#else + using X509Certificate2 x509 = new(rsaPfx, @"P@zzw0rD!SqlvN3x+"); +#endif + Assert.True(x509.HasPrivateKey); + + try { - if (cryptoParameter.CryptNativeTestVectorTypeVal == CryptNativeTestVectorType.Rsa) - { - // Verify that we are using the right padding scheme for RSA encryption - byte[] plaintext = CertificateUtility.DecryptRsaDirectly(rsaPfx, cryptoParameter.CiphertextCek, @"Test"); - Assert.True(cryptoParameter.PlaintextCek.SequenceEqual(plaintext), "Plaintext CEK Value does not match with the native code baseline."); + store = new(StoreName.My, StoreLocation.CurrentUser); + store.Open(OpenFlags.ReadWrite); - // Verify that the signed blob is conforming to our envelope (SHA-256, PKCS 1 padding) - bool signatureVerified = CertificateUtility.VerifyRsaSignatureDirectly(cryptoParameter.HashedCek, cryptoParameter.SignedCek, rsaPfx); - Assert.True(signatureVerified, "Plaintext CEK signature scheme does not match with the native code baseline."); + store.Add(x509); + addedToStore = true; - //// TODO: Programmatically install the in-memory PFX into the right store (based on path) & use the public API - //plaintext = Utility.VerifyRsaSignature(cryptoParameter.PathCek, cryptoParameter.FinalcellCek, rsaPfx); - //CError.Compare(cryptoParameter.PlaintextCek.SequenceEqual(plaintext), "Plaintext CEK Value does not match with the native code baseline (end to end)."); + // For each crypto vector, run the test to compare the output generated through sqlclient's code and the native code. + foreach (CryptoVector cryptoParameter in cryptoParametersListForTest) + { + if (cryptoParameter.CryptNativeTestVectorTypeVal == CryptNativeTestVectorType.Rsa) + { + Assert.NotNull(cryptoParameter.PathCek); + Assert.StartsWith("CurrentUser/My", cryptoParameter.PathCek); + + // Decrypt the supplied final cell CEK, and ensure that the plaintext CEK value matches the native code baseline. + byte[] plaintext = rsaProvider.DecryptColumnEncryptionKey(cryptoParameter.PathCek, "RSA_OAEP", cryptoParameter.FinalcellCek); + Assert.Equal(cryptoParameter.PlaintextCek, plaintext); + } + } + } + finally + { + if (addedToStore) + { + store.Remove(x509); } + + store?.Dispose(); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs index 7e564362a5..50a31ef2fd 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs @@ -33,9 +33,6 @@ private CertificateUtility() public static MethodInfo sqlAeadAes256CbcHmac256FactoryCreate = sqlAeadAes256CbcHmac256Factory.GetMethod("Create", BindingFlags.Instance | BindingFlags.NonPublic); public static Type sqlClientEncryptionAlgorithm = systemData.GetType("Microsoft.Data.SqlClient.SqlClientEncryptionAlgorithm"); public static MethodInfo sqlClientEncryptionAlgorithmEncryptData = sqlClientEncryptionAlgorithm.GetMethod("EncryptData", BindingFlags.Instance | BindingFlags.NonPublic); - public static Type SqlColumnEncryptionCertificateStoreProvider = systemData.GetType("Microsoft.Data.SqlClient.SqlColumnEncryptionCertificateStoreProvider"); - public static MethodInfo SqlColumnEncryptionCertificateStoreProviderRSADecrypt = SqlColumnEncryptionCertificateStoreProvider.GetMethod("RSADecrypt", BindingFlags.Instance | BindingFlags.NonPublic); - public static MethodInfo SqlColumnEncryptionCertificateStoreProviderRSAVerifySignature = SqlColumnEncryptionCertificateStoreProvider.GetMethod("RSAVerifySignature", BindingFlags.Instance | BindingFlags.NonPublic); public static MethodInfo sqlClientEncryptionAlgorithmDecryptData = sqlClientEncryptionAlgorithm.GetMethod("DecryptData", BindingFlags.Instance | BindingFlags.NonPublic); public static Type SqlSymmetricKeyCache = systemData.GetType("Microsoft.Data.SqlClient.SqlSymmetricKeyCache"); public static MethodInfo SqlSymmetricKeyCacheGetInstance = SqlSymmetricKeyCache.GetMethod("GetInstance", BindingFlags.Static | BindingFlags.NonPublic); @@ -102,41 +99,6 @@ internal static void CleanSqlClientCache() ClearCache(cache); } - internal static byte[] DecryptRsaDirectly(byte[] rsaPfx, byte[] ciphertextCek, string masterKeyPath) - { - Debug.Assert(rsaPfx != null && rsaPfx.Length > 0); - // The rest of the parameters may be invalid for exception handling test cases - -#if NET9_0_OR_GREATER - X509Certificate2 x509 = X509CertificateLoader.LoadPkcs12(rsaPfx, @"P@zzw0rD!SqlvN3x+"); -#else - X509Certificate2 x509 = new(rsaPfx, @"P@zzw0rD!SqlvN3x+"); -#endif - Debug.Assert(x509.HasPrivateKey); - - SqlColumnEncryptionCertificateStoreProvider rsaProvider = new SqlColumnEncryptionCertificateStoreProvider(); - Object RsaDecryptionResult = SqlColumnEncryptionCertificateStoreProviderRSADecrypt.Invoke(rsaProvider, new object[] { ciphertextCek, x509 }); - - return (byte[])RsaDecryptionResult; - } - - internal static bool VerifyRsaSignatureDirectly(byte[] hashedCek, byte[] signedCek, byte[] rsaPfx) - { - Debug.Assert(rsaPfx != null && rsaPfx.Length > 0); - -#if NET9_0_OR_GREATER - X509Certificate2 x509 = X509CertificateLoader.LoadPkcs12(rsaPfx, @"P@zzw0rD!SqlvN3x+"); -#else - X509Certificate2 x509 = new(rsaPfx, @"P@zzw0rD!SqlvN3x+"); -#endif - Debug.Assert(x509.HasPrivateKey); - - SqlColumnEncryptionCertificateStoreProvider rsaProvider = new SqlColumnEncryptionCertificateStoreProvider(); - Object RsaVerifySignatureResult = SqlColumnEncryptionCertificateStoreProviderRSAVerifySignature.Invoke(rsaProvider, new object[] { hashedCek, signedCek, x509 }); - - return (bool)RsaVerifySignatureResult; - } - /// /// Decrypt Data using AEAD ///