From bf96e9b72832e8db3cb40efa1c560c7d802c497b Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Thu, 11 Sep 2025 07:51:38 +0100
Subject: [PATCH 1/8] Port SqlColumnEncryptionCngProvider to using new AE
primitives
---
.../SqlColumnEncryptionCngProvider.Windows.cs | 284 +++---------------
1 file changed, 37 insertions(+), 247 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs
index 2a6c86357c..839c38a05f 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs
@@ -2,10 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using Microsoft.Data.SqlClient.AlwaysEncrypted;
using System;
-using System.Diagnostics;
using System.Security.Cryptography;
-using System.Text;
+
+#nullable enable
namespace Microsoft.Data.SqlClient
{
@@ -31,11 +32,6 @@ public class SqlColumnEncryptionCngProvider : SqlColumnEncryptionKeyStoreProvide
///
private const string RSAEncryptionAlgorithmWithOAEP = @"RSA_OAEP";
- ///
- /// Algorithm version
- ///
- private readonly byte[] _version = new byte[] { 0x01 };
-
///
public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey)
{
@@ -47,7 +43,7 @@ public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string e
throw SQL.NullEncryptedColumnEncryptionKey();
}
- if (0 == encryptedColumnEncryptionKey.Length)
+ if (encryptedColumnEncryptionKey.Length == 0)
{
throw SQL.EmptyEncryptedColumnEncryptionKey();
}
@@ -56,78 +52,10 @@ public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string e
ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true);
// Create RSA Provider with the given CNG name and key name
- RSACng rsaCngProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: true);
-
- // Validate whether the key is RSA one or not and then get the key size
- int keySizeInBytes = GetKeySize(rsaCngProvider);
-
- // Validate and decrypt the EncryptedColumnEncryptionKey
- // Format is
- // version + keyPathLength + ciphertextLength + keyPath + ciphervtext + signature
- //
- // keyPath is present in the encrypted column encryption key for identifying the original source of the asymmetric key pair and
- // we will not validate it against the data contained in the CMK metadata (masterKeyPath).
-
- // Validate the version byte
- if (encryptedColumnEncryptionKey[0] != _version[0])
- {
- throw SQL.InvalidAlgorithmVersionInEncryptedCEK(encryptedColumnEncryptionKey[0], _version[0]);
- }
-
- // Get key path length
- int currentIndex = _version.Length;
- UInt16 keyPathLength = BitConverter.ToUInt16(encryptedColumnEncryptionKey, currentIndex);
- currentIndex += sizeof(UInt16);
-
- // Get ciphertext length
- UInt16 cipherTextLength = BitConverter.ToUInt16(encryptedColumnEncryptionKey, currentIndex);
- currentIndex += sizeof(UInt16);
+ RSA rsaCngProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: true);
+ using EncryptedColumnEncryptionKeyParameters cekDecryptionParameters = new(rsaCngProvider, masterKeyPath, MasterKeyType, KeyPathReference);
- // Skip KeyPath
- // KeyPath exists only for troubleshooting purposes and doesnt need validation.
- currentIndex += keyPathLength;
-
- // validate the ciphertext length
- if (cipherTextLength != keySizeInBytes)
- {
- throw SQL.InvalidCiphertextLengthInEncryptedCEKCng(cipherTextLength, keySizeInBytes, masterKeyPath);
- }
-
- // Validate the signature length
- // Signature length should be same as the key side for RSA PKCSv1.5
- int signatureLength = encryptedColumnEncryptionKey.Length - currentIndex - cipherTextLength;
- if (signatureLength != keySizeInBytes)
- {
- throw SQL.InvalidSignatureInEncryptedCEKCng(signatureLength, keySizeInBytes, masterKeyPath);
- }
-
- // Get ciphertext
- byte[] cipherText = new byte[cipherTextLength];
- Buffer.BlockCopy(encryptedColumnEncryptionKey, currentIndex, cipherText, 0, cipherText.Length);
- currentIndex += cipherTextLength;
-
- // Get signature
- byte[] signature = new byte[signatureLength];
- Buffer.BlockCopy(encryptedColumnEncryptionKey, currentIndex, signature, 0, signature.Length);
-
- // Compute the hash to validate the signature
- byte[] hash;
- using (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, rsaCngProvider))
- {
- throw SQL.InvalidAsymmetricKeySignature(masterKeyPath);
- }
-
- // Decrypt the CEK
- return RSADecrypt(rsaCngProvider, cipherText);
+ return cekDecryptionParameters.Decrypt(encryptedColumnEncryptionKey);
}
///
@@ -140,7 +68,8 @@ public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string e
{
throw SQL.NullColumnEncryptionKey();
}
- else if (0 == columnEncryptionKey.Length)
+
+ if (columnEncryptionKey.Length == 0)
{
throw SQL.EmptyColumnEncryptionKey();
}
@@ -149,75 +78,10 @@ public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string e
ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: false);
// CreateCNGProviderWithKey
- RSACng rsaCngProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: false);
-
- // Validate whether the key is RSA one or not and then get the key size
- int keySizeInBytes = GetKeySize(rsaCngProvider);
-
- // Construct the encryptedColumnEncryptionKey
- // Format is
- // version + keyPathLength + ciphertextLength + ciphertext + keyPath + signature
- //
- // We currently only support one version
- byte[] version = new byte[] { _version[0] };
-
- // Get the Unicode encoded bytes of cultureinvariant lower case masterKeyPath
- byte[] masterKeyPathBytes = Encoding.Unicode.GetBytes(masterKeyPath.ToLowerInvariant());
- byte[] keyPathLength = BitConverter.GetBytes((Int16)masterKeyPathBytes.Length);
-
- // Encrypt the plain text
- byte[] cipherText = RSAEncrypt(rsaCngProvider, columnEncryptionKey);
- byte[] cipherTextLength = BitConverter.GetBytes((Int16)cipherText.Length);
- Debug.Assert(cipherText.Length == keySizeInBytes, @"cipherText length does not match the RSA key size");
-
- // Compute hash
- // SHA-2-256(version + keyPathLength + ciphertextLength + keyPath + ciphertext)
- byte[] hash;
- using (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, rsaCngProvider);
- Debug.Assert(signedHash.Length == keySizeInBytes, @"signed hash length does not match the RSA key size");
- Debug.Assert(RSAVerifySignature(hash, signedHash, rsaCngProvider), @"Invalid signature of the encrypted column encryption key computed.");
-
- // Construct the encrypted column encryption key
- // EncryptedColumnEncryptionKey = version + keyPathLength + ciphertextLength + keyPath + ciphertext + signature
- int encryptedColumnEncryptionKeyLength = version.Length + cipherTextLength.Length + keyPathLength.Length + cipherText.Length + masterKeyPathBytes.Length + signedHash.Length;
- byte[] encryptedColumnEncryptionKey = new byte[encryptedColumnEncryptionKeyLength];
-
- // Copy version byte
- int currentIndex = 0;
- Buffer.BlockCopy(version, 0, encryptedColumnEncryptionKey, currentIndex, version.Length);
- currentIndex += version.Length;
-
- // Copy key path length
- Buffer.BlockCopy(keyPathLength, 0, encryptedColumnEncryptionKey, currentIndex, keyPathLength.Length);
- currentIndex += keyPathLength.Length;
-
- // Copy ciphertext length
- Buffer.BlockCopy(cipherTextLength, 0, encryptedColumnEncryptionKey, currentIndex, cipherTextLength.Length);
- currentIndex += cipherTextLength.Length;
-
- // Copy key path
- Buffer.BlockCopy(masterKeyPathBytes, 0, encryptedColumnEncryptionKey, currentIndex, masterKeyPathBytes.Length);
- currentIndex += masterKeyPathBytes.Length;
-
- // Copy ciphertext
- Buffer.BlockCopy(cipherText, 0, encryptedColumnEncryptionKey, currentIndex, cipherText.Length);
- currentIndex += cipherText.Length;
-
- // copy the signature
- Buffer.BlockCopy(signedHash, 0, encryptedColumnEncryptionKey, currentIndex, signedHash.Length);
+ RSA rsaCngProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: false);
+ using EncryptedColumnEncryptionKeyParameters cekEncryptionParameters = new(rsaCngProvider, masterKeyPath, MasterKeyType, KeyPathReference);
- return encryptedColumnEncryptionKey;
+ return cekEncryptionParameters.Encrypt(columnEncryptionKey);
}
///
@@ -238,10 +102,10 @@ public override bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool al
///
/// Asymmetric key encryptio algorithm
/// Indicates if ADO.NET calls or the customer calls the API
- private void ValidateEncryptionAlgorithm(string encryptionAlgorithm, bool isSystemOp)
+ private static void ValidateEncryptionAlgorithm(string encryptionAlgorithm, bool isSystemOp)
{
// This validates that the encryption algorithm is RSA_OAEP
- if (encryptionAlgorithm == null)
+ if (encryptionAlgorithm is null)
{
throw SQL.NullKeyEncryptionAlgorithm(isSystemOp);
}
@@ -257,107 +121,31 @@ private void ValidateEncryptionAlgorithm(string encryptionAlgorithm, bool isSyst
///
/// keypath containing the CNG provider name and key name
/// Indicates if ADO.NET calls or the customer calls the API
- private void ValidateNonEmptyKeyPath(string masterKeyPath, bool isSystemOp)
+ private static void ValidateNonEmptyKeyPath(string masterKeyPath, bool isSystemOp)
{
- if (string.IsNullOrWhiteSpace(masterKeyPath))
+ if (masterKeyPath is null)
{
- if (masterKeyPath == null)
- {
- throw SQL.NullCngKeyPath(isSystemOp);
- }
- else
- {
- throw SQL.InvalidCngPath(masterKeyPath, isSystemOp);
- }
+ throw SQL.NullCngKeyPath(isSystemOp);
}
- }
-
- ///
- /// Encrypt the text using specified CNG key.
- ///
- /// RSA CNG Provider.
- /// Plain text Column Encryption Key.
- /// Returns an encrypted blob or throws an exception if there are any errors.
- private byte[] RSAEncrypt(RSACng rsaCngProvider, byte[] columnEncryptionKey)
- {
- Debug.Assert(columnEncryptionKey != null);
- Debug.Assert(rsaCngProvider != null);
-
- return rsaCngProvider.Encrypt(columnEncryptionKey, RSAEncryptionPadding.OaepSHA1);
- }
-
- ///
- /// Decrypt the text using the specified CNG key.
- ///
- /// RSA CNG Provider.
- /// Encrypted Column Encryption Key.
- /// Returns the decrypted plaintext Column Encryption Key or throws an exception if there are any errors.
- private byte[] RSADecrypt(RSACng rsaCngProvider, byte[] encryptedColumnEncryptionKey)
- {
- Debug.Assert((encryptedColumnEncryptionKey != null) && (encryptedColumnEncryptionKey.Length != 0));
- Debug.Assert(rsaCngProvider != null);
-
- return rsaCngProvider.Decrypt(encryptedColumnEncryptionKey, RSAEncryptionPadding.OaepSHA1);
- }
-
- ///
- /// Generates signature based on RSA PKCS#v1.5 scheme using a specified CNG Key.
- ///
- /// Text to sign.
- /// RSA CNG Provider.
- /// Signature
- private byte[] RSASignHashedData(byte[] dataToSign, RSACng rsaCngProvider)
- {
- Debug.Assert((dataToSign != null) && (dataToSign.Length != 0));
- Debug.Assert(rsaCngProvider != null);
-
- // CodeQL [SM03796] Required for backwards compatibility with existing data and cross-driver compatability
- return rsaCngProvider.SignData(dataToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
- }
-
- ///
- /// Verifies the given RSA PKCSv1.5 signature.
- ///
- ///
- ///
- /// RSA CNG Provider.
- /// true if signature is valid, false if it is not valid
- private bool RSAVerifySignature(byte[] dataToVerify, byte[] signature, RSACng rsaCngProvider)
- {
- Debug.Assert((dataToVerify != null) && (dataToVerify.Length != 0));
- Debug.Assert((signature != null) && (signature.Length != 0));
- Debug.Assert(rsaCngProvider != null);
- // CodeQL [SM03796] Required for backwards compatibility with existing data and cross-driver compatability
- return rsaCngProvider.VerifyData(dataToVerify, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
- }
-
- ///
- /// Gets the public Key size in bytes
- ///
- /// RSA CNG Provider.
- /// Key size in bytes
- private int GetKeySize(RSACng rsaCngProvider)
- {
- Debug.Assert(rsaCngProvider != null);
-
- return rsaCngProvider.KeySize / 8; // Convert from bits to byte
+ if (string.IsNullOrWhiteSpace(masterKeyPath))
+ {
+ throw SQL.InvalidCngPath(masterKeyPath, isSystemOp);
+ }
}
///
- /// Creates a RSACng object from the given keyName
+ /// Creates a RSACng object from the given keyPath.
///
///
/// Indicates if ADO.NET calls or the customer calls the API
///
- private RSACng CreateRSACngProvider(string keyPath, bool isSystemOp)
+ private static RSACng CreateRSACngProvider(string keyPath, bool isSystemOp)
{
// Get CNGProvider and the KeyID
- string cngProviderName;
- string keyIdentifier;
- GetCngProviderAndKeyId(keyPath, isSystemOp, out cngProviderName, out keyIdentifier);
+ GetCngProviderAndKeyId(keyPath, isSystemOp, out string cngProviderName, out string keyIdentifier);
- CngProvider cngProvider = new CngProvider(cngProviderName);
+ CngProvider cngProvider = new(cngProviderName);
CngKey cngKey;
try
@@ -369,7 +157,10 @@ private RSACng CreateRSACngProvider(string keyPath, bool isSystemOp)
throw SQL.InvalidCngKey(keyPath, cngProviderName, keyIdentifier, isSystemOp);
}
- return new RSACng(cngKey);
+ using (cngKey)
+ {
+ return new RSACng(cngKey);
+ }
}
///
@@ -379,26 +170,25 @@ private RSACng CreateRSACngProvider(string keyPath, bool isSystemOp)
/// Indicates if ADO.NET calls or the customer calls the API
/// CNG Provider
/// Key identifier inside the CNG provider
- private void GetCngProviderAndKeyId(string keyPath, bool isSystemOp, out string cngProvider, out string keyIdentifier)
+ private static void GetCngProviderAndKeyId(string keyPath, bool isSystemOp, out string cngProvider, out string keyIdentifier)
{
- int indexOfSlash = keyPath.IndexOf(@"/", StringComparison.Ordinal);
+ int indexOfSlash = keyPath.IndexOf('/');
+
if (indexOfSlash == -1)
{
throw SQL.InvalidCngPath(keyPath, isSystemOp);
}
-
- cngProvider = keyPath.Substring(0, indexOfSlash);
- keyIdentifier = keyPath.Substring(indexOfSlash + 1, keyPath.Length - (indexOfSlash + 1));
-
- if (cngProvider.Length == 0)
+ else if (indexOfSlash == 0)
{
throw SQL.EmptyCngName(keyPath, isSystemOp);
}
-
- if (keyIdentifier.Length == 0)
+ else if (indexOfSlash == keyPath.Length - 1)
{
throw SQL.EmptyCngKeyId(keyPath, isSystemOp);
}
+
+ cngProvider = keyPath.Substring(0, indexOfSlash);
+ keyIdentifier = keyPath.Substring(indexOfSlash + 1);
}
}
}
From 4d8147a1ad7b2037950e29ba7ad3ebda65915118 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Thu, 11 Sep 2025 20:18:20 +0100
Subject: [PATCH 2/8] Port SqlColumnEncryptionCspProvider to using the new AE
primitives
---
.../SqlColumnEncryptionCngProvider.Windows.cs | 4 +-
.../SqlColumnEncryptionCspProvider.Windows.cs | 309 +++---------------
.../SqlColumnEncryptionCspProviderShould.cs | 4 +-
3 files changed, 46 insertions(+), 271 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs
index 839c38a05f..b5d1ac8186 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs
@@ -38,7 +38,7 @@ public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string e
// Validate the input parameters
ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: true);
- if (encryptedColumnEncryptionKey == null)
+ if (encryptedColumnEncryptionKey is null)
{
throw SQL.NullEncryptedColumnEncryptionKey();
}
@@ -64,7 +64,7 @@ public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string e
// Validate the input parameters
ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: false);
- if (columnEncryptionKey == null)
+ if (columnEncryptionKey is null)
{
throw SQL.NullColumnEncryptionKey();
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs
index db659a1784..6e0909f3b1 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs
@@ -2,11 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using Microsoft.Data.SqlClient.AlwaysEncrypted;
+using Microsoft.Win32;
using System;
-using System.Diagnostics;
using System.Security.Cryptography;
-using System.Text;
-using Microsoft.Win32;
+
+#nullable enable
namespace Microsoft.Data.SqlClient
{
@@ -34,28 +35,18 @@ public class SqlColumnEncryptionCspProvider : SqlColumnEncryptionKeyStoreProvide
///
private const string RSAEncryptionAlgorithmWithOAEP = @"RSA_OAEP";
- ///
- /// 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
ValidateNonEmptyCSPKeyPath(masterKeyPath, isSystemOp: true);
- if (encryptedColumnEncryptionKey == null)
+ if (encryptedColumnEncryptionKey is null)
{
throw SQL.NullEncryptedColumnEncryptionKey();
}
- if (0 == encryptedColumnEncryptionKey.Length)
+ if (encryptedColumnEncryptionKey.Length == 0)
{
throw SQL.EmptyEncryptedColumnEncryptionKey();
}
@@ -65,77 +56,9 @@ public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string e
// Create RSA Provider with the given CSP name and key name
RSACryptoServiceProvider rsaProvider = CreateRSACryptoProvider(masterKeyPath, isSystemOp: true);
+ using EncryptedColumnEncryptionKeyParameters cekDecryptionParameters = new(rsaProvider, masterKeyPath, MasterKeyType, KeyPathReference);
- // Validate whether the key is RSA one or not and then get the key size
- int keySizeInBytes = GetKeySize(rsaProvider);
-
- // Validate and decrypt the EncryptedColumnEncryptionKey
- // Format is
- // version + keyPathLength + ciphertextLength + keyPath + ciphervtext + signature
- //
- // keyPath is present in the encrypted column encryption key for identifying the original source of the asymmetric key pair and
- // we will not validate it against the data contained in the CMK metadata (masterKeyPath).
-
- // Validate the version byte
- if (encryptedColumnEncryptionKey[0] != _version[0])
- {
- throw SQL.InvalidAlgorithmVersionInEncryptedCEK(encryptedColumnEncryptionKey[0], _version[0]);
- }
-
- // Get key path length
- int currentIndex = _version.Length;
- UInt16 keyPathLength = BitConverter.ToUInt16(encryptedColumnEncryptionKey, currentIndex);
- currentIndex += sizeof(UInt16);
-
- // Get ciphertext length
- UInt16 cipherTextLength = BitConverter.ToUInt16(encryptedColumnEncryptionKey, currentIndex);
- currentIndex += sizeof(UInt16);
-
- // Skip KeyPath
- // KeyPath exists only for troubleshooting purposes and doesnt need validation.
- currentIndex += keyPathLength;
-
- // validate the ciphertext length
- if (cipherTextLength != keySizeInBytes)
- {
- throw SQL.InvalidCiphertextLengthInEncryptedCEKCsp(cipherTextLength, keySizeInBytes, masterKeyPath);
- }
-
- // Validate the signature length
- // Signature length should be same as the key side for RSA PKCSv1.5
- int signatureLength = encryptedColumnEncryptionKey.Length - currentIndex - cipherTextLength;
- if (signatureLength != keySizeInBytes)
- {
- throw SQL.InvalidSignatureInEncryptedCEKCsp(signatureLength, keySizeInBytes, masterKeyPath);
- }
-
- // Get ciphertext
- byte[] cipherText = new byte[cipherTextLength];
- Buffer.BlockCopy(encryptedColumnEncryptionKey, currentIndex, cipherText, 0, cipherText.Length);
- currentIndex += cipherTextLength;
-
- // Get signature
- byte[] signature = new byte[signatureLength];
- Buffer.BlockCopy(encryptedColumnEncryptionKey, currentIndex, signature, 0, signature.Length);
-
- // Compute the hash to validate the signature
- byte[] hash;
- using (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, rsaProvider))
- {
- throw SQL.InvalidAsymmetricKeySignature(masterKeyPath);
- }
-
- // Decrypt the CEK
- return RSADecrypt(rsaProvider, cipherText);
+ return cekDecryptionParameters.Decrypt(encryptedColumnEncryptionKey);
}
///
@@ -144,11 +67,12 @@ public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string e
// Validate the input parameters
ValidateNonEmptyCSPKeyPath(masterKeyPath, isSystemOp: false);
- if (columnEncryptionKey == null)
+ if (columnEncryptionKey is null)
{
throw SQL.NullColumnEncryptionKey();
}
- else if (0 == columnEncryptionKey.Length)
+
+ if (columnEncryptionKey.Length == 0)
{
throw SQL.EmptyColumnEncryptionKey();
}
@@ -158,74 +82,9 @@ public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string e
// Create RSA Provider with the given CSP name and key name
RSACryptoServiceProvider rsaProvider = CreateRSACryptoProvider(masterKeyPath, isSystemOp: false);
+ using EncryptedColumnEncryptionKeyParameters cekEncryptionParameters = new(rsaProvider, masterKeyPath, MasterKeyType, KeyPathReference);
- // Validate whether the key is RSA one or not and then get the key size
- int keySizeInBytes = GetKeySize(rsaProvider);
-
- // Construct the encryptedColumnEncryptionKey
- // Format is
- // version + keyPathLength + ciphertextLength + ciphertext + keyPath + signature
- //
- // We currently only support one version
- byte[] version = new byte[] { _version[0] };
-
- // Get the Unicode encoded bytes of cultureinvariant lower case masterKeyPath
- byte[] masterKeyPathBytes = Encoding.Unicode.GetBytes(masterKeyPath.ToLowerInvariant());
- byte[] keyPathLength = BitConverter.GetBytes((Int16)masterKeyPathBytes.Length);
-
- // Encrypt the plain text
- byte[] cipherText = RSAEncrypt(rsaProvider, columnEncryptionKey);
- byte[] cipherTextLength = BitConverter.GetBytes((Int16)cipherText.Length);
- Debug.Assert(cipherText.Length == keySizeInBytes, @"cipherText length does not match the RSA key size");
-
- // Compute hash
- // SHA-2-256(version + keyPathLength + ciphertextLength + keyPath + ciphertext)
- byte[] hash;
- using (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, rsaProvider);
- Debug.Assert(signedHash.Length == keySizeInBytes, @"signed hash length does not match the RSA key size");
- Debug.Assert(RSAVerifySignature(hash, signedHash, rsaProvider), @"Invalid signature of the encrypted column encryption key computed.");
-
- // Construct the encrypted column encryption key
- // EncryptedColumnEncryptionKey = version + keyPathLength + ciphertextLength + keyPath + ciphertext + signature
- int encryptedColumnEncryptionKeyLength = version.Length + cipherTextLength.Length + keyPathLength.Length + cipherText.Length + masterKeyPathBytes.Length + signedHash.Length;
- byte[] encryptedColumnEncryptionKey = new byte[encryptedColumnEncryptionKeyLength];
-
- // Copy version byte
- int currentIndex = 0;
- Buffer.BlockCopy(version, 0, encryptedColumnEncryptionKey, currentIndex, version.Length);
- currentIndex += version.Length;
-
- // Copy key path length
- Buffer.BlockCopy(keyPathLength, 0, encryptedColumnEncryptionKey, currentIndex, keyPathLength.Length);
- currentIndex += keyPathLength.Length;
-
- // Copy ciphertext length
- Buffer.BlockCopy(cipherTextLength, 0, encryptedColumnEncryptionKey, currentIndex, cipherTextLength.Length);
- currentIndex += cipherTextLength.Length;
-
- // Copy key path
- Buffer.BlockCopy(masterKeyPathBytes, 0, encryptedColumnEncryptionKey, currentIndex, masterKeyPathBytes.Length);
- currentIndex += masterKeyPathBytes.Length;
-
- // Copy ciphertext
- Buffer.BlockCopy(cipherText, 0, encryptedColumnEncryptionKey, currentIndex, cipherText.Length);
- currentIndex += cipherText.Length;
-
- // copy the signature
- Buffer.BlockCopy(signedHash, 0, encryptedColumnEncryptionKey, currentIndex, signedHash.Length);
-
- return encryptedColumnEncryptionKey;
+ return cekEncryptionParameters.Encrypt(columnEncryptionKey);
}
///
@@ -246,109 +105,36 @@ public override bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool al
///
/// Asymmetric key encryption algorithm
/// Indicates if ADO.NET calls or the customer calls the API
- private void ValidateEncryptionAlgorithm(string encryptionAlgorithm, bool isSystemOp)
+ private static void ValidateEncryptionAlgorithm(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);
}
}
-
///
/// Checks if the CSP key path is Empty or Null (and raises exception if they are).
///
/// CSP key path.
/// Indicates if ADO.NET calls or the customer calls the API
- private void ValidateNonEmptyCSPKeyPath(string masterKeyPath, bool isSystemOp)
+ private static void ValidateNonEmptyCSPKeyPath(string masterKeyPath, bool isSystemOp)
{
- if (string.IsNullOrWhiteSpace(masterKeyPath))
+ if (masterKeyPath is null)
{
- if (masterKeyPath == null)
- {
- throw SQL.NullCspKeyPath(isSystemOp);
- }
- else
- {
- throw SQL.InvalidCspPath(masterKeyPath, isSystemOp);
- }
+ throw SQL.NullCspKeyPath(isSystemOp);
}
- }
-
- ///
- /// Encrypt the text using specified CSP key.
- ///
- /// RSACryptoServiceProvider
- /// Plain text Column Encryption Key.
- /// Returns an encrypted blob or throws an exception if there are any errors.
- private byte[] RSAEncrypt(RSACryptoServiceProvider rscp, byte[] columnEncryptionKey)
- {
- Debug.Assert(columnEncryptionKey != null);
- Debug.Assert(rscp != null);
-
- return rscp.Encrypt(columnEncryptionKey, fOAEP: true);
- }
-
- ///
- /// Decrypt the text using specified CSP key.
- ///
- /// RSACryptoServiceProvider
- /// Encrypted Column Encryption Key.
- /// Returns the decrypted plaintext Column Encryption Key or throws an exception if there are any errors.
- private byte[] RSADecrypt(RSACryptoServiceProvider rscp, byte[] encryptedColumnEncryptionKey)
- {
- Debug.Assert((encryptedColumnEncryptionKey != null) && (encryptedColumnEncryptionKey.Length != 0));
- Debug.Assert(rscp != null);
-
- return rscp.Decrypt(encryptedColumnEncryptionKey, fOAEP: true);
- }
-
- ///
- /// Generates signature based on RSA PKCS#v1.5 scheme using a specified CSP Key URL.
- ///
- /// Text to sign.
- /// RSA Provider with a given key
- /// Signature
- private byte[] RSASignHashedData(byte[] dataToSign, RSACryptoServiceProvider rscp)
- {
- Debug.Assert((dataToSign != null) && (dataToSign.Length != 0));
- Debug.Assert(rscp != null);
- return rscp.SignData(dataToSign, HashingAlgorithm);
- }
-
- ///
- /// Verifies the given RSA PKCSv1.5 signature.
- ///
- ///
- ///
- /// RSA Provider with a given key
- /// true if signature is valid, false if it is not valid
- private bool RSAVerifySignature(byte[] dataToVerify, byte[] signature, RSACryptoServiceProvider rscp)
- {
- Debug.Assert((dataToVerify != null) && (dataToVerify.Length != 0));
- Debug.Assert((signature != null) && (signature.Length != 0));
- Debug.Assert(rscp != null);
-
- return rscp.VerifyData(dataToVerify, HashingAlgorithm, signature);
- }
-
- ///
- /// Gets the public Key size in bytes
- ///
- /// RSA Provider with a given key
- /// Key size in bytes
- private int GetKeySize(RSACryptoServiceProvider rscp)
- {
- Debug.Assert(rscp != null);
-
- return rscp.KeySize / 8;
+ if (string.IsNullOrWhiteSpace(masterKeyPath))
+ {
+ throw SQL.InvalidCspPath(masterKeyPath, isSystemOp);
+ }
}
///
@@ -357,25 +143,21 @@ private int GetKeySize(RSACryptoServiceProvider rscp)
/// key path in the format of [CAPI provider name]/[key name]
/// Indicates if ADO.NET calls or the customer calls the API
///
- private RSACryptoServiceProvider CreateRSACryptoProvider(string keyPath, bool isSystemOp)
+ private static RSACryptoServiceProvider CreateRSACryptoProvider(string keyPath, bool isSystemOp)
{
// Get CNGProvider and the KeyID
- string cspProviderName;
- string keyName;
- GetCspProviderAndKeyName(keyPath, isSystemOp, out cspProviderName, out keyName);
+ GetCspProviderAndKeyName(keyPath, isSystemOp, out string cspProviderName, out string keyName);
// Verify the existence of CSP and then get the provider type
int providerType = GetProviderType(cspProviderName, keyPath, isSystemOp);
// Create a new instance of CspParameters for an RSA container.
- CspParameters cspParams = new CspParameters(providerType, cspProviderName, keyName);
- cspParams.Flags = CspProviderFlags.UseExistingKey;
-
- RSACryptoServiceProvider rscp = null;
+ CspParameters cspParams = new(providerType, cspProviderName, keyName) { Flags = CspProviderFlags.UseExistingKey };
+ RSACryptoServiceProvider rscp;
try
{
- //Create a new instance of RSACryptoServiceProvider
+ // Create a new instance of RSACryptoServiceProvider
rscp = new RSACryptoServiceProvider(cspParams);
}
catch (CryptographicException e)
@@ -388,7 +170,7 @@ private RSACryptoServiceProvider CreateRSACryptoProvider(string keyPath, bool is
}
else
{
- // bubble up the exception
+ // Bubble up the exception
throw;
}
}
@@ -403,26 +185,25 @@ private RSACryptoServiceProvider CreateRSACryptoProvider(string keyPath, bool is
/// Indicates if ADO.NET calls or the customer calls the API
/// output containing the CSP provider name
/// output containing the key name
- private void GetCspProviderAndKeyName(string keyPath, bool isSystemOp, out string cspProviderName, out string keyIdentifier)
+ private static void GetCspProviderAndKeyName(string keyPath, bool isSystemOp, out string cspProviderName, out string keyIdentifier)
{
- int indexOfSlash = keyPath.IndexOf(@"/", StringComparison.Ordinal);
+ int indexOfSlash = keyPath.IndexOf('/');
+
if (indexOfSlash == -1)
{
throw SQL.InvalidCspPath(keyPath, isSystemOp);
}
-
- cspProviderName = keyPath.Substring(0, indexOfSlash);
- keyIdentifier = keyPath.Substring(indexOfSlash + 1, keyPath.Length - (indexOfSlash + 1));
-
- if (cspProviderName.Length == 0)
+ else if (indexOfSlash == 0)
{
throw SQL.EmptyCspName(keyPath, isSystemOp);
}
-
- if (keyIdentifier.Length == 0)
+ else if (indexOfSlash == keyPath.Length - 1)
{
throw SQL.EmptyCspKeyId(keyPath, isSystemOp);
}
+
+ cspProviderName = keyPath.Substring(0, indexOfSlash);
+ keyIdentifier = keyPath.Substring(indexOfSlash + 1);
}
///
@@ -432,19 +213,13 @@ private void GetCspProviderAndKeyName(string keyPath, bool isSystemOp, out strin
/// key path in the format of [CSP provider name]/[key name]
/// Indicates if ADO.NET calls or the customer calls the API
///
- private int GetProviderType(string providerName, string keyPath, bool isSystemOp)
+ private static int GetProviderType(string providerName, string keyPath, bool isSystemOp)
{
- string keyName = String.Format(@"SOFTWARE\Microsoft\Cryptography\Defaults\Provider\{0}", providerName);
- RegistryKey key = Registry.LocalMachine.OpenSubKey(keyName);
- if (key == null)
- {
- throw SQL.InvalidCspName(providerName, keyPath, isSystemOp);
- }
-
- int providerType = (int)key.GetValue(@"Type");
- key.Close();
+ using RegistryKey key = Registry.LocalMachine.OpenSubKey(@$"SOFTWARE\Microsoft\Cryptography\Defaults\Provider\{providerName}")
+ ?? throw SQL.InvalidCspName(providerName, keyPath, isSystemOp);
- return providerType;
+ return (int)(key.GetValue(@"Type")
+ ?? throw SQL.InvalidCspName(providerName, keyPath, isSystemOp));
}
}
}
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCspProviderShould.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCspProviderShould.cs
index ad5ac690ec..45e0414b05 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCspProviderShould.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCspProviderShould.cs
@@ -95,8 +95,8 @@ public class InvalidDecryptionParameters : DataAttribute
private const string TCE_EmptyCspKeyId = @"Internal error. Empty key identifier specified in column master key path: 'MSSQL_CSP_PROVIDER/'. Use the following format for a key stored in a Microsoft cryptographic service provider \(CSP\): \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?";
private const string TCE_InvalidCspKey = @"Internal error. Invalid Microsoft cryptographic service provider \(CSP\) name: 'MSSQL_CSP_PROVIDER'. Verify that the CSP provider name in column master key path: 'MSSQL_CSP_PROVIDER/KeyName' is valid and installed on the machine.\s+\(?Parameter (name: )?'?masterKeyPath('\))?";
private const string TCE_InvalidAlgorithmVersion = @"Specified encrypted column encryption key contains an invalid encryption algorithm version '02'. Expected version is '01'.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?";
- private const string TCE_InvalidCiphertextLengthInEncryptedCEK = @"The specified encrypted column encryption key's ciphertext length: 128 does not match the ciphertext length: 256 when using column master key \(asymmetric key\) in 'Microsoft Enhanced RSA and AES Cryptographic Provider/KeyName'. The encrypted column encryption key may be corrupt, or the specified Microsoft Cryptographic Service provider \(CSP\) path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?";
- private const string TCE_InvalidSignatureInEncryptedCEK = @"The specified encrypted column encryption key's signature length: 128 does not match the signature length: 256 when using column master key \(asymmetric key\) in 'Microsoft Enhanced RSA and AES Cryptographic Provider/KeyName'. The encrypted column encryption key may be corrupt, or the specified Microsoft cryptographic service provider \(CSP\) path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?";
+ private const string TCE_InvalidCiphertextLengthInEncryptedCEK = @"The specified encrypted column encryption key's ciphertext length: 128 does not match the ciphertext length: 256 when using column master key \(asymmetric key\) in 'Microsoft Enhanced RSA and AES Cryptographic Provider/KeyName'. The encrypted column encryption key may be corrupt, or the specified Microsoft Cryptographic Service Provider \(CSP\) path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?";
+ private const string TCE_InvalidSignatureInEncryptedCEK = @"The specified encrypted column encryption key's signature length: 128 does not match the signature length: 256 when using column master key \(asymmetric key\) in 'Microsoft Enhanced RSA and AES Cryptographic Provider/KeyName'. The encrypted column encryption key may be corrupt, or the specified Microsoft Cryptographic Service Provider \(CSP\) path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?";
private const string TCE_InvalidSignature = @"The specified encrypted column encryption key signature does not match the signature computed with the column master key \(asymmetric key\) in 'Microsoft Enhanced RSA and AES Cryptographic Provider/KeyName'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?";
private string TCE_InvalidCspKeyId = $@"Internal error. Invalid key identifier: 'KeyName/{DUMMY_KEY}'. Verify that the key identifier in column master key path: 'Microsoft Enhanced RSA and AES Cryptographic Provider/KeyName/{DUMMY_KEY}' is valid and exists in the CSP.\s+\(?Parameter (name: )?'?masterKeyPath('\))?";
From 6a8f00a7352d6256fde362b6b477194baf3a7c20 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Thu, 11 Sep 2025 21:35:33 +0100
Subject: [PATCH 3/8] Implement NRTs on both providers
---
.../netfx/src/Microsoft.Data.SqlClient.csproj | 3 +++
.../SqlColumnEncryptionCngProvider.Windows.cs | 25 ++++++++++---------
.../SqlColumnEncryptionCspProvider.Windows.cs | 22 ++++++++--------
.../src/System/Diagnostics/CodeAnalysis.cs | 5 ++++
4 files changed, 31 insertions(+), 24 deletions(-)
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 17eeb3a472..789d03b4a5 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
@@ -978,6 +978,9 @@
System\Buffers\ArrayBufferWriter.netfx.cs
+
+ System\Diagnostics\CodeAnalysis.cs
+
System\IO\StreamExtensions.netfx.cs
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs
index b5d1ac8186..a422b97725 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs
@@ -4,6 +4,7 @@
using Microsoft.Data.SqlClient.AlwaysEncrypted;
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
#nullable enable
@@ -33,7 +34,7 @@ public class SqlColumnEncryptionCngProvider : SqlColumnEncryptionKeyStoreProvide
private const string RSAEncryptionAlgorithmWithOAEP = @"RSA_OAEP";
///
- public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey)
+ public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey)
{
// Validate the input parameters
ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: true);
@@ -52,14 +53,14 @@ public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string e
ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true);
// Create RSA Provider with the given CNG name and key name
- RSA rsaCngProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: true);
- using EncryptedColumnEncryptionKeyParameters cekDecryptionParameters = new(rsaCngProvider, masterKeyPath, MasterKeyType, KeyPathReference);
+ RSA rsaProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: true);
+ using EncryptedColumnEncryptionKeyParameters cekDecryptionParameters = new(rsaProvider, masterKeyPath, MasterKeyType, KeyPathReference);
return cekDecryptionParameters.Decrypt(encryptedColumnEncryptionKey);
}
///
- public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey)
+ public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey)
{
// Validate the input parameters
ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: false);
@@ -77,21 +78,21 @@ public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string e
// Validate encryptionAlgorithm
ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: false);
- // CreateCNGProviderWithKey
- RSA rsaCngProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: false);
- using EncryptedColumnEncryptionKeyParameters cekEncryptionParameters = new(rsaCngProvider, masterKeyPath, MasterKeyType, KeyPathReference);
+ // Create RSACNGProviderWithKey
+ RSA rsaProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: false);
+ using EncryptedColumnEncryptionKeyParameters cekEncryptionParameters = new(rsaProvider, masterKeyPath, MasterKeyType, KeyPathReference);
return cekEncryptionParameters.Encrypt(columnEncryptionKey);
}
///
- public override byte[] SignColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations)
+ public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations)
{
throw new NotSupportedException();
}
///
- public override bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations, byte[] signature)
+ public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature)
{
throw new NotSupportedException();
}
@@ -100,9 +101,9 @@ public override bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool al
/// This function validates that the encryption algorithm is RSA_OAEP and if it is not,
/// then throws an exception
///
- /// Asymmetric key encryptio algorithm
+ /// Asymmetric key encryption algorithm
/// Indicates if ADO.NET calls or the customer calls the API
- private static 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 is null)
@@ -121,7 +122,7 @@ private static void ValidateEncryptionAlgorithm(string encryptionAlgorithm, bool
///
/// keypath containing the CNG provider name and key name
/// Indicates if ADO.NET calls or the customer calls the API
- private static void ValidateNonEmptyKeyPath(string masterKeyPath, bool isSystemOp)
+ private static void ValidateNonEmptyKeyPath([NotNull] string? masterKeyPath, bool isSystemOp)
{
if (masterKeyPath is null)
{
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs
index 6e0909f3b1..e26264c700 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs
@@ -5,6 +5,7 @@
using Microsoft.Data.SqlClient.AlwaysEncrypted;
using Microsoft.Win32;
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
#nullable enable
@@ -36,7 +37,7 @@ public class SqlColumnEncryptionCspProvider : SqlColumnEncryptionKeyStoreProvide
private const string RSAEncryptionAlgorithmWithOAEP = @"RSA_OAEP";
///
- public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey)
+ public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey)
{
// Validate the input parameters
ValidateNonEmptyCSPKeyPath(masterKeyPath, isSystemOp: true);
@@ -55,14 +56,14 @@ public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string e
ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true);
// Create RSA Provider with the given CSP name and key name
- RSACryptoServiceProvider rsaProvider = CreateRSACryptoProvider(masterKeyPath, isSystemOp: true);
+ RSA rsaProvider = CreateRSACryptoProvider(masterKeyPath, isSystemOp: true);
using EncryptedColumnEncryptionKeyParameters cekDecryptionParameters = new(rsaProvider, masterKeyPath, MasterKeyType, KeyPathReference);
return cekDecryptionParameters.Decrypt(encryptedColumnEncryptionKey);
}
///
- public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey)
+ public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey)
{
// Validate the input parameters
ValidateNonEmptyCSPKeyPath(masterKeyPath, isSystemOp: false);
@@ -81,20 +82,20 @@ public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string e
ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: false);
// Create RSA Provider with the given CSP name and key name
- RSACryptoServiceProvider rsaProvider = CreateRSACryptoProvider(masterKeyPath, isSystemOp: false);
+ RSA rsaProvider = CreateRSACryptoProvider(masterKeyPath, isSystemOp: false);
using EncryptedColumnEncryptionKeyParameters cekEncryptionParameters = new(rsaProvider, masterKeyPath, MasterKeyType, KeyPathReference);
return cekEncryptionParameters.Encrypt(columnEncryptionKey);
}
///
- public override byte[] SignColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations)
+ public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations)
{
throw new NotSupportedException();
}
///
- public override bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations, byte[] signature)
+ public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature)
{
throw new NotSupportedException();
}
@@ -105,7 +106,7 @@ public override bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool al
///
/// Asymmetric key encryption algorithm
/// Indicates if ADO.NET calls or the customer calls the API
- private static 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 is null)
@@ -124,7 +125,7 @@ private static void ValidateEncryptionAlgorithm(string encryptionAlgorithm, bool
///
/// CSP key path.
/// Indicates if ADO.NET calls or the customer calls the API
- private static void ValidateNonEmptyCSPKeyPath(string masterKeyPath, bool isSystemOp)
+ private static void ValidateNonEmptyCSPKeyPath([NotNull] string? masterKeyPath, bool isSystemOp)
{
if (masterKeyPath is null)
{
@@ -153,12 +154,11 @@ private static RSACryptoServiceProvider CreateRSACryptoProvider(string keyPath,
// Create a new instance of CspParameters for an RSA container.
CspParameters cspParams = new(providerType, cspProviderName, keyName) { Flags = CspProviderFlags.UseExistingKey };
- RSACryptoServiceProvider rscp;
try
{
// Create a new instance of RSACryptoServiceProvider
- rscp = new RSACryptoServiceProvider(cspParams);
+ return new RSACryptoServiceProvider(cspParams);
}
catch (CryptographicException e)
{
@@ -174,8 +174,6 @@ private static RSACryptoServiceProvider CreateRSACryptoProvider(string keyPath,
throw;
}
}
-
- return rscp;
}
///
diff --git a/src/Microsoft.Data.SqlClient/src/System/Diagnostics/CodeAnalysis.cs b/src/Microsoft.Data.SqlClient/src/System/Diagnostics/CodeAnalysis.cs
index aaca20a34d..ffb0003f64 100644
--- a/src/Microsoft.Data.SqlClient/src/System/Diagnostics/CodeAnalysis.cs
+++ b/src/Microsoft.Data.SqlClient/src/System/Diagnostics/CodeAnalysis.cs
@@ -38,5 +38,10 @@ public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
public string[] Members { get; }
}
+
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
+ internal sealed class NotNullAttribute : Attribute
+ {
+ }
#endif
}
From faa828b3d4e48838af4273aafc2b275d38c956d6 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Thu, 11 Sep 2025 21:57:31 +0100
Subject: [PATCH 4/8] Misc. internal comments cleanup
Also simplified exception handling in SqlColumnEncryptionCspProvider
---
.../SqlColumnEncryptionCngProvider.Windows.cs | 30 +++++------
.../SqlColumnEncryptionCspProvider.Windows.cs | 52 ++++++++-----------
2 files changed, 37 insertions(+), 45 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs
index a422b97725..92655b7c43 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs
@@ -52,7 +52,7 @@ public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string?
// Validate encryptionAlgorithm
ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true);
- // Create RSA Provider with the given CNG name and key name
+ // Create RSA Provider with the given CNG provider name and key name
RSA rsaProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: true);
using EncryptedColumnEncryptionKeyParameters cekDecryptionParameters = new(rsaProvider, masterKeyPath, MasterKeyType, KeyPathReference);
@@ -78,7 +78,7 @@ public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string?
// Validate encryptionAlgorithm
ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: false);
- // Create RSACNGProviderWithKey
+ // Create RSA Provider with the given CNG provider name and key name
RSA rsaProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: false);
using EncryptedColumnEncryptionKeyParameters cekEncryptionParameters = new(rsaProvider, masterKeyPath, MasterKeyType, KeyPathReference);
@@ -99,10 +99,10 @@ public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool a
///
/// This function validates that the encryption algorithm is RSA_OAEP and if it is not,
- /// then throws an exception
+ /// then throws an exception.
///
- /// Asymmetric key encryption algorithm
- /// Indicates if ADO.NET calls or the customer calls the API
+ /// Asymmetric key encryption algorithm.
+ /// Indicates if ADO.NET calls or the customer calls the API.
private static void ValidateEncryptionAlgorithm([NotNull] string? encryptionAlgorithm, bool isSystemOp)
{
// This validates that the encryption algorithm is RSA_OAEP
@@ -120,8 +120,8 @@ private static void ValidateEncryptionAlgorithm([NotNull] string? encryptionAlgo
///
/// Checks if the CNG key path is Empty or Null (and raises exception if they are).
///
- /// keypath containing the CNG provider name and key name
- /// Indicates if ADO.NET calls or the customer calls the API
+ /// Key path containing the CNG provider name and key name.
+ /// Indicates if ADO.NET calls or the customer calls the API.
private static void ValidateNonEmptyKeyPath([NotNull] string? masterKeyPath, bool isSystemOp)
{
if (masterKeyPath is null)
@@ -136,10 +136,10 @@ private static void ValidateNonEmptyKeyPath([NotNull] string? masterKeyPath, boo
}
///
- /// Creates a RSACng object from the given keyPath.
+ /// Creates a RSACng from the given key path.
///
- ///
- /// Indicates if ADO.NET calls or the customer calls the API
+ /// Key path in the format of [CNG provider name]/[key name].
+ /// Indicates if ADO.NET calls or the customer calls the API.
///
private static RSACng CreateRSACngProvider(string keyPath, bool isSystemOp)
{
@@ -165,12 +165,12 @@ private static RSACng CreateRSACngProvider(string keyPath, bool isSystemOp)
}
///
- /// Extracts the CNG provider and key name from the key path
+ /// Extracts the CNG provider name and key name from the given key path.
///
- /// keypath in the format [CNG Provider]/[KeyName]
- /// Indicates if ADO.NET calls or the customer calls the API
- /// CNG Provider
- /// Key identifier inside the CNG provider
+ /// Key path in the format [CNG provider name]/[key name].
+ /// Indicates if ADO.NET calls or the customer calls the API.
+ /// CNG provider name.
+ /// Key name inside the CNG provider.
private static void GetCngProviderAndKeyId(string keyPath, bool isSystemOp, out string cngProvider, out string keyIdentifier)
{
int indexOfSlash = keyPath.IndexOf('/');
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs
index e26264c700..5b4ce129f0 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs
@@ -26,7 +26,7 @@ public class SqlColumnEncryptionCspProvider : SqlColumnEncryptionKeyStoreProvide
internal const string MasterKeyType = @"asymmetric key";
///
- /// This encryption keystore uses the master key path to reference a CSP.
+ /// This encryption keystore uses the master key path to reference a CSP provider.
///
internal const string KeyPathReference = @"Microsoft Cryptographic Service Provider (CSP)";
@@ -102,10 +102,10 @@ public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool a
///
/// This function validates that the encryption algorithm is RSA_OAEP and if it is not,
- /// then throws an exception
+ /// then throws an exception.
///
- /// Asymmetric key encryption algorithm
- /// Indicates if ADO.NET calls or the customer calls the API
+ /// Asymmetric key encryption algorithm.
+ /// Indicates if ADO.NET calls or the customer calls the API.
private static void ValidateEncryptionAlgorithm([NotNull] string? encryptionAlgorithm, bool isSystemOp)
{
// This validates that the encryption algorithm is RSA_OAEP
@@ -123,8 +123,8 @@ private static void ValidateEncryptionAlgorithm([NotNull] string? encryptionAlgo
///
/// Checks if the CSP key path is Empty or Null (and raises exception if they are).
///
- /// CSP key path.
- /// Indicates if ADO.NET calls or the customer calls the API
+ /// Key path containing the CSP provider name and key name.
+ /// Indicates if ADO.NET calls or the customer calls the API.
private static void ValidateNonEmptyCSPKeyPath([NotNull] string? masterKeyPath, bool isSystemOp)
{
if (masterKeyPath is null)
@@ -139,10 +139,10 @@ private static void ValidateNonEmptyCSPKeyPath([NotNull] string? masterKeyPath,
}
///
- /// Creates a RSACryptoServiceProvider from the given key path which contains both CSP name and key name
+ /// Creates a RSACryptoServiceProvider from the given key path.
///
- /// key path in the format of [CAPI provider name]/[key name]
- /// Indicates if ADO.NET calls or the customer calls the API
+ /// Key path in the format of [CSP provider name]/[key name].
+ /// Indicates if ADO.NET calls or the customer calls the API.
///
private static RSACryptoServiceProvider CreateRSACryptoProvider(string keyPath, bool isSystemOp)
{
@@ -154,35 +154,27 @@ private static RSACryptoServiceProvider CreateRSACryptoProvider(string keyPath,
// Create a new instance of CspParameters for an RSA container.
CspParameters cspParams = new(providerType, cspProviderName, keyName) { Flags = CspProviderFlags.UseExistingKey };
+ const int KEYSETDOESNOTEXIST = -2146893802;
try
{
// Create a new instance of RSACryptoServiceProvider
return new RSACryptoServiceProvider(cspParams);
}
- catch (CryptographicException e)
+ catch (CryptographicException e) when (e.HResult == KEYSETDOESNOTEXIST)
{
- const int KEYSETDOESNOTEXIST = -2146893802;
- if (e.HResult == KEYSETDOESNOTEXIST)
- {
- // Key does not exist
- throw SQL.InvalidCspKeyIdentifier(keyName, keyPath, isSystemOp);
- }
- else
- {
- // Bubble up the exception
- throw;
- }
+ // Key does not exist
+ throw SQL.InvalidCspKeyIdentifier(keyName, keyPath, isSystemOp);
}
}
///
- /// Extracts the CSP provider name and key name from the given key path
+ /// Extracts the CSP provider name and key name from the given key path.
///
- /// key path in the format of [CSP provider name]/[key name]
- /// Indicates if ADO.NET calls or the customer calls the API
- /// output containing the CSP provider name
- /// output containing the key name
+ /// Key path in the format [CSP provider name]/[key name].
+ /// Indicates if ADO.NET calls or the customer calls the API.
+ /// CSP provider name.
+ /// Key name inside the CSP provider.
private static void GetCspProviderAndKeyName(string keyPath, bool isSystemOp, out string cspProviderName, out string keyIdentifier)
{
int indexOfSlash = keyPath.IndexOf('/');
@@ -205,11 +197,11 @@ private static void GetCspProviderAndKeyName(string keyPath, bool isSystemOp, ou
}
///
- /// Gets the provider type from a given CAPI provider name
+ /// Gets the type from a given CSP provider name.
///
- /// CAPI provider name
- /// key path in the format of [CSP provider name]/[key name]
- /// Indicates if ADO.NET calls or the customer calls the API
+ /// CSP provider name.
+ /// Key path in the format of [CSP provider name]/[key name].
+ /// Indicates if ADO.NET calls or the customer calls the API.
///
private static int GetProviderType(string providerName, string keyPath, bool isSystemOp)
{
From f5de5c14b0b69b72952fdb56ed8668b5135b36ba Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Thu, 11 Sep 2025 22:07:16 +0100
Subject: [PATCH 5/8] Sync XML comments from primary classes to Unix versions
---
...olumnEncryptionCngProvider.netcore.Unix.cs | 61 ++++-------------
...olumnEncryptionCspProvider.netcore.Unix.cs | 65 ++++---------------
2 files changed, 25 insertions(+), 101 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.netcore.Unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.netcore.Unix.cs
index 5f75192562..7c53b61a78 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.netcore.Unix.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.netcore.Unix.cs
@@ -6,26 +6,14 @@
using System;
+#nullable enable
+
namespace Microsoft.Data.SqlClient
{
- ///
- /// Provides implementation similar to certificate store provider.
- /// A CEK encrypted with certificate provider should be decryptable by this provider and vice versa.
- ///
- /// Envolope Format for the encrypted column encryption key
- /// version + keyPathLength + ciphertextLength + keyPath + ciphertext + signature
- /// version: A single byte indicating the format version.
- /// keyPathLength: Length of the keyPath.
- /// ciphertextLength: ciphertext length
- /// keyPath: keyPath used to encrypt the column encryption key. This is only used for troubleshooting purposes and is not verified during decryption.
- /// ciphertext: Encrypted column encryption key
- /// signature: Signature of the entire byte array. Signature is validated before decrypting the column encryption key.
- ///
+ ///
public class SqlColumnEncryptionCngProvider : SqlColumnEncryptionKeyStoreProvider
{
- ///
- /// Name for the CNG key store provider.
- ///
+ ///
public const string ProviderName = @"MSSQL_CNG_STORE";
///
@@ -38,51 +26,26 @@ public class SqlColumnEncryptionCngProvider : SqlColumnEncryptionKeyStoreProvide
///
internal const string KeyPathReference = @"Microsoft Cryptography API: Next Generation (CNG) provider";
- ///
- /// This function uses the asymmetric key specified by the key path
- /// and decrypts an encrypted CEK with RSA encryption algorithm.
- ///
- /// Complete path of an asymmetric key in CNG
- /// Asymmetric Key Encryption Algorithm
- /// Encrypted Column Encryption Key
- /// Plain text column encryption key
- public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey)
+ ///
+ public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey)
{
throw new PlatformNotSupportedException();
}
- ///
- /// This function uses the asymmetric key specified by the key path
- /// and encrypts CEK with RSA encryption algorithm.
- ///
- /// Complete path of an asymmetric key in AKV
- /// Asymmetric Key Encryption Algorithm
- /// The plaintext column encryption key
- /// Encrypted column encryption key
- public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey)
+ ///
+ public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey)
{
throw new PlatformNotSupportedException();
}
- ///
- /// Throws NotSupportedException. In this version of .NET Framework this provider does not support signing column master key metadata.
- ///
- /// Complete path of an asymmetric key. Path format is specific to a key store provider.
- /// Boolean indicating whether this key can be sent to trusted enclave
- /// Encrypted column encryption key
- public override byte[] SignColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations)
+ ///
+ public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations)
{
throw new PlatformNotSupportedException();
}
- ///
- /// Throws NotSupportedException. In this version of .NET Framework this provider does not support verifying signatures of column master key metadata.
- ///
- /// Complete path of an asymmetric key. Path format is specific to a key store provider.
- /// Boolean indicating whether this key can be sent to trusted enclave
- /// Signature for the master key metadata
- /// Boolean indicating whether the master key metadata can be verified based on the provided signature
- public override bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations, byte[] signature)
+ ///
+ public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature)
{
throw new PlatformNotSupportedException();
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.netcore.Unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.netcore.Unix.cs
index 4e2836c020..d28fcf9000 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.netcore.Unix.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.netcore.Unix.cs
@@ -6,26 +6,14 @@
using System;
+#nullable enable
+
namespace Microsoft.Data.SqlClient
{
- ///
- /// Provides implementation similar to certificate store provider.
- /// A CEK encrypted with certificate store provider should be decryptable by this provider and vice versa.
- ///
- /// Envolope Format for the encrypted column encryption key
- /// version + keyPathLength + ciphertextLength + keyPath + ciphertext + signature
- /// version: A single byte indicating the format version.
- /// keyPathLength: Length of the keyPath.
- /// ciphertextLength: ciphertext length
- /// keyPath: keyPath used to encrypt the column encryption key. This is only used for troubleshooting purposes and is not verified during decryption.
- /// ciphertext: Encrypted column encryption key
- /// signature: Signature of the entire byte array. Signature is validated before decrypting the column encryption key.
- ///
+ ///
public class SqlColumnEncryptionCspProvider : SqlColumnEncryptionKeyStoreProvider
{
- ///
- /// Name for the CSP key store provider.
- ///
+ ///
public const string ProviderName = @"MSSQL_CSP_PROVIDER";
///
@@ -34,57 +22,30 @@ public class SqlColumnEncryptionCspProvider : SqlColumnEncryptionKeyStoreProvide
internal const string MasterKeyType = @"asymmetric key";
///
- /// This encryption keystore uses the master key path to reference a CSP.
+ /// This encryption keystore uses the master key path to reference a CSP provider.
///
internal const string KeyPathReference = @"Microsoft Cryptographic Service Provider (CSP)";
- ///
- /// This function uses the asymmetric key specified by the key path
- /// and decrypts an encrypted CEK with RSA encryption algorithm.
- ///
- /// Complete path of an asymmetric key in CSP
- /// Asymmetric Key Encryption Algorithm
- /// Encrypted Column Encryption Key
- /// Plain text column encryption key
- public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm,
- byte[] encryptedColumnEncryptionKey)
+ ///
+ public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey)
{
throw new PlatformNotSupportedException();
}
- ///
- /// This function uses the asymmetric key specified by the key path
- /// and encrypts CEK with RSA encryption algorithm.
- ///
- /// Complete path of an asymmetric key in AKV
- /// Asymmetric Key Encryption Algorithm
- /// The plaintext column encryption key
- /// Encrypted column encryption key
- public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm,
- byte[] columnEncryptionKey)
+ ///
+ public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey)
{
throw new PlatformNotSupportedException();
}
- ///
- /// Throws NotSupportedException. In this version of .NET Framework this provider does not support signing column master key metadata.
- ///
- /// Complete path of an asymmetric key. Path format is specific to a key store provider.
- /// Boolean indicating whether this key can be sent to trusted enclave
- /// Encrypted column encryption key
- public override byte[] SignColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations)
+ ///
+ public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations)
{
throw new PlatformNotSupportedException();
}
- ///
- /// Throws NotSupportedException. In this version of .NET Framework this provider does not support verifying signatures of column master key metadata.
- ///
- /// Complete path of an asymmetric key. Path format is specific to a key store provider.
- /// Boolean indicating whether this key can be sent to trusted enclave
- /// Signature for the master key metadata
- /// Boolean indicating whether the master key metadata can be verified based on the provided signature
- public override bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations, byte[] signature)
+ ///
+ public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature)
{
throw new PlatformNotSupportedException();
}
From 127960b87f31976f4f8d9ef734b02f692dfaf531 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Thu, 11 Sep 2025 22:34:44 +0100
Subject: [PATCH 6/8] Prune unused resource strings
---
.../src/Microsoft/Data/SqlClient/SqlUtil.cs | 25 -----------
.../src/Resources/Strings.Designer.cs | 45 -------------------
.../src/Resources/Strings.resx | 15 -------
3 files changed, 85 deletions(-)
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 55e41147b6..7c128df5d5 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs
@@ -1788,16 +1788,6 @@ internal static Exception InvalidCiphertextLengthInEncryptedCEKCertificate(int a
return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidCiphertextLengthInEncryptedCEKCertificate, actual, expected, certificateName), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
}
- internal static Exception InvalidCiphertextLengthInEncryptedCEKCsp(int actual, int expected, string masterKeyPath)
- {
- return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidCiphertextLengthInEncryptedCEKCsp, actual, expected, masterKeyPath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
- }
-
- internal static Exception InvalidCiphertextLengthInEncryptedCEKCng(int actual, int expected, string masterKeyPath)
- {
- return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidCiphertextLengthInEncryptedCEKCng, actual, expected, masterKeyPath), 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);
@@ -1808,16 +1798,6 @@ internal static Exception InvalidSignatureInEncryptedCEKCertificate(int actual,
return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidSignatureInEncryptedCEKCertificate, actual, expected, masterKeyPath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
}
- internal static Exception InvalidSignatureInEncryptedCEKCsp(int actual, int expected, string masterKeyPath)
- {
- return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidSignatureInEncryptedCEKCsp, actual, expected, masterKeyPath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
- }
-
- internal static Exception InvalidSignatureInEncryptedCEKCng(int actual, int expected, string masterKeyPath)
- {
- return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidSignatureInEncryptedCEKCng, actual, expected, masterKeyPath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
- }
-
internal static Exception InvalidSignature(string masterKeyPath, string keyType)
{
return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidSignature, keyType, masterKeyPath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
@@ -1828,11 +1808,6 @@ internal static Exception InvalidCertificateSignature(string certificatePath)
return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidCertificateSignature, certificatePath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
}
- internal static Exception InvalidAsymmetricKeySignature(string masterKeyPath)
- {
- return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidAsymmetricKeySignature, masterKeyPath), 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 16cf52c86d..50132bed20 100644
--- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs
+++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs
@@ -12702,24 +12702,6 @@ internal static string TCE_InvalidCiphertextLengthInEncryptedCEKCertificate {
}
}
- ///
- /// Looks up a localized string similar to The specified encrypted column encryption key's ciphertext length: {0} does not match the ciphertext length: {1} when using column master key (asymmetric key) in '{2}'. The encrypted column encryption key may be corrupt, or the specified Microsoft Cryptography API: Next Generation (CNG) provider path may be incorrect..
- ///
- internal static string TCE_InvalidCiphertextLengthInEncryptedCEKCng {
- get {
- return ResourceManager.GetString("TCE_InvalidCiphertextLengthInEncryptedCEKCng", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to The specified encrypted column encryption key's ciphertext length: {0} does not match the ciphertext length: {1} when using column master key (asymmetric key) in '{2}'. The encrypted column encryption key may be corrupt, or the specified Microsoft Cryptographic Service provider (CSP) path may be incorrect..
- ///
- internal static string TCE_InvalidCiphertextLengthInEncryptedCEKCsp {
- get {
- return ResourceManager.GetString("TCE_InvalidCiphertextLengthInEncryptedCEKCsp", 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..
///
@@ -12918,15 +12900,6 @@ internal static string TCE_InvalidSignature {
}
}
- ///
- /// Looks up a localized string similar to The specified encrypted column encryption key signature does not match the signature computed with the column master key (asymmetric key) in '{0}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect..
- ///
- internal static string TCE_InvalidAsymmetricKeySignature {
- get {
- return ResourceManager.GetString("TCE_InvalidAsymmetricKeySignature", resourceCulture);
- }
- }
-
///
/// Looks up a localized string similar to The specified encrypted column encryption key's signature length: {0} does not match the signature length: {1} when using column master key ({2}) in '{3}'. The encrypted column encryption key may be corrupt, or the specified {4} path may be incorrect..
///
@@ -12945,24 +12918,6 @@ internal static string TCE_InvalidSignatureInEncryptedCEKCertificate {
}
}
- ///
- /// Looks up a localized string similar to The specified encrypted column encryption key's signature length: {0} does not match the signature length: {1} when using column master key (asymmetric key) in '{2}'. The encrypted column encryption key may be corrupt, or the specified Microsoft Cryptography API: Next Generation (CNG) provider path may be incorrect..
- ///
- internal static string TCE_InvalidSignatureInEncryptedCEKCng {
- get {
- return ResourceManager.GetString("TCE_InvalidSignatureInEncryptedCEKCng", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to The specified encrypted column encryption key's signature length: {0} does not match the signature length: {1} when using column master key (asymmetric key) in '{2}'. The encrypted column encryption key may be corrupt, or the specified Microsoft cryptographic service provider (CSP) path may be incorrect..
- ///
- internal static string TCE_InvalidSignatureInEncryptedCEKCsp {
- get {
- return ResourceManager.GetString("TCE_InvalidSignatureInEncryptedCEKCsp", 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 2eb501cec8..75f3c52874 100644
--- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx
+++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx
@@ -4080,33 +4080,18 @@
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 ciphertext length: {0} does not match the ciphertext length: {1} when using column master key (asymmetric key) in '{2}'. The encrypted column encryption key may be corrupt, or the specified Microsoft Cryptographic Service provider (CSP) 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 (asymmetric key) in '{2}'. The encrypted column encryption key may be corrupt, or the specified Microsoft Cryptography API: Next Generation (CNG) provider path may be incorrect.
-
The specified encrypted column encryption key's signature length: {0} does not match the signature length: {1} when using column master key ({2}) in '{3}'. The encrypted column encryption key may be corrupt, or the specified {4} path may be incorrect.
The specified encrypted column encryption key's signature length: {0} does not match the signature length: {1} when using column master key (certificate) in '{2}'. The encrypted column encryption key may be corrupt, or the specified certificate path may be incorrect.
-
- The specified encrypted column encryption key's signature length: {0} does not match the signature length: {1} when using column master key (asymmetric key) in '{2}'. The encrypted column encryption key may be corrupt, or the specified Microsoft cryptographic service provider (CSP) 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 (asymmetric key) in '{2}'. The encrypted column encryption key may be corrupt, or the specified Microsoft Cryptography API: Next Generation (CNG) provider path may be incorrect.
-
The specified encrypted column encryption key signature does not match the signature computed with the column master key ({0}) in '{1}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.
The specified encrypted column encryption key signature does not match the signature computed with the column master key (certificate) in '{0}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.
-
- The specified encrypted column encryption key signature does not match the signature computed with the column master key (asymmetric key) in '{0}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.
-
Certificate specified in key path '{0}' does not have a private key to encrypt a column encryption key. Verify the certificate is imported correctly.
From 315236170d266c2e7c7fa9a60d100d0bbf5307f8 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Fri, 12 Sep 2025 20:31:17 +0100
Subject: [PATCH 7/8] Initial code review
Simplify CreateRSACngProvider.
Use static CngProvider instances for well-known providers where possible.
---
.../SqlColumnEncryptionCngProvider.Windows.cs | 49 +++++++++++++------
1 file changed, 33 insertions(+), 16 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs
index 92655b7c43..e55b62ad28 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs
@@ -144,36 +144,32 @@ private static void ValidateNonEmptyKeyPath([NotNull] string? masterKeyPath, boo
private static RSACng CreateRSACngProvider(string keyPath, bool isSystemOp)
{
// Get CNGProvider and the KeyID
- GetCngProviderAndKeyId(keyPath, isSystemOp, out string cngProviderName, out string keyIdentifier);
-
- CngProvider cngProvider = new(cngProviderName);
- CngKey cngKey;
+ GetCngProviderAndKeyId(keyPath, isSystemOp, out CngProvider cngProvider, out string keyIdentifier);
try
{
- cngKey = CngKey.Open(keyIdentifier, cngProvider);
+ using CngKey cngKey = CngKey.Open(keyIdentifier, cngProvider);
+
+ // The RSACng constructor copies the input CngKey, so it is safe to dispose the original.
+ return new RSACng(cngKey);
}
catch (CryptographicException)
{
- throw SQL.InvalidCngKey(keyPath, cngProviderName, keyIdentifier, isSystemOp);
- }
-
- using (cngKey)
- {
- return new RSACng(cngKey);
+ throw SQL.InvalidCngKey(keyPath, cngProvider.Provider, keyIdentifier, isSystemOp);
}
}
///
- /// Extracts the CNG provider name and key name from the given key path.
+ /// Extracts the CNG provider and key name from the given key path.
///
/// Key path in the format [CNG provider name]/[key name].
/// Indicates if ADO.NET calls or the customer calls the API.
- /// CNG provider name.
+ /// CNG provider.
/// Key name inside the CNG provider.
- private static void GetCngProviderAndKeyId(string keyPath, bool isSystemOp, out string cngProvider, out string keyIdentifier)
+ private static void GetCngProviderAndKeyId(string keyPath, bool isSystemOp, out CngProvider cngProvider, out string keyIdentifier)
{
- int indexOfSlash = keyPath.IndexOf('/');
+ ReadOnlySpan keyPathSpan = keyPath.AsSpan();
+ int indexOfSlash = keyPathSpan.IndexOf('/');
if (indexOfSlash == -1)
{
@@ -188,7 +184,28 @@ private static void GetCngProviderAndKeyId(string keyPath, bool isSystemOp, out
throw SQL.EmptyCngKeyId(keyPath, isSystemOp);
}
- cngProvider = keyPath.Substring(0, indexOfSlash);
+ ReadOnlySpan cngProviderName = keyPathSpan.Slice(0, indexOfSlash);
+
+ // If the provider is one of the well-known providers, use the static instance instead of allocating a new string and CngProvider.
+ if (cngProviderName.Equals(CngProvider.MicrosoftSoftwareKeyStorageProvider.Provider.AsSpan(), StringComparison.OrdinalIgnoreCase))
+ {
+ cngProvider = CngProvider.MicrosoftSoftwareKeyStorageProvider;
+ }
+ else if (cngProviderName.Equals(CngProvider.MicrosoftSmartCardKeyStorageProvider.Provider.AsSpan(), StringComparison.OrdinalIgnoreCase))
+ {
+ cngProvider = CngProvider.MicrosoftSmartCardKeyStorageProvider;
+ }
+#if NET
+ else if (cngProviderName.Equals(CngProvider.MicrosoftPlatformCryptoProvider.Provider.AsSpan(), StringComparison.OrdinalIgnoreCase))
+ {
+ cngProvider = CngProvider.MicrosoftPlatformCryptoProvider;
+ }
+#endif
+ else
+ {
+ cngProvider = new(cngProviderName.ToString());
+ }
+
keyIdentifier = keyPath.Substring(indexOfSlash + 1);
}
}
From b09cfea9b81494fdeae5d9b3bec067a2f7071e3f Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Wed, 24 Sep 2025 20:38:28 +0100
Subject: [PATCH 8/8] Reorder static methods before instance methods
---
.../SqlColumnEncryptionCngProvider.Windows.cs | 128 +++++++++---------
.../SqlColumnEncryptionCspProvider.Windows.cs | 128 +++++++++---------
2 files changed, 128 insertions(+), 128 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs
index e55b62ad28..bd8b2391fa 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Windows.cs
@@ -33,70 +33,6 @@ public class SqlColumnEncryptionCngProvider : SqlColumnEncryptionKeyStoreProvide
///
private const string RSAEncryptionAlgorithmWithOAEP = @"RSA_OAEP";
- ///
- public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey)
- {
- // Validate the input parameters
- ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: true);
-
- if (encryptedColumnEncryptionKey is null)
- {
- throw SQL.NullEncryptedColumnEncryptionKey();
- }
-
- if (encryptedColumnEncryptionKey.Length == 0)
- {
- throw SQL.EmptyEncryptedColumnEncryptionKey();
- }
-
- // Validate encryptionAlgorithm
- ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true);
-
- // Create RSA Provider with the given CNG provider name and key name
- RSA rsaProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: true);
- using EncryptedColumnEncryptionKeyParameters cekDecryptionParameters = new(rsaProvider, masterKeyPath, MasterKeyType, KeyPathReference);
-
- return cekDecryptionParameters.Decrypt(encryptedColumnEncryptionKey);
- }
-
- ///
- public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey)
- {
- // Validate the input parameters
- ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: false);
-
- if (columnEncryptionKey is null)
- {
- throw SQL.NullColumnEncryptionKey();
- }
-
- if (columnEncryptionKey.Length == 0)
- {
- throw SQL.EmptyColumnEncryptionKey();
- }
-
- // Validate encryptionAlgorithm
- ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: false);
-
- // Create RSA Provider with the given CNG provider name and key name
- RSA rsaProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: false);
- using EncryptedColumnEncryptionKeyParameters cekEncryptionParameters = new(rsaProvider, masterKeyPath, MasterKeyType, KeyPathReference);
-
- return cekEncryptionParameters.Encrypt(columnEncryptionKey);
- }
-
- ///
- public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations)
- {
- throw new NotSupportedException();
- }
-
- ///
- public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature)
- {
- throw new NotSupportedException();
- }
-
///
/// This function validates that the encryption algorithm is RSA_OAEP and if it is not,
/// then throws an exception.
@@ -208,5 +144,69 @@ private static void GetCngProviderAndKeyId(string keyPath, bool isSystemOp, out
keyIdentifier = keyPath.Substring(indexOfSlash + 1);
}
+
+ ///
+ public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey)
+ {
+ // Validate the input parameters
+ ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: true);
+
+ if (encryptedColumnEncryptionKey is null)
+ {
+ throw SQL.NullEncryptedColumnEncryptionKey();
+ }
+
+ if (encryptedColumnEncryptionKey.Length == 0)
+ {
+ throw SQL.EmptyEncryptedColumnEncryptionKey();
+ }
+
+ // Validate encryptionAlgorithm
+ ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true);
+
+ // Create RSA Provider with the given CNG provider name and key name
+ RSA rsaProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: true);
+ using EncryptedColumnEncryptionKeyParameters cekDecryptionParameters = new(rsaProvider, masterKeyPath, MasterKeyType, KeyPathReference);
+
+ return cekDecryptionParameters.Decrypt(encryptedColumnEncryptionKey);
+ }
+
+ ///
+ public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey)
+ {
+ // Validate the input parameters
+ ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: false);
+
+ if (columnEncryptionKey is null)
+ {
+ throw SQL.NullColumnEncryptionKey();
+ }
+
+ if (columnEncryptionKey.Length == 0)
+ {
+ throw SQL.EmptyColumnEncryptionKey();
+ }
+
+ // Validate encryptionAlgorithm
+ ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: false);
+
+ // Create RSA Provider with the given CNG provider name and key name
+ RSA rsaProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: false);
+ using EncryptedColumnEncryptionKeyParameters cekEncryptionParameters = new(rsaProvider, masterKeyPath, MasterKeyType, KeyPathReference);
+
+ return cekEncryptionParameters.Encrypt(columnEncryptionKey);
+ }
+
+ ///
+ public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature)
+ {
+ throw new NotSupportedException();
+ }
}
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs
index 5b4ce129f0..ec2b7554a8 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs
@@ -36,70 +36,6 @@ public class SqlColumnEncryptionCspProvider : SqlColumnEncryptionKeyStoreProvide
///
private const string RSAEncryptionAlgorithmWithOAEP = @"RSA_OAEP";
- ///
- public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey)
- {
- // Validate the input parameters
- ValidateNonEmptyCSPKeyPath(masterKeyPath, isSystemOp: true);
-
- if (encryptedColumnEncryptionKey is null)
- {
- throw SQL.NullEncryptedColumnEncryptionKey();
- }
-
- if (encryptedColumnEncryptionKey.Length == 0)
- {
- throw SQL.EmptyEncryptedColumnEncryptionKey();
- }
-
- // Validate encryptionAlgorithm
- ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true);
-
- // Create RSA Provider with the given CSP name and key name
- RSA rsaProvider = CreateRSACryptoProvider(masterKeyPath, isSystemOp: true);
- using EncryptedColumnEncryptionKeyParameters cekDecryptionParameters = new(rsaProvider, masterKeyPath, MasterKeyType, KeyPathReference);
-
- return cekDecryptionParameters.Decrypt(encryptedColumnEncryptionKey);
- }
-
- ///
- public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey)
- {
- // Validate the input parameters
- ValidateNonEmptyCSPKeyPath(masterKeyPath, isSystemOp: false);
-
- if (columnEncryptionKey is null)
- {
- throw SQL.NullColumnEncryptionKey();
- }
-
- if (columnEncryptionKey.Length == 0)
- {
- throw SQL.EmptyColumnEncryptionKey();
- }
-
- // Validate encryptionAlgorithm
- ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: false);
-
- // Create RSA Provider with the given CSP name and key name
- RSA rsaProvider = CreateRSACryptoProvider(masterKeyPath, isSystemOp: false);
- using EncryptedColumnEncryptionKeyParameters cekEncryptionParameters = new(rsaProvider, masterKeyPath, MasterKeyType, KeyPathReference);
-
- return cekEncryptionParameters.Encrypt(columnEncryptionKey);
- }
-
- ///
- public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations)
- {
- throw new NotSupportedException();
- }
-
- ///
- public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature)
- {
- throw new NotSupportedException();
- }
-
///
/// This function validates that the encryption algorithm is RSA_OAEP and if it is not,
/// then throws an exception.
@@ -211,5 +147,69 @@ private static int GetProviderType(string providerName, string keyPath, bool isS
return (int)(key.GetValue(@"Type")
?? throw SQL.InvalidCspName(providerName, keyPath, isSystemOp));
}
+
+ ///
+ public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey)
+ {
+ // Validate the input parameters
+ ValidateNonEmptyCSPKeyPath(masterKeyPath, isSystemOp: true);
+
+ if (encryptedColumnEncryptionKey is null)
+ {
+ throw SQL.NullEncryptedColumnEncryptionKey();
+ }
+
+ if (encryptedColumnEncryptionKey.Length == 0)
+ {
+ throw SQL.EmptyEncryptedColumnEncryptionKey();
+ }
+
+ // Validate encryptionAlgorithm
+ ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true);
+
+ // Create RSA Provider with the given CSP name and key name
+ RSA rsaProvider = CreateRSACryptoProvider(masterKeyPath, isSystemOp: true);
+ using EncryptedColumnEncryptionKeyParameters cekDecryptionParameters = new(rsaProvider, masterKeyPath, MasterKeyType, KeyPathReference);
+
+ return cekDecryptionParameters.Decrypt(encryptedColumnEncryptionKey);
+ }
+
+ ///
+ public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey)
+ {
+ // Validate the input parameters
+ ValidateNonEmptyCSPKeyPath(masterKeyPath, isSystemOp: false);
+
+ if (columnEncryptionKey is null)
+ {
+ throw SQL.NullColumnEncryptionKey();
+ }
+
+ if (columnEncryptionKey.Length == 0)
+ {
+ throw SQL.EmptyColumnEncryptionKey();
+ }
+
+ // Validate encryptionAlgorithm
+ ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: false);
+
+ // Create RSA Provider with the given CSP name and key name
+ RSA rsaProvider = CreateRSACryptoProvider(masterKeyPath, isSystemOp: false);
+ using EncryptedColumnEncryptionKeyParameters cekEncryptionParameters = new(rsaProvider, masterKeyPath, MasterKeyType, KeyPathReference);
+
+ return cekEncryptionParameters.Encrypt(columnEncryptionKey);
+ }
+
+ ///
+ public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature)
+ {
+ throw new NotSupportedException();
+ }
}
}