Imported Upstream version 4.3.2.467

Former-commit-id: 9c2cb47f45fa221e661ab616387c9cda183f283d
This commit is contained in:
Xamarin Public Jenkins
2016-02-22 11:00:01 -05:00
parent f302175246
commit f3e3aab35a
4097 changed files with 122406 additions and 82300 deletions

View File

@@ -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 {

View File

@@ -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>
//------------------------------------------------------------------------------

View File

@@ -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 {

View File

@@ -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 {

View 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;
}
}
}

View 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; }
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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;
}
}
}

View File

@@ -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) {

View File

@@ -1 +1 @@
91812a7b96c2d1123b38454b099aa1892ab0a3c5
2a4b96c02a36a45bbc09c39c729d107f10314af7

View File

@@ -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,
}
}

View 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View 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
}
}

View 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;
}
}
}

View 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);
}
}
}
}

View 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