Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace Microsoft.Data.SqlClient.AlwaysEncrypted;
/// This metadata is a lower-case string which is laid out in the following format:
/// <list type="number">
/// <item>
/// Provider name. This always <see cref="SqlColumnEncryptionCertificateStoreProvider.ProviderName"/>.
/// Provider name. This is always <see cref="SqlColumnEncryptionCertificateStoreProvider.ProviderName"/>.
/// </item>
/// <item>
/// Master key path. This will be in the format [LocalMachine|CurrentUser]/My/[SHA1 thumbprint].
Expand Down Expand Up @@ -50,6 +50,25 @@ private struct Sha256Hash
private readonly RSA _rsa;

// @TODO: SqlColumnEncryptionCertificateStoreProvider.SignMasterKeyMetadata and .VerifyMasterKeyMetadata should use this type.
/// <summary>
/// Represents metadata associated with a column master key, including its cryptographic hash, path, provider name,
/// and enclave computation settings.
/// </summary>
/// <remarks>
/// This struct is used to encapsulate the metadata required for signing or verifying a column master key. The metadata includes
/// the provider name, the master key path, and whether enclave computations are allowed. The metadata is hashed using SHA-256
/// to ensure integrity.
/// </remarks>
/// <param name="rsa">The RSA cryptographic provider used for signing or verifying the metadata.</param>
/// <param name="masterKeyPath">The path to the column master key. This must be a valid path in one of the following formats:
/// <list type="bullet">
/// <item>[LocalMachine|CurrentUser]/My/[40-character SHA1 thumbprint]</item>
/// <item>My/[40-character SHA1 thumbprint]</item>
/// <item>[40-character SHA1 thumbprint]</item>
/// </list>
/// The path is case-insensitive and will be converted to lowercase for processing.</param>
/// <param name="providerName">The name of the provider associated with the column master key.</param>
/// <param name="allowEnclaveComputations">A value indicating whether enclave computations are allowed for this column master key.</param>
public ColumnMasterKeyMetadata(RSA rsa, string masterKeyPath, string providerName, bool allowEnclaveComputations)
{
// Lay the column master key metadata out in memory. Then, calculate the hash of this metadata ready for signature or verification.
Expand Down Expand Up @@ -100,22 +119,42 @@ public ColumnMasterKeyMetadata(RSA rsa, string masterKeyPath, string providerNam
SHA256.HashData(masterKeyMetadataBytes, _hash);
#else
byte[] masterKeyMetadataBytes = Encoding.Unicode.GetBytes(masterKeyMetadata);
using SHA256 sha256 = SHA256.Create();

// Compute hash
sha256.TransformFinalBlock(masterKeyMetadataBytes, 0, masterKeyMetadataBytes.Length);
_hash = sha256.Hash;
using (SHA256 sha256 = SHA256.Create())
{
// Compute hash
sha256.TransformFinalBlock(masterKeyMetadataBytes, 0, masterKeyMetadataBytes.Length);
_hash = sha256.Hash;
}
#endif

_rsa = rsa;
}

/// <summary>
/// Signs the current master key metadata using the RSA key associated with this instance.
/// </summary>
/// <returns>
/// A byte array containing the digital signature of the master key metadata.
/// </returns>
public byte[] Sign() =>
_rsa.SignHash(_hash, s_hashAlgorithm, RSASignaturePadding.Pkcs1);

/// <summary>
/// Verifies the specified master key metadata signature against the computed hash using the RSA key associated with this instance.
/// </summary>
/// <param name="signature">The digital signature to verify. This must be a valid signature generated by <see cref="Sign"/>.</param>
/// <returns>
/// <see langword="true"/> if the signature is valid and matches the computed hash; otherwise, <see langword="false"/>.
/// </returns>
public bool Verify(byte[] signature) =>
_rsa.VerifyHash(_hash, signature, s_hashAlgorithm, RSASignaturePadding.Pkcs1);

/// <summary>
/// Releases all resources used by this <see cref="ColumnMasterKeyMetadata"/>.
/// </summary>
/// <remarks>
/// This method disposes the <see cref="RSA"/> instance used to construct this <see cref="ColumnMasterKeyMetadata" /> instance.
/// </remarks>
public void Dispose() =>
_rsa.Dispose();
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ namespace Microsoft.Data.SqlClient.AlwaysEncrypted;
/// Version: 1 byte, always 0x01
/// </item>
/// <item>
/// Key path length: 2 bytes, length of the key path in bytes
/// Key path length: 2 bytes, length of the key path in bytes. Written in little-endian byte order.
/// </item>
/// <item>
/// Ciphertext length: 2 bytes, length of the ciphertext in bytes
/// Ciphertext length: 2 bytes, length of the ciphertext in bytes. Written in little-endian byte order.
/// </item>
/// <item>
/// Key path: variable length, Unicode-encoded string representing the key path
/// Key path: variable length, string representing the key path. Encoded with UTF-16.
/// </item>
/// <item>
/// Ciphertext: variable length, encrypted data. Length determined by size of the RSA key used for encryption
Expand Down Expand Up @@ -65,6 +65,23 @@ namespace Microsoft.Data.SqlClient.AlwaysEncrypted;
private readonly string _keyPathReference;

// @TODO: SqlColumnEncryptionCertificateStoreProvider, SqlColumnEncryptionCngProvider and SqlColumnEncryptionCspProvider should use this type.
/// <summary>
/// Initializes a new instance of the <see cref="EncryptedColumnEncryptionKeyParameters"/> struct with the specified
/// RSA key, key path, key type, and key path reference.
/// </summary>
/// <remarks>
/// This constructor is used to initialize the parameters required for encrypting a column encryption key. The
/// <paramref name="keyType"/> and <paramref name="keyPathReference"/> must correspond to the supported types and
/// references defined by the specific encryption provider being used.
/// </remarks>
/// <param name="rsa">The <see cref="RSA"/> object representing the RSA key used for encryption.</param>
/// <param name="keyPath">The path initially used to locate <paramref name="rsa"/>.</param>
/// <param name="keyType">The type of the encryption key. This must be one of the supported key types, such
/// as <see cref="SqlColumnEncryptionCertificateStoreProvider.MasterKeyType"/>, <see cref="SqlColumnEncryptionCngProvider.MasterKeyType"/>,
/// or <see cref="SqlColumnEncryptionCspProvider.MasterKeyType"/>.</param>
/// <param name="keyPathReference">The type of object which contains the RSA key referenced by the <paramref name="rsa"/>
/// parameter. This must be one of the supported object types, such as <see cref="SqlColumnEncryptionCertificateStoreProvider.KeyPathReference"/>,
/// <see cref="SqlColumnEncryptionCngProvider.KeyPathReference"/>, or <see cref="SqlColumnEncryptionCspProvider.KeyPathReference"/>.</param>
public EncryptedColumnEncryptionKeyParameters(RSA rsa, string keyPath, string keyType, string keyPathReference)
{
_rsa = rsa;
Expand All @@ -79,10 +96,20 @@ public EncryptedColumnEncryptionKeyParameters(RSA rsa, string keyPath, string ke
_keyPathReference = keyPathReference;
}

/// <summary>
/// Encrypts the specified column encryption key using the RSA key associated with this instance.
/// </summary>
/// <param name="columnEncryptionKey">The plaintext column encryption key to encrypt.</param>
/// <returns>
/// The encrypted column encryption key, including metadata such as the key path, ciphertext, and a digital signature
/// for integrity verification.
/// </returns>
public byte[] Encrypt(byte[] columnEncryptionKey)
{
ushort keyPathSize = (ushort)Encoding.Unicode.GetByteCount(_keyPath);
int cekSize = sizeof(byte) + sizeof(ushort) + sizeof(ushort) + keyPathSize + _rsaKeySize + _rsaKeySize;
// The signature size is always the same as the RSA key size
int signatureSize = _rsaKeySize;
int cekSize = sizeof(byte) + sizeof(ushort) + sizeof(ushort) + keyPathSize + _rsaKeySize + signatureSize;
byte[] encryptedColumnEncryptionKey = new byte[cekSize];
int bytesWritten;
int cipherTextOffset = KeyPathOffset + keyPathSize;
Expand Down Expand Up @@ -147,12 +174,24 @@ public byte[] Encrypt(byte[] columnEncryptionKey)
return encryptedColumnEncryptionKey;
}

/// <summary>
/// Decrypts an encrypted column encryption key (CEK) using the RSA key associated with this instance.
/// </summary>
/// <remarks>
/// This method validates the algorithm version, ciphertext length, and signature length before
/// decrypting the CEK. It also verifies the integrity of the encrypted CEK using the provided signature.
/// </remarks>
/// <param name="encryptedCek">A byte array containing the encrypted column encryption key. The array must include
/// the algorithm version, key path length, ciphertext, and signature.</param>
/// <returns>
/// The decrypted column encryption key.
/// </returns>
public byte[] Decrypt(byte[] encryptedCek)
{
// Validate the version byte
if (encryptedCek[0] != AlgorithmVersion)
if (encryptedCek[AlgorithmOffset] != AlgorithmVersion)
{
throw SQL.InvalidAlgorithmVersionInEncryptedCEK(encryptedCek[0], AlgorithmVersion);
throw SQL.InvalidAlgorithmVersionInEncryptedCEK(encryptedCek[AlgorithmOffset], AlgorithmVersion);
}

// Get key path length, but skip reading it. It exists only for troubleshooting purposes and doesn't need validation.
Expand Down Expand Up @@ -215,6 +254,12 @@ public byte[] Decrypt(byte[] encryptedCek)
return _rsa.Decrypt(cipherText, RSAEncryptionPadding.OaepSHA1);
}

/// <summary>
/// Releases all resources used by this <see cref="EncryptedColumnEncryptionKeyParameters"/>.
/// </summary>
/// <remarks>
/// This method disposes the <see cref="RSA"/> instance used to construct this <see cref="EncryptedColumnEncryptionKeyParameters" /> instance.
/// </remarks>
public void Dispose() =>
_rsa.Dispose();
}
Loading