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 2a6c86357c..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
@@ -2,10 +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 System;
-using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
-using System.Text;
+
+#nullable enable
namespace Microsoft.Data.SqlClient
{
@@ -32,373 +34,179 @@ public class SqlColumnEncryptionCngProvider : SqlColumnEncryptionKeyStoreProvide
private const string RSAEncryptionAlgorithmWithOAEP = @"RSA_OAEP";
///
- /// Algorithm version
+ /// This function validates that the encryption algorithm is RSA_OAEP and if it is not,
+ /// then throws an exception.
///
- private readonly byte[] _version = new byte[] { 0x01 };
-
- ///
- public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey)
+ /// Asymmetric key encryption algorithm.
+ /// Indicates if ADO.NET calls or the customer calls the API.
+ private static void ValidateEncryptionAlgorithm([NotNull] string? encryptionAlgorithm, bool isSystemOp)
{
- // Validate the input parameters
- ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: true);
-
- if (encryptedColumnEncryptionKey == null)
- {
- throw SQL.NullEncryptedColumnEncryptionKey();
- }
-
- if (0 == encryptedColumnEncryptionKey.Length)
+ // This validates that the encryption algorithm is RSA_OAEP
+ if (encryptionAlgorithm is null)
{
- throw SQL.EmptyEncryptedColumnEncryptionKey();
+ throw SQL.NullKeyEncryptionAlgorithm(isSystemOp);
}
- // Validate encryptionAlgorithm
- ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true);
-
- // Create RSA Provider with the given CNG name and key name
- RSACng rsaCngProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: true);
-
- // Validate whether the key is RSA one or not and then get the key size
- int keySizeInBytes = GetKeySize(rsaCngProvider);
-
- // Validate and decrypt the EncryptedColumnEncryptionKey
- // Format is
- // version + keyPathLength + ciphertextLength + keyPath + ciphervtext + signature
- //
- // keyPath is present in the encrypted column encryption key for identifying the original source of the asymmetric key pair and
- // we will not validate it against the data contained in the CMK metadata (masterKeyPath).
-
- // Validate the version byte
- if (encryptedColumnEncryptionKey[0] != _version[0])
+ if (!string.Equals(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, StringComparison.OrdinalIgnoreCase))
{
- throw SQL.InvalidAlgorithmVersionInEncryptedCEK(encryptedColumnEncryptionKey[0], _version[0]);
+ throw SQL.InvalidKeyEncryptionAlgorithm(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, isSystemOp);
}
+ }
- // 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)
+ ///
+ /// Checks if the CNG key path is Empty or Null (and raises exception if they are).
+ ///
+ /// 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)
{
- throw SQL.InvalidCiphertextLengthInEncryptedCEKCng(cipherTextLength, keySizeInBytes, masterKeyPath);
+ throw SQL.NullCngKeyPath(isSystemOp);
}
- // 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)
+ if (string.IsNullOrWhiteSpace(masterKeyPath))
{
- throw SQL.InvalidSignatureInEncryptedCEKCng(signatureLength, keySizeInBytes, masterKeyPath);
+ throw SQL.InvalidCngPath(masterKeyPath, isSystemOp);
}
+ }
- // 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);
+ ///
+ /// Creates a RSACng from the given key path.
+ ///
+ /// 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)
+ {
+ // Get CNGProvider and the KeyID
+ GetCngProviderAndKeyId(keyPath, isSystemOp, out CngProvider cngProvider, out string keyIdentifier);
- // Compute the hash to validate the signature
- byte[] hash;
- using (SHA256 sha256 = SHA256.Create())
+ try
{
- 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.");
+ using CngKey cngKey = CngKey.Open(keyIdentifier, cngProvider);
- // Validate the signature
- if (!RSAVerifySignature(hash, signature, rsaCngProvider))
+ // The RSACng constructor copies the input CngKey, so it is safe to dispose the original.
+ return new RSACng(cngKey);
+ }
+ catch (CryptographicException)
{
- throw SQL.InvalidAsymmetricKeySignature(masterKeyPath);
+ throw SQL.InvalidCngKey(keyPath, cngProvider.Provider, keyIdentifier, isSystemOp);
}
-
- // Decrypt the CEK
- return RSADecrypt(rsaCngProvider, cipherText);
}
- ///
- public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey)
+ ///
+ /// 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.
+ /// Key name inside the CNG provider.
+ private static void GetCngProviderAndKeyId(string keyPath, bool isSystemOp, out CngProvider cngProvider, out string keyIdentifier)
{
- // Validate the input parameters
- ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: false);
+ ReadOnlySpan keyPathSpan = keyPath.AsSpan();
+ int indexOfSlash = keyPathSpan.IndexOf('/');
- if (columnEncryptionKey == null)
+ if (indexOfSlash == -1)
{
- throw SQL.NullColumnEncryptionKey();
+ throw SQL.InvalidCngPath(keyPath, isSystemOp);
}
- else if (0 == columnEncryptionKey.Length)
+ else if (indexOfSlash == 0)
{
- throw SQL.EmptyColumnEncryptionKey();
+ throw SQL.EmptyCngName(keyPath, isSystemOp);
}
-
- // Validate encryptionAlgorithm
- ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: false);
-
- // CreateCNGProviderWithKey
- RSACng rsaCngProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: false);
-
- // Validate whether the key is RSA one or not and then get the key size
- int keySizeInBytes = GetKeySize(rsaCngProvider);
-
- // Construct the encryptedColumnEncryptionKey
- // Format is
- // version + keyPathLength + ciphertextLength + ciphertext + keyPath + signature
- //
- // We currently only support one version
- byte[] version = new byte[] { _version[0] };
-
- // Get the Unicode encoded bytes of cultureinvariant lower case masterKeyPath
- byte[] masterKeyPathBytes = Encoding.Unicode.GetBytes(masterKeyPath.ToLowerInvariant());
- byte[] keyPathLength = BitConverter.GetBytes((Int16)masterKeyPathBytes.Length);
-
- // Encrypt the plain text
- byte[] cipherText = RSAEncrypt(rsaCngProvider, columnEncryptionKey);
- byte[] cipherTextLength = BitConverter.GetBytes((Int16)cipherText.Length);
- Debug.Assert(cipherText.Length == keySizeInBytes, @"cipherText length does not match the RSA key size");
-
- // Compute hash
- // SHA-2-256(version + keyPathLength + ciphertextLength + keyPath + ciphertext)
- byte[] hash;
- using (SHA256 sha256 = SHA256.Create())
+ else if (indexOfSlash == keyPath.Length - 1)
{
- 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;
+ throw SQL.EmptyCngKeyId(keyPath, isSystemOp);
}
- // 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.");
+ ReadOnlySpan cngProviderName = keyPathSpan.Slice(0, indexOfSlash);
- // Construct the encrypted column encryption key
- // EncryptedColumnEncryptionKey = version + keyPathLength + ciphertextLength + keyPath + ciphertext + signature
- int encryptedColumnEncryptionKeyLength = version.Length + cipherTextLength.Length + keyPathLength.Length + cipherText.Length + masterKeyPathBytes.Length + signedHash.Length;
- byte[] encryptedColumnEncryptionKey = new byte[encryptedColumnEncryptionKeyLength];
-
- // Copy version byte
- int currentIndex = 0;
- Buffer.BlockCopy(version, 0, encryptedColumnEncryptionKey, currentIndex, version.Length);
- currentIndex += version.Length;
-
- // Copy key path length
- Buffer.BlockCopy(keyPathLength, 0, encryptedColumnEncryptionKey, currentIndex, keyPathLength.Length);
- currentIndex += keyPathLength.Length;
-
- // Copy ciphertext length
- Buffer.BlockCopy(cipherTextLength, 0, encryptedColumnEncryptionKey, currentIndex, cipherTextLength.Length);
- currentIndex += cipherTextLength.Length;
-
- // Copy key path
- Buffer.BlockCopy(masterKeyPathBytes, 0, encryptedColumnEncryptionKey, currentIndex, masterKeyPathBytes.Length);
- currentIndex += masterKeyPathBytes.Length;
-
- // Copy ciphertext
- Buffer.BlockCopy(cipherText, 0, encryptedColumnEncryptionKey, currentIndex, cipherText.Length);
- currentIndex += cipherText.Length;
-
- // copy the signature
- Buffer.BlockCopy(signedHash, 0, encryptedColumnEncryptionKey, currentIndex, signedHash.Length);
-
- return encryptedColumnEncryptionKey;
- }
-
- ///
- public override byte[] SignColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations)
- {
- throw new NotSupportedException();
- }
-
- ///
- public override bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations, byte[] signature)
- {
- throw new NotSupportedException();
- }
-
- ///
- /// This function validates that the encryption algorithm is RSA_OAEP and if it is not,
- /// then throws an exception
- ///
- /// Asymmetric key encryptio algorithm
- /// Indicates if ADO.NET calls or the customer calls the API
- private void ValidateEncryptionAlgorithm(string encryptionAlgorithm, bool isSystemOp)
- {
- // This validates that the encryption algorithm is RSA_OAEP
- if (encryptionAlgorithm == null)
+ // 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))
{
- throw SQL.NullKeyEncryptionAlgorithm(isSystemOp);
+ cngProvider = CngProvider.MicrosoftSoftwareKeyStorageProvider;
}
-
- if (!string.Equals(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, StringComparison.OrdinalIgnoreCase))
+ else if (cngProviderName.Equals(CngProvider.MicrosoftSmartCardKeyStorageProvider.Provider.AsSpan(), StringComparison.OrdinalIgnoreCase))
{
- throw SQL.InvalidKeyEncryptionAlgorithm(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, isSystemOp);
+ cngProvider = CngProvider.MicrosoftSmartCardKeyStorageProvider;
}
- }
-
- ///
- /// Checks if the CNG key path is Empty or Null (and raises exception if they are).
- ///
- /// keypath containing the CNG provider name and key name
- /// Indicates if ADO.NET calls or the customer calls the API
- private void ValidateNonEmptyKeyPath(string masterKeyPath, bool isSystemOp)
- {
- if (string.IsNullOrWhiteSpace(masterKeyPath))
+#if NET
+ else if (cngProviderName.Equals(CngProvider.MicrosoftPlatformCryptoProvider.Provider.AsSpan(), StringComparison.OrdinalIgnoreCase))
{
- if (masterKeyPath == null)
- {
- throw SQL.NullCngKeyPath(isSystemOp);
- }
- else
- {
- throw SQL.InvalidCngPath(masterKeyPath, isSystemOp);
- }
+ cngProvider = CngProvider.MicrosoftPlatformCryptoProvider;
+ }
+#endif
+ else
+ {
+ cngProvider = new(cngProviderName.ToString());
}
- }
-
- ///
- /// 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);
+ keyIdentifier = keyPath.Substring(indexOfSlash + 1);
}
- ///
- /// 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)
+ ///
+ public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey)
{
- Debug.Assert((dataToSign != null) && (dataToSign.Length != 0));
- Debug.Assert(rsaCngProvider != null);
+ // Validate the input parameters
+ ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: true);
- // CodeQL [SM03796] Required for backwards compatibility with existing data and cross-driver compatability
- return rsaCngProvider.SignData(dataToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
- }
+ if (encryptedColumnEncryptionKey is null)
+ {
+ throw SQL.NullEncryptedColumnEncryptionKey();
+ }
- ///
- /// 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);
+ if (encryptedColumnEncryptionKey.Length == 0)
+ {
+ throw SQL.EmptyEncryptedColumnEncryptionKey();
+ }
- // CodeQL [SM03796] Required for backwards compatibility with existing data and cross-driver compatability
- return rsaCngProvider.VerifyData(dataToVerify, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
- }
+ // Validate encryptionAlgorithm
+ ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true);
- ///
- /// Gets the public Key size in bytes
- ///
- /// RSA CNG Provider.
- /// Key size in bytes
- private int GetKeySize(RSACng rsaCngProvider)
- {
- Debug.Assert(rsaCngProvider != null);
+ // 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 rsaCngProvider.KeySize / 8; // Convert from bits to byte
+ return cekDecryptionParameters.Decrypt(encryptedColumnEncryptionKey);
}
- ///
- /// Creates a RSACng object from the given keyName
- ///
- ///
- /// Indicates if ADO.NET calls or the customer calls the API
- ///
- private RSACng CreateRSACngProvider(string keyPath, bool isSystemOp)
+ ///
+ public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey)
{
- // Get CNGProvider and the KeyID
- string cngProviderName;
- string keyIdentifier;
- GetCngProviderAndKeyId(keyPath, isSystemOp, out cngProviderName, out keyIdentifier);
-
- CngProvider cngProvider = new CngProvider(cngProviderName);
- CngKey cngKey;
+ // Validate the input parameters
+ ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: false);
- try
+ if (columnEncryptionKey is null)
{
- cngKey = CngKey.Open(keyIdentifier, cngProvider);
+ throw SQL.NullColumnEncryptionKey();
}
- catch (CryptographicException)
+
+ if (columnEncryptionKey.Length == 0)
{
- throw SQL.InvalidCngKey(keyPath, cngProviderName, keyIdentifier, isSystemOp);
+ throw SQL.EmptyColumnEncryptionKey();
}
- return new RSACng(cngKey);
- }
+ // Validate encryptionAlgorithm
+ ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: false);
- ///
- /// Extracts the CNG provider and key name from the key path
- ///
- /// keypath in the format [CNG Provider]/[KeyName]
- /// Indicates if ADO.NET calls or the customer calls the API
- /// CNG Provider
- /// Key identifier inside the CNG provider
- private void GetCngProviderAndKeyId(string keyPath, bool isSystemOp, out string cngProvider, out string keyIdentifier)
- {
- int indexOfSlash = keyPath.IndexOf(@"/", StringComparison.Ordinal);
- if (indexOfSlash == -1)
- {
- throw SQL.InvalidCngPath(keyPath, isSystemOp);
- }
+ // 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);
- cngProvider = keyPath.Substring(0, indexOfSlash);
- keyIdentifier = keyPath.Substring(indexOfSlash + 1, keyPath.Length - (indexOfSlash + 1));
+ return cekEncryptionParameters.Encrypt(columnEncryptionKey);
+ }
- if (cngProvider.Length == 0)
- {
- throw SQL.EmptyCngName(keyPath, isSystemOp);
- }
+ ///
+ public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations)
+ {
+ throw new NotSupportedException();
+ }
- if (keyIdentifier.Length == 0)
- {
- throw SQL.EmptyCngKeyId(keyPath, isSystemOp);
- }
+ ///
+ public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature)
+ {
+ throw new NotSupportedException();
}
}
}
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.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Windows.cs
index db659a1784..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
@@ -2,11 +2,13 @@
// 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.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
-using System.Text;
-using Microsoft.Win32;
+
+#nullable enable
namespace Microsoft.Data.SqlClient
{
@@ -24,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)";
@@ -35,416 +37,179 @@ public class SqlColumnEncryptionCspProvider : SqlColumnEncryptionKeyStoreProvide
private const string RSAEncryptionAlgorithmWithOAEP = @"RSA_OAEP";
///
- /// Hashing algorithm used for signing
- ///
- private const string HashingAlgorithm = @"SHA256";
-
- ///
- /// Algorithm version
+ /// This function validates that the encryption algorithm is RSA_OAEP and if it is not,
+ /// then throws an exception.
///
- private readonly byte[] _version = new byte[] { 0x01 };
-
- ///
- public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey)
+ /// Asymmetric key encryption algorithm.
+ /// Indicates if ADO.NET calls or the customer calls the API.
+ private static void ValidateEncryptionAlgorithm([NotNull] string? encryptionAlgorithm, bool isSystemOp)
{
- // Validate the input parameters
- ValidateNonEmptyCSPKeyPath(masterKeyPath, isSystemOp: true);
-
- if (encryptedColumnEncryptionKey == null)
+ // This validates that the encryption algorithm is RSA_OAEP
+ if (encryptionAlgorithm is null)
{
- throw SQL.NullEncryptedColumnEncryptionKey();
+ throw SQL.NullKeyEncryptionAlgorithm(isSystemOp);
}
- if (0 == encryptedColumnEncryptionKey.Length)
+ if (!string.Equals(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, StringComparison.OrdinalIgnoreCase))
{
- throw SQL.EmptyEncryptedColumnEncryptionKey();
+ throw SQL.InvalidKeyEncryptionAlgorithm(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, isSystemOp);
}
+ }
- // Validate encryptionAlgorithm
- ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true);
-
- // Create RSA Provider with the given CSP name and key name
- RSACryptoServiceProvider rsaProvider = CreateRSACryptoProvider(masterKeyPath, isSystemOp: true);
-
- // Validate whether the key is RSA one or not and then get the key size
- int keySizeInBytes = GetKeySize(rsaProvider);
-
- // Validate and decrypt the EncryptedColumnEncryptionKey
- // Format is
- // version + keyPathLength + ciphertextLength + keyPath + ciphervtext + signature
- //
- // keyPath is present in the encrypted column encryption key for identifying the original source of the asymmetric key pair and
- // we will not validate it against the data contained in the CMK metadata (masterKeyPath).
-
- // Validate the version byte
- if (encryptedColumnEncryptionKey[0] != _version[0])
+ ///
+ /// Checks if the CSP key path is Empty or Null (and raises exception if they are).
+ ///
+ /// 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)
{
- throw SQL.InvalidAlgorithmVersionInEncryptedCEK(encryptedColumnEncryptionKey[0], _version[0]);
+ throw SQL.NullCspKeyPath(isSystemOp);
}
- // 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)
+ if (string.IsNullOrWhiteSpace(masterKeyPath))
{
- throw SQL.InvalidCiphertextLengthInEncryptedCEKCsp(cipherTextLength, keySizeInBytes, masterKeyPath);
+ throw SQL.InvalidCspPath(masterKeyPath, isSystemOp);
}
+ }
- // 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);
- }
+ ///
+ /// Creates a RSACryptoServiceProvider 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.
+ ///
+ private static RSACryptoServiceProvider CreateRSACryptoProvider(string keyPath, bool isSystemOp)
+ {
+ // Get CNGProvider and the KeyID
+ GetCspProviderAndKeyName(keyPath, isSystemOp, out string cspProviderName, out string keyName);
- // Get ciphertext
- byte[] cipherText = new byte[cipherTextLength];
- Buffer.BlockCopy(encryptedColumnEncryptionKey, currentIndex, cipherText, 0, cipherText.Length);
- currentIndex += cipherTextLength;
+ // Verify the existence of CSP and then get the provider type
+ int providerType = GetProviderType(cspProviderName, keyPath, isSystemOp);
- // Get signature
- byte[] signature = new byte[signatureLength];
- Buffer.BlockCopy(encryptedColumnEncryptionKey, currentIndex, signature, 0, signature.Length);
+ // Create a new instance of CspParameters for an RSA container.
+ CspParameters cspParams = new(providerType, cspProviderName, keyName) { Flags = CspProviderFlags.UseExistingKey };
+ const int KEYSETDOESNOTEXIST = -2146893802;
- // Compute the hash to validate the signature
- byte[] hash;
- using (SHA256 sha256 = SHA256.Create())
+ try
{
- sha256.TransformFinalBlock(encryptedColumnEncryptionKey, 0, encryptedColumnEncryptionKey.Length - signature.Length);
- hash = sha256.Hash;
+ // Create a new instance of RSACryptoServiceProvider
+ return new RSACryptoServiceProvider(cspParams);
}
-
- Debug.Assert(hash != null, @"hash should not be null while decrypting encrypted column encryption key.");
-
- // Validate the signature
- if (!RSAVerifySignature(hash, signature, rsaProvider))
+ catch (CryptographicException e) when (e.HResult == KEYSETDOESNOTEXIST)
{
- throw SQL.InvalidAsymmetricKeySignature(masterKeyPath);
+ // Key does not exist
+ throw SQL.InvalidCspKeyIdentifier(keyName, keyPath, isSystemOp);
}
-
- // Decrypt the CEK
- return RSADecrypt(rsaProvider, cipherText);
}
- ///
- public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey)
+ ///
+ /// Extracts the CSP provider name and key name from the given key path.
+ ///
+ /// 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)
{
- // Validate the input parameters
- ValidateNonEmptyCSPKeyPath(masterKeyPath, isSystemOp: false);
+ int indexOfSlash = keyPath.IndexOf('/');
- if (columnEncryptionKey == null)
+ if (indexOfSlash == -1)
{
- throw SQL.NullColumnEncryptionKey();
+ throw SQL.InvalidCspPath(keyPath, isSystemOp);
}
- else if (0 == columnEncryptionKey.Length)
+ else if (indexOfSlash == 0)
{
- throw SQL.EmptyColumnEncryptionKey();
+ throw SQL.EmptyCspName(keyPath, isSystemOp);
}
-
- // Validate encryptionAlgorithm
- ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: false);
-
- // Create RSA Provider with the given CSP name and key name
- RSACryptoServiceProvider rsaProvider = CreateRSACryptoProvider(masterKeyPath, isSystemOp: false);
-
- // Validate whether the key is RSA one or not and then get the key size
- int keySizeInBytes = GetKeySize(rsaProvider);
-
- // Construct the encryptedColumnEncryptionKey
- // Format is
- // version + keyPathLength + ciphertextLength + ciphertext + keyPath + signature
- //
- // We currently only support one version
- byte[] version = new byte[] { _version[0] };
-
- // Get the Unicode encoded bytes of cultureinvariant lower case masterKeyPath
- byte[] masterKeyPathBytes = Encoding.Unicode.GetBytes(masterKeyPath.ToLowerInvariant());
- byte[] keyPathLength = BitConverter.GetBytes((Int16)masterKeyPathBytes.Length);
-
- // Encrypt the plain text
- byte[] cipherText = RSAEncrypt(rsaProvider, columnEncryptionKey);
- byte[] cipherTextLength = BitConverter.GetBytes((Int16)cipherText.Length);
- Debug.Assert(cipherText.Length == keySizeInBytes, @"cipherText length does not match the RSA key size");
-
- // Compute hash
- // SHA-2-256(version + keyPathLength + ciphertextLength + keyPath + ciphertext)
- byte[] hash;
- using (SHA256 sha256 = SHA256.Create())
+ else if (indexOfSlash == keyPath.Length - 1)
{
- 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;
+ throw SQL.EmptyCspKeyId(keyPath, isSystemOp);
}
- // 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;
+ cspProviderName = keyPath.Substring(0, indexOfSlash);
+ keyIdentifier = keyPath.Substring(indexOfSlash + 1);
}
- ///
- public override byte[] SignColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations)
+ ///
+ /// Gets the type from a given CSP provider name.
+ ///
+ /// 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)
{
- throw new NotSupportedException();
- }
+ using RegistryKey key = Registry.LocalMachine.OpenSubKey(@$"SOFTWARE\Microsoft\Cryptography\Defaults\Provider\{providerName}")
+ ?? throw SQL.InvalidCspName(providerName, keyPath, isSystemOp);
- ///
- public override bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations, byte[] signature)
- {
- throw new NotSupportedException();
+ return (int)(key.GetValue(@"Type")
+ ?? throw SQL.InvalidCspName(providerName, keyPath, isSystemOp));
}
- ///
- /// This function validates that the encryption algorithm is RSA_OAEP and if it is not,
- /// then throws an exception
- ///
- /// Asymmetric key encryption algorithm
- /// Indicates if ADO.NET calls or the customer calls the API
- private void ValidateEncryptionAlgorithm(string encryptionAlgorithm, bool isSystemOp)
+ ///
+ public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey)
{
- // This validates that the encryption algorithm is RSA_OAEP
- if (encryptionAlgorithm == null)
- {
- throw SQL.NullKeyEncryptionAlgorithm(isSystemOp);
- }
+ // Validate the input parameters
+ ValidateNonEmptyCSPKeyPath(masterKeyPath, isSystemOp: true);
- if (string.Equals(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, StringComparison.OrdinalIgnoreCase) != true)
+ if (encryptedColumnEncryptionKey is null)
{
- throw SQL.InvalidKeyEncryptionAlgorithm(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, isSystemOp);
+ throw SQL.NullEncryptedColumnEncryptionKey();
}
- }
-
- ///
- /// Checks if the CSP key path is Empty or Null (and raises exception if they are).
- ///
- /// CSP key path.
- /// Indicates if ADO.NET calls or the customer calls the API
- private void ValidateNonEmptyCSPKeyPath(string masterKeyPath, bool isSystemOp)
- {
- if (string.IsNullOrWhiteSpace(masterKeyPath))
+ if (encryptedColumnEncryptionKey.Length == 0)
{
- if (masterKeyPath == null)
- {
- throw SQL.NullCspKeyPath(isSystemOp);
- }
- else
- {
- throw SQL.InvalidCspPath(masterKeyPath, isSystemOp);
- }
+ throw SQL.EmptyEncryptedColumnEncryptionKey();
}
- }
-
- ///
- /// 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);
- }
+ // Validate encryptionAlgorithm
+ ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true);
- ///
- /// 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);
+ // 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 rscp.KeySize / 8;
+ return cekDecryptionParameters.Decrypt(encryptedColumnEncryptionKey);
}
- ///
- /// Creates a RSACryptoServiceProvider from the given key path which contains both CSP name and key name
- ///
- /// key path in the format of [CAPI provider name]/[key name]
- /// Indicates if ADO.NET calls or the customer calls the API
- ///
- private RSACryptoServiceProvider CreateRSACryptoProvider(string keyPath, bool isSystemOp)
+ ///
+ public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey)
{
- // Get CNGProvider and the KeyID
- string cspProviderName;
- string keyName;
- GetCspProviderAndKeyName(keyPath, isSystemOp, out cspProviderName, out keyName);
-
- // Verify the existence of CSP and then get the provider type
- int providerType = GetProviderType(cspProviderName, keyPath, isSystemOp);
-
- // Create a new instance of CspParameters for an RSA container.
- CspParameters cspParams = new CspParameters(providerType, cspProviderName, keyName);
- cspParams.Flags = CspProviderFlags.UseExistingKey;
-
- RSACryptoServiceProvider rscp = null;
+ // Validate the input parameters
+ ValidateNonEmptyCSPKeyPath(masterKeyPath, isSystemOp: false);
- try
+ if (columnEncryptionKey is null)
{
- //Create a new instance of RSACryptoServiceProvider
- rscp = new RSACryptoServiceProvider(cspParams);
- }
- catch (CryptographicException e)
- {
- const int KEYSETDOESNOTEXIST = -2146893802;
- if (e.HResult == KEYSETDOESNOTEXIST)
- {
- // Key does not exist
- throw SQL.InvalidCspKeyIdentifier(keyName, keyPath, isSystemOp);
- }
- else
- {
- // bubble up the exception
- throw;
- }
+ throw SQL.NullColumnEncryptionKey();
}
- return rscp;
- }
-
- ///
- /// Extracts the CSP provider name and key name from the given key path
- ///
- /// key path in the format of [CSP provider name]/[key name]
- /// Indicates if ADO.NET calls or the customer calls the API
- /// output containing the CSP provider name
- /// output containing the key name
- private void GetCspProviderAndKeyName(string keyPath, bool isSystemOp, out string cspProviderName, out string keyIdentifier)
- {
- int indexOfSlash = keyPath.IndexOf(@"/", StringComparison.Ordinal);
- if (indexOfSlash == -1)
+ if (columnEncryptionKey.Length == 0)
{
- throw SQL.InvalidCspPath(keyPath, isSystemOp);
+ throw SQL.EmptyColumnEncryptionKey();
}
- cspProviderName = keyPath.Substring(0, indexOfSlash);
- keyIdentifier = keyPath.Substring(indexOfSlash + 1, keyPath.Length - (indexOfSlash + 1));
+ // Validate encryptionAlgorithm
+ ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: false);
- if (cspProviderName.Length == 0)
- {
- throw SQL.EmptyCspName(keyPath, isSystemOp);
- }
+ // 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);
- if (keyIdentifier.Length == 0)
- {
- throw SQL.EmptyCspKeyId(keyPath, isSystemOp);
- }
+ return cekEncryptionParameters.Encrypt(columnEncryptionKey);
}
- ///
- /// Gets the provider type from a given CAPI provider name
- ///
- /// CAPI provider name
- /// key path in the format of [CSP provider name]/[key name]
- /// Indicates if ADO.NET calls or the customer calls the API
- ///
- private int GetProviderType(string providerName, string keyPath, bool isSystemOp)
+ ///
+ public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations)
{
- 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();
+ throw new NotSupportedException();
+ }
- return providerType;
+ ///
+ 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.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();
}
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.
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
}
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('\))?";