You've already forked linux-packaging-mono
Imported Upstream version 4.3.2.467
Former-commit-id: 9c2cb47f45fa221e661ab616387c9cda183f283d
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
// <copyright file="OnChangedEventHandler.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <owner current="true" primary="true">[....]</owner>
|
||||
// <owner current="true" primary="false">[....]</owner>
|
||||
// <owner current="true" primary="true">ramp</owner>
|
||||
// <owner current="true" primary="false">blained</owner>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace System.Data.SqlClient {
|
||||
|
@@ -2,8 +2,8 @@
|
||||
// <copyright file="ParameterPeekAheadValue.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <owner current="true" primary="true">[....]</owner>
|
||||
// <owner current="true" primary="false">[....]</owner>
|
||||
// <owner current="true" primary="true">alazela</owner>
|
||||
// <owner current="true" primary="false">billin</owner>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
@@ -2,8 +2,8 @@
|
||||
// <copyright file="RowsCopiedEventHandler.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <owner current="true" primary="true">[....]</owner>
|
||||
// <owner current="true" primary="false">[....]</owner>
|
||||
// <owner current="true" primary="true">mithomas</owner>
|
||||
// <owner current="true" primary="false">blained</owner>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace System.Data.SqlClient {
|
||||
|
@@ -2,9 +2,9 @@
|
||||
// <copyright file="SqlMetaData.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <owner current="true" primary="true">[....]</owner>
|
||||
// <owner current="true" primary="false">[....]</owner>
|
||||
// <owner current="true" primary="false">[....]</owner>
|
||||
// <owner current="true" primary="true">alazela</owner>
|
||||
// <owner current="true" primary="false">laled</owner>
|
||||
// <owner current="true" primary="false">billin</owner>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace System.Data.SqlClient {
|
||||
|
406
external/referencesource/System.Data/System/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs
vendored
Normal file
406
external/referencesource/System.Data/System/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs
vendored
Normal file
@@ -0,0 +1,406 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="SqlAeadAes256CbcHmac256Algorithm.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <owner current="true" primary="true">balnee</owner>
|
||||
// <owner current="true" primary="false">krishnib</owner>
|
||||
//------------------------------------------------------------------------------
|
||||
namespace System.Data.SqlClient
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlClient;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
/// <summary>
|
||||
/// This class implements authenticated encryption algorithm with associated data as described in
|
||||
/// http://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05. More specifically this implements
|
||||
/// AEAD_AES_256_CBC_HMAC_SHA256 algorithm.
|
||||
/// </summary>
|
||||
internal class SqlAeadAes256CbcHmac256Algorithm : SqlClientEncryptionAlgorithm
|
||||
{
|
||||
/// <summary>
|
||||
/// Algorithm Name
|
||||
/// </summary>
|
||||
internal const string AlgorithmName = @"AEAD_AES_256_CBC_HMAC_SHA256";
|
||||
|
||||
/// <summary>
|
||||
/// Key size in bytes
|
||||
/// </summary>
|
||||
private const int _KeySizeInBytes = SqlAeadAes256CbcHmac256EncryptionKey.KeySize / 8;
|
||||
|
||||
/// <summary>
|
||||
/// Block size in bytes. AES uses 16 byte blocks.
|
||||
/// </summary>
|
||||
private const int _BlockSizeInBytes = 16;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum Length of cipherText without authentication tag. This value is 1 (version byte) + 16 (IV) + 16 (minimum of 1 block of cipher Text)
|
||||
/// </summary>
|
||||
private const int _MinimumCipherTextLengthInBytesNoAuthenticationTag = sizeof(byte) + _BlockSizeInBytes + _BlockSizeInBytes;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum Length of cipherText. This value is 1 (version byte) + 32 (authentication tag) + 16 (IV) + 16 (minimum of 1 block of cipher Text)
|
||||
/// </summary>
|
||||
private const int _MinimumCipherTextLengthInBytesWithAuthenticationTag = _MinimumCipherTextLengthInBytesNoAuthenticationTag + _KeySizeInBytes;
|
||||
|
||||
/// <summary>
|
||||
/// Cipher Mode. For this algorithm, we only use CBC mode.
|
||||
/// </summary>
|
||||
private const CipherMode _cipherMode = CipherMode.CBC;
|
||||
|
||||
/// <summary>
|
||||
/// Padding mode. This algorithm uses PKCS7.
|
||||
/// </summary>
|
||||
private const PaddingMode _paddingMode = PaddingMode.PKCS7;
|
||||
|
||||
/// <summary>
|
||||
/// Variable indicating whether this algorithm should work in Deterministic mode or Randomized mode.
|
||||
/// For deterministic encryption, we derive an IV from the plaintext data.
|
||||
/// For randomized encryption, we generate a cryptographically random IV.
|
||||
/// </summary>
|
||||
private readonly bool _isDeterministic;
|
||||
|
||||
/// <summary>
|
||||
/// Algorithm Version.
|
||||
/// </summary>
|
||||
private readonly byte _algorithmVersion;
|
||||
|
||||
/// <summary>
|
||||
/// Column Encryption Key. This has a root key and three derived keys.
|
||||
/// </summary>
|
||||
private readonly SqlAeadAes256CbcHmac256EncryptionKey _columnEncryptionKey;
|
||||
|
||||
/// <summary>
|
||||
/// The pool of crypto providers to use for encrypt/decrypt operations.
|
||||
/// </summary>
|
||||
private readonly ConcurrentQueue<AesCryptoServiceProvider> _cryptoProviderPool;
|
||||
|
||||
/// <summary>
|
||||
/// Byte array with algorithm version used for authentication tag computation.
|
||||
/// </summary>
|
||||
private static readonly byte[] _version = new byte[] {0x01};
|
||||
|
||||
/// <summary>
|
||||
/// Byte array with algorithm version size used for authentication tag computation.
|
||||
/// </summary>
|
||||
private static readonly byte[] _versionSize = new byte[] {sizeof(byte)};
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of SqlAeadAes256CbcHmac256Algorithm algorithm with a given key and encryption type
|
||||
/// </summary>
|
||||
/// <param name="encryptionKey">
|
||||
/// Root encryption key from which three other keys will be derived
|
||||
/// </param>
|
||||
/// <param name="encryptionType">Encryption Type, accepted values are Deterministic and Randomized.
|
||||
/// For Deterministic encryption, a synthetic IV will be genenrated during encryption
|
||||
/// For Randomized encryption, a random IV will be generated during encryption.
|
||||
/// </param>
|
||||
/// <param name="algorithmVersion">
|
||||
/// Algorithm version
|
||||
/// </param>
|
||||
internal SqlAeadAes256CbcHmac256Algorithm(SqlAeadAes256CbcHmac256EncryptionKey encryptionKey, SqlClientEncryptionType encryptionType, byte algorithmVersion) {
|
||||
_columnEncryptionKey = encryptionKey;
|
||||
_algorithmVersion = algorithmVersion;
|
||||
_version[0] = algorithmVersion;
|
||||
|
||||
Debug.Assert (null != encryptionKey, "Null encryption key detected in AeadAes256CbcHmac256 algorithm");
|
||||
Debug.Assert (0x01 == algorithmVersion, "Unknown algorithm version passed to AeadAes256CbcHmac256");
|
||||
|
||||
// Validate encryption type for this algorithm
|
||||
// This algorithm can only provide randomized or deterministic encryption types.
|
||||
if (encryptionType == SqlClientEncryptionType.Deterministic) {
|
||||
_isDeterministic = true;
|
||||
}
|
||||
else {
|
||||
Debug.Assert (SqlClientEncryptionType.Randomized == encryptionType, "Invalid Encryption Type detected in SqlAeadAes256CbcHmac256Algorithm, this should've been caught in factory class");
|
||||
}
|
||||
|
||||
_cryptoProviderPool = new ConcurrentQueue<AesCryptoServiceProvider>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encryption Algorithm
|
||||
/// cell_iv = HMAC_SHA-2-256(iv_key, cell_data) truncated to 128 bits
|
||||
/// cell_ciphertext = AES-CBC-256(enc_key, cell_iv, cell_data) with PKCS7 padding.
|
||||
/// cell_tag = HMAC_SHA-2-256(mac_key, versionbyte + cell_iv + cell_ciphertext + versionbyte_length)
|
||||
/// cell_blob = versionbyte + cell_tag + cell_iv + cell_ciphertext
|
||||
/// </summary>
|
||||
/// <param name="plainText">Plaintext data to be encrypted</param>
|
||||
/// <returns>Returns the ciphertext corresponding to the plaintext.</returns>
|
||||
internal override byte[] EncryptData(byte[] plainText) {
|
||||
return EncryptData(plainText, hasAuthenticationTag: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encryption Algorithm
|
||||
/// cell_iv = HMAC_SHA-2-256(iv_key, cell_data) truncated to 128 bits
|
||||
/// cell_ciphertext = AES-CBC-256(enc_key, cell_iv, cell_data) with PKCS7 padding.
|
||||
/// (optional) cell_tag = HMAC_SHA-2-256(mac_key, versionbyte + cell_iv + cell_ciphertext + versionbyte_length)
|
||||
/// cell_blob = versionbyte + [cell_tag] + cell_iv + cell_ciphertext
|
||||
/// </summary>
|
||||
/// <param name="plainText">Plaintext data to be encrypted</param>
|
||||
/// <param name="hasAuthenticationTag">Does the algorithm require authentication tag.</param>
|
||||
/// <returns>Returns the ciphertext corresponding to the plaintext.</returns>
|
||||
protected byte[] EncryptData(byte[] plainText, bool hasAuthenticationTag) {
|
||||
// Empty values get encrypted and decrypted properly for both Deterministic and Randomized encryptions.
|
||||
Debug.Assert(plainText != null);
|
||||
|
||||
byte[] iv = new byte[_BlockSizeInBytes];
|
||||
|
||||
// Prepare IV
|
||||
// Should be 1 single block (16 bytes)
|
||||
if (_isDeterministic) {
|
||||
SqlSecurityUtility.GetHMACWithSHA256(plainText, _columnEncryptionKey.IVKey, iv);
|
||||
}
|
||||
else {
|
||||
SqlSecurityUtility.GenerateRandomBytes(iv);
|
||||
}
|
||||
|
||||
int numBlocks = plainText.Length / _BlockSizeInBytes + 1;
|
||||
|
||||
// Final blob we return = version + HMAC + iv + cipherText
|
||||
const int hmacStartIndex = 1;
|
||||
int authenticationTagLen = hasAuthenticationTag ? _KeySizeInBytes : 0;
|
||||
int ivStartIndex = hmacStartIndex + authenticationTagLen;
|
||||
int cipherStartIndex = ivStartIndex + _BlockSizeInBytes; // this is where hmac starts.
|
||||
|
||||
// Output buffer size = size of VersionByte + Authentication Tag + IV + cipher Text blocks.
|
||||
int outputBufSize = sizeof(byte) + authenticationTagLen + iv.Length + (numBlocks*_BlockSizeInBytes);
|
||||
byte[] outBuffer = new byte[outputBufSize];
|
||||
|
||||
// Store the version and IV rightaway
|
||||
outBuffer[0] = _algorithmVersion;
|
||||
Buffer.BlockCopy(iv, 0, outBuffer, ivStartIndex, iv.Length);
|
||||
|
||||
AesCryptoServiceProvider aesAlg;
|
||||
|
||||
// Try to get a provider from the pool.
|
||||
// If no provider is available, create a new one.
|
||||
if (!_cryptoProviderPool.TryDequeue(out aesAlg)) {
|
||||
aesAlg = new AesCryptoServiceProvider();
|
||||
|
||||
try {
|
||||
// Set various algorithm properties
|
||||
aesAlg.Key = _columnEncryptionKey.EncryptionKey;
|
||||
aesAlg.Mode = _cipherMode;
|
||||
aesAlg.Padding = _paddingMode;
|
||||
}
|
||||
catch (Exception) {
|
||||
if (aesAlg != null) {
|
||||
aesAlg.Dispose();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Always set the IV since it changes from cell to cell.
|
||||
aesAlg.IV = iv;
|
||||
|
||||
// Compute CipherText and authentication tag in a single pass
|
||||
using (ICryptoTransform encryptor = aesAlg.CreateEncryptor()) {
|
||||
Debug.Assert(encryptor.CanTransformMultipleBlocks, "AES Encryptor can transform multiple blocks");
|
||||
int count = 0;
|
||||
int cipherIndex = cipherStartIndex; // this is where cipherText starts
|
||||
if (numBlocks > 1) {
|
||||
count = (numBlocks - 1) * _BlockSizeInBytes;
|
||||
cipherIndex += encryptor.TransformBlock(plainText, 0, count, outBuffer, cipherIndex);
|
||||
}
|
||||
|
||||
byte[] buffTmp = encryptor.TransformFinalBlock(plainText, count, plainText.Length - count); // done encrypting
|
||||
Buffer.BlockCopy(buffTmp, 0, outBuffer, cipherIndex, buffTmp.Length);
|
||||
cipherIndex += buffTmp.Length;
|
||||
}
|
||||
|
||||
if (hasAuthenticationTag) {
|
||||
using (HMACSHA256 hmac = new HMACSHA256(_columnEncryptionKey.MACKey)) {
|
||||
Debug.Assert(hmac.CanTransformMultipleBlocks, "HMAC can't transform multiple blocks");
|
||||
hmac.TransformBlock(_version, 0, _version.Length, _version, 0);
|
||||
hmac.TransformBlock(iv, 0, iv.Length, iv, 0);
|
||||
|
||||
// Compute HMAC on final block
|
||||
hmac.TransformBlock(outBuffer, cipherStartIndex, numBlocks * _BlockSizeInBytes, outBuffer, cipherStartIndex);
|
||||
hmac.TransformFinalBlock(_versionSize, 0, _versionSize.Length);
|
||||
byte[] hash = hmac.Hash;
|
||||
Debug.Assert(hash.Length >= authenticationTagLen, "Unexpected hash size");
|
||||
Buffer.BlockCopy(hash, 0, outBuffer, hmacStartIndex, authenticationTagLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
// Return the provider to the pool.
|
||||
_cryptoProviderPool.Enqueue(aesAlg);
|
||||
}
|
||||
|
||||
return outBuffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decryption steps
|
||||
/// 1. Validate version byte
|
||||
/// 2. Validate Authentication tag
|
||||
/// 3. Decrypt the message
|
||||
/// </summary>
|
||||
/// <param name="cipherText"></param>
|
||||
/// <returns></returns>
|
||||
internal override byte[] DecryptData(byte[] cipherText) {
|
||||
return DecryptData(cipherText, hasAuthenticationTag: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decryption steps
|
||||
/// 1. Validate version byte
|
||||
/// 2. (optional) Validate Authentication tag
|
||||
/// 3. Decrypt the message
|
||||
/// </summary>
|
||||
/// <param name="cipherText"></param>
|
||||
/// <param name="hasAuthenticationTag"></param>
|
||||
/// <returns></returns>
|
||||
protected byte[] DecryptData(byte[] cipherText, bool hasAuthenticationTag) {
|
||||
Debug.Assert(cipherText != null);
|
||||
|
||||
byte[] iv = new byte[_BlockSizeInBytes];
|
||||
|
||||
int minimumCipherTextLength = hasAuthenticationTag ? _MinimumCipherTextLengthInBytesWithAuthenticationTag : _MinimumCipherTextLengthInBytesNoAuthenticationTag;
|
||||
if (cipherText.Length < minimumCipherTextLength) {
|
||||
throw SQL.InvalidCipherTextSize(cipherText.Length, minimumCipherTextLength);
|
||||
}
|
||||
|
||||
// Validate the version byte
|
||||
int startIndex = 0;
|
||||
if (cipherText[startIndex] != _algorithmVersion) {
|
||||
// Cipher text was computed with a different algorithm version than this.
|
||||
throw SQL.InvalidAlgorithmVersion(cipherText[startIndex], _algorithmVersion);
|
||||
}
|
||||
|
||||
startIndex += 1;
|
||||
int authenticationTagOffset = 0;
|
||||
|
||||
// Read authentication tag
|
||||
if (hasAuthenticationTag) {
|
||||
authenticationTagOffset = startIndex;
|
||||
startIndex += _KeySizeInBytes; // authentication tag size is _KeySizeInBytes
|
||||
}
|
||||
|
||||
// Read cell IV
|
||||
Buffer.BlockCopy(cipherText, startIndex, iv, 0, iv.Length);
|
||||
startIndex += iv.Length;
|
||||
|
||||
// Read encrypted text
|
||||
int cipherTextOffset = startIndex;
|
||||
int cipherTextCount = cipherText.Length - startIndex;
|
||||
|
||||
if (hasAuthenticationTag) {
|
||||
// Compute authentication tag
|
||||
byte[] authenticationTag = PrepareAuthenticationTag(iv, cipherText, cipherTextOffset, cipherTextCount);
|
||||
if (!SqlSecurityUtility.CompareBytes(authenticationTag, cipherText, authenticationTagOffset, authenticationTag.Length)) {
|
||||
// Potentially tampered data, throw an exception
|
||||
throw SQL.InvalidAuthenticationTag();
|
||||
}
|
||||
}
|
||||
|
||||
// Decrypt the text and return
|
||||
return DecryptData(iv, cipherText, cipherTextOffset, cipherTextCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts plain text data using AES in CBC mode
|
||||
/// </summary>
|
||||
/// <param name="plainText"> cipher text data to be decrypted</param>
|
||||
/// <param name="iv">IV to be used for decryption</param>
|
||||
/// <returns>Returns decrypted plain text data</returns>
|
||||
private byte[] DecryptData(byte[] iv, byte[] cipherText, int offset, int count) {
|
||||
Debug.Assert((iv != null) && (cipherText != null));
|
||||
Debug.Assert (offset > -1 && count > -1);
|
||||
Debug.Assert ((count+offset) <= cipherText.Length);
|
||||
|
||||
byte[] plainText;
|
||||
AesCryptoServiceProvider aesAlg;
|
||||
|
||||
// Try to get a provider from the pool.
|
||||
// If no provider is available, create a new one.
|
||||
if (!_cryptoProviderPool.TryDequeue(out aesAlg)) {
|
||||
aesAlg = new AesCryptoServiceProvider();
|
||||
|
||||
try {
|
||||
// Set various algorithm properties
|
||||
aesAlg.Key = _columnEncryptionKey.EncryptionKey;
|
||||
aesAlg.Mode = _cipherMode;
|
||||
aesAlg.Padding = _paddingMode;
|
||||
}
|
||||
catch (Exception) {
|
||||
if (aesAlg != null) {
|
||||
aesAlg.Dispose();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Always set the IV since it changes from cell to cell.
|
||||
aesAlg.IV = iv;
|
||||
|
||||
// Create the streams used for decryption.
|
||||
using (MemoryStream msDecrypt = new MemoryStream()) {
|
||||
// Create an encryptor to perform the stream transform.
|
||||
using (ICryptoTransform decryptor = aesAlg.CreateDecryptor()) {
|
||||
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Write)) {
|
||||
// Decrypt the secret message and get the plain text data
|
||||
csDecrypt.Write(cipherText, offset, count);
|
||||
csDecrypt.FlushFinalBlock();
|
||||
plainText = msDecrypt.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
// Return the provider to the pool.
|
||||
_cryptoProviderPool.Enqueue(aesAlg);
|
||||
}
|
||||
|
||||
return plainText;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares an authentication tag.
|
||||
/// Authentication Tag = HMAC_SHA-2-256(mac_key, versionbyte + cell_iv + cell_ciphertext + versionbyte_length)
|
||||
/// </summary>
|
||||
/// <param name="cipherText"></param>
|
||||
/// <returns></returns>
|
||||
private byte[] PrepareAuthenticationTag(byte[] iv, byte[] cipherText, int offset, int length) {
|
||||
Debug.Assert(cipherText != null);
|
||||
|
||||
byte[] computedHash;
|
||||
byte[] authenticationTag = new byte[_KeySizeInBytes];
|
||||
|
||||
// Raw Tag Length:
|
||||
// 1 for the version byte
|
||||
// 1 block for IV (16 bytes)
|
||||
// cipherText.Length
|
||||
// 1 byte for version byte length
|
||||
|
||||
using (HMACSHA256 hmac = new HMACSHA256(_columnEncryptionKey.MACKey)) {
|
||||
int retVal = 0;
|
||||
retVal = hmac.TransformBlock(_version, 0, _version.Length, _version, 0);
|
||||
Debug.Assert(retVal == _version.Length);
|
||||
retVal = hmac.TransformBlock(iv, 0, iv.Length, iv, 0);
|
||||
Debug.Assert(retVal == iv.Length);
|
||||
retVal = hmac.TransformBlock(cipherText, offset, length, cipherText, offset);
|
||||
Debug.Assert(retVal == length);
|
||||
hmac.TransformFinalBlock(_versionSize, 0, _versionSize.Length);
|
||||
computedHash = hmac.Hash;
|
||||
}
|
||||
|
||||
Debug.Assert (computedHash.Length >= authenticationTag.Length);
|
||||
Buffer.BlockCopy (computedHash, 0, authenticationTag, 0, authenticationTag.Length);
|
||||
return authenticationTag;
|
||||
}
|
||||
}
|
||||
}
|
128
external/referencesource/System.Data/System/Data/SqlClient/SqlAeadAes256CbcHmac256EncryptionKey.cs
vendored
Normal file
128
external/referencesource/System.Data/System/Data/SqlClient/SqlAeadAes256CbcHmac256EncryptionKey.cs
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="SqlAeadAes256CbcHmac256EncryptionKey.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <owner current="true" primary="true">balnee</owner>
|
||||
// <owner current="true" primary="false">krishnib</owner>
|
||||
//------------------------------------------------------------------------------
|
||||
namespace System.Data.SqlClient
|
||||
{
|
||||
using System;
|
||||
using System.Data.SqlClient;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Encryption key class containing 4 keys. This class is used by SqlAeadAes256CbcHmac256Algorithm and SqlAes256CbcAlgorithm
|
||||
/// 1) root key - Main key that is used to derive the keys used in the encryption algorithm
|
||||
/// 2) encryption key - A derived key that is used to encrypt the plain text and generate cipher text
|
||||
/// 3) mac_key - A derived key that is used to compute HMAC of the cipher text
|
||||
/// 4) iv_key - A derived key that is used to generate a synthetic IV from plain text data.
|
||||
/// </summary>
|
||||
internal class SqlAeadAes256CbcHmac256EncryptionKey : SqlClientSymmetricKey
|
||||
{
|
||||
/// <summary>
|
||||
/// Key size in bits
|
||||
/// </summary>
|
||||
internal const int KeySize = 256;
|
||||
|
||||
/// <summary>
|
||||
/// Encryption Key Salt format. This is used to derive the encryption key from the root key.
|
||||
/// </summary>
|
||||
private const string _encryptionKeySaltFormat = @"Microsoft SQL Server cell encryption key with encryption algorithm:{0} and key length:{1}";
|
||||
|
||||
/// <summary>
|
||||
/// MAC Key Salt format. This is used to derive the MAC key from the root key.
|
||||
/// </summary>
|
||||
private const string _macKeySaltFormat = @"Microsoft SQL Server cell MAC key with encryption algorithm:{0} and key length:{1}";
|
||||
|
||||
/// <summary>
|
||||
/// IV Key Salt format. This is used to derive the IV key from the root key. This is only used for Deterministic encryption.
|
||||
/// </summary>
|
||||
private const string _ivKeySaltFormat = @"Microsoft SQL Server cell IV key with encryption algorithm:{0} and key length:{1}";
|
||||
|
||||
/// <summary>
|
||||
/// Encryption Key
|
||||
/// </summary>
|
||||
private readonly SqlClientSymmetricKey _encryptionKey;
|
||||
|
||||
/// <summary>
|
||||
/// MAC key
|
||||
/// </summary>
|
||||
private readonly SqlClientSymmetricKey _macKey;
|
||||
|
||||
/// <summary>
|
||||
/// IV Key
|
||||
/// </summary>
|
||||
private readonly SqlClientSymmetricKey _ivKey;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the algorithm this key will be used with.
|
||||
/// </summary>
|
||||
private readonly string _algorithmName;
|
||||
|
||||
/// <summary>
|
||||
/// Derives all the required keys from the given root key
|
||||
/// </summary>
|
||||
/// <param name="rootKey">Root key used to derive all the required derived keys</param>
|
||||
internal SqlAeadAes256CbcHmac256EncryptionKey(byte[] rootKey, string algorithmName): base(rootKey)
|
||||
{
|
||||
_algorithmName = algorithmName;
|
||||
|
||||
int keySizeInBytes = KeySize / 8;
|
||||
|
||||
// Key validation
|
||||
if (rootKey.Length != keySizeInBytes)
|
||||
{
|
||||
throw SQL.InvalidKeySize(_algorithmName,
|
||||
rootKey.Length,
|
||||
keySizeInBytes);
|
||||
}
|
||||
|
||||
// Derive keys from the root key
|
||||
//
|
||||
// Derive encryption key
|
||||
string encryptionKeySalt = string.Format(_encryptionKeySaltFormat,
|
||||
_algorithmName,
|
||||
KeySize);
|
||||
byte[] buff1 = new byte[keySizeInBytes];
|
||||
SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(encryptionKeySalt), RootKey, buff1);
|
||||
_encryptionKey = new SqlClientSymmetricKey(buff1);
|
||||
|
||||
// Derive mac key
|
||||
string macKeySalt = string.Format(_macKeySaltFormat, _algorithmName, KeySize);
|
||||
byte[] buff2 = new byte[keySizeInBytes];
|
||||
SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(macKeySalt),RootKey,buff2);
|
||||
_macKey = new SqlClientSymmetricKey(buff2);
|
||||
|
||||
// Derive iv key
|
||||
string ivKeySalt = string.Format(_ivKeySaltFormat, _algorithmName, KeySize);
|
||||
byte[] buff3 = new byte[keySizeInBytes];
|
||||
SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(ivKeySalt),RootKey,buff3);
|
||||
_ivKey = new SqlClientSymmetricKey(buff3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encryption key should be used for encryption and decryption
|
||||
/// </summary>
|
||||
internal byte[] EncryptionKey
|
||||
{
|
||||
get { return _encryptionKey.RootKey; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MAC key should be used to compute and validate HMAC
|
||||
/// </summary>
|
||||
internal byte[] MACKey
|
||||
{
|
||||
get { return _macKey.RootKey; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IV key should be used to compute synthetic IV from a given plain text
|
||||
/// </summary>
|
||||
internal byte[] IVKey
|
||||
{
|
||||
get { return _ivKey.RootKey; }
|
||||
}
|
||||
}
|
||||
}
|
80
external/referencesource/System.Data/System/Data/SqlClient/SqlAeadAes256CbcHmac256Factory.cs
vendored
Normal file
80
external/referencesource/System.Data/System/Data/SqlClient/SqlAeadAes256CbcHmac256Factory.cs
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="SqlAeadAes256CbcHmac256Factory.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <owner current="true" primary="true">balnee</owner>
|
||||
// <owner current="true" primary="false">krishnib</owner>
|
||||
//------------------------------------------------------------------------------
|
||||
namespace System.Data.SqlClient {
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Data.SqlClient;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// This is a factory class for AEAD_AES_256_CBC_HMAC_SHA256
|
||||
/// </summary>
|
||||
internal class SqlAeadAes256CbcHmac256Factory : SqlClientEncryptionAlgorithmFactory {
|
||||
/// <summary>
|
||||
/// Factory classes caches the SqlAeadAes256CbcHmac256EncryptionKey objects to avoid computation of the derived keys
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, SqlAeadAes256CbcHmac256Algorithm> _encryptionAlgorithms =
|
||||
new ConcurrentDictionary<string, SqlAeadAes256CbcHmac256Algorithm>(concurrencyLevel: 4 * Environment.ProcessorCount /* default value in ConcurrentDictionary*/, capacity: 2);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of AeadAes256CbcHmac256Algorithm class with a given key
|
||||
/// </summary>
|
||||
/// <param name="encryptionKey">Root key</param>
|
||||
/// <param name="encryptionType">Encryption Type. Expected values are either Determinitic or Randomized.</param>
|
||||
/// <param name="encryptionAlgorithm">Encryption Algorithm.</param>
|
||||
/// <returns></returns>
|
||||
internal override SqlClientEncryptionAlgorithm Create(SqlClientSymmetricKey encryptionKey, SqlClientEncryptionType encryptionType, string encryptionAlgorithm) {
|
||||
// Callers should have validated the encryption algorithm and the encryption key
|
||||
Debug.Assert(encryptionKey != null);
|
||||
Debug.Assert(string.Equals(encryptionAlgorithm, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName, StringComparison.OrdinalIgnoreCase) == true);
|
||||
|
||||
// Validate encryption type
|
||||
if (!((encryptionType == SqlClientEncryptionType.Deterministic) || (encryptionType == SqlClientEncryptionType.Randomized))) {
|
||||
throw SQL.InvalidEncryptionType(SqlAeadAes256CbcHmac256Algorithm.AlgorithmName,
|
||||
encryptionType,
|
||||
SqlClientEncryptionType.Deterministic,
|
||||
SqlClientEncryptionType.Randomized);
|
||||
}
|
||||
|
||||
// Get the cached encryption algorithm if one exists or create a new one, add it to cache and use it
|
||||
//
|
||||
// For now, we only have one version. In future, we may need to parse the algorithm names to derive the version byte.
|
||||
const byte algorithmVersion = 0x1;
|
||||
|
||||
StringBuilder algorithmKeyBuilder = new StringBuilder(Convert.ToBase64String(encryptionKey.RootKey), SqlSecurityUtility.GetBase64LengthFromByteLength(encryptionKey.RootKey.Length) + 4/*separators, type and version*/);
|
||||
|
||||
#if DEBUG
|
||||
int capacity = algorithmKeyBuilder.Capacity;
|
||||
#endif //DEBUG
|
||||
|
||||
algorithmKeyBuilder.Append(":");
|
||||
algorithmKeyBuilder.Append((int)encryptionType);
|
||||
algorithmKeyBuilder.Append(":");
|
||||
algorithmKeyBuilder.Append(algorithmVersion);
|
||||
|
||||
string algorithmKey = algorithmKeyBuilder.ToString();
|
||||
|
||||
#if DEBUG
|
||||
Debug.Assert(algorithmKey.Length <= capacity, "We needed to allocate a larger array");
|
||||
#endif //DEBUG
|
||||
|
||||
SqlAeadAes256CbcHmac256Algorithm aesAlgorithm;
|
||||
if (!_encryptionAlgorithms.TryGetValue(algorithmKey, out aesAlgorithm)) {
|
||||
SqlAeadAes256CbcHmac256EncryptionKey encryptedKey = new SqlAeadAes256CbcHmac256EncryptionKey(encryptionKey.RootKey, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName);
|
||||
aesAlgorithm = new SqlAeadAes256CbcHmac256Algorithm(encryptedKey, encryptionType, algorithmVersion);
|
||||
|
||||
// In case multiple threads reach here at the same time, the first one adds the value
|
||||
// the second one will be a no-op, the allocated memory will be claimed by Garbage Collector.
|
||||
_encryptionAlgorithms.TryAdd(algorithmKey, aesAlgorithm);
|
||||
}
|
||||
|
||||
return aesAlgorithm;
|
||||
}
|
||||
}
|
||||
}
|
65
external/referencesource/System.Data/System/Data/SqlClient/SqlAes256CbcAlgorithm.cs
vendored
Normal file
65
external/referencesource/System.Data/System/Data/SqlClient/SqlAes256CbcAlgorithm.cs
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="SqlAes256CbcAlgorithm.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <owner current="true" primary="true">balnee</owner>
|
||||
// <owner current="true" primary="false">krishnib</owner>
|
||||
//------------------------------------------------------------------------------
|
||||
namespace System.Data.SqlClient
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlClient;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
/// <summary>
|
||||
/// This class implements AES_256_CBC algorithm.
|
||||
/// </summary>
|
||||
internal class SqlAes256CbcAlgorithm : SqlAeadAes256CbcHmac256Algorithm
|
||||
{
|
||||
/// <summary>
|
||||
/// Algorithm Name
|
||||
/// </summary>
|
||||
internal new const string AlgorithmName = @"AES_256_CBC";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of SqlAes256CbcAlgorithm algorithm with a given key and encryption type
|
||||
/// </summary>
|
||||
/// <param name="encryptionKey">
|
||||
/// Root encryption key from which three other keys will be derived
|
||||
/// </param>
|
||||
/// <param name="encryptionType">Encryption Type, accepted values are Deterministic and Randomized.
|
||||
/// For Deterministic encryption, a synthetic IV will be genenrated during encryption
|
||||
/// For Randomized encryption, a random IV will be generated during encryption.
|
||||
/// </param>
|
||||
/// <param name="algorithmVersion">
|
||||
/// Algorithm version
|
||||
/// </param>
|
||||
internal SqlAes256CbcAlgorithm(SqlAeadAes256CbcHmac256EncryptionKey encryptionKey, SqlClientEncryptionType encryptionType, byte algorithmVersion)
|
||||
:base(encryptionKey, encryptionType, algorithmVersion)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Encryption Algorithm
|
||||
/// Simply call the base class, indicating we don't need an authentication tag.
|
||||
/// </summary>
|
||||
/// <param name="plainText">Plaintext data to be encrypted</param>
|
||||
/// <returns>Returns the ciphertext corresponding to the plaintext.</returns>
|
||||
internal override byte[] EncryptData(byte[] plainText) {
|
||||
return EncryptData(plainText, hasAuthenticationTag: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decryption Algorithm
|
||||
/// Simply call the base class, indicating we don't have an authentication tag.
|
||||
/// </summary>
|
||||
/// <param name="cipherText"></param>
|
||||
/// <returns></returns>
|
||||
internal override byte[] DecryptData(byte[] cipherText) {
|
||||
return base.DecryptData(cipherText, hasAuthenticationTag: false);
|
||||
}
|
||||
}
|
||||
}
|
85
external/referencesource/System.Data/System/Data/SqlClient/SqlAes256CbcFactory.cs
vendored
Normal file
85
external/referencesource/System.Data/System/Data/SqlClient/SqlAes256CbcFactory.cs
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="SqlAeadAes256CbcHmac256Factory.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <owner current="true" primary="true">balnee</owner>
|
||||
// <owner current="true" primary="false">krishnib</owner>
|
||||
//------------------------------------------------------------------------------
|
||||
namespace System.Data.SqlClient
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Data.SqlClient;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// This is a factory class for AES_256_CBC.
|
||||
/// </summary>
|
||||
internal class SqlAes256CbcFactory : SqlAeadAes256CbcHmac256Factory
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory classes caches the SqlAeadAes256CbcHmac256EncryptionKey objects to avoid computation of the derived keys
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, SqlAes256CbcAlgorithm> _encryptionAlgorithms =
|
||||
new ConcurrentDictionary<string, SqlAes256CbcAlgorithm>(concurrencyLevel: 4 * Environment.ProcessorCount /* default value in ConcurrentDictionary*/, capacity: 2);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of SqlAes256CbcAlgorithm class with a given key
|
||||
/// </summary>
|
||||
/// <param name="encryptionKey">Root key</param>
|
||||
/// <param name="encryptionType">Encryption Type. Expected values are either Determinitic or Randomized.</param>
|
||||
/// <param name="encryptionAlgorithm">Encryption Algorithm.</param>
|
||||
/// <returns></returns>
|
||||
internal override SqlClientEncryptionAlgorithm Create(SqlClientSymmetricKey encryptionKey, SqlClientEncryptionType encryptionType, string encryptionAlgorithm)
|
||||
{
|
||||
// Callers should have validated the encryption algorithm and the encryption key
|
||||
Debug.Assert(encryptionKey != null);
|
||||
Debug.Assert(string.Equals(encryptionAlgorithm, SqlAes256CbcAlgorithm.AlgorithmName, StringComparison.OrdinalIgnoreCase) == true);
|
||||
|
||||
// Validate encryption type
|
||||
if (!((encryptionType == SqlClientEncryptionType.Deterministic) || (encryptionType == SqlClientEncryptionType.Randomized)))
|
||||
{
|
||||
throw SQL.InvalidEncryptionType(SqlAes256CbcAlgorithm.AlgorithmName,
|
||||
encryptionType,
|
||||
SqlClientEncryptionType.Deterministic,
|
||||
SqlClientEncryptionType.Randomized);
|
||||
}
|
||||
|
||||
// Get the cached encryption algorithm if one exists or create a new one, add it to cache and use it
|
||||
//
|
||||
// For now, we only have one version. In future, we may need to parse the algorithm names to derive the version byte.
|
||||
const byte algorithmVersion = 0x1;
|
||||
|
||||
StringBuilder algorithmKeyBuilder = new StringBuilder(Convert.ToBase64String(encryptionKey.RootKey), SqlSecurityUtility.GetBase64LengthFromByteLength(encryptionKey.RootKey.Length) + 4/*separators, type and version*/);
|
||||
|
||||
#if DEBUG
|
||||
int capacity = algorithmKeyBuilder.Capacity;
|
||||
#endif //DEBUG
|
||||
|
||||
algorithmKeyBuilder.Append(":");
|
||||
algorithmKeyBuilder.Append((int)encryptionType);
|
||||
algorithmKeyBuilder.Append(":");
|
||||
algorithmKeyBuilder.Append(algorithmVersion);
|
||||
|
||||
string algorithmKey = algorithmKeyBuilder.ToString();
|
||||
|
||||
#if DEBUG
|
||||
Debug.Assert(algorithmKey.Length <= capacity, "We needed to allocate a larger array");
|
||||
#endif //DEBUG
|
||||
|
||||
SqlAes256CbcAlgorithm aesAlgorithm;
|
||||
if (!_encryptionAlgorithms.TryGetValue(algorithmKey, out aesAlgorithm))
|
||||
{
|
||||
SqlAeadAes256CbcHmac256EncryptionKey encryptedKey = new SqlAeadAes256CbcHmac256EncryptionKey(encryptionKey.RootKey, SqlAes256CbcAlgorithm.AlgorithmName);
|
||||
aesAlgorithm = new SqlAes256CbcAlgorithm(encryptedKey, encryptionType, algorithmVersion);
|
||||
|
||||
// In case multiple threads reach here at the same time, the first one adds the value
|
||||
// the second one will be a no-op, the allocated memory will be claimed by Garbage Collector.
|
||||
_encryptionAlgorithms.TryAdd(algorithmKey, aesAlgorithm);
|
||||
}
|
||||
|
||||
return aesAlgorithm;
|
||||
}
|
||||
}
|
||||
}
|
@@ -868,11 +868,11 @@ namespace System.Data.SqlClient {
|
||||
_isNull = false;
|
||||
}
|
||||
|
||||
internal void SetToTime(byte[] bytes, int length, byte scale) {
|
||||
internal void SetToTime(byte[] bytes, int length, byte scale, byte denormalizedScale) {
|
||||
Debug.Assert(IsEmpty, "setting value a second time?");
|
||||
|
||||
_type = StorageType.Time;
|
||||
FillInTimeInfo(ref _value._timeInfo, bytes, length, scale);
|
||||
FillInTimeInfo(ref _value._timeInfo, bytes, length, scale, denormalizedScale);
|
||||
_isNull = false;
|
||||
}
|
||||
|
||||
@@ -885,11 +885,11 @@ namespace System.Data.SqlClient {
|
||||
_isNull = false;
|
||||
}
|
||||
|
||||
internal void SetToDateTime2(byte[] bytes, int length, byte scale) {
|
||||
internal void SetToDateTime2(byte[] bytes, int length, byte scale, byte denormalizedScale) {
|
||||
Debug.Assert(IsEmpty, "setting value a second time?");
|
||||
|
||||
_type = StorageType.DateTime2;
|
||||
FillInTimeInfo(ref _value._dateTime2Info.timeInfo, bytes, length - 3, scale); // remaining 3 bytes is for date
|
||||
FillInTimeInfo(ref _value._dateTime2Info.timeInfo, bytes, length - 3, scale, denormalizedScale); // remaining 3 bytes is for date
|
||||
_value._dateTime2Info.date = GetDateFromByteArray(bytes, length - 3); // 3 bytes for date
|
||||
_isNull = false;
|
||||
}
|
||||
@@ -904,11 +904,11 @@ namespace System.Data.SqlClient {
|
||||
_isNull = false;
|
||||
}
|
||||
|
||||
internal void SetToDateTimeOffset(byte[] bytes, int length, byte scale) {
|
||||
internal void SetToDateTimeOffset(byte[] bytes, int length, byte scale, byte denormalizedScale) {
|
||||
Debug.Assert(IsEmpty, "setting value a second time?");
|
||||
|
||||
_type = StorageType.DateTimeOffset;
|
||||
FillInTimeInfo(ref _value._dateTimeOffsetInfo.dateTime2Info.timeInfo, bytes, length - 5, scale); // remaining 5 bytes are for date and offset
|
||||
FillInTimeInfo(ref _value._dateTimeOffsetInfo.dateTime2Info.timeInfo, bytes, length - 5, scale, denormalizedScale); // remaining 5 bytes are for date and offset
|
||||
_value._dateTimeOffsetInfo.dateTime2Info.date = GetDateFromByteArray(bytes, length - 5); // 3 bytes for date
|
||||
_value._dateTimeOffsetInfo.offset = (Int16)(bytes[length - 2] + (bytes[length - 1] << 8)); // 2 bytes for offset (Int16)
|
||||
_isNull = false;
|
||||
@@ -926,9 +926,10 @@ namespace System.Data.SqlClient {
|
||||
_isNull = false;
|
||||
}
|
||||
|
||||
private static void FillInTimeInfo(ref TimeInfo timeInfo, byte[] timeBytes, int length, byte scale) {
|
||||
private static void FillInTimeInfo(ref TimeInfo timeInfo, byte[] timeBytes, int length, byte scale, byte denormalizedScale) {
|
||||
Debug.Assert(3 <= length && length <= 5, "invalid data length for timeInfo: " + length);
|
||||
Debug.Assert(0 <= scale && scale <= 7, "invalid scale: " + scale);
|
||||
Debug.Assert(0 <= denormalizedScale && denormalizedScale <= 7, "invalid denormalized scale: " + denormalizedScale);
|
||||
|
||||
Int64 tickUnits = (Int64)timeBytes[0] + ((Int64)timeBytes[1] << 8) + ((Int64)timeBytes[2] << 16);
|
||||
if (length > 3) {
|
||||
@@ -938,7 +939,11 @@ namespace System.Data.SqlClient {
|
||||
tickUnits += ((Int64)timeBytes[4] << 32);
|
||||
}
|
||||
timeInfo.ticks = tickUnits * TdsEnums.TICKS_FROM_SCALE[scale];
|
||||
timeInfo.scale = scale;
|
||||
|
||||
// Once the deserialization has been completed using the value scale, we need to set the actual denormalized scale,
|
||||
// coming from the data type, on the original result, so that it has the proper scale setting.
|
||||
// This only applies for values that got serialized/deserialized for encryption. Otherwise, both scales should be equal.
|
||||
timeInfo.scale = denormalizedScale;
|
||||
}
|
||||
|
||||
private static Int32 GetDateFromByteArray(byte[] buf, int offset) {
|
||||
|
@@ -1 +1 @@
|
||||
91812a7b96c2d1123b38454b099aa1892ab0a3c5
|
||||
2a4b96c02a36a45bbc09c39c729d107f10314af7
|
@@ -2,8 +2,8 @@
|
||||
// <copyright file="SqlBulkCopyOptions.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <owner current="true" primary="true">[....]</owner>
|
||||
// <owner current="true" primary="false">[....]</owner>
|
||||
// <owner current="true" primary="true">mithomas</owner>
|
||||
// <owner current="true" primary="false">blained</owner>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace System.Data.SqlClient {
|
||||
@@ -17,6 +17,7 @@ namespace System.Data.SqlClient {
|
||||
KeepNulls = 1 << 3,
|
||||
FireTriggers = 1 << 4,
|
||||
UseInternalTransaction = 1 << 5,
|
||||
AllowEncryptedValueModifications = 1 << 6,
|
||||
}
|
||||
}
|
||||
|
||||
|
33
external/referencesource/System.Data/System/Data/SqlClient/SqlClientEncryptionAlgorithm.cs
vendored
Normal file
33
external/referencesource/System.Data/System/Data/SqlClient/SqlClientEncryptionAlgorithm.cs
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="SqlException.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <owner current="true" primary="true">balnee</owner>
|
||||
// <owner current="true" primary="false">krishnib</owner>
|
||||
//------------------------------------------------------------------------------
|
||||
namespace System.Data.SqlClient
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Abstract base class for all TCE encryption algorithms. It exposes two functions
|
||||
/// 1. Encrypt - This function is used by SqlClient under the covers to transparently encrypt TCE enabled column data.
|
||||
/// 2. Decrypt - This function is used by SqlClient under the covers to transparently decrypt TCE enabled column data.
|
||||
/// </summary>
|
||||
internal abstract class SqlClientEncryptionAlgorithm
|
||||
{
|
||||
/// <summary>
|
||||
/// Encrypts the plainText with a column encryption key
|
||||
/// </summary>
|
||||
/// <param name="plainText">Plain text value to be encrypted</param>
|
||||
/// <returns></returns>
|
||||
internal abstract byte[] EncryptData(byte[] plainText);
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts the cipherText with a column encryption key
|
||||
/// </summary>
|
||||
/// <param name="cipherText">Ciphertext value to be decrypted</param>
|
||||
/// <returns></returns>
|
||||
internal abstract byte[] DecryptData(byte[] cipherText);
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="SqlException.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <owner current="true" primary="true">balnee</owner>
|
||||
// <owner current="true" primary="false">krishnib</owner>
|
||||
//------------------------------------------------------------------------------
|
||||
namespace System.Data.SqlClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract base class for all TCE encryption algorithm factory classes. Factory classes create instances of an encryption algorithm
|
||||
/// with a given key. At runtime when we determine a particular column is marked for TCE, based on the encryption algorithm we invoke
|
||||
/// the corresponding factory class and retrieve an object to an encryption algorithm.
|
||||
/// </summary>
|
||||
internal abstract class SqlClientEncryptionAlgorithmFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an encrytion algorithm with a given key.
|
||||
/// </summary>
|
||||
/// <param name="rootKey">encryption key that should be passed to the encryption algorithm to be created</param>
|
||||
/// <param name="encryptionType">Encryption Type, some algorithms will need this</param>
|
||||
/// <param name="encryptionAlgorithm">Encryption algorithm name. Needed for extracting version bits</param>
|
||||
/// <returns>Return a newly created SqlClientEncryptionAlgorithm instance</returns>
|
||||
internal abstract SqlClientEncryptionAlgorithm Create(SqlClientSymmetricKey encryptionKey, SqlClientEncryptionType encryptionType, string encryptionAlgorithm);
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="SqlClientEncryptionAlgorithmFactoryList.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <owner current="true" primary="true">krishnib</owner>
|
||||
// <owner current="true" primary="false">balnee</owner>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace System.Data.SqlClient {
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// <para> Implements a global directory of all the encryption algorithms registered with client.</para>
|
||||
/// </summary>
|
||||
sealed internal class SqlClientEncryptionAlgorithmFactoryList {
|
||||
private readonly ConcurrentDictionary<string, SqlClientEncryptionAlgorithmFactory> _encryptionAlgoFactoryList;
|
||||
private static readonly SqlClientEncryptionAlgorithmFactoryList _singletonInstance = new SqlClientEncryptionAlgorithmFactoryList();
|
||||
|
||||
private SqlClientEncryptionAlgorithmFactoryList () {
|
||||
_encryptionAlgoFactoryList = new ConcurrentDictionary<string, SqlClientEncryptionAlgorithmFactory>(concurrencyLevel: 4 * Environment.ProcessorCount /* default value in ConcurrentDictionary*/, capacity: 2);
|
||||
|
||||
// Add wellknown algorithms
|
||||
_encryptionAlgoFactoryList.TryAdd(SqlAeadAes256CbcHmac256Algorithm.AlgorithmName, new SqlAeadAes256CbcHmac256Factory());
|
||||
_encryptionAlgoFactoryList.TryAdd(SqlAes256CbcAlgorithm.AlgorithmName, new SqlAes256CbcFactory());
|
||||
}
|
||||
|
||||
internal static SqlClientEncryptionAlgorithmFactoryList GetInstance () {
|
||||
return _singletonInstance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the registered list of algorithms as a comma seperated list with algorithm names
|
||||
/// wrapped in single quotes.
|
||||
/// <summary>
|
||||
internal string GetRegisteredCipherAlgorithmNames () {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
bool firstElem = true;
|
||||
foreach (string key in _encryptionAlgoFactoryList.Keys) {
|
||||
if (firstElem) {
|
||||
builder.Append("'");
|
||||
firstElem = false;
|
||||
}
|
||||
else {
|
||||
builder.Append(", '");
|
||||
}
|
||||
builder.Append (key);
|
||||
builder.Append ("'");
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the algorithm handle instance for a given algorithm and instantiates it using the provided key and the encryption type.
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="algorithmName"></param>
|
||||
/// <param name="encryptionAlgorithm"></param>
|
||||
internal void GetAlgorithm(SqlClientSymmetricKey key, byte type, string algorithmName, out SqlClientEncryptionAlgorithm encryptionAlgorithm) {
|
||||
encryptionAlgorithm = null;
|
||||
|
||||
SqlClientEncryptionAlgorithmFactory factory = null;
|
||||
if (!_encryptionAlgoFactoryList.TryGetValue (algorithmName, out factory)) {
|
||||
throw SQL.UnknownColumnEncryptionAlgorithm(algorithmName,
|
||||
SqlClientEncryptionAlgorithmFactoryList.GetInstance().GetRegisteredCipherAlgorithmNames());
|
||||
}
|
||||
|
||||
Debug.Assert (null != factory, "Null Algorithm Factory class detected");
|
||||
|
||||
// If the factory exists, following method will Create an algorithm object. If this fails,
|
||||
// it will raise an exception.
|
||||
encryptionAlgorithm = factory.Create(key, (SqlClientEncryptionType)type, algorithmName);
|
||||
}
|
||||
}
|
||||
}
|
19
external/referencesource/System.Data/System/Data/SqlClient/SqlClientEncryptionType.cs
vendored
Normal file
19
external/referencesource/System.Data/System/Data/SqlClient/SqlClientEncryptionType.cs
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="SqlException.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <owner current="true" primary="true">balnee</owner>
|
||||
// <owner current="true" primary="false">krishnib</owner>
|
||||
//------------------------------------------------------------------------------
|
||||
namespace System.Data.SqlClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Encryption types supported in TCE
|
||||
/// </summary>
|
||||
internal enum SqlClientEncryptionType
|
||||
{
|
||||
PlainText = 0,
|
||||
Deterministic,
|
||||
Randomized
|
||||
}
|
||||
}
|
73
external/referencesource/System.Data/System/Data/SqlClient/SqlClientSymmetricKey.cs
vendored
Normal file
73
external/referencesource/System.Data/System/Data/SqlClient/SqlClientSymmetricKey.cs
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="SqlException.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <owner current="true" primary="true">balnee</owner>
|
||||
// <owner current="true" primary="false">krishnib</owner>
|
||||
//------------------------------------------------------------------------------
|
||||
namespace System.Data.SqlClient
|
||||
{
|
||||
using System;
|
||||
using System.Data.SqlClient;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
/// <summary>
|
||||
/// Base class containing raw key bytes for symmetric key algorithms. Some encryption algorithms can use the key directly while others derive sub keys from this.
|
||||
/// If an algorithm needs to derive more keys, have a derived class from this and use it in the corresponding encryption algorithm.
|
||||
/// </summary>
|
||||
internal class SqlClientSymmetricKey
|
||||
{
|
||||
/// <summary>
|
||||
/// DPAPI protected key
|
||||
/// </summary>
|
||||
protected readonly byte[] _rootKey;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that initializes the root key.
|
||||
/// </summary>
|
||||
/// <param name="rootKey">root key</param>
|
||||
internal SqlClientSymmetricKey(byte[] rootKey)
|
||||
{
|
||||
// Key validation
|
||||
if (rootKey == null || rootKey.Length == 0) {
|
||||
throw SQL.NullColumnEncryptionKeySysErr();
|
||||
}
|
||||
|
||||
_rootKey = rootKey;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a copy of the plain text key
|
||||
/// This is needed for actual encryption/decryption.
|
||||
/// </summary>
|
||||
internal virtual byte[] RootKey
|
||||
{
|
||||
get
|
||||
{
|
||||
return _rootKey;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes SHA256 value of the plain text key bytes
|
||||
/// </summary>
|
||||
/// <returns>A string containing SHA256 hash of the root key</returns>
|
||||
internal virtual string GetKeyHash()
|
||||
{
|
||||
return SqlSecurityUtility.GetSHA256Hash(RootKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the root key
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns the length of the root key
|
||||
/// </returns>
|
||||
internal virtual int Length()
|
||||
{
|
||||
// Note: DPAPI preserves the original byte length
|
||||
// so for now, this is as same as returning the length of the raw key.
|
||||
return _rootKey.Length;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
414
external/referencesource/System.Data/System/Data/SqlClient/SqlColumnEncryptionCngProvider.cs
vendored
Normal file
414
external/referencesource/System.Data/System/Data/SqlClient/SqlColumnEncryptionCngProvider.cs
vendored
Normal file
@@ -0,0 +1,414 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="SqlColumnEncryptionCngProvider.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <owner current="true" primary="true">balnee</owner>
|
||||
// <owner current="true" primary="false">krishnib</owner>
|
||||
//------------------------------------------------------------------------------
|
||||
namespace System.Data.SqlClient
|
||||
{
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Data.Common;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Security;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class SqlColumnEncryptionCngProvider : SqlColumnEncryptionKeyStoreProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Name for the CNG key store provider.
|
||||
/// </summary>
|
||||
public const string ProviderName = @"MSSQL_CNG_STORE";
|
||||
|
||||
/// <summary>
|
||||
/// RSA_OAEP is the only algorithm supported for encrypting/decrypting column encryption keys using this provider.
|
||||
/// For now, we are keeping all the providers in [....].
|
||||
/// </summary>
|
||||
private const string RSAEncryptionAlgorithmWithOAEP = @"RSA_OAEP";
|
||||
|
||||
/// <summary>
|
||||
/// Algorithm version
|
||||
/// </summary>
|
||||
private readonly byte[] _version = new byte[] { 0x01 };
|
||||
|
||||
/// <summary>
|
||||
/// This function uses the asymmetric key specified by the key path
|
||||
/// and decrypts an encrypted CEK with RSA encryption algorithm.
|
||||
/// </summary>
|
||||
/// <param name="masterKeyPath">Complete path of an asymmetric key in CNG</param>
|
||||
/// <param name="encryptionAlgorithm">Asymmetric Key Encryption Algorithm</param>
|
||||
/// <param name="encryptedColumnEncryptionKey">Encrypted Column Encryption Key</param>
|
||||
/// <returns>Plain text column encryption key</returns>
|
||||
public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey)
|
||||
{
|
||||
// Validate the input parameters
|
||||
ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: true);
|
||||
|
||||
if (null == encryptedColumnEncryptionKey)
|
||||
{
|
||||
throw SQL.NullEncryptedColumnEncryptionKey();
|
||||
}
|
||||
|
||||
if (0 == encryptedColumnEncryptionKey.Length)
|
||||
{
|
||||
throw SQL.EmptyEncryptedColumnEncryptionKey();
|
||||
}
|
||||
|
||||
// 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])
|
||||
{
|
||||
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.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 (SHA256Cng sha256 = new SHA256Cng())
|
||||
{
|
||||
sha256.TransformFinalBlock(encryptedColumnEncryptionKey, 0, encryptedColumnEncryptionKey.Length - signature.Length);
|
||||
hash = sha256.Hash;
|
||||
}
|
||||
|
||||
Debug.Assert(hash != null, @"hash should not be null while decrypting encrypted column encryption key.");
|
||||
|
||||
// Validate the signature
|
||||
if (!RSAVerifySignature(hash, signature, rsaCngProvider))
|
||||
{
|
||||
throw SQL.InvalidSignature(masterKeyPath);
|
||||
}
|
||||
|
||||
// Decrypt the CEK
|
||||
return RSADecrypt(rsaCngProvider, cipherText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function uses the asymmetric key specified by the key path
|
||||
/// and encrypts CEK with RSA encryption algorithm.
|
||||
/// </summary>
|
||||
/// <param name="keyPath">Complete path of an asymmetric key in AKV</param>
|
||||
/// <param name="encryptionAlgorithm">Asymmetric Key Encryption Algorithm</param>
|
||||
/// <param name="columnEncryptionKey">Plain text column encryption key</param>
|
||||
/// <returns>Encrypted column encryption key</returns>
|
||||
public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey)
|
||||
{
|
||||
// Validate the input parameters
|
||||
ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: false);
|
||||
|
||||
if (null == columnEncryptionKey)
|
||||
{
|
||||
throw SQL.NullColumnEncryptionKey();
|
||||
}
|
||||
else if (0 == columnEncryptionKey.Length)
|
||||
{
|
||||
throw SQL.EmptyColumnEncryptionKey();
|
||||
}
|
||||
|
||||
// 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 (SHA256Cng sha256 = new SHA256Cng())
|
||||
{
|
||||
sha256.TransformBlock(version, 0, version.Length, version, 0);
|
||||
sha256.TransformBlock(keyPathLength, 0, keyPathLength.Length, keyPathLength, 0);
|
||||
sha256.TransformBlock(cipherTextLength, 0, cipherTextLength.Length, cipherTextLength, 0);
|
||||
sha256.TransformBlock(masterKeyPathBytes, 0, masterKeyPathBytes.Length, masterKeyPathBytes, 0);
|
||||
sha256.TransformFinalBlock(cipherText, 0, cipherText.Length);
|
||||
hash = sha256.Hash;
|
||||
}
|
||||
|
||||
// Sign the hash
|
||||
byte[] signedHash = RSASignHashedData(hash, 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);
|
||||
|
||||
return encryptedColumnEncryptionKey;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function validates that the encryption algorithm is RSA_OAEP and if it is not,
|
||||
/// then throws an exception
|
||||
/// </summary>
|
||||
/// <param name="encryptionAlgorithm">Asymmetric key encryptio algorithm</param>
|
||||
/// <param name="isSystemOp">Indicates if ADO.NET calls or the customer calls the API</param>
|
||||
private void ValidateEncryptionAlgorithm(string encryptionAlgorithm, bool isSystemOp)
|
||||
{
|
||||
// This validates that the encryption algorithm is RSA_OAEP
|
||||
if (null == encryptionAlgorithm)
|
||||
{
|
||||
throw SQL.NullKeyEncryptionAlgorithm(isSystemOp);
|
||||
}
|
||||
|
||||
if (!string.Equals(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw SQL.InvalidKeyEncryptionAlgorithm(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, isSystemOp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the CNG key path is Empty or Null (and raises exception if they are).
|
||||
/// </summary>
|
||||
/// <param name="masterKeyPath">keypath containing the CNG provider name and key name</param>
|
||||
/// <param name="isSystemOp">Indicates if ADO.NET calls or the customer calls the API</param>
|
||||
private void ValidateNonEmptyKeyPath(string masterKeyPath, bool isSystemOp)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(masterKeyPath))
|
||||
{
|
||||
if (null == masterKeyPath)
|
||||
{
|
||||
throw SQL.NullCngKeyPath(isSystemOp);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw SQL.InvalidCngPath(masterKeyPath, isSystemOp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt the text using specified CNG key.
|
||||
/// </summary>
|
||||
/// <param name="rsaCngProvider">RSA CNG Provider.</param>
|
||||
/// <param name="columnEncryptionKey">Plain text Column Encryption Key.</param>
|
||||
/// <returns>Returns an encrypted blob or throws an exception if there are any errors.</returns>
|
||||
private byte[] RSAEncrypt(RSACng rsaCngProvider, byte[] columnEncryptionKey)
|
||||
{
|
||||
Debug.Assert(columnEncryptionKey != null);
|
||||
Debug.Assert(rsaCngProvider != null);
|
||||
|
||||
return rsaCngProvider.Encrypt(columnEncryptionKey, RSAEncryptionPadding.OaepSHA1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the text using the specified CNG key.
|
||||
/// </summary>
|
||||
/// <param name="rsaCngProvider">RSA CNG Provider.</param>
|
||||
/// <param name="encryptedColumnEncryptionKey">Encrypted Column Encryption Key.</param>
|
||||
/// <returns>Returns the decrypted plaintext Column Encryption Key or throws an exception if there are any errors.</returns>
|
||||
private byte[] RSADecrypt(RSACng rsaCngProvider, byte[] encryptedColumnEncryptionKey)
|
||||
{
|
||||
Debug.Assert((encryptedColumnEncryptionKey != null) && (encryptedColumnEncryptionKey.Length != 0));
|
||||
Debug.Assert(rsaCngProvider != null);
|
||||
|
||||
return rsaCngProvider.Decrypt(encryptedColumnEncryptionKey, RSAEncryptionPadding.OaepSHA1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates signature based on RSA PKCS#v1.5 scheme using a specified CNG Key.
|
||||
/// </summary>
|
||||
/// <param name="dataToSign">Text to sign.</param>
|
||||
/// <param name="rsaCngProvider">RSA CNG Provider.</param>
|
||||
/// <returns>Signature</returns>
|
||||
private byte[] RSASignHashedData(byte[] dataToSign, RSACng rsaCngProvider)
|
||||
{
|
||||
Debug.Assert((dataToSign != null) && (dataToSign.Length != 0));
|
||||
Debug.Assert(rsaCngProvider != null);
|
||||
|
||||
return rsaCngProvider.SignData(dataToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the given RSA PKCSv1.5 signature.
|
||||
/// </summary>
|
||||
/// <param name="dataToVerify"></param>
|
||||
/// <param name="signature"></param>
|
||||
/// <param name="rsaCngProvider">RSA CNG Provider.</param>
|
||||
/// <returns>true if signature is valid, false if it is not valid</returns>
|
||||
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);
|
||||
|
||||
return rsaCngProvider.VerifyData(dataToVerify, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the public Key size in bytes
|
||||
/// </summary>
|
||||
/// <param name="rsaCngProvider">RSA CNG Provider.</param>
|
||||
/// <returns>Key size in bytes</returns>
|
||||
private int GetKeySize(RSACng rsaCngProvider)
|
||||
{
|
||||
Debug.Assert(rsaCngProvider != null);
|
||||
|
||||
return rsaCngProvider.KeySize / 8; // Convert from bits to byte
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a RSACng object from the given keyName
|
||||
/// </summary>
|
||||
/// <param name="keyPath"></param>
|
||||
/// <param name="isSystemOp">Indicates if ADO.NET calls or the customer calls the API</param>
|
||||
/// <returns></returns>
|
||||
private RSACng CreateRSACngProvider(string keyPath, bool isSystemOp)
|
||||
{
|
||||
// Get CNGProvider and the KeyID
|
||||
string cngProviderName;
|
||||
string keyIdentifier;
|
||||
GetCngProviderAndKeyId(keyPath, isSystemOp, out cngProviderName, out keyIdentifier);
|
||||
|
||||
CngProvider cngProvider = new CngProvider(cngProviderName);
|
||||
CngKey cngKey;
|
||||
|
||||
try
|
||||
{
|
||||
cngKey = CngKey.Open(keyIdentifier, cngProvider);
|
||||
}
|
||||
catch (CryptographicException)
|
||||
{
|
||||
throw SQL.InvalidCngKey(keyPath, cngProviderName, keyIdentifier, isSystemOp);
|
||||
}
|
||||
|
||||
return new RSACng(cngKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the CNG provider and key name from the key path
|
||||
/// </summary>
|
||||
/// <param name="masterKeyPath">keypath in the format [CNG Provider]\[KeyName]</param>
|
||||
/// <param name="isSystemOp">Indicates if ADO.NET calls or the customer calls the API</param>
|
||||
/// <param name="cngProvider">CNG Provider</param>
|
||||
/// <param name="keyIdentifier">Key identifier inside the CNG provider</param>
|
||||
private void GetCngProviderAndKeyId(string keyPath, bool isSystemOp, out string cngProvider, out string keyIdentifier)
|
||||
{
|
||||
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)
|
||||
{
|
||||
throw SQL.EmptyCngName(keyPath, isSystemOp);
|
||||
}
|
||||
|
||||
if (keyIdentifier.Length == 0)
|
||||
{
|
||||
throw SQL.EmptyCngKeyId(keyPath, isSystemOp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
462
external/referencesource/System.Data/System/Data/SqlClient/SqlColumnEncryptionCspProvider.cs
vendored
Normal file
462
external/referencesource/System.Data/System/Data/SqlClient/SqlColumnEncryptionCspProvider.cs
vendored
Normal file
@@ -0,0 +1,462 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="SqlColumnEncryptionCspProvider.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <owner current="true" primary="true">balnee</owner>
|
||||
// <owner current="true" primary="false">krishnib</owner>
|
||||
//------------------------------------------------------------------------------
|
||||
namespace System.Data.SqlClient
|
||||
{
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Data.Common;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Security;
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.Win32;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class SqlColumnEncryptionCspProvider : SqlColumnEncryptionKeyStoreProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Name for the CSP key store provider.
|
||||
/// </summary>
|
||||
public const string ProviderName = @"MSSQL_CSP_PROVIDER";
|
||||
|
||||
/// <summary>
|
||||
/// RSA_OAEP is the only algorithm supported for encrypting/decrypting column encryption keys using this provider.
|
||||
/// For now, we are keeping all the providers in [....].
|
||||
/// </summary>
|
||||
private const string RSAEncryptionAlgorithmWithOAEP = @"RSA_OAEP";
|
||||
|
||||
/// <summary>
|
||||
/// Hashing algoirthm used for signing
|
||||
/// </summary>
|
||||
private const string HashingAlgorithm = @"SHA256";
|
||||
|
||||
/// <summary>
|
||||
/// Algorithm version
|
||||
/// </summary>
|
||||
private readonly byte[] _version = new byte[] { 0x01 };
|
||||
|
||||
/// <summary>
|
||||
/// This function uses the asymmetric key specified by the key path
|
||||
/// and decrypts an encrypted CEK with RSA encryption algorithm.
|
||||
/// </summary>
|
||||
/// <param name="masterKeyPath">Complete path of an asymmetric key in CSP</param>
|
||||
/// <param name="encryptionAlgorithm">Asymmetric Key Encryption Algorithm</param>
|
||||
/// <param name="encryptedColumnEncryptionKey">Encrypted Column Encryption Key</param>
|
||||
/// <returns>Plain text column encryption key</returns>
|
||||
public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey)
|
||||
{
|
||||
// Validate the input parameters
|
||||
ValidateNonEmptyCSPKeyPath(masterKeyPath, isSystemOp: true);
|
||||
|
||||
if (null == encryptedColumnEncryptionKey)
|
||||
{
|
||||
throw SQL.NullEncryptedColumnEncryptionKey();
|
||||
}
|
||||
|
||||
if (0 == encryptedColumnEncryptionKey.Length)
|
||||
{
|
||||
throw SQL.EmptyEncryptedColumnEncryptionKey();
|
||||
}
|
||||
|
||||
// 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])
|
||||
{
|
||||
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 (SHA256Cng sha256 = new SHA256Cng())
|
||||
{
|
||||
sha256.TransformFinalBlock(encryptedColumnEncryptionKey, 0, encryptedColumnEncryptionKey.Length - signature.Length);
|
||||
hash = sha256.Hash;
|
||||
}
|
||||
|
||||
Debug.Assert(hash != null, @"hash should not be null while decrypting encrypted column encryption key.");
|
||||
|
||||
// Validate the signature
|
||||
if (!RSAVerifySignature(hash, signature, rsaProvider))
|
||||
{
|
||||
throw SQL.InvalidSignature(masterKeyPath);
|
||||
}
|
||||
|
||||
// Decrypt the CEK
|
||||
return RSADecrypt(rsaProvider, cipherText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function uses the asymmetric key specified by the key path
|
||||
/// and encrypts CEK with RSA encryption algorithm.
|
||||
/// </summary>
|
||||
/// <param name="keyPath">Complete path of an asymmetric key in AKV</param>
|
||||
/// <param name="encryptionAlgorithm">Asymmetric Key Encryption Algorithm</param>
|
||||
/// <param name="columnEncryptionKey">Plain text column encryption key</param>
|
||||
/// <returns>Encrypted column encryption key</returns>
|
||||
public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey)
|
||||
{
|
||||
// Validate the input parameters
|
||||
ValidateNonEmptyCSPKeyPath(masterKeyPath, isSystemOp: false);
|
||||
|
||||
if (null == columnEncryptionKey)
|
||||
{
|
||||
throw SQL.NullColumnEncryptionKey();
|
||||
}
|
||||
else if (0 == columnEncryptionKey.Length)
|
||||
{
|
||||
throw SQL.EmptyColumnEncryptionKey();
|
||||
}
|
||||
|
||||
// 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 (SHA256Cng sha256 = new SHA256Cng())
|
||||
{
|
||||
sha256.TransformBlock(version, 0, version.Length, version, 0);
|
||||
sha256.TransformBlock(keyPathLength, 0, keyPathLength.Length, keyPathLength, 0);
|
||||
sha256.TransformBlock(cipherTextLength, 0, cipherTextLength.Length, cipherTextLength, 0);
|
||||
sha256.TransformBlock(masterKeyPathBytes, 0, masterKeyPathBytes.Length, masterKeyPathBytes, 0);
|
||||
sha256.TransformFinalBlock(cipherText, 0, cipherText.Length);
|
||||
hash = sha256.Hash;
|
||||
}
|
||||
|
||||
// Sign the hash
|
||||
byte[] signedHash = RSASignHashedData(hash, 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function validates that the encryption algorithm is RSA_OAEP and if it is not,
|
||||
/// then throws an exception
|
||||
/// </summary>
|
||||
/// <param name="encryptionAlgorithm">Asymmetric key encryptio algorithm</param>
|
||||
/// <param name="isSystemOp">Indicates if ADO.NET calls or the customer calls the API</param>
|
||||
private void ValidateEncryptionAlgorithm(string encryptionAlgorithm, bool isSystemOp)
|
||||
{
|
||||
// This validates that the encryption algorithm is RSA_OAEP
|
||||
if (null == encryptionAlgorithm)
|
||||
{
|
||||
throw SQL.NullKeyEncryptionAlgorithm(isSystemOp);
|
||||
}
|
||||
|
||||
if (string.Equals(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, StringComparison.OrdinalIgnoreCase) != true)
|
||||
{
|
||||
throw SQL.InvalidKeyEncryptionAlgorithm(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, isSystemOp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the CSP key path is Empty or Null (and raises exception if they are).
|
||||
/// </summary>
|
||||
/// <param name="masterKeyPath">CSP key path.</param>
|
||||
/// <param name="isSystemOp">Indicates if ADO.NET calls or the customer calls the API</param>
|
||||
private void ValidateNonEmptyCSPKeyPath(string masterKeyPath, bool isSystemOp)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(masterKeyPath))
|
||||
{
|
||||
if (null == masterKeyPath)
|
||||
{
|
||||
throw SQL.NullCspKeyPath(isSystemOp);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw SQL.InvalidCspPath(masterKeyPath, isSystemOp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt the text using specified CSP key.
|
||||
/// </summary>
|
||||
/// <param name="masterKeyPath">CSP key path.</param>
|
||||
/// <param name="encryptionAlgorithm">Encryption Algorithm.</param>
|
||||
/// <param name="columnEncryptionKey">Plain text Column Encryption Key.</param>
|
||||
/// <returns>Returns an encrypted blob or throws an exception if there are any errors.</returns>
|
||||
private byte[] RSAEncrypt(RSACryptoServiceProvider rscp, byte[] columnEncryptionKey)
|
||||
{
|
||||
Debug.Assert(columnEncryptionKey != null);
|
||||
Debug.Assert(rscp != null);
|
||||
|
||||
return rscp.Encrypt(columnEncryptionKey, fOAEP: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the text using specified CSP key.
|
||||
/// </summary>
|
||||
/// <param name="masterKeyPath">CSP key url.</param>
|
||||
/// <param name="encryptionAlgorithm">Encryption Algorithm.</param>
|
||||
/// <param name="encryptedColumnEncryptionKey">Encrypted Column Encryption Key.</param>
|
||||
/// <returns>Returns the decrypted plaintext Column Encryption Key or throws an exception if there are any errors.</returns>
|
||||
private byte[] RSADecrypt(RSACryptoServiceProvider rscp, byte[] encryptedColumnEncryptionKey)
|
||||
{
|
||||
Debug.Assert((encryptedColumnEncryptionKey != null) && (encryptedColumnEncryptionKey.Length != 0));
|
||||
Debug.Assert(rscp != null);
|
||||
|
||||
return rscp.Decrypt(encryptedColumnEncryptionKey, fOAEP: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates signature based on RSA PKCS#v1.5 scheme using a specified CSP Key URL.
|
||||
/// </summary>
|
||||
/// <param name="dataToSign">Text to sign.</param>
|
||||
/// <param name="rscp">RSA Provider with a given key</param>
|
||||
/// <returns>Signature</returns>
|
||||
private byte[] RSASignHashedData(byte[] dataToSign, RSACryptoServiceProvider rscp)
|
||||
{
|
||||
Debug.Assert((dataToSign != null) && (dataToSign.Length != 0));
|
||||
Debug.Assert(rscp != null);
|
||||
|
||||
return rscp.SignData(dataToSign, HashingAlgorithm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the given RSA PKCSv1.5 signature.
|
||||
/// </summary>
|
||||
/// <param name="dataToVerify"></param>
|
||||
/// <param name="signature"></param>
|
||||
/// <param name="rscp">RSA Provider with a given key</param>
|
||||
/// <returns>true if signature is valid, false if it is not valid</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the public Key size in bytes
|
||||
/// </summary>
|
||||
/// <param name="rscp">RSA Provider with a given key</param>
|
||||
/// <returns>Key size in bytes</returns>
|
||||
private int GetKeySize(RSACryptoServiceProvider rscp)
|
||||
{
|
||||
Debug.Assert(rscp != null);
|
||||
|
||||
return rscp.KeySize / 8;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a RSACryptoServiceProvider from the given key path which contains both CSP name and key name
|
||||
/// </summary>
|
||||
/// <param name="keyPath">key path in the format of [CAPI provider name]\[key name]</param>
|
||||
/// <param name="isSystemOp">Indicates if ADO.NET calls or the customer calls the API</param>
|
||||
/// <returns></returns>
|
||||
private RSACryptoServiceProvider CreateRSACryptoProvider(string keyPath, bool isSystemOp)
|
||||
{
|
||||
// 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;
|
||||
|
||||
try
|
||||
{
|
||||
//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;
|
||||
}
|
||||
}
|
||||
|
||||
return rscp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the CSP provider name and key name from the given key path
|
||||
/// </summary>
|
||||
/// <param name="keyPath">key path in the format of [CSP provider name]\[key name]</param>
|
||||
/// <param name="isSystemOp">Indicates if ADO.NET calls or the customer calls the API</param>
|
||||
/// <param name="cspProviderName">output containing the CSP provider name</param>
|
||||
/// <param name="keyIdentifier">output containing the key name</param>
|
||||
private void GetCspProviderAndKeyName(string keyPath, bool isSystemOp, out string cspProviderName, out string keyIdentifier)
|
||||
{
|
||||
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)
|
||||
{
|
||||
throw SQL.EmptyCspName(keyPath, isSystemOp);
|
||||
}
|
||||
|
||||
if (keyIdentifier.Length == 0)
|
||||
{
|
||||
throw SQL.EmptyCspKeyId(keyPath, isSystemOp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the provider type from a given CAPI provider name
|
||||
/// </summary>
|
||||
/// <param name="providerName">CAPI provider name</param>
|
||||
/// <param name="keyPath">key path in the format of [CSP provider name]\[key name]</param>
|
||||
/// <param name="isSystemOp">Indicates if ADO.NET calls or the customer calls the API</param>
|
||||
/// <returns></returns>
|
||||
private 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();
|
||||
|
||||
return providerType;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user