diff options
author | dotnet-bot <dotnet-bot@microsoft.com> | 2015-10-16 00:43:47 +0300 |
---|---|---|
committer | Marek Safar <marek.safar@gmail.com> | 2016-05-03 12:32:33 +0300 |
commit | 8f2487b3ce2817d0d9013981272b876371222f64 (patch) | |
tree | 6d69f0f6b4778b89b2307a2c045200f2377ececa /mcs/class/referencesource/System.Data/System/Data/SqlClient | |
parent | 5f82524097130bf3e29844adcb47d1695bb14c6b (diff) |
Update Reference Sources to .NET Framework 4.6
Diffstat (limited to 'mcs/class/referencesource/System.Data/System/Data/SqlClient')
88 files changed, 8432 insertions, 629 deletions
diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/OnChangedEventHandler.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/OnChangedEventHandler.cs index f79cecdf903..ffd6bf3395a 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/OnChangedEventHandler.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/OnChangedEventHandler.cs @@ -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 { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/ParameterPeekAheadValue.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/ParameterPeekAheadValue.cs index 79b4b96778c..ea460cf9081 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/ParameterPeekAheadValue.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/ParameterPeekAheadValue.cs @@ -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> //------------------------------------------------------------------------------ diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/RowsCopiedEventArgs.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/RowsCopiedEventArgs.cs index 0a93882a2d9..cc294b15712 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/RowsCopiedEventArgs.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/RowsCopiedEventArgs.cs @@ -2,8 +2,8 @@ // <copyright file="RowsCopiedEvent.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/RowsCopiedEventHandler.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/RowsCopiedEventHandler.cs index c239d77e61d..b6d5adc503c 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/RowsCopiedEventHandler.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/RowsCopiedEventHandler.cs @@ -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 { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SortOrder.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SortOrder.cs index 2eeef117441..6c19e1cac55 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SortOrder.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SortOrder.cs @@ -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 { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs new file mode 100644 index 00000000000..6c6a016c1dd --- /dev/null +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs @@ -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; + } + } +} diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlAeadAes256CbcHmac256EncryptionKey.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlAeadAes256CbcHmac256EncryptionKey.cs new file mode 100644 index 00000000000..acb2734e9be --- /dev/null +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlAeadAes256CbcHmac256EncryptionKey.cs @@ -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; } + } + } +} diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlAeadAes256CbcHmac256Factory.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlAeadAes256CbcHmac256Factory.cs new file mode 100644 index 00000000000..dcbaa8ae34e --- /dev/null +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlAeadAes256CbcHmac256Factory.cs @@ -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; + } + } +} diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlAes256CbcAlgorithm.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlAes256CbcAlgorithm.cs new file mode 100644 index 00000000000..cd21c4df548 --- /dev/null +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlAes256CbcAlgorithm.cs @@ -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); + } + } +} diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlAes256CbcFactory.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlAes256CbcFactory.cs new file mode 100644 index 00000000000..09d7468728f --- /dev/null +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlAes256CbcFactory.cs @@ -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; + } + } +} diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlBuffer.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlBuffer.cs index aa838b29c5f..9c4084086bb 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlBuffer.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlBuffer.cs @@ -2,9 +2,9 @@ // <copyright file="SqlDataReader.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { @@ -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) { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlBulkCopy.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlBulkCopy.cs index 91812a7b96c..cd3484f1391 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlBulkCopy.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlBulkCopy.cs @@ -2,8 +2,8 @@ // <copyright file="SqlBulkCopy.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ // todo list: @@ -16,22 +16,34 @@ namespace System.Data.SqlClient { using System; using System.Collections; using System.Collections.Generic; +#if !PROJECTK using System.ComponentModel; +#endif // !PROJECTK using System.Data; using System.Data.Common; +#if !PROJECTK using System.Data.Sql; +#endif //PROJECTK using System.Data.SqlTypes; using System.Diagnostics; using System.Globalization; using System.Runtime.CompilerServices; +#if !PROJECTK using System.Runtime.ConstrainedExecution; +#endif // !PROJECTK using System.Text; using System.Threading; using System.Threading.Tasks; +#if !PROJECTK using System.Transactions; +#endif //PROJECTK using System.Xml; +#if !PROJECTK using MSS = Microsoft.SqlServer.Server; +#else + using Res = System.SR; +#endif //PROJECTK // ------------------------------------------------------------------------------------------------- // this internal class helps us to associate the metadata (from the target) @@ -161,12 +173,15 @@ namespace System.Data.SqlClient { Owner, TableName, } +#if !PROJECTK private enum ValueSourceType { Unspecified = 0, IDataReader, DataTable, - RowArray + RowArray, + DbDataReader } +#endif //PROJECTK // Enum for specifying SqlDataReader.Get method used private enum ValueMethod : byte { @@ -224,11 +239,17 @@ namespace System.Data.SqlClient { private int _rowsUntilNotification; private bool _insideRowsCopiedEvent; +#if !PROJECTK private object _rowSource; +#endif //PROJECTK private SqlDataReader _SqlDataReaderRowSource; +#if !PROJECTK private bool _rowSourceIsSqlDataReaderSmi; +#endif //PROJECTK private DbDataReader _DbDataReaderRowSource; +#if !PROJECTK private DataTable _dataTableSource; +#endif //PROJECTK private SqlBulkCopyColumnMappingCollection _columnMappings; private SqlBulkCopyColumnMappingCollection _localColumnMappings; @@ -237,11 +258,13 @@ namespace System.Data.SqlClient { private SqlTransaction _internalTransaction; private SqlTransaction _externalTransaction; +#if !PROJECTK private ValueSourceType _rowSourceType = ValueSourceType.Unspecified; private DataRow _currentRow; private int _currentRowLength; private DataRowState _rowStateToSkip; private IEnumerator _rowEnumerator; +#endif //PROJECTK private TdsParser _parser; private TdsParserStateObject _stateObj; @@ -249,8 +272,10 @@ namespace System.Data.SqlClient { private SqlRowsCopiedEventHandler _rowsCopiedEventHandler; +#if !PROJECTK private static int _objectTypeCount; // Bid counter internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); +#endif //PROJECTK //newly added member variables for Async modification, m = member variable to bcp private int _savedBatchSize = 0; //save the batchsize so that changes are not affected unexpectedly @@ -381,11 +406,13 @@ namespace System.Data.SqlClient { } } +#if !PROJECTK internal int ObjectID { get { return _objectID; } } +#endif //PROJECTK public event SqlRowsCopiedEventHandler SqlRowsCopied { add { @@ -438,19 +465,27 @@ namespace System.Data.SqlClient { TDSCommand = "select @@trancount; SET FMTONLY ON select * from " + this.DestinationTableName + " SET FMTONLY OFF "; +#if !PROJECTK if (_connection.IsShiloh) { +#endif //PROJECTK // If its a temp DB then try to connect string TableCollationsStoredProc; if (_connection.IsKatmaiOrNewer) { TableCollationsStoredProc = "sp_tablecollations_100"; } +#if !PROJECTK else if (_connection.IsYukonOrNewer) { +#else + else { +#endif //PROJECTK TableCollationsStoredProc = "sp_tablecollations_90"; } +#if !PROJECTK else { TableCollationsStoredProc = "sp_tablecollations"; } +#endif //PROJECTK string TableName = parts[MultipartIdentifier.TableIndex]; bool isTempTable = TableName.Length > 0 && '#' == TableName[0]; @@ -489,21 +524,27 @@ namespace System.Data.SqlClient { TableName ); } +#if !PROJECTK } +#endif //PROJECTK return TDSCommand; } // Creates and then executes initial query to get information about the targettable - // When __isAsyncBulkCopy == false (i.e. it is [....] copy): out result contains the resulset. Returns null. + // When __isAsyncBulkCopy == false (i.e. it is Sync copy): out result contains the resulset. Returns null. // When __isAsyncBulkCopy == true (i.e. it is Async copy): This still uses the _parser.Run method synchronously and return Task<BulkCopySimpleResultSet>. // We need to have a _parser.RunAsync to make it real async. private Task<BulkCopySimpleResultSet> CreateAndExecuteInitialQueryAsync(out BulkCopySimpleResultSet result) { string TDSCommand = CreateInitialQuery(); +#if !PROJECTK Bid.Trace("<sc.SqlBulkCopy.CreateAndExecuteInitialQueryAsync|INFO> Initial Query: '%ls' \n", TDSCommand); Bid.CorrelationTrace("<sc.SqlBulkCopy.CreateAndExecuteInitialQueryAsync|Info|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID); Task executeTask = _parser.TdsExecuteSQLBatch(TDSCommand, this.BulkCopyTimeout, null, _stateObj, sync: !_isAsyncBulkCopy, callerHasConnectionLock: true); +#else + Task executeTask = _parser.TdsExecuteSQLBatch(TDSCommand, this.BulkCopyTimeout, _stateObj, sync: !_isAsyncBulkCopy, callerHasConnectionLock: true); +#endif // !PROJECTK if (executeTask == null) { result = new BulkCopySimpleResultSet(); @@ -533,7 +574,11 @@ namespace System.Data.SqlClient { private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet internalResults) { StringBuilder updateBulkCommandText = new StringBuilder(); +#if !PROJECTK if (_connection.IsShiloh && 0 == internalResults[CollationResultId].Count) { +#else + if (0 == internalResults[CollationResultId].Count) { +#endif //PROJECTK throw SQL.BulkLoadNoCollation(); } @@ -546,12 +591,16 @@ namespace System.Data.SqlClient { bool isInTransaction; +#if !PROJECTK if(_parser.IsYukonOrNewer) { +#endif //PROJECTK isInTransaction = _connection.HasLocalTransaction; +#if !PROJECTK } else { isInTransaction = (bool)(0 < (SqlInt32)(internalResults[TranCountResultId][TranCountRowId][TranCountValueId])); } +#endif //PROJECTK // Throw if there is a transaction but no flag is set if(isInTransaction && null == _externalTransaction && null == _internalTransaction && (_connection.Parser != null && _connection.Parser.CurrentTransaction != null && _connection.Parser.CurrentTransaction.IsLocal)) { throw SQL.BulkLoadExistingTransaction(); @@ -598,11 +647,19 @@ namespace System.Data.SqlClient { AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "sql_variant"); } else if(metadata.type == SqlDbType.Udt) { +#if !PROJECTK // UDTs are sent as varbinary AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "varbinary"); +#else + throw ADP.DbTypeNotSupported(SqlDbType.Udt.ToString()); +#endif //PROJECTK } else { +#if !PROJECTK AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, typeof(SqlDbType).GetEnumName(metadata.type)); +#else + AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, metadata.type.ToString()); +#endif //PROJECTK } switch(metadata.metaType.NullableType) { @@ -652,15 +709,35 @@ namespace System.Data.SqlClient { } } +#if !PROJECTK if(_connection.IsShiloh) { +#endif //PROJECTK // Shiloh or above! // get collation for column i Result rowset = internalResults[CollationResultId]; object rowvalue = rowset[i][CollationId]; - if(rowvalue != null) { + + bool shouldSendCollation; + switch (metadata.type) { + case SqlDbType.Char: + case SqlDbType.NChar: + case SqlDbType.VarChar: + case SqlDbType.NVarChar: + case SqlDbType.Text: + case SqlDbType.NText: + shouldSendCollation = true; + break; + + default: + shouldSendCollation = false; + break; + } + + if (rowvalue != null && shouldSendCollation) { Debug.Assert(rowvalue is SqlString); SqlString collation_name = (SqlString)rowvalue; + if(!collation_name.IsNull) { updateBulkCommandText.Append(" COLLATE " + collation_name.Value); // VSTFDEVDIV 461426: compare collations only if the collation value was set on the metadata @@ -675,7 +752,9 @@ namespace System.Data.SqlClient { } } } +#if !PROJECTK } +#endif //PROJECTK break; } // end if found } // end of (inner) for loop @@ -723,9 +802,13 @@ namespace System.Data.SqlClient { // submitts the updatebulk command // private Task SubmitUpdateBulkCommand(string TDSCommand) { +#if !PROJECTK Bid.CorrelationTrace("<sc.SqlBulkCopy.SubmitUpdateBulkCommand|Info|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID); Task executeTask = _parser.TdsExecuteSQLBatch(TDSCommand, this.BulkCopyTimeout, null, _stateObj, sync: !_isAsyncBulkCopy, callerHasConnectionLock: true); +#else + Task executeTask = _parser.TdsExecuteSQLBatch(TDSCommand, this.BulkCopyTimeout, _stateObj, sync: !_isAsyncBulkCopy, callerHasConnectionLock: true); +#endif // !PROJECTK if (executeTask == null) { RunParser(); @@ -810,8 +893,11 @@ namespace System.Data.SqlClient { _SqlMetaData metadata = _sortedColumnMappings[destRowIndex]._metadata; int sourceOrdinal = _sortedColumnMappings[destRowIndex]._sourceColumnOrdinal; +#if !PROJECTK switch(_rowSourceType) { case ValueSourceType.IDataReader: + case ValueSourceType.DbDataReader: +#endif //PROJECTK // Handle data feeds (common for both DbDataReader and SqlDataReader) if (_currentRowMetadata[destRowIndex].IsDataFeed) { if (_DbDataReaderRowSource.IsDBNull(sourceOrdinal)) { @@ -889,7 +975,11 @@ namespace System.Data.SqlClient { else { isDataFeed = false; +#if !PROJECTK IDataReader rowSourceAsIDataReader = (IDataReader)_rowSource; +#else + var rowSourceAsIDataReader = _DbDataReaderRowSource; +#endif //PROJECTK // Back-compat with 4.0 and 4.5 - only use IsDbNull when streaming is enabled and only for non-SqlDataReader if ((_enableStreaming) && (_SqlDataReaderRowSource == null) && (rowSourceAsIDataReader.IsDBNull(sourceOrdinal))) { @@ -903,7 +993,8 @@ namespace System.Data.SqlClient { return columnValue; } } - + +#if !PROJECTK case ValueSourceType.DataTable: case ValueSourceType.RowArray: { Debug.Assert(_currentRow != null, "uninitialized _currentRow"); @@ -966,17 +1057,21 @@ namespace System.Data.SqlClient { throw ADP.NotSupported(); } } +#endif //PROJECTK } // unified method to read a row from the current rowsource // When _isAsyncBulkCopy == true (i.e. async copy): returns Task<bool> when IDataReader is a DbDataReader, Null for others. - // When _isAsyncBulkCopy == false (i.e. [....] copy): returns null. Uses ReadFromRowSource to get the boolean value. + // When _isAsyncBulkCopy == false (i.e. sync copy): returns null. Uses ReadFromRowSource to get the boolean value. // "more" -- should be used by the caller only when the return value is null. private Task ReadFromRowSourceAsync(CancellationToken cts) { - DbDataReader dbRowSource = _rowSource as DbDataReader; - if (_isAsyncBulkCopy && _rowSourceType == ValueSourceType.IDataReader && (_rowSource as DbDataReader) != null) { +#if !PROJECTK + if (_isAsyncBulkCopy && (_DbDataReaderRowSource != null)) { +#else + if (_isAsyncBulkCopy) { +#endif //PROJECTK //This will call ReadAsync for DbDataReader (for SqlDataReader it will be truely async read; for non-SqlDataReader it may block.) - return ((DbDataReader)_rowSource).ReadAsync(cts).ContinueWith((t) => { + return _DbDataReaderRowSource.ReadAsync(cts).ContinueWith((t) => { if (t.Status == TaskStatus.RanToCompletion) { _hasMoreRowToCopy = t.Result; } @@ -986,7 +1081,11 @@ namespace System.Data.SqlClient { else { //this will call Read for DataRows, DataTable and IDataReader (this includes all IDataReader except DbDataReader) _hasMoreRowToCopy = false; try { +#if !PROJECTK _hasMoreRowToCopy = ReadFromRowSource(); //Synchronous calls for DataRows and DataTable won't block. For IDataReader, it may block. +#else + _hasMoreRowToCopy = _DbDataReaderRowSource.Read(); +#endif //PROJECTK } catch(Exception ex) { if (_isAsyncBulkCopy) { @@ -1002,8 +1101,10 @@ namespace System.Data.SqlClient { } } +#if !PROJECTK private bool ReadFromRowSource() { switch(_rowSourceType) { + case ValueSourceType.DbDataReader: case ValueSourceType.IDataReader: return ((IDataReader)_rowSource).Read(); @@ -1031,6 +1132,7 @@ namespace System.Data.SqlClient { throw ADP.NotSupported(); } } +#endif //PROJECTK private SourceColumnMetadata GetColumnMetadata(int ordinal) { int sourceOrdinal = _sortedColumnMappings[ordinal]._sourceColumnOrdinal; @@ -1040,14 +1142,22 @@ namespace System.Data.SqlClient { ValueMethod method; bool isSqlType; bool isDataFeed; +#if !PROJECTK if (((_SqlDataReaderRowSource != null) || (_dataTableSource != null)) && ((metadata.metaType.NullableType == TdsEnums.SQLDECIMALN) || (metadata.metaType.NullableType == TdsEnums.SQLNUMERICN))) { +#else + if (((_SqlDataReaderRowSource != null)) && ((metadata.metaType.NullableType == TdsEnums.SQLDECIMALN) || (metadata.metaType.NullableType == TdsEnums.SQLNUMERICN))) { +#endif //PROJECTK isDataFeed = false; Type t; +#if !PROJECTK switch(_rowSourceType) { + case ValueSourceType.DbDataReader: case ValueSourceType.IDataReader: +#endif //PROJECTK t = _SqlDataReaderRowSource.GetFieldType(sourceOrdinal); - break; +#if !PROJECTK + break; case ValueSourceType.DataTable: case ValueSourceType.RowArray: t = _dataTableSource.Columns[sourceOrdinal].DataType; @@ -1057,6 +1167,7 @@ namespace System.Data.SqlClient { Debug.Assert(false, string.Format("Unknown value source: {0}", _rowSourceType)); break; } +#endif //PROJECTK if (typeof(SqlDecimal) == t || typeof(Decimal) == t) { isSqlType = true; @@ -1076,7 +1187,11 @@ namespace System.Data.SqlClient { } } // Check for data streams +#if !PROJECTK else if ((_enableStreaming) && (metadata.length == MAX_LENGTH) && (!_rowSourceIsSqlDataReaderSmi)) { +#else + else if ((_enableStreaming) && (metadata.length == MAX_LENGTH)) { +#endif //PROJECTK isSqlType = false; if (_SqlDataReaderRowSource != null) { @@ -1102,7 +1217,11 @@ namespace System.Data.SqlClient { method = ValueMethod.GetValue; } } +#if !PROJECTK else if (_DbDataReaderRowSource != null) { +#else + else { +#endif //PROJECTK if (metadata.type == SqlDbType.VarBinary) { isDataFeed = true; method = ValueMethod.DataFeedStream; @@ -1116,10 +1235,12 @@ namespace System.Data.SqlClient { method = ValueMethod.GetValue; } } +#if !PROJECTK else { isDataFeed = false; method = ValueMethod.GetValue; } +#endif //PROJECTK } else { isSqlType = false; @@ -1136,9 +1257,11 @@ namespace System.Data.SqlClient { if(null == _connection) { throw ADP.ConnectionRequired(method); } +#if !PROJECTK if (_connection.IsContextConnection) { throw SQL.NotAvailableOnContextConnection(); } +#endif //PROJECTK if(_ownConnection && _connection.State != ConnectionState.Open) { _connection.Open(); @@ -1177,7 +1300,11 @@ namespace System.Data.SqlClient { SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection(); internalConnection.ThreadHasParserLockForClose = true; try { +#if !PROJECTK _parser.RunReliably(RunBehavior.UntilDone, null, null, bulkCopyHandler, _stateObj); +#else + _parser.Run(RunBehavior.UntilDone, null, null, bulkCopyHandler, _stateObj); +#endif //PROJECTK } finally { internalConnection.ThreadHasParserLockForClose = false; @@ -1285,6 +1412,22 @@ namespace System.Data.SqlClient { MetaType type = metadata.metaType; bool typeChanged = false; + + // If the column is encrypted then we are going to transparently encrypt this column + // (based on connection string setting)- Use the metaType for the underlying + // value (unencrypted value) for conversion/casting purposes (below). + // Note - this flag is set if connection string options has TCE turned on + byte scale = metadata.scale; + byte precision = metadata.precision; + int length = metadata.length; + if (metadata.isEncrypted) { + Debug.Assert (_parser.ShouldEncryptValuesForBulkCopy()); + type = metadata.baseTI.metaType; + scale = metadata.baseTI.scale; + precision = metadata.baseTI.precision; + length = metadata.baseTI.length; + } + try { MetaType mt; switch(type.NullableType) { @@ -1294,15 +1437,15 @@ namespace System.Data.SqlClient { value = SqlParameter.CoerceValue(value, mt, out coercedToDataFeed, out typeChanged, false); // Convert Source Decimal Percision and Scale to Destination Percision and Scale - // Fix Bug: 385971 sql decimal data could get corrupted on insert if the scale of - // the source and destination weren't the same. The BCP protocal, specifies the - // scale of the incoming data in the insert statement, we just tell the server we - // are inserting the same scale back. This then created a bug inside the BCP opperation - // if the scales didn't match. The fix is to do the same thing that SQL Paramater does, - // and adjust the scale before writing. In Orcas is scale adjustment should be removed from - // SqlParamater and SqlBulkCopy and Isoloated inside SqlParamater.CoerceValue, but becouse of - // where we are in the cycle, the changes must be kept at minimum, so I'm just bringing the - // code over to SqlBulkCopy. + // Fix + + + + + + + + SqlDecimal sqlValue; if ((isSqlType) && (!typeChanged)) { @@ -1312,8 +1455,8 @@ namespace System.Data.SqlClient { sqlValue = new SqlDecimal((Decimal)value); } - if (sqlValue.Scale != metadata.scale) { - sqlValue = TdsParser.AdjustSqlDecimalScale(sqlValue, metadata.scale); + if (sqlValue.Scale != scale) { + sqlValue = TdsParser.AdjustSqlDecimalScale(sqlValue, scale); } // Perf: It is more effecient to write a SqlDecimal than a decimal since we need to break it into its 'bits' when writing @@ -1321,7 +1464,7 @@ namespace System.Data.SqlClient { isSqlType = true; typeChanged = false; // Setting this to false as SqlParameter.CoerceValue will only set it to true when coverting to a CLR type - if (sqlValue.Precision > metadata.precision) { + if (sqlValue.Precision > precision) { throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, ADP.ParameterValueOutOfRange(sqlValue)); } break; @@ -1357,7 +1500,7 @@ namespace System.Data.SqlClient { value = SqlParameter.CoerceValue(value, mt, out coercedToDataFeed, out typeChanged, false); if (!coercedToDataFeed) { // We do not need to test for TextDataFeed as it is only assigned to (N)VARCHAR(MAX) int len = ((isSqlType) && (!typeChanged)) ? ((SqlString)value).Value.Length : ((string)value).Length; - if (len > metadata.length / 2) { + if (len > length / 2) { throw SQL.BulkLoadStringTooLong(); } } @@ -1367,6 +1510,7 @@ namespace System.Data.SqlClient { typeChanged = true; break; case TdsEnums.SQLUDT: +#if !PROJECTK // UDTs are sent as varbinary so we need to get the raw bytes // unlike other types the parser does not like SQLUDT in form of SqlType // so we cast to a CLR type. @@ -1378,6 +1522,9 @@ namespace System.Data.SqlClient { typeChanged = true; } break; +#else // PROJECTK + throw ADP.DbTypeNotSupported("UDT"); +#endif // !PROJECTK case TdsEnums.SQLXMLTYPE: // Could be either string, SqlCachedBuffer, XmlReader or XmlDataFeed Debug.Assert((value is XmlReader) || (value is SqlCachedBuffer) || (value is string) || (value is SqlString) || (value is XmlDataFeed), "Invalid value type of Xml datatype"); @@ -1390,7 +1537,7 @@ namespace System.Data.SqlClient { default: Debug.Assert(false, "Unknown TdsType!" + type.NullableType.ToString("x2", (IFormatProvider)null)); - throw SQL.BulkLoadCannotConvertValue(value.GetType(), metadata.metaType, null); + throw SQL.BulkLoadCannotConvertValue(value.GetType(), type, null); } if (typeChanged) { @@ -1404,10 +1551,47 @@ namespace System.Data.SqlClient { if(!ADP.IsCatchableExceptionType(e)) { throw; } - throw SQL.BulkLoadCannotConvertValue(value.GetType(), metadata.metaType, e); + throw SQL.BulkLoadCannotConvertValue(value.GetType(), type, e); } } + public void WriteToServer(DbDataReader reader) { +#if !PROJECTK + SqlConnection.ExecutePermission.Demand(); +#endif // !PROJECTK + + if (reader == null) { + throw new ArgumentNullException("reader"); + } + + if (_isBulkCopyingInProgress) { + throw SQL.BulkLoadPendingOperation(); + } + + SqlStatistics statistics = Statistics; + try { + statistics = SqlStatistics.StartTimer(Statistics); +#if !PROJECTK + _rowSource = reader; +#endif //PROJECTK + _DbDataReaderRowSource = reader; + _SqlDataReaderRowSource = reader as SqlDataReader; +#if !PROJECTK + if (_SqlDataReaderRowSource != null) { + _rowSourceIsSqlDataReaderSmi = _SqlDataReaderRowSource is SqlDataReaderSmi; + } + _dataTableSource = null; + _rowSourceType = ValueSourceType.DbDataReader; +#endif //PROJECTK + _isAsyncBulkCopy = false; + WriteRowSourceToServerAsync(reader.FieldCount, CancellationToken.None); //It returns null since _isAsyncBulkCopy = false; + } + finally { + SqlStatistics.StopTimer(statistics); + } + } + +#if !PROJECTK public void WriteToServer(IDataReader reader) { SqlConnection.ExecutePermission.Demand(); @@ -1557,7 +1741,47 @@ namespace System.Data.SqlClient { } return resultTask; } +#endif //PROJECTK + + public Task WriteToServerAsync(DbDataReader reader) { + return WriteToServerAsync(reader, CancellationToken.None); + } + public Task WriteToServerAsync(DbDataReader reader, CancellationToken cancellationToken) { + Task resultTask = null; +#if !PROJECTK + SqlConnection.ExecutePermission.Demand(); + +#endif // !PROJECTK + if (reader == null) { + throw new ArgumentNullException("reader"); + } + if (_isBulkCopyingInProgress) { + throw SQL.BulkLoadPendingOperation(); + } + + SqlStatistics statistics = Statistics; + try { + statistics = SqlStatistics.StartTimer(Statistics); +#if !PROJECTK + _rowSource = reader; +#endif //PROJECTK + _SqlDataReaderRowSource = reader as SqlDataReader; + _DbDataReaderRowSource = reader; +#if !PROJECTK + _dataTableSource = null; + _rowSourceType = ValueSourceType.DbDataReader; +#endif //PROJECTK + _isAsyncBulkCopy = true; + resultTask = WriteRowSourceToServerAsync(reader.FieldCount, cancellationToken); //It returns Task since _isAsyncBulkCopy = true; + } + finally { + SqlStatistics.StopTimer(statistics); + } + return resultTask; + } + +#if !PROJECTK public Task WriteToServerAsync(IDataReader reader) { return WriteToServerAsync(reader, CancellationToken.None); } @@ -1628,10 +1852,15 @@ namespace System.Data.SqlClient { } return resultTask; } +#endif //PROJECTK // Writes row source. // private Task WriteRowSourceToServerAsync(int columnCount, CancellationToken ctoken) { +#if PROJECTK + // Only DbDataReaders are supported in ProjectK\CoreCLR + Debug.Assert(_DbDataReaderRowSource != null, "No DbDataReader was provided"); +#endif //PROJECTK Task reconnectTask = _connection._currentReconnectionTask; if (reconnectTask != null && !reconnectTask.IsCompleted) { if (this._isAsyncBulkCopy) { @@ -1662,19 +1891,23 @@ namespace System.Data.SqlClient { _parserLock = internalConnection._parserLock; _parserLock.Wait(canReleaseFromAnyThread: _isAsyncBulkCopy); +#if !PROJECTK TdsParser bestEffortCleanupTarget = null; RuntimeHelpers.PrepareConstrainedRegions(); +#endif // !PROJECTK try { +#if !PROJECTK #if DEBUG TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); RuntimeHelpers.PrepareConstrainedRegions(); try { tdsReliabilitySection.Start(); -#else +#else // !DEBUG { #endif //DEBUG bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection); +#endif // !PROJECTK WriteRowSourceToServerCommon(columnCount); //this is common in both sync and async Task resultTask = WriteToServerInternalAsync(ctoken); // resultTask is null for sync, but Task for async. if (resultTask != null) { @@ -1693,6 +1926,7 @@ namespace System.Data.SqlClient { }, TaskScheduler.Default).Unwrap(); } return null; +#if !PROJECTK } #if DEBUG @@ -1700,9 +1934,11 @@ namespace System.Data.SqlClient { tdsReliabilitySection.Stop(); } #endif //DEBUG +#endif // !PROJECTK } +#if !PROJECTK catch (System.OutOfMemoryException e) { _connection.Abort(e); throw; @@ -1716,6 +1952,7 @@ namespace System.Data.SqlClient { SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); throw; } +#endif // !PROJECTK finally { _columnMappings.ReadOnly = false; if (finishedSynchronously) { @@ -1767,6 +2004,7 @@ namespace System.Data.SqlClient { if (bulkCopyColumn._internalSourceColumnOrdinal == -1) { string unquotedColumnName = UnquotedName(bulkCopyColumn.SourceColumn); +#if !PROJECTK switch (this._rowSourceType) { case ValueSourceType.DataTable: index = ((DataTable)_rowSource).Columns.IndexOf(unquotedColumnName); @@ -1774,15 +2012,23 @@ namespace System.Data.SqlClient { case ValueSourceType.RowArray: index = ((DataRow[])_rowSource)[0].Table.Columns.IndexOf(unquotedColumnName); break; + case ValueSourceType.DbDataReader: case ValueSourceType.IDataReader: +#endif //PROJECTK try { +#if !PROJECTK index = ((IDataRecord)this._rowSource).GetOrdinal(unquotedColumnName); +#else + index = _DbDataReaderRowSource.GetOrdinal(unquotedColumnName); +#endif //PROJECTK } catch (IndexOutOfRangeException e) { throw (SQL.BulkLoadNonMatchingColumnName(unquotedColumnName, e)); } +#if !PROJECTK break; } +#endif //PROJECTK if (index == -1) { throw (SQL.BulkLoadNonMatchingColumnName(unquotedColumnName)); } @@ -1830,7 +2076,7 @@ namespace System.Data.SqlClient { // Reads a cell and then writes it. // Read may block at this moment since there is no getValueAsync or DownStream async at this moment. // When _isAsyncBulkCopy == true: Write will return Task (when async method runs asynchronously) or Null (when async call actually ran synchronously) for performance. - // When _isAsyncBulkCopy == false: Writes are purely [....]. This method reutrn null at the end. + // When _isAsyncBulkCopy == false: Writes are purely sync. This method reutrn null at the end. // private Task ReadWriteColumnValueAsync(int col) { bool isSqlType; @@ -1841,6 +2087,14 @@ namespace System.Data.SqlClient { _SqlMetaData metadata = _sortedColumnMappings[col]._metadata; if (!isDataFeed) { value = ConvertValue(value, metadata, isNull, ref isSqlType, out isDataFeed); + + // If column encryption is requested via connection string option, perform encryption here + if (!isNull && // if value is not NULL + metadata.isEncrypted) { // If we are transparently encrypting + Debug.Assert (_parser.ShouldEncryptValuesForBulkCopy()); + value = _parser.EncryptColumnValue(value, metadata, metadata.column, _stateObj, isDataFeed, isSqlType); + isSqlType = false; // Its not a sql type anymore + } } //write part @@ -1850,6 +2104,8 @@ namespace System.Data.SqlClient { writeTask = _parser.WriteBulkCopyValue(value, metadata, _stateObj, isSqlType, isDataFeed, isNull); //returns Task/Null } else { + // Target type shouldn't be encrypted + Debug.Assert (!metadata.isEncrypted, "Can't encrypt SQL Variant type"); SqlBuffer.StorageType variantInternalType = SqlBuffer.StorageType.Empty; if ((_SqlDataReaderRowSource != null) && (_connection.IsKatmaiOrNewer)) { variantInternalType = _SqlDataReaderRowSource.GetVariantInternalStorageType(_sortedColumnMappings[col]._sourceColumnOrdinal); @@ -1946,7 +2202,9 @@ namespace System.Data.SqlClient { // it's also the user's chance to cause an exception ... _stateObj.BcpLock = true; abortOperation = FireRowsCopiedEvent(_rowsCopied); +#if !PROJECTK Bid.Trace("<sc.SqlBulkCopy.WriteToServerInternal|INFO> \n"); +#endif // !PROJECTK // just in case some pathological person closes the target connection ... if (ConnectionState.Open != _connection.State) { @@ -2129,7 +2387,7 @@ namespace System.Data.SqlClient { AsyncHelper.ContinueTask(commandTask, source, () => { Task continuedTask = CopyBatchesAsyncContinued(internalResults, updateBulkCommandText, cts, source); if (continuedTask == null) { - // Continuation finished [....], recall into CopyBatchesAsync to continue + // Continuation finished sync, recall into CopyBatchesAsync to continue CopyBatchesAsync(internalResults, updateBulkCommandText, cts, source); } }, _connection.GetOpenTdsConnection()); @@ -2163,6 +2421,12 @@ namespace System.Data.SqlClient { Debug.Assert(source == null || !source.Task.IsCompleted, "Called into CopyBatchesAsync with a completed task!"); try { WriteMetaData(internalResults); + + // Load encryption keys now (if needed) + _parser.LoadColumnEncryptionKeys( + internalResults[MetaDataResultId].MetaData, + _connection.DataSource); + Task task = CopyRowsAsync(0, _savedBatchSize, cts); //this is copying 1 batch of rows and setting _hasMoreRowToCopy = true/false. //post->after every batch @@ -2174,7 +2438,7 @@ namespace System.Data.SqlClient { AsyncHelper.ContinueTask(task, source, () => { Task continuedTask = CopyBatchesAsyncContinuedOnSuccess(internalResults, updateBulkCommandText, cts, source); if (continuedTask == null) { - // Continuation finished [....], recall into CopyBatchesAsync to continue + // Continuation finished sync, recall into CopyBatchesAsync to continue CopyBatchesAsync(internalResults, updateBulkCommandText, cts, source); } }, _connection.GetOpenTdsConnection(), _ => CopyBatchesAsyncContinuedOnError(cleanupParser: false), () => CopyBatchesAsyncContinuedOnError(cleanupParser: true)); @@ -2246,6 +2510,7 @@ namespace System.Data.SqlClient { // Takes care of cleaning up the parser, stateObj and transaction when CopyBatchesAsync fails private void CopyBatchesAsyncContinuedOnError(bool cleanupParser) { SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection(); +#if !PROJECTK RuntimeHelpers.PrepareConstrainedRegions(); try { #if DEBUG @@ -2254,6 +2519,7 @@ namespace System.Data.SqlClient { try { tdsReliabilitySection.Start(); #endif //DEBUG +#endif // !PROJECTK if ((cleanupParser) && (_parser != null) && (_stateObj != null)) { _parser._asyncWrite = false; Task task = _parser.WriteBulkCopyDone(_stateObj); @@ -2264,6 +2530,7 @@ namespace System.Data.SqlClient { if (_stateObj != null) { CleanUpStateObjectOnError(); } +#if !PROJECTK #if DEBUG } finally { @@ -2283,6 +2550,7 @@ namespace System.Data.SqlClient { internalConnection.DoomThisConnection(); throw; } +#endif // !PROJECTK AbortTransaction(); } @@ -2313,7 +2581,7 @@ namespace System.Data.SqlClient { // The continuation part of WriteToServerInternalRest. Executes when the initial query task is completed. (see, WriteToServerInternalRest). // It carries on the source which is passed from the WriteToServerInternalRest and performs SetResult when the entire copy is done. - // The carried on source may be null in case of [....] copy. So no need to SetResult at that time. + // The carried on source may be null in case of Sync copy. So no need to SetResult at that time. // It launches the copy operation. // private void WriteToServerInternalRestContinuedAsync(BulkCopySimpleResultSet internalResults, CancellationToken cts, TaskCompletionSource<object> source) { @@ -2410,7 +2678,7 @@ namespace System.Data.SqlClient { // Rest of the WriteToServerInternalAsync method. // It carries on the source from its caller WriteToServerInternal. - // source is null in case of [....] bcp. But valid in case of Async bcp. + // source is null in case of Sync bcp. But valid in case of Async bcp. // It calls the WriteToServerInternalRestContinuedAsync as a continuation of the initial query task. // private void WriteToServerInternalRestAsync(CancellationToken cts, TaskCompletionSource<object> source) { @@ -2487,7 +2755,9 @@ namespace System.Data.SqlClient { try { _stateObj = _parser.GetSession(this); _stateObj._bulkCopyOpperationInProgress = true; +#if !PROJECTK _stateObj.StartSession(ObjectID); +#endif //PROJECTK } finally { internalConnection.ThreadHasParserLockForClose = false; @@ -2518,7 +2788,7 @@ namespace System.Data.SqlClient { } } - // This returns Task for Async, Null for [....] + // This returns Task for Async, Null for Sync // private Task WriteToServerInternalAsync(CancellationToken ctoken) { TaskCompletionSource<object> source = null; diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlBulkCopyColumnMapping.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlBulkCopyColumnMapping.cs index d4166c2d911..16b85f3a23d 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlBulkCopyColumnMapping.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlBulkCopyColumnMapping.cs @@ -2,8 +2,8 @@ // <copyright file="SqlBulkCopyColumnMapping.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ // Todo: rename the file diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlBulkCopyColumnMappingCollection.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlBulkCopyColumnMappingCollection.cs index ac37a1d924c..d11c23c8c61 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlBulkCopyColumnMappingCollection.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlBulkCopyColumnMappingCollection.cs @@ -2,8 +2,8 @@ // <copyright file="SqlBulkCopyMappingCollection.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ // todo: rename the file diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlBulkCopyOptions.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlBulkCopyOptions.cs index 9c7d1ed1710..62baf0f812f 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlBulkCopyOptions.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlBulkCopyOptions.cs @@ -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 { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlCachedBuffer.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlCachedBuffer.cs index 5e5e2c22da5..ded56bee758 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlCachedBuffer.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlCachedBuffer.cs @@ -2,8 +2,8 @@ // <copyright file="SqlCachedBuffer.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientEncryptionAlgorithm.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientEncryptionAlgorithm.cs new file mode 100644 index 00000000000..d8cc2dde566 --- /dev/null +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientEncryptionAlgorithm.cs @@ -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); + } +} diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientEncryptionAlgorithmFactory.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientEncryptionAlgorithmFactory.cs new file mode 100644 index 00000000000..bdd0c783365 --- /dev/null +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientEncryptionAlgorithmFactory.cs @@ -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); + } +} diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientEncryptionAlgorithmFactoryList.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientEncryptionAlgorithmFactoryList.cs new file mode 100644 index 00000000000..e012c24a03c --- /dev/null +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientEncryptionAlgorithmFactoryList.cs @@ -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); + } + } +} diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientEncryptionType.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientEncryptionType.cs new file mode 100644 index 00000000000..3ba5a31986a --- /dev/null +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientEncryptionType.cs @@ -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 + } +} diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientFactory.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientFactory.cs index b42193a8046..4763dd80d45 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientFactory.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientFactory.cs @@ -2,8 +2,8 @@ // <copyright file="SqlClientFactory.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ using System; diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientMetaDataCollectionNames.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientMetaDataCollectionNames.cs index 46d0f0da589..01530e81bd4 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientMetaDataCollectionNames.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientMetaDataCollectionNames.cs @@ -2,8 +2,8 @@ // <copyright file="SqlClientMetaDataCollectionNames.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient{ diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientPermission.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientPermission.cs index d668f116979..695b9adad72 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientPermission.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientPermission.cs @@ -2,8 +2,8 @@ // <copyright file="SqlClientPermission.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientSymmetricKey.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientSymmetricKey.cs new file mode 100644 index 00000000000..112108ae14b --- /dev/null +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientSymmetricKey.cs @@ -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; + } + } +} diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientWrapperSmiStream.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientWrapperSmiStream.cs index e4e984da90b..32ef810a406 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientWrapperSmiStream.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientWrapperSmiStream.cs @@ -2,8 +2,8 @@ // <copyright file="SqlClientWrapperSmiStream.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace Microsoft.SqlServer.Server { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientWrapperSmiStreamChars.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientWrapperSmiStreamChars.cs index f5ae0f3df6c..1818a2e0c1d 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientWrapperSmiStreamChars.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlClientWrapperSmiStreamChars.cs @@ -2,8 +2,8 @@ // <copyright file="SqlClientWrapperSmiStream.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace Microsoft.SqlServer.Server { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs new file mode 100644 index 00000000000..44357a9b363 --- /dev/null +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs @@ -0,0 +1,555 @@ +//------------------------------------------------------------------------------ +// <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.Text; + using System.Data.Common; + using System.Diagnostics; + using System.Globalization; + using System.Security; + using System.Security.Cryptography; + using System.Security.Cryptography.X509Certificates; + + /// <summary> + /// Certificate Key Store Provider class + /// </summary> + sealed public class SqlColumnEncryptionCertificateStoreProvider : SqlColumnEncryptionKeyStoreProvider + { + // Constants + // + // Assumption: Certificate Locations (LocalMachine & CurrentUser), Certificate Store name "My" + // Certificate provider name (CertificateStore) dont need to be localized. + + /// <summary> + /// RSA_OAEP is the only algorithm supported for encrypting/decrypting column encryption keys. + /// </summary> + internal const string RSAEncryptionAlgorithmWithOAEP = @"RSA_OAEP"; + + /// <summary> + /// Name for the certificate key store provider. + /// </summary> + internal const string ProviderName = @"MSSQL_CERTIFICATE_STORE"; + + /// <summary> + /// LocalMachine certificate store location. Valid certificate locations are LocalMachine and CurrentUser. + /// </summary> + private const string _certLocationLocalMachine = @"LocalMachine"; + + /// <summary> + /// CurrentUser certificate store location. Valid certificate locations are LocalMachine and CurrentUser. + /// </summary> + private const string _certLocationCurrentUser = @"CurrentUser"; + + /// <summary> + /// Valid certificate store + /// </summary> + private const string _myCertificateStore = @"My"; + + /// <summary> + /// Certificate path format. This is a custom format. + /// </summary> + private const string _certificatePathFormat = @"[LocalMachine|CurrentUser]/My/[Thumbprint]"; + + /// <summary> + /// Hashig 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 a certificate specified by the key path + /// and decrypts an encrypted CEK with RSA encryption algorithm. + /// </summary> + /// <param name="masterKeyPath">Complete path of a certificate</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 + ValidateNonEmptyCertificatePath(masterKeyPath, isSystemOp: true); + + if (null == encryptedColumnEncryptionKey) + { + throw SQL.NullEncryptedColumnEncryptionKey(); + } + else if (0 == encryptedColumnEncryptionKey.Length) + { + throw SQL.EmptyEncryptedColumnEncryptionKey(); + } + + // Validate encryptionAlgorithm + ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true); + + // Validate key path length + ValidateCertificatePathLength(masterKeyPath, isSystemOp: true); + + // Parse the path and get the X509 cert + X509Certificate2 certificate = GetCertificateByPath(masterKeyPath, isSystemOp: true); + int keySizeInBytes = certificate.PublicKey.Key.KeySize / 8; + + // Validate and decrypt the EncryptedColumnEncryptionKey + // Format is + // version + keyPathLength + ciphertextLength + keyPath + ciphertext + 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; + Int16 keyPathLength = BitConverter.ToInt16(encryptedColumnEncryptionKey, currentIndex); + currentIndex += sizeof(Int16); + + // Get ciphertext length + int cipherTextLength = BitConverter.ToInt16(encryptedColumnEncryptionKey, currentIndex); + currentIndex += sizeof(Int16); + + // Skip KeyPath + // KeyPath exists only for troubleshooting purposes and doesnt need validation. + currentIndex += keyPathLength; + + // validate the ciphertext length + if (cipherTextLength != keySizeInBytes) + { + throw SQL.InvalidCiphertextLengthInEncryptedCEK(cipherTextLength, keySizeInBytes, masterKeyPath); + } + + // Validate the signature length + int signatureLength = encryptedColumnEncryptionKey.Length - currentIndex - cipherTextLength; + if (signatureLength != keySizeInBytes) + { + throw SQL.InvalidSignatureInEncryptedCEK(signatureLength, keySizeInBytes, masterKeyPath); + } + + // Get ciphertext + byte[] cipherText = new byte[cipherTextLength]; + Buffer.BlockCopy(encryptedColumnEncryptionKey, currentIndex, cipherText, 0, cipherTextLength); + 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, certificate)) + { + throw SQL.InvalidCertificateSignature(masterKeyPath); + } + + // Decrypt the CEK + return RSADecrypt(cipherText, certificate); + } + + /// <summary> + /// This function uses a certificate specified by the key path + /// and encrypts CEK with RSA encryption algorithm. + /// </summary> + /// <param name="keyPath">Complete path of a certificate</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 + ValidateNonEmptyCertificatePath(masterKeyPath, isSystemOp: false); + if (null == columnEncryptionKey) + { + throw SQL.NullColumnEncryptionKey(); + } + else if (0 == columnEncryptionKey.Length) + { + throw SQL.EmptyColumnEncryptionKey(); + } + + // Validate encryptionAlgorithm + ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: false); + + // Validate masterKeyPath Length + ValidateCertificatePathLength(masterKeyPath, isSystemOp: false); + + // Parse the certificate path and get the X509 cert + X509Certificate2 certificate = GetCertificateByPath(masterKeyPath, isSystemOp: false); + int keySizeInBytes = certificate.PublicKey.Key.KeySize / 8; + + // 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(columnEncryptionKey, certificate); + 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, certificate); + Debug.Assert(signedHash.Length == keySizeInBytes, @"signed hash length does not match the RSA key size"); + Debug.Assert(RSAVerifySignature(hash, signedHash, certificate), @"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> + 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> + /// Certificate path length has to fit in two bytes, so check its value against Int16.MaxValue + /// </summary> + /// <param name="masterKeyPath"></param> + /// <param name="isSystemOp"></param> + private void ValidateCertificatePathLength(string masterKeyPath, bool isSystemOp) + { + if (masterKeyPath.Length >= Int16.MaxValue) + { + throw SQL.LargeCertificatePathLength(masterKeyPath.Length, Int16.MaxValue, isSystemOp); + } + } + + /// <summary> + /// Gets a string array containing Valid certificate locations. + /// </summary> + private string[] GetValidCertificateLocations() + { + return new string[2] {_certLocationLocalMachine, _certLocationCurrentUser}; + } + + /// <summary> + /// Checks if the certificate path is Empty or Null (and raises exception if they are). + /// </summary> + private void ValidateNonEmptyCertificatePath(string masterKeyPath, bool isSystemOp) + { + if (string.IsNullOrWhiteSpace(masterKeyPath)) + { + if (null == masterKeyPath) + { + throw SQL.NullCertificatePath(GetValidCertificateLocations(), isSystemOp); + } + else + { + throw SQL.InvalidCertificatePath(masterKeyPath, GetValidCertificateLocations(), isSystemOp); + } + } + } + + /// <summary> + /// Parses the given certificate path, searches in certificate store and returns a matching certificate + /// </summary> + /// <param name="keyPath"> + /// Certificate key path. Format of the path is [LocalMachine|CurrentUser]/[storename]/thumbprint + /// </param> + /// <returns>Returns the certificate identified by the certificate path</returns> + private X509Certificate2 GetCertificateByPath(string keyPath, bool isSystemOp) + { + Debug.Assert(!string.IsNullOrEmpty(keyPath)); + + // Assign default values for omitted fields + StoreLocation storeLocation = StoreLocation.LocalMachine; // Default to Local Machine + StoreName storeName = StoreName.My; + string[] certParts = keyPath.Split('/'); + + // Validate certificate path + // Certificate path should only contain 3 parts (Certificate Location, Certificate Store Name and Thumbprint) + if (certParts.Length > 3) + { + throw SQL.InvalidCertificatePath(keyPath, GetValidCertificateLocations(), isSystemOp); + } + + // Extract the store location where the cert is stored + if (certParts.Length > 2) + { + if (string.Equals(certParts[0], _certLocationLocalMachine, StringComparison.OrdinalIgnoreCase) == true) + { + storeLocation = StoreLocation.LocalMachine; + } + else if (string.Equals(certParts[0], _certLocationCurrentUser, StringComparison.OrdinalIgnoreCase) == true) + { + storeLocation = StoreLocation.CurrentUser; + } + else + { + // throw an invalid certificate location exception + throw SQL.InvalidCertificateLocation(certParts[0], keyPath, GetValidCertificateLocations(), isSystemOp); + } + } + + // Parse the certificate store name + if (certParts.Length > 1) + { + if (string.Equals(certParts[certParts.Length - 2], _myCertificateStore, StringComparison.OrdinalIgnoreCase) == true) + { + storeName = StoreName.My; + } + else + { + // We only support storing them in My certificate store + throw SQL.InvalidCertificateStore(certParts[certParts.Length - 2], keyPath, _myCertificateStore, isSystemOp); + } + } + + // Get thumpbrint + string thumbprint = certParts[certParts.Length - 1]; + if (string.IsNullOrEmpty(thumbprint)) + { + // An empty thumbprint specified + throw SQL.EmptyCertificateThumbprint(keyPath, isSystemOp); + } + + // Find the certificate and return + return GetCertificate(storeLocation, storeName, keyPath, thumbprint, isSystemOp); + } + + /// <summary> + /// Searches for a certificate in certificate store and returns the matching certificate + /// </summary> + /// <param name="storeLocation">Store Location: This can be one of LocalMachine or UserName</param> + /// <param name="storeName">Store Location: Currently this can only be My store.</param> + /// <param name="thumbprint">Certificate thumbprint</param> + /// <returns>Matching certificate</returns> + private X509Certificate2 GetCertificate(StoreLocation storeLocation, StoreName storeName, string masterKeyPath, string thumbprint, bool isSystemOp) + { + // Open specified certificate store + X509Store certificateStore = null; + + try + { + certificateStore = new X509Store(storeName, storeLocation); + certificateStore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); + + // Search for the specified certificate + X509Certificate2Collection matchingCertificates = + certificateStore.Certificates.Find(X509FindType.FindByThumbprint, + thumbprint, + false); + + // Throw an exception if a cert with the specified thumbprint is not found + if (matchingCertificates == null || matchingCertificates.Count == 0) + { + throw SQL.CertificateNotFound(thumbprint, storeName.ToString(), storeLocation.ToString(), isSystemOp); + } + + X509Certificate2 certificate = matchingCertificates[0]; + if (!certificate.HasPrivateKey) + { + // ensure the certificate has private key + throw SQL.CertificateWithNoPrivateKey(masterKeyPath, isSystemOp); + } + + // return the matching certificate + return certificate; + } + finally + { + // Close the certificate store + if (certificateStore != null) + { + certificateStore.Close(); + } + } + } + + /// <summary> + /// Encrypt the text using specified certificate. + /// </summary> + /// <param name="plaintext">Text to encrypt.</param> + /// <param name="certificate">Certificate object.</param> + /// <param name="masterKeyPath">Master key path that was used.</param> + /// <returns>Returns an encrypted blob or throws an exception if there are any errors.</returns> + private byte[] RSAEncrypt(byte[] plainText, X509Certificate2 certificate) + { + Debug.Assert(plainText != null); + Debug.Assert(certificate != null); + Debug.Assert(certificate.HasPrivateKey, "Attempting to encrypt with cert without privatekey"); + + RSACryptoServiceProvider rscp = (RSACryptoServiceProvider)certificate.PublicKey.Key; + return rscp.Encrypt(plainText, fOAEP: true); + } + + /// <summary> + /// Encrypt the text using specified certificate. + /// </summary> + /// <param name="plaintext">Text to decrypt.</param> + /// <param name="certificate">Certificate object.</param> + /// <param name="masterKeyPath">Master key path that was used.</param> + private byte[] RSADecrypt(byte[] cipherText, X509Certificate2 certificate) + { + Debug.Assert((cipherText != null) && (cipherText.Length != 0)); + Debug.Assert(certificate != null); + Debug.Assert(certificate.HasPrivateKey, "Attempting to decrypt with cert without privatekey"); + + RSACryptoServiceProvider rscp = (RSACryptoServiceProvider)certificate.PrivateKey; + return rscp.Decrypt(cipherText, fOAEP: true); + } + + /// <summary> + /// Generates signature based on RSA PKCS#v1.5 scheme using a specified certificate. + /// </summary> + /// <param name="dataToSign">Text to sign.</param> + /// <param name="certificate">Certificate object.</param> + /// <returns>Signature</returns> + private byte[] RSASignHashedData(byte[] dataToSign, X509Certificate2 certificate) + { + Debug.Assert((dataToSign != null) && (dataToSign.Length != 0)); + Debug.Assert(certificate != null); + Debug.Assert(certificate.HasPrivateKey, "Attempting to sign with cert without privatekey"); + + // Prepare RSACryptoServiceProvider from certificate's private key + RSACryptoServiceProvider rscp = GetCSPFromCertificatePrivateKey(certificate); + + // Prepare RSAPKCS1SignatureFormatter for signing the passed in hash + RSAPKCS1SignatureFormatter rsaFormatter = new RSAPKCS1SignatureFormatter(rscp); + + //Set the hash algorithm to SHA256. + rsaFormatter.SetHashAlgorithm(_hashingAlgorithm); + + //Create a signature for HashValue and return it. + return rsaFormatter.CreateSignature(dataToSign); + } + + /// <summary> + /// Verifies the given RSA PKCSv1.5 signature. + /// </summary> + /// <param name="dataToVerify"></param> + /// <param name="signature"></param> + /// <param name="certificate"></param> + /// <returns>true if signature is valid, false if it is not valid</returns> + private bool RSAVerifySignature(byte[] dataToVerify, byte[] signature, X509Certificate2 certificate) + { + Debug.Assert((dataToVerify != null) && (dataToVerify.Length != 0)); + Debug.Assert((signature != null) && (signature.Length != 0)); + Debug.Assert(certificate != null); + Debug.Assert(certificate.HasPrivateKey, "Attempting to sign with cert without privatekey"); + + // Prepare RSACryptoServiceProvider from certificate's private key + RSACryptoServiceProvider rscp = GetCSPFromCertificatePrivateKey(certificate); + + // Prepare RSAPKCS1SignatureFormatter for signing the passed in hash + RSAPKCS1SignatureDeformatter rsaDeFormatter = new RSAPKCS1SignatureDeformatter(rscp); + + //Set the hash algorithm to SHA256. + rsaDeFormatter.SetHashAlgorithm(_hashingAlgorithm); + + //Create a signature for HashValue and return it. + return rsaDeFormatter.VerifySignature(dataToVerify, signature); + } + + /// <summary> + /// Prepares RSACryptoServiceProvider from a given certificate's private key + /// </summary> + /// <param name="certificate"></param> + /// <returns></returns> + private RSACryptoServiceProvider GetCSPFromCertificatePrivateKey(X509Certificate2 certificate) + { + const int rsaAesProviderType = 24; + + CspParameters privateKeyParams = new CspParameters(); + privateKeyParams = new CspParameters(); + privateKeyParams.KeyContainerName = ((RSACryptoServiceProvider)certificate.PrivateKey).CspKeyContainerInfo.KeyContainerName; + privateKeyParams.ProviderType = rsaAesProviderType /*PROV_RSA_AES*/; + privateKeyParams.KeyNumber = (int)((RSACryptoServiceProvider)certificate.PrivateKey).CspKeyContainerInfo.KeyNumber; + + // For CurrentUser store, use UseExistingKey + // For LocalMachine store, use UseMachineKeyStore + // CspKeyContainerInfo.MachineKeyStore already contains the appropriate information so just use it. + if (((RSACryptoServiceProvider)certificate.PrivateKey).CspKeyContainerInfo.MachineKeyStore) + { + privateKeyParams.Flags = CspProviderFlags.UseMachineKeyStore; + } + else + { + privateKeyParams.Flags = CspProviderFlags.UseExistingKey; + } + + return new RSACryptoServiceProvider(privateKeyParams); + } + } +} diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlColumnEncryptionKeyStoreProvider.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlColumnEncryptionKeyStoreProvider.cs new file mode 100644 index 00000000000..afcf970bc46 --- /dev/null +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlColumnEncryptionKeyStoreProvider.cs @@ -0,0 +1,39 @@ +//------------------------------------------------------------------------------ +// <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 column encryption Key Store providers. It exposes two functions + /// 1. DecryptColumnEncryptionKey - This is the function used by SqlClient under the covers to decrypt encrypted column encryption key blob. + /// 2. EncryptColumnEncryptionKey - This will be used by client tools that generate DDL for customers + /// </summary> + public abstract class SqlColumnEncryptionKeyStoreProvider + { + /// <summary> + /// This function must be implemented by the corresponding Key Store providers. This function should use an asymmetric key identified by the key path + /// and decrypt an encrypted column encryption key with a given encryption algorithm. + /// </summary> + /// <param name="masterKeyPath">Complete path of an asymmetric key. Path format is specific to a key store provider.</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 abstract byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey); + + /// <summary> + /// This function must be implemented by the corresponding Key Store providers. This function should use an asymmetric key identified by a key path + /// and encrypt a plain text column encryption key with a given asymmetric key encryption algorithm. + /// </summary> + /// <param name="keyPath">Complete path of an asymmetric key. Path format is specific to a key store provider.</param> + /// <param name="encryptionAlgorithm">Asymmetric Key Encryption Algorithm</param> + /// <param name="columnEncryptionKey">Plain text column encryption key to be encrypted</param> + /// <returns>Encrypted column encryption key</returns> + public abstract byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey); + } +}
\ No newline at end of file diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlCommand.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlCommand.cs index 3079ff01518..3e247da7d86 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlCommand.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlCommand.cs @@ -2,14 +2,15 @@ // <copyright file="SqlCommand.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { using System; using System.Collections; using System.Collections.Generic; + using System.Collections.ObjectModel; using System.ComponentModel; using System.Configuration.Assemblies; using System.Data; @@ -18,8 +19,10 @@ namespace System.Data.SqlClient { using System.Data.Sql; using System.Data.SqlTypes; using System.Diagnostics; + using System.Diagnostics.Tracing; using System.Globalization; using System.IO; + using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; @@ -48,8 +51,38 @@ namespace System.Data.SqlClient { private int _commandTimeout = ADP.DefaultCommandTimeout; private UpdateRowSource _updatedRowSource = UpdateRowSource.Both; private bool _designTimeInvisible; + + /// <summary> + /// Indicates if the column encryption setting was set at-least once in the batch rpc mode, when using AddBatchCommand. + /// </summary> + private bool _wasBatchModeColumnEncryptionSettingSetOnce; + + /// <summary> + /// Column Encryption Override. Defaults to SqlConnectionSetting, in which case + /// it will be Enabled if SqlConnectionOptions.IsColumnEncryptionSettingEnabled = true, Disabled if false. + /// This may also be used to set other behavior which overrides connection level setting. + /// </summary> + private SqlCommandColumnEncryptionSetting _columnEncryptionSetting = SqlCommandColumnEncryptionSetting.UseConnectionSetting; + internal SqlDependency _sqlDep; +#if DEBUG + /// <summary> + /// Force the client to sleep during sp_describe_parameter_encryption in the function TryFetchInputParameterEncryptionInfo. + /// </summary> + private static bool _sleepDuringTryFetchInputParameterEncryptionInfo = false; + + /// <summary> + /// Force the client to sleep during sp_describe_parameter_encryption in the function RunExecuteReaderTds. + /// </summary> + private static bool _sleepDuringRunExecuteReaderTdsForSpDescribeParameterEncryption = false; + + /// <summary> + /// Force the client to sleep during sp_describe_parameter_encryption after ReadDescribeEncryptionParameterResults. + /// </summary> + private static bool _sleepAfterReadDescribeEncryptionParameterResults = false; +#endif + // devnote: Prepare // Against 7.0 Server (Sphinx) a prepare/unprepare requires an extra roundtrip to the server. // @@ -83,6 +116,7 @@ namespace System.Data.SqlClient { private bool _dirty = false; // true if the user changes the commandtext or number of parameters after the command is already prepared private EXECTYPE _execType = EXECTYPE.UNPREPARED; // by default, assume the user is not sharing a connection so the command has not been prepared private _SqlRPC[] _rpcArrayOf1 = null; // Used for RPC executes + private _SqlRPC _rpcForEncryption = null; // Used for sp_describe_parameter_encryption RPC executes // cut down on object creation and cache all these // cached metadata @@ -100,6 +134,21 @@ namespace System.Data.SqlClient { } } + /// <summary> + /// Return if column encryption setting is enabled. + /// The order in the below if is important since _activeConnection.Parser can throw if the + /// underlying tds connection is closed and we don't want to change the behavior for folks + /// not trying to use transparent parameter encryption i.e. who don't use (SqlCommandColumnEncryptionSetting.Enabled or _activeConnection.IsColumnEncryptionSettingEnabled) here. + /// </summary> + internal bool IsColumnEncryptionEnabled { + get { + return (_columnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled + || (_columnEncryptionSetting == SqlCommandColumnEncryptionSetting.UseConnectionSetting && _activeConnection.IsColumnEncryptionSettingEnabled)) + && _activeConnection.Parser != null + && _activeConnection.Parser.IsColumnEncryptionSupported; + } + } + // Cached info for async executions private class CachedAsyncState { private int _cachedAsyncCloseCount = -1; // value of the connection's CloseCount property when the asyncResult was set; tracks when connections are closed after an async operation @@ -189,6 +238,10 @@ namespace System.Data.SqlClient { // _rowsAffected is cumulative for ExecuteNonQuery across all rpc batches internal int _rowsAffected = -1; // rows affected by the command + // number of rows affected by sp_describe_parameter_encryption. + // The below line is used only for debug asserts and not exposed publicly or impacts functionality otherwise. + private int _rowsAffectedBySpDescribeParameterEncryption = -1; + private SqlNotificationRequest _notification; private bool _notificationAutoEnlist = true; // Notifications auto enlistment is turned on by default @@ -209,9 +262,31 @@ namespace System.Data.SqlClient { private bool _batchRPCMode; private List<_SqlRPC> _RPCList; private _SqlRPC[] _SqlRPCBatchArray; + private _SqlRPC[] _sqlRPCParameterEncryptionReqArray; private List<SqlParameterCollection> _parameterCollectionList; private int _currentlyExecutingBatch; + /// <summary> + /// This variable is used to keep track of which RPC batch's results are being read when reading the results of + /// describe parameter encryption RPC requests in BatchRPCMode. + /// </summary> + private int _currentlyExecutingDescribeParameterEncryptionRPC; + + /// <summary> + /// A flag to indicate if we have in-progress describe parameter encryption RPC requests. + /// Reset to false when completed. + /// </summary> + private bool _isDescribeParameterEncryptionRPCCurrentlyInProgress; + + /// <summary> + /// Return the flag that indicates if describe parameter encryption RPC requests are in-progress. + /// </summary> + internal bool IsDescribeParameterEncryptionRPCCurrentlyInProgress { + get { + return _isDescribeParameterEncryptionRPCCurrentlyInProgress; + } + } + // // Smi execution-specific stuff // @@ -272,6 +347,7 @@ namespace System.Data.SqlClient { private SmiContext _smiRequestContext; // context that _smiRequest came from private CommandEventSink _smiEventSink; private SmiEventSink_DeferedProcessing _outParamEventSink; + private CommandEventSink EventSink { get { if ( null == _smiEventSink ) { @@ -316,6 +392,13 @@ namespace System.Data.SqlClient { Transaction = transaction; } + public SqlCommand(string cmdText, SqlConnection connection, SqlTransaction transaction, SqlCommandColumnEncryptionSetting columnEncryptionSetting) : this() { + CommandText = cmdText; + Connection = connection; + Transaction = transaction; + _columnEncryptionSetting = columnEncryptionSetting; + } + private SqlCommand(SqlCommand from) : this() { // Clone CommandText = from.CommandText; CommandTimeout = from.CommandTimeout; @@ -324,6 +407,7 @@ namespace System.Data.SqlClient { DesignTimeVisible = from.DesignTimeVisible; Transaction = from.Transaction; UpdatedRowSource = from.UpdatedRowSource; + _columnEncryptionSetting = from.ColumnEncryptionSetting; SqlParameterCollection parameters = Parameters; foreach(object parameter in from.Parameters) { @@ -401,7 +485,8 @@ namespace System.Data.SqlClient { } } - _activeConnection = value; // UNDONE: Designers need this setter. Should we block other scenarios? + _activeConnection = value; // + Bid.Trace("<sc.SqlCommand.set_Connection|API> %d#, %d#\n", ObjectID, ((null != value) ? value.ObjectID : -1)); } } @@ -541,6 +626,18 @@ namespace System.Data.SqlClient { } [ + Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), + ResCategoryAttribute(Res.DataCategory_Data), + ResDescriptionAttribute(Res.TCE_SqlCommand_ColumnEncryptionSetting), + ] + public SqlCommandColumnEncryptionSetting ColumnEncryptionSetting { + get { + return _columnEncryptionSetting; + } + } + + [ ResCategoryAttribute(Res.DataCategory_Data), ResDescriptionAttribute(Res.DbCommand_CommandTimeout), ] @@ -1012,16 +1109,25 @@ namespace System.Data.SqlClient { Bid.ScopeEnter(out hscp, "<sc.SqlCommand.ExecuteScalar|API> %d#", ObjectID); Bid.CorrelationTrace("<sc.SqlCommand.ExecuteScalar|API|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID); - + bool success = false; + int? sqlExceptionNumber = null; try { statistics = SqlStatistics.StartTimer(Statistics); + WriteBeginExecuteEvent(); SqlDataReader ds; ds = RunExecuteReader(0, RunBehavior.ReturnImmediately, true, ADP.ExecuteScalar); - return CompleteExecuteScalar(ds, false); + object result = CompleteExecuteScalar(ds, false); + success = true; + return result; + } + catch (SqlException ex) { + sqlExceptionNumber = ex.Number; + throw; } finally { SqlStatistics.StopTimer(statistics); Bid.ScopeLeave(ref hscp); + WriteEndExecuteEvent(success, sqlExceptionNumber, synchronous: true); } } @@ -1059,14 +1165,23 @@ namespace System.Data.SqlClient { IntPtr hscp; Bid.ScopeEnter(out hscp, "<sc.SqlCommand.ExecuteNonQuery|API> %d#", ObjectID); Bid.CorrelationTrace("<sc.SqlCommand.ExecuteNonQuery|API|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID); + bool success = false; + int? sqlExceptionNumber = null; try { statistics = SqlStatistics.StartTimer(Statistics); + WriteBeginExecuteEvent(); InternalExecuteNonQuery(null, ADP.ExecuteNonQuery, false, CommandTimeout); + success = true; return _rowsAffected; } + catch (SqlException ex) { + sqlExceptionNumber = ex.Number; + throw; + } finally { SqlStatistics.StopTimer(statistics); Bid.ScopeLeave(ref hscp); + WriteEndExecuteEvent(success, sqlExceptionNumber, synchronous : true); } } @@ -1120,6 +1235,7 @@ namespace System.Data.SqlClient { SqlStatistics statistics = null; try { statistics = SqlStatistics.StartTimer(Statistics); + WriteBeginExecuteEvent(); TaskCompletionSource<object> completion = new TaskCompletionSource<object>(stateObject); try { // InternalExecuteNonQuery already has reliability block, but if failure will not put stateObj back into pool. @@ -1221,6 +1337,18 @@ namespace System.Data.SqlClient { else if (completionTask.IsFaulted) { throw completionTask.Exception.InnerException; } + + // If transparent parameter encryption was attempted, then we need to skip other checks like those on EndMethodName + // since we want to wait for async results before checking those fields. + if (IsColumnEncryptionEnabled) { + if (_activeConnection.State != ConnectionState.Open) { + // If the connection is not 'valid' then it was closed while we were executing + throw ADP.ClosedConnectionError(); + } + + return; + } + if (cachedAsyncState.EndMethodName == null) { throw ADP.MethodCalledTwice(endMethod); } @@ -1283,6 +1411,8 @@ namespace System.Data.SqlClient { TdsParser bestEffortCleanupTarget = null; RuntimeHelpers.PrepareConstrainedRegions(); + bool success = false; + int? sqlExceptionNumber = null; try { #if DEBUG TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); @@ -1298,6 +1428,23 @@ namespace System.Data.SqlClient { VerifyEndExecuteState((Task)asyncResult, ADP.EndExecuteNonQuery); WaitForAsyncResults(asyncResult); + // If Transparent parameter encryption was attempted, then we would have skipped the below + // checks in VerifyEndExecuteState since we wanted to wait for WaitForAsyncResults to complete. + if (IsColumnEncryptionEnabled) { + if (cachedAsyncState.EndMethodName == null) { + throw ADP.MethodCalledTwice(ADP.EndExecuteNonQuery); + } + + if (ADP.EndExecuteNonQuery != cachedAsyncState.EndMethodName) { + throw ADP.MismatchedAsyncResult(cachedAsyncState.EndMethodName, ADP.EndExecuteNonQuery); + } + + if (!cachedAsyncState.IsActiveConnectionValid(_activeConnection)) { + // If the connection is not 'valid' then it was closed while we were executing + throw ADP.ClosedConnectionError(); + } + } + bool processFinallyBlock = true; try { NotifyDependency(); @@ -1322,6 +1469,10 @@ namespace System.Data.SqlClient { } } } + catch (SqlException e) { + sqlExceptionNumber = e.Number; + throw; + } catch (Exception e) { processFinallyBlock = ADP.IsCatchableExceptionType(e); throw; @@ -1333,6 +1484,7 @@ namespace System.Data.SqlClient { } Debug.Assert(null == _stateObj, "non-null state object in EndExecuteNonQuery"); + success = true; return _rowsAffected; } #if DEBUG @@ -1365,6 +1517,7 @@ namespace System.Data.SqlClient { } finally { SqlStatistics.StopTimer(statistics); + WriteEndExecuteEvent(success, sqlExceptionNumber, synchronous: false); } } @@ -1463,17 +1616,27 @@ namespace System.Data.SqlClient { IntPtr hscp; Bid.ScopeEnter(out hscp, "<sc.SqlCommand.ExecuteXmlReader|API> %d#", ObjectID); Bid.CorrelationTrace("<sc.SqlCommand.ExecuteXmlReader|API|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID); + bool success = false; + int? sqlExceptionNumber = null; try { statistics = SqlStatistics.StartTimer(Statistics); + WriteBeginExecuteEvent(); // use the reader to consume metadata SqlDataReader ds; ds = RunExecuteReader(CommandBehavior.SequentialAccess, RunBehavior.ReturnImmediately, true, ADP.ExecuteXmlReader); - return CompleteXmlReader(ds); + XmlReader result = CompleteXmlReader(ds); + success = true; + return result; + } + catch (SqlException ex) { + sqlExceptionNumber = ex.Number; + throw; } finally { SqlStatistics.StopTimer(statistics); Bid.ScopeLeave(ref hscp); + WriteEndExecuteEvent(success, sqlExceptionNumber, synchronous : true); } } @@ -1505,6 +1668,7 @@ namespace System.Data.SqlClient { SqlStatistics statistics = null; try { statistics = SqlStatistics.StartTimer(Statistics); + WriteBeginExecuteEvent(); TaskCompletionSource<object> completion = new TaskCompletionSource<object>(stateObject); Task writeTask; @@ -1621,8 +1785,22 @@ namespace System.Data.SqlClient { } private XmlReader EndExecuteXmlReaderInternal(IAsyncResult asyncResult) { + bool success = false; + int? sqlExceptionNumber = null; try { - return CompleteXmlReader(InternalEndExecuteReader(asyncResult, ADP.EndExecuteXmlReader)); + XmlReader result = CompleteXmlReader(InternalEndExecuteReader(asyncResult, ADP.EndExecuteXmlReader)); + success = true; + return result; + } + catch (SqlException e){ + sqlExceptionNumber = e.Number; + if (cachedAsyncState != null) { + cachedAsyncState.ResetAsyncState(); + }; + + // SqlException is always catchable + ReliablePutStateObject(); + throw; } catch (Exception e) { if (cachedAsyncState != null) { @@ -1633,6 +1811,9 @@ namespace System.Data.SqlClient { }; throw; } + finally { + WriteEndExecuteEvent(success, sqlExceptionNumber, synchronous : false); + } } private XmlReader CompleteXmlReader(SqlDataReader ds) { @@ -1717,7 +1898,7 @@ namespace System.Data.SqlClient { } internal SqlDataReader ExecuteReader(CommandBehavior behavior, string method) { - SqlConnection.ExecutePermission.Demand(); // TODO: Need to move this to public methods... + SqlConnection.ExecutePermission.Demand(); // // Reset _pendingCancel upon entry into any Execute - used to synchronize state // between entry into Execute* API and the thread obtaining the stateObject. @@ -1727,7 +1908,10 @@ namespace System.Data.SqlClient { TdsParser bestEffortCleanupTarget = null; RuntimeHelpers.PrepareConstrainedRegions(); + bool success = false; + int? sqlExceptionNumber = null; try { + WriteBeginExecuteEvent(); #if DEBUG TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); @@ -1739,7 +1923,9 @@ namespace System.Data.SqlClient { #endif //DEBUG bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); statistics = SqlStatistics.StartTimer(Statistics); - return RunExecuteReader(behavior, RunBehavior.ReturnImmediately, true, method); + SqlDataReader result = RunExecuteReader(behavior, RunBehavior.ReturnImmediately, true, method); + success = true; + return result; } #if DEBUG finally { @@ -1747,6 +1933,10 @@ namespace System.Data.SqlClient { } #endif //DEBUG } + catch (SqlException e) { + sqlExceptionNumber = e.Number; + throw; + } catch (System.OutOfMemoryException e) { _activeConnection.Abort(e); throw; @@ -1762,6 +1952,7 @@ namespace System.Data.SqlClient { } finally { SqlStatistics.StopTimer(statistics); + WriteEndExecuteEvent(success, sqlExceptionNumber, synchronous : true); } } @@ -1794,9 +1985,24 @@ namespace System.Data.SqlClient { private SqlDataReader EndExecuteReaderInternal(IAsyncResult asyncResult) { SqlStatistics statistics = null; + bool success = false; + int? sqlExceptionNumber = null; try { statistics = SqlStatistics.StartTimer(Statistics); - return InternalEndExecuteReader(asyncResult, ADP.EndExecuteReader); + SqlDataReader result = InternalEndExecuteReader(asyncResult, ADP.EndExecuteReader); + success = true; + return result; + } + catch (SqlException e) { + sqlExceptionNumber = e.Number; + if (cachedAsyncState != null) + { + cachedAsyncState.ResetAsyncState(); + }; + + // SqlException is always catchable + ReliablePutStateObject(); + throw; } catch (Exception e) { if (cachedAsyncState != null) { @@ -1809,6 +2015,7 @@ namespace System.Data.SqlClient { } finally { SqlStatistics.StopTimer(statistics); + WriteEndExecuteEvent(success, sqlExceptionNumber, synchronous : false); } } @@ -1824,6 +2031,7 @@ namespace System.Data.SqlClient { SqlStatistics statistics = null; try { statistics = SqlStatistics.StartTimer(Statistics); + WriteBeginExecuteEvent(); TaskCompletionSource<object> completion = new TaskCompletionSource<object>(stateObject); ValidateAsyncCommand(); // Special case - done outside of try/catches to prevent putting a stateObj @@ -1920,6 +2128,23 @@ namespace System.Data.SqlClient { VerifyEndExecuteState((Task) asyncResult, endMethod); WaitForAsyncResults(asyncResult); + // If Transparent parameter encryption was attempted, then we would have skipped the below + // checks in VerifyEndExecuteState since we wanted to wait for WaitForAsyncResults to complete. + if (IsColumnEncryptionEnabled) { + if (cachedAsyncState.EndMethodName == null) { + throw ADP.MethodCalledTwice(endMethod); + } + + if (endMethod != cachedAsyncState.EndMethodName) { + throw ADP.MismatchedAsyncResult(cachedAsyncState.EndMethodName, endMethod); + } + + if (!cachedAsyncState.IsActiveConnectionValid(_activeConnection)) { + // If the connection is not 'valid' then it was closed while we were executing + throw ADP.ClosedConnectionError(); + } + } + CheckThrowSNIException(); TdsParser bestEffortCleanupTarget = null; @@ -2757,7 +2982,740 @@ namespace System.Data.SqlClient { EventSink.ProcessMessagesAndThrow(ignoreNonFatalMessages: true); } } - } + } + + /// <summary> + /// Resets the encryption related state of the command object and each of the parameters. + /// BatchRPC doesn't need special handling to cleanup the state of each RPC object and its parameters since a new RPC object and + /// parameters are generated on every execution. + /// </summary> + private void ResetEncryptionState() { + // First reset the command level state. + ClearDescribeParameterEncryptionRequests(); + + // Reset the state of each of the parameters. + if (_parameters != null) { + for (int i = 0; i < _parameters.Count; i++) { + _parameters[i].CipherMetadata = null; + _parameters[i].HasReceivedMetadata = false; + } + } + } + + /// <summary> + /// Steps to be executed in the Prepare Transparent Encryption finally block. + /// </summary> + private void PrepareTransparentEncryptionFinallyBlock( bool closeDataReader, + bool clearDataStructures, + bool decrementAsyncCount, + bool wasDescribeParameterEncryptionNeeded, + ReadOnlyDictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcMap, + SqlDataReader describeParameterEncryptionDataReader) { + if (clearDataStructures) { + // Clear some state variables in SqlCommand that reflect in-progress describe parameter encryption requests. + ClearDescribeParameterEncryptionRequests(); + + if (describeParameterEncryptionRpcOriginalRpcMap != null) { + describeParameterEncryptionRpcOriginalRpcMap = null; + } + } + + // Decrement the async count. + if (decrementAsyncCount) { + SqlInternalConnectionTds internalConnectionTds = _activeConnection.GetOpenTdsConnection(); + if (internalConnectionTds != null) { + internalConnectionTds.DecrementAsyncCount(); + } + } + + if (closeDataReader) { + // Close the data reader to reset the _stateObj + if (null != describeParameterEncryptionDataReader) { + describeParameterEncryptionDataReader.Close(); + } + } + } + + /// <summary> + /// Executes the reader after checking to see if we need to encrypt input parameters and then encrypting it if required. + /// TryFetchInputParameterEncryptionInfo() -> ReadDescribeEncryptionParameterResults()-> EncryptInputParameters() ->RunExecuteReaderTds() + /// </summary> + /// <param name="cmdBehavior"></param> + /// <param name="returnStream"></param> + /// <param name="async"></param> + /// <param name="timeout"></param> + /// <param name="task"></param> + /// <param name="asyncWrite"></param> + /// <returns></returns> + private void PrepareForTransparentEncryption(CommandBehavior cmdBehavior, bool returnStream, bool async, int timeout, TaskCompletionSource<object> completion, out Task returnTask, bool asyncWrite) + { + // Fetch reader with input params + Task fetchInputParameterEncryptionInfoTask = null; + bool describeParameterEncryptionNeeded = false; + SqlDataReader describeParameterEncryptionDataReader = null; + returnTask = null; + + Debug.Assert(_activeConnection != null, "_activeConnection should not be null in PrepareForTransparentEncryption."); + Debug.Assert(_activeConnection.Parser != null, "_activeConnection.Parser should not be null in PrepareForTransparentEncryption."); + Debug.Assert(_activeConnection.Parser.IsColumnEncryptionSupported, + "_activeConnection.Parser.IsColumnEncryptionSupported should be true in PrepareForTransparentEncryption."); + Debug.Assert(_columnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled + || (_columnEncryptionSetting == SqlCommandColumnEncryptionSetting.UseConnectionSetting && _activeConnection.IsColumnEncryptionSettingEnabled), + "ColumnEncryption setting should be enabled for input parameter encryption."); + Debug.Assert(async == (completion != null), "completion should can be null if and only if mode is async."); + + // A flag to indicate if finallyblock needs to execute. + bool processFinallyBlock = true; + + // A flag to indicate if we need to decrement async count on the connection in finally block. + bool decrementAsyncCountInFinallyBlock = async; + + // Flag to indicate if exception is caught during the execution, to govern clean up. + bool exceptionCaught = false; + + // Used in BatchRPCMode to maintain a map of describe parameter encryption RPC requests (Keys) and their corresponding original RPC requests (Values). + ReadOnlyDictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcMap = null; + + TdsParser bestEffortCleanupTarget = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { +#if DEBUG + TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); + + RuntimeHelpers.PrepareConstrainedRegions(); + try { + tdsReliabilitySection.Start(); +#else + { +#endif //DEBUG + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); + try { + // Fetch the encryption information that applies to any of the input parameters. + describeParameterEncryptionDataReader = TryFetchInputParameterEncryptionInfo(timeout, + async, + asyncWrite, + out describeParameterEncryptionNeeded, + out fetchInputParameterEncryptionInfoTask, + out describeParameterEncryptionRpcOriginalRpcMap); + + Debug.Assert(describeParameterEncryptionNeeded || describeParameterEncryptionDataReader == null, + "describeParameterEncryptionDataReader should be null if we don't need to request describe parameter encryption request."); + + Debug.Assert(fetchInputParameterEncryptionInfoTask == null || async, + "Task returned by TryFetchInputParameterEncryptionInfo, when in sync mode, in PrepareForTransparentEncryption."); + + Debug.Assert((describeParameterEncryptionRpcOriginalRpcMap != null) == BatchRPCMode, + "describeParameterEncryptionRpcOriginalRpcMap can be non-null if and only if it is in BatchRPCMode."); + + // If we didn't have parameters, we can fall back to regular code path, by simply returning. + if (!describeParameterEncryptionNeeded) { + Debug.Assert(null == fetchInputParameterEncryptionInfoTask, + "fetchInputParameterEncryptionInfoTask should not be set if describe parameter encryption is not needed."); + + Debug.Assert(null == describeParameterEncryptionDataReader, + "SqlDataReader created for describe parameter encryption params when it is not needed."); + + return; + } + + Debug.Assert(describeParameterEncryptionDataReader != null, + "describeParameterEncryptionDataReader should not be null, as it is required to get results of describe parameter encryption."); + + // Fire up another task to read the results of describe parameter encryption + if (fetchInputParameterEncryptionInfoTask != null) { + // Mark that we should not process the finally block since we have async execution pending. + // Note that this should be done outside the task's continuation delegate. + processFinallyBlock = false; + returnTask = AsyncHelper.CreateContinuationTask(fetchInputParameterEncryptionInfoTask, () => { + bool processFinallyBlockAsync = true; + + RuntimeHelpers.PrepareConstrainedRegions(); + try { +#if DEBUG + TdsParser.ReliabilitySection tdsReliabilitySectionAsync = new TdsParser.ReliabilitySection(); + RuntimeHelpers.PrepareConstrainedRegions(); + try { + tdsReliabilitySectionAsync.Start(); +#endif //DEBUG + // Check for any exceptions on network write, before reading. + CheckThrowSNIException(); + + // If it is async, then TryFetchInputParameterEncryptionInfo-> RunExecuteReaderTds would have incremented the async count. + // Decrement it when we are about to complete async execute reader. + SqlInternalConnectionTds internalConnectionTds = _activeConnection.GetOpenTdsConnection(); + if (internalConnectionTds != null) + { + internalConnectionTds.DecrementAsyncCount(); + decrementAsyncCountInFinallyBlock = false; + } + + // Complete executereader. + describeParameterEncryptionDataReader = CompleteAsyncExecuteReader(); + Debug.Assert(null == _stateObj, "non-null state object in PrepareForTransparentEncryption."); + + // Read the results of describe parameter encryption. + ReadDescribeEncryptionParameterResults(describeParameterEncryptionDataReader, describeParameterEncryptionRpcOriginalRpcMap); + +#if DEBUG + // Failpoint to force the thread to halt to simulate cancellation of SqlCommand. + if (_sleepAfterReadDescribeEncryptionParameterResults) { + Thread.Sleep(10000); + } + } + finally { + tdsReliabilitySectionAsync.Stop(); + } +#endif //DEBUG + } + catch (Exception e) { + processFinallyBlockAsync = ADP.IsCatchableExceptionType(e); + throw; + } + finally { + PrepareTransparentEncryptionFinallyBlock( closeDataReader: processFinallyBlockAsync, + decrementAsyncCount: decrementAsyncCountInFinallyBlock, + clearDataStructures: processFinallyBlockAsync, + wasDescribeParameterEncryptionNeeded: describeParameterEncryptionNeeded, + describeParameterEncryptionRpcOriginalRpcMap: describeParameterEncryptionRpcOriginalRpcMap, + describeParameterEncryptionDataReader: describeParameterEncryptionDataReader); + } + }, + onFailure: ((exception) => { + if (_cachedAsyncState != null) { + _cachedAsyncState.ResetAsyncState(); + } + if (exception != null) { + throw exception; + }})); + } + else { + // If it was async, ending the reader is still pending. + if (async) { + // Mark that we should not process the finally block since we have async execution pending. + // Note that this should be done outside the task's continuation delegate. + processFinallyBlock = false; + returnTask = Task.Run(() => { + bool processFinallyBlockAsync = true; + + RuntimeHelpers.PrepareConstrainedRegions(); + try { +#if DEBUG + TdsParser.ReliabilitySection tdsReliabilitySectionAsync = new TdsParser.ReliabilitySection(); + RuntimeHelpers.PrepareConstrainedRegions(); + try { + tdsReliabilitySectionAsync.Start(); +#endif //DEBUG + + // Check for any exceptions on network write, before reading. + CheckThrowSNIException(); + + // If it is async, then TryFetchInputParameterEncryptionInfo-> RunExecuteReaderTds would have incremented the async count. + // Decrement it when we are about to complete async execute reader. + SqlInternalConnectionTds internalConnectionTds = _activeConnection.GetOpenTdsConnection(); + if (internalConnectionTds != null) { + internalConnectionTds.DecrementAsyncCount(); + decrementAsyncCountInFinallyBlock = false; + } + + // Complete executereader. + describeParameterEncryptionDataReader = CompleteAsyncExecuteReader(); + Debug.Assert(null == _stateObj, "non-null state object in PrepareForTransparentEncryption."); + + // Read the results of describe parameter encryption. + ReadDescribeEncryptionParameterResults(describeParameterEncryptionDataReader, describeParameterEncryptionRpcOriginalRpcMap); +#if DEBUG + // Failpoint to force the thread to halt to simulate cancellation of SqlCommand. + if (_sleepAfterReadDescribeEncryptionParameterResults) { + Thread.Sleep(10000); + } +#endif +#if DEBUG + } + finally { + tdsReliabilitySectionAsync.Stop(); + } +#endif //DEBUG + } + catch (Exception e) { + processFinallyBlockAsync = ADP.IsCatchableExceptionType(e); + throw; + } + finally { + PrepareTransparentEncryptionFinallyBlock( closeDataReader: processFinallyBlockAsync, + decrementAsyncCount: decrementAsyncCountInFinallyBlock, + clearDataStructures: processFinallyBlockAsync, + wasDescribeParameterEncryptionNeeded: describeParameterEncryptionNeeded, + describeParameterEncryptionRpcOriginalRpcMap: describeParameterEncryptionRpcOriginalRpcMap, + describeParameterEncryptionDataReader: describeParameterEncryptionDataReader); + } + }); + } + else { + // For synchronous execution, read the results of describe parameter encryption here. + ReadDescribeEncryptionParameterResults(describeParameterEncryptionDataReader, describeParameterEncryptionRpcOriginalRpcMap); + } + +#if DEBUG + // Failpoint to force the thread to halt to simulate cancellation of SqlCommand. + if (_sleepAfterReadDescribeEncryptionParameterResults) { + Thread.Sleep(10000); + } +#endif + } + } + catch (Exception e) { + processFinallyBlock = ADP.IsCatchableExceptionType(e); + exceptionCaught = true; + throw; + } + finally { + // Free up the state only for synchronous execution. For asynchronous execution, free only if there was an exception. + PrepareTransparentEncryptionFinallyBlock(closeDataReader: (processFinallyBlock && !async) || exceptionCaught, + decrementAsyncCount: decrementAsyncCountInFinallyBlock && exceptionCaught, + clearDataStructures: (processFinallyBlock && !async) || exceptionCaught, + wasDescribeParameterEncryptionNeeded: describeParameterEncryptionNeeded, + describeParameterEncryptionRpcOriginalRpcMap: describeParameterEncryptionRpcOriginalRpcMap, + describeParameterEncryptionDataReader: describeParameterEncryptionDataReader); + } + } +#if DEBUG + finally { + tdsReliabilitySection.Stop(); + } +#endif //DEBUG + } + catch (System.OutOfMemoryException e) { + _activeConnection.Abort(e); + throw; + } + catch (System.StackOverflowException e) { + _activeConnection.Abort(e); + throw; + } + catch (System.Threading.ThreadAbortException e) { + _activeConnection.Abort(e); + SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); + throw; + } + catch (Exception e) { + if (cachedAsyncState != null) { + cachedAsyncState.ResetAsyncState(); + } + + if (ADP.IsCatchableExceptionType(e)) { + ReliablePutStateObject(); + } + + throw; + } + } + + /// <summary> + /// Executes an RPC to fetch param encryption info from SQL Engine. If this method is not done writing + /// the request to wire, it'll set the "task" parameter which can be used to create continuations. + /// </summary> + /// <param name="timeout"></param> + /// <param name="async"></param> + /// <param name="asyncWrite"></param> + /// <param name="inputParameterEncryptionNeeded"></param> + /// <param name="task"></param> + /// <param name="describeParameterEncryptionRpcOriginalRpcMap"></param> + /// <returns></returns> + private SqlDataReader TryFetchInputParameterEncryptionInfo(int timeout, + bool async, + bool asyncWrite, + out bool inputParameterEncryptionNeeded, + out Task task, + out ReadOnlyDictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcMap) { + inputParameterEncryptionNeeded = false; + task = null; + describeParameterEncryptionRpcOriginalRpcMap = null; + + if (BatchRPCMode) { + // Count the rpc requests that need to be transparently encrypted + // We simply look for any parameters in a request and add the request to be queried for parameter encryption + Dictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcDictionary = new Dictionary<_SqlRPC, _SqlRPC>(); + + for (int i = 0; i < _SqlRPCBatchArray.Length; i++) { + // In BatchRPCMode, the actual T-SQL query is in the first parameter and not present as the rpcName, as is the case with non-BatchRPCMode. + // So input parameters start at parameters[1]. parameters[0] is the actual T-SQL Statement. rpcName is sp_executesql. + if (_SqlRPCBatchArray[i].parameters.Length > 1) { + _SqlRPCBatchArray[i].needsFetchParameterEncryptionMetadata = true; + + // Since we are going to need multiple RPC objects, allocate a new one here for each command in the batch. + _SqlRPC rpcDescribeParameterEncryptionRequest = new _SqlRPC(); + + // Prepare the describe parameter encryption request. + PrepareDescribeParameterEncryptionRequest(_SqlRPCBatchArray[i], ref rpcDescribeParameterEncryptionRequest); + Debug.Assert(rpcDescribeParameterEncryptionRequest != null, "rpcDescribeParameterEncryptionRequest should not be null, after call to PrepareDescribeParameterEncryptionRequest."); + + Debug.Assert(!describeParameterEncryptionRpcOriginalRpcDictionary.ContainsKey(rpcDescribeParameterEncryptionRequest), + "There should not already be a key referring to the current rpcDescribeParameterEncryptionRequest, in the dictionary describeParameterEncryptionRpcOriginalRpcDictionary."); + + // Add the describe parameter encryption RPC request as the key and its corresponding original rpc request to the dictionary. + describeParameterEncryptionRpcOriginalRpcDictionary.Add(rpcDescribeParameterEncryptionRequest, _SqlRPCBatchArray[i]); + } + } + + describeParameterEncryptionRpcOriginalRpcMap = new ReadOnlyDictionary<_SqlRPC, _SqlRPC>(describeParameterEncryptionRpcOriginalRpcDictionary); + + if (describeParameterEncryptionRpcOriginalRpcMap.Count == 0) { + // If no parameters are present, nothing to do, simply return. + return null; + } + else { + inputParameterEncryptionNeeded = true; + } + + _sqlRPCParameterEncryptionReqArray = describeParameterEncryptionRpcOriginalRpcMap.Keys.ToArray(); + + Debug.Assert(_sqlRPCParameterEncryptionReqArray.Length > 0, "There should be at-least 1 describe parameter encryption rpc request."); + Debug.Assert(_sqlRPCParameterEncryptionReqArray.Length <= _SqlRPCBatchArray.Length, + "The number of decribe parameter encryption RPC requests is more than the number of original RPC requests."); + } + else if (0 != GetParameterCount(_parameters)) { + // Fetch params for a single batch + inputParameterEncryptionNeeded = true; + _sqlRPCParameterEncryptionReqArray = new _SqlRPC[1]; + + _SqlRPC rpc = null; + GetRPCObject(_parameters.Count, ref rpc); + Debug.Assert(rpc != null, "GetRPCObject should not return rpc as null."); + + rpc.rpcName = CommandText; + + int i = 0; + foreach (SqlParameter sqlParam in _parameters) { + rpc.parameters[i++] = sqlParam; + } + + // Prepare the RPC request for describe parameter encryption procedure. + PrepareDescribeParameterEncryptionRequest(rpc, ref _sqlRPCParameterEncryptionReqArray[0]); + Debug.Assert(_sqlRPCParameterEncryptionReqArray[0] != null, "_sqlRPCParameterEncryptionReqArray[0] should not be null, after call to PrepareDescribeParameterEncryptionRequest."); + } + + if (inputParameterEncryptionNeeded) { + // Set the flag that indicates that parameter encryption requests are currently in-progress. + _isDescribeParameterEncryptionRPCCurrentlyInProgress = true; + +#if DEBUG + // Failpoint to force the thread to halt to simulate cancellation of SqlCommand. + if (_sleepDuringTryFetchInputParameterEncryptionInfo) { + Thread.Sleep(10000); + } +#endif + + // Execute the RPC. + return RunExecuteReaderTds( CommandBehavior.Default, + runBehavior: RunBehavior.ReturnImmediately, // Other RunBehavior modes will skip reading rows. + returnStream: true, + async: async, + timeout: timeout, + task: out task, + asyncWrite: asyncWrite, + ds: null, + describeParameterEncryptionRequest: true); + } + else { + return null; + } + } + + /// <summary> + /// Constructs a SqlParameter with a given string value + /// </summary> + /// <param name="queryText"></param> + /// <returns></returns> + private SqlParameter GetSqlParameterWithQueryText(string queryText) + { + SqlParameter sqlParam = new SqlParameter(null, ((queryText.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText, queryText.Length); + sqlParam.Value = queryText; + + return sqlParam; + } + + /// <summary> + /// Constructs the sp_describe_parameter_encryption request with the values from the original RPC call. + /// Prototype for <sp_describe_parameter_encryption> is + /// exec sp_describe_parameter_encryption @tsql=N'[SQL Statement]', @params=N'@p1 varbinary(256)' + /// </summary> + /// <param name="originalRpcRequest">Original RPC request</param> + /// <param name="describeParameterEncryptionRequest">sp_describe_parameter_encryption request being built</param> + private void PrepareDescribeParameterEncryptionRequest(_SqlRPC originalRpcRequest, ref _SqlRPC describeParameterEncryptionRequest) { + Debug.Assert(originalRpcRequest != null); + + // Construct the RPC request for sp_describe_parameter_encryption + // sp_describe_parameter_encryption always has 2 parameters (stmt, paramlist). + GetRPCObject(2, ref describeParameterEncryptionRequest, forSpDescribeParameterEncryption:true); + describeParameterEncryptionRequest.rpcName = "sp_describe_parameter_encryption"; + + // Prepare @tsql parameter + SqlParameter sqlParam; + string text; + + // In BatchRPCMode, The actual T-SQL query is in the first parameter and not present as the rpcName, as is the case with non-BatchRPCMode. + if (BatchRPCMode) { + Debug.Assert(originalRpcRequest.parameters != null && originalRpcRequest.parameters.Length > 0, + "originalRpcRequest didn't have at-least 1 parameter in BatchRPCMode, in PrepareDescribeParameterEncryptionRequest."); + text = (string)originalRpcRequest.parameters[0].Value; + sqlParam = GetSqlParameterWithQueryText(text); + } + else { + text = originalRpcRequest.rpcName; + if (CommandType == Data.CommandType.StoredProcedure) { + // For stored procedures, we need to prepare @tsql in the following format + // N'EXEC sp_name @param1=@param1, @param1=@param2, ..., @paramN=@paramN' + sqlParam = BuildStoredProcedureStatementForColumnEncryption(text, originalRpcRequest.parameters); + } + else { + sqlParam = GetSqlParameterWithQueryText(text); + } + } + + Debug.Assert(text != null, "@tsql parameter is null in PrepareDescribeParameterEncryptionRequest."); + + describeParameterEncryptionRequest.parameters[0] = sqlParam; + string parameterList = null; + + // In BatchRPCMode, the input parameters start at parameters[1]. parameters[0] is the T-SQL statement. rpcName is sp_executesql. + // And it is already in the format expected out of BuildParamList, which is not the case with Non-BatchRPCMode. + if (BatchRPCMode) { + if (originalRpcRequest.parameters.Length > 1) { + parameterList = (string)originalRpcRequest.parameters[1].Value; + } + } + else { + // Prepare @params parameter + // Need to create new parameters as we cannot have the same parameter being part of two SqlCommand objects + SqlParameter paramCopy; + SqlParameterCollection tempCollection = new SqlParameterCollection(); + + for (int i = 0; i < _parameters.Count; i++) { + SqlParameter param = originalRpcRequest.parameters[i]; + paramCopy = new SqlParameter(param.ParameterName, param.SqlDbType, param.Size, param.Direction, param.Precision, param.Scale, param.SourceColumn, param.SourceVersion, + param.SourceColumnNullMapping, param.Value, param.XmlSchemaCollectionDatabase, param.XmlSchemaCollectionOwningSchema, param.XmlSchemaCollectionName); + tempCollection.Add(paramCopy); + } + + Debug.Assert(_stateObj == null, "_stateObj should be null at this time, in PrepareDescribeParameterEncryptionRequest."); + Debug.Assert(_activeConnection != null, "_activeConnection should not be null at this time, in PrepareDescribeParameterEncryptionRequest."); + TdsParser tdsParser = null; + + if (_activeConnection.Parser != null) { + tdsParser = _activeConnection.Parser; + if ((tdsParser == null) || (tdsParser.State == TdsParserState.Broken) || (tdsParser.State == TdsParserState.Closed)) { + // Connection's parser is null as well, therefore we must be closed + throw ADP.ClosedConnectionError(); + } + } + + parameterList = BuildParamList(tdsParser, tempCollection, includeReturnValue:true); + } + + Debug.Assert(!string.IsNullOrWhiteSpace(parameterList), "parameterList should not be null or empty or whitespace."); + + sqlParam = new SqlParameter(null, ((parameterList.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText, parameterList.Length); + sqlParam.Value = parameterList; + describeParameterEncryptionRequest.parameters[1] = sqlParam; + } + + /// <summary> + /// Read the output of sp_describe_parameter_encryption + /// </summary> + /// <param name="ds">Resultset from calling to sp_describe_parameter_encryption</param> + /// <param name="describeParameterEncryptionRpcOriginalRpcMap"> Readonly dictionary with the map of parameter encryption rpc requests with the corresponding original rpc requests.</param> + private void ReadDescribeEncryptionParameterResults(SqlDataReader ds, ReadOnlyDictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcMap) { + _SqlRPC rpc = null; + int currentOrdinal = -1; + SqlTceCipherInfoEntry cipherInfoEntry; + Dictionary<int, SqlTceCipherInfoEntry> columnEncryptionKeyTable = new Dictionary<int, SqlTceCipherInfoEntry>(); + + Debug.Assert((describeParameterEncryptionRpcOriginalRpcMap != null) == BatchRPCMode, + "describeParameterEncryptionRpcOriginalRpcMap should be non-null if and only if it is BatchRPCMode."); + + // Indicates the current result set we are reading, used in BatchRPCMode, where we can have more than 1 result set. + int resultSetSequenceNumber = 0; + +#if DEBUG + // Keep track of the number of rows in the result sets. + int rowsAffected = 0; +#endif + + // A flag that used in BatchRPCMode, to assert the result of lookup in to the dictionary maintaining the map of describe parameter encryption requests + // and the corresponding original rpc requests. + bool lookupDictionaryResult; + + do { + if (BatchRPCMode) { + // If we got more RPC results from the server than what was requested. + if (resultSetSequenceNumber >= _sqlRPCParameterEncryptionReqArray.Length) { + Debug.Assert(false, "Server sent back more results than what was expected for describe parameter encryption requests in BatchRPCMode."); + // Ignore the rest of the results from the server, if for whatever reason it sends back more than what we expect. + break; + } + } + + // First read the column encryption key list + while (ds.Read()) { + +#if DEBUG + rowsAffected++; +#endif + + // Column Encryption Key Ordinal. + currentOrdinal = ds.GetInt32((int)DescribeParameterEncryptionResultSet1.KeyOrdinal); + Debug.Assert(currentOrdinal >= 0, "currentOrdinal cannot be negative."); + + // Try to see if there was already an entry for the current ordinal. + if (!columnEncryptionKeyTable.TryGetValue(currentOrdinal, out cipherInfoEntry)) { + // If an entry for this ordinal was not found, create an entry in the columnEncryptionKeyTable for this ordinal. + cipherInfoEntry = new SqlTceCipherInfoEntry(currentOrdinal); + columnEncryptionKeyTable.Add(currentOrdinal, cipherInfoEntry); + } + + Debug.Assert(!cipherInfoEntry.Equals(default(SqlTceCipherInfoEntry)), "cipherInfoEntry should not be un-initialized."); + + // Read the CEK. + byte[] encryptedKey = null; + int encryptedKeyLength = (int)ds.GetBytes((int)DescribeParameterEncryptionResultSet1.EncryptedKey, 0, encryptedKey, 0, 0); + encryptedKey = new byte[encryptedKeyLength]; + ds.GetBytes((int)DescribeParameterEncryptionResultSet1.EncryptedKey, 0, encryptedKey, 0, encryptedKeyLength); + + // Read the metadata version of the key. + // It should always be 8 bytes. + byte[] keyMdVersion = new byte[8]; + ds.GetBytes((int)DescribeParameterEncryptionResultSet1.KeyMdVersion, 0, keyMdVersion, 0, keyMdVersion.Length); + + // Validate the provider name + string providerName = ds.GetString((int)DescribeParameterEncryptionResultSet1.ProviderName); + //SqlColumnEncryptionKeyStoreProvider keyStoreProvider; + //if (!SqlConnection.TryGetColumnEncryptionKeyStoreProvider (providerName, out keyStoreProvider)) { + // // unknown provider, skip processing this cek. + // Bid.Trace("<sc.SqlCommand.ReadDescribeEncryptionParameterResults|INFO>Unknown provider name recevied %s, skipping\n", providerName); + // continue; + //} + + cipherInfoEntry.Add(encryptedKey: encryptedKey, + databaseId: ds.GetInt32((int)DescribeParameterEncryptionResultSet1.DbId), + cekId: ds.GetInt32((int)DescribeParameterEncryptionResultSet1.KeyId), + cekVersion: ds.GetInt32((int)DescribeParameterEncryptionResultSet1.KeyVersion), + cekMdVersion: keyMdVersion, + keyPath: ds.GetString((int)DescribeParameterEncryptionResultSet1.KeyPath), + keyStoreName: providerName, + algorithmName: ds.GetString((int)DescribeParameterEncryptionResultSet1.KeyEncryptionAlgorithm)); + } + + if (!ds.NextResult()) { + throw SQL.UnexpectedDescribeParamFormat (); + } + + // Find the RPC command that generated this tce request + if (BatchRPCMode) { + Debug.Assert(_sqlRPCParameterEncryptionReqArray[resultSetSequenceNumber] != null, "_sqlRPCParameterEncryptionReqArray[resultSetSequenceNumber] should not be null."); + + // Lookup in the dictionary to get the original rpc request corresponding to the describe parameter encryption request + // pointed to by _sqlRPCParameterEncryptionReqArray[resultSetSequenceNumber] + rpc = null; + lookupDictionaryResult = describeParameterEncryptionRpcOriginalRpcMap.TryGetValue(_sqlRPCParameterEncryptionReqArray[resultSetSequenceNumber++], out rpc); + + Debug.Assert(lookupDictionaryResult, + "Describe Parameter Encryption RPC request key must be present in the dictionary describeParameterEncryptionRpcOriginalRpcMap"); + Debug.Assert(rpc != null, + "Describe Parameter Encryption RPC request's corresponding original rpc request must not be null in the dictionary describeParameterEncryptionRpcOriginalRpcMap"); + } + else { + rpc = _rpcArrayOf1[0]; + } + + Debug.Assert(rpc != null, "rpc should not be null here."); + + // This is the index in the parameters array where the actual parameters start. + // In BatchRPCMode, parameters[0] has the t-sql, parameters[1] has the param list + // and actual parameters of the query start at parameters[2]. + int parameterStartIndex = (BatchRPCMode ? 2 : 0); + + // Iterate over the parameter names to read the encryption type info + int paramIdx; + while (ds.Read()) { +#if DEBUG + rowsAffected++; +#endif + Debug.Assert(rpc != null, "Describe Parameter Encryption requested for non-tce spec proc"); + string parameterName = ds.GetString((int)DescribeParameterEncryptionResultSet2.ParameterName); + + // When the RPC object gets reused, the parameter array has more parameters that the valid params for the command. + // Null is used to indicate the end of the valid part of the array. Refer to GetRPCObject(). + for (paramIdx = parameterStartIndex; paramIdx < rpc.parameters.Length && rpc.parameters[paramIdx] != null; paramIdx++) { + SqlParameter sqlParameter = rpc.parameters[paramIdx]; + Debug.Assert(sqlParameter != null, "sqlParameter should not be null."); + + if (sqlParameter.ParameterNameFixed.Equals(parameterName, StringComparison.Ordinal)) { + Debug.Assert(sqlParameter.CipherMetadata == null, "param.CipherMetadata should be null."); + sqlParameter.HasReceivedMetadata = true; + + // Found the param, setup the encryption info. + byte columnEncryptionType = ds.GetByte((int)DescribeParameterEncryptionResultSet2.ColumnEncrytionType); + if ((byte)SqlClientEncryptionType.PlainText != columnEncryptionType) { + byte cipherAlgorithmId = ds.GetByte((int)DescribeParameterEncryptionResultSet2.ColumnEncryptionAlgorithm); + int columnEncryptionKeyOrdinal = ds.GetInt32((int)DescribeParameterEncryptionResultSet2.ColumnEncryptionKeyOrdinal); + byte columnNormalizationRuleVersion = ds.GetByte((int)DescribeParameterEncryptionResultSet2.NormalizationRuleVersion); + + // Lookup the key, failing which throw an exception + if (!columnEncryptionKeyTable.TryGetValue(columnEncryptionKeyOrdinal, out cipherInfoEntry)) { + throw SQL.InvalidEncryptionKeyOrdinal(columnEncryptionKeyOrdinal, columnEncryptionKeyTable.Count); + } + + sqlParameter.CipherMetadata = new SqlCipherMetadata(sqlTceCipherInfoEntry: cipherInfoEntry, + ordinal: unchecked((ushort)-1), + cipherAlgorithmId: cipherAlgorithmId, + cipherAlgorithmName: null, + encryptionType: columnEncryptionType, + normalizationRuleVersion: columnNormalizationRuleVersion); + + // Decrypt the symmetric key.(This will also validate and throw if needed). + Debug.Assert(_activeConnection != null, @"_activeConnection should not be null"); + SqlSecurityUtility.DecryptSymmetricKey(sqlParameter.CipherMetadata, this._activeConnection.DataSource); + + // This is effective only for BatchRPCMode even though we set it for non-BatchRPCMode also, + // since for non-BatchRPCMode mode, paramoptions gets thrown away and reconstructed in BuildExecuteSql. + rpc.paramoptions[paramIdx] |= TdsEnums.RPC_PARAM_ENCRYPTED; + } + + break; + } + } + } + + // When the RPC object gets reused, the parameter array has more parameters that the valid params for the command. + // Null is used to indicate the end of the valid part of the array. Refer to GetRPCObject(). + for (paramIdx = parameterStartIndex; paramIdx < rpc.parameters.Length && rpc.parameters[paramIdx] != null; paramIdx++) { + if (!rpc.parameters[paramIdx].HasReceivedMetadata && rpc.parameters[paramIdx].Direction != ParameterDirection.ReturnValue) { + // Encryption MD wasn't sent by the server - we expect the metadata to be sent for all the parameters + // that were sent in the original sp_describe_parameter_encryption but not necessarily for return values, + // since there might be multiple return values but server will only send for one of them. + // For parameters that don't need encryption, the encryption type is set to plaintext. + throw SQL.ParamEncryptionMetadataMissing(rpc.parameters[paramIdx].ParameterName, rpc.GetCommandTextOrRpcName()); + } + } + +#if DEBUG + Debug.Assert(rowsAffected == RowsAffectedByDescribeParameterEncryption, + "number of rows received for describe parameter encryption should be equal to rows affected by describe parameter encryption."); +#endif + + // The server has responded with encryption related information for this rpc request. So clear the needsFetchParameterEncryptionMetadata flag. + rpc.needsFetchParameterEncryptionMetadata = false; + } while (ds.NextResult()); + + // Verify that we received response for each rpc call needs tce + if (BatchRPCMode) { + for (int i = 0; i < _SqlRPCBatchArray.Length; i++) { + if (_SqlRPCBatchArray[i].needsFetchParameterEncryptionMetadata) { + throw SQL.ProcEncryptionMetadataMissing(_SqlRPCBatchArray[i].rpcName); + } + } + } + } internal SqlDataReader RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, bool returnStream, string method) { Task unused; // sync execution @@ -2773,6 +3731,7 @@ namespace System.Data.SqlClient { task = null; _rowsAffected = -1; + _rowsAffectedBySpDescribeParameterEncryption = -1; if (0 != (CommandBehavior.SingleRow & cmdBehavior)) { // CommandBehavior.SingleRow implies CommandBehavior.SingleResult @@ -2810,10 +3769,20 @@ namespace System.Data.SqlClient { } } + // Reset the encryption related state of the command and its parameters. + ResetEncryptionState(); if ( _activeConnection.IsContextConnection ) { return RunExecuteReaderSmi( cmdBehavior, runBehavior, returnStream ); } + else if (IsColumnEncryptionEnabled) { + Task returnTask = null; + PrepareForTransparentEncryption(cmdBehavior, returnStream, async, timeout, completion, out returnTask, asyncWrite && async); + Debug.Assert(async == (returnTask != null), @"returnTask should be null if and only if async is false."); + + return RunExecuteReaderTdsWithTransparentParameterEncryption( cmdBehavior, runBehavior, returnStream, async, timeout, out task, asyncWrite && async, ds: null, + describeParameterEncryptionRequest: false, describeParameterEncryptionTask: returnTask); + } else { return RunExecuteReaderTds( cmdBehavior, runBehavior, returnStream, async, timeout, out task, asyncWrite && async); } @@ -2840,7 +3809,73 @@ namespace System.Data.SqlClient { } } - private SqlDataReader RunExecuteReaderTds( CommandBehavior cmdBehavior, RunBehavior runBehavior, bool returnStream, bool async, int timeout, out Task task, bool asyncWrite, SqlDataReader ds=null) { + /// <summary> + /// RunExecuteReaderTds after Transparent Parameter Encryption is complete. + /// </summary> + /// <param name="cmdBehavior"></param> + /// <param name="runBehavior"></param> + /// <param name="returnStream"></param> + /// <param name="async"></param> + /// <param name="timeout"></param> + /// <param name="task"></param> + /// <param name="asyncWrite"></param> + /// <param name="ds"></param> + /// <param name="describeParameterEncryptionRequest"></param> + /// <param name="describeParameterEncryptionTask"></param> + /// <returns></returns> + private SqlDataReader RunExecuteReaderTdsWithTransparentParameterEncryption(CommandBehavior cmdBehavior, + RunBehavior runBehavior, + bool returnStream, + bool async, + int timeout, + out Task task, + bool asyncWrite, + SqlDataReader ds=null, + bool describeParameterEncryptionRequest = false, + Task describeParameterEncryptionTask = null) { + Debug.Assert(!asyncWrite || async, "AsyncWrite should be always accompanied by Async"); + Debug.Assert((describeParameterEncryptionTask != null) == async, @"async should be true if and only if describeParameterEncryptionTask is not null."); + + if (ds == null && returnStream) { + ds = new SqlDataReader(this, cmdBehavior); + } + + if (describeParameterEncryptionTask != null) { + long parameterEncryptionStart = ADP.TimerCurrent(); + TaskCompletionSource<object> completion = new TaskCompletionSource<object>(); + AsyncHelper.ContinueTask(describeParameterEncryptionTask, completion, + () => { + Task subTask = null; + RunExecuteReaderTds(cmdBehavior, runBehavior, returnStream, async, TdsParserStaticMethods.GetRemainingTimeout(timeout, parameterEncryptionStart), out subTask, asyncWrite, ds); + if (subTask == null) { + completion.SetResult(null); + } + else { + AsyncHelper.ContinueTask(subTask, completion, () => completion.SetResult(null)); + } + }, connectionToDoom: null, + onFailure: ((exception) => { + if (_cachedAsyncState != null) { + _cachedAsyncState.ResetAsyncState(); + } + if (exception != null) { + throw exception; + }}), + onCancellation: (() => { + if (_cachedAsyncState != null) { + _cachedAsyncState.ResetAsyncState(); + }}), + connectionToAbort: _activeConnection); + task = completion.Task; + return ds; + } + else { + // Synchronous execution. + return RunExecuteReaderTds(cmdBehavior, runBehavior, returnStream, async, timeout, out task, asyncWrite, ds); + } + } + + private SqlDataReader RunExecuteReaderTds( CommandBehavior cmdBehavior, RunBehavior runBehavior, bool returnStream, bool async, int timeout, out Task task, bool asyncWrite, SqlDataReader ds=null, bool describeParameterEncryptionRequest = false) { Debug.Assert(!asyncWrite || async, "AsyncWrite should be always accompanied by Async"); if (ds == null && returnStream) { @@ -2912,7 +3947,17 @@ namespace System.Data.SqlClient { GetStateObject(); Task writeTask = null; - if (BatchRPCMode) { + if (describeParameterEncryptionRequest) { +#if DEBUG + if (_sleepDuringRunExecuteReaderTdsForSpDescribeParameterEncryption) { + Thread.Sleep(10000); + } +#endif + + Debug.Assert(_sqlRPCParameterEncryptionReqArray != null, "RunExecuteReader rpc array not provided for describe parameter encryption request."); + writeTask = _stateObj.Parser.TdsExecuteRPC(this, _sqlRPCParameterEncryptionReqArray, timeout, inSchema, this.Notification, _stateObj, CommandType.StoredProcedure == CommandType, sync: !asyncWrite); + } + else if (BatchRPCMode) { Debug.Assert(inSchema == false, "Batch RPC does not support schema only command beahvior"); Debug.Assert(!IsPrepared, "Batch RPC should not be prepared!"); Debug.Assert(!IsDirty, "Batch RPC should not be marked as dirty!"); @@ -2921,7 +3966,7 @@ namespace System.Data.SqlClient { // Bid.Trace("<sc.SqlCommand.ExecuteReader|INFO> %d#, Command executed as batch RPC.\n", ObjectID); //} Debug.Assert(_SqlRPCBatchArray != null, "RunExecuteReader rpc array not provided"); - writeTask = _stateObj.Parser.TdsExecuteRPC(_SqlRPCBatchArray, timeout, inSchema, this.Notification, _stateObj, CommandType.StoredProcedure == CommandType, sync: !asyncWrite ); + writeTask = _stateObj.Parser.TdsExecuteRPC(this, _SqlRPCBatchArray, timeout, inSchema, this.Notification, _stateObj, CommandType.StoredProcedure == CommandType, sync: !asyncWrite ); } else if ((System.Data.CommandType.Text == this.CommandType) && (0 == GetParameterCount(_parameters))) { // Send over SQL Batch command if we are not a stored proc and have no parameters @@ -2975,7 +4020,7 @@ namespace System.Data.SqlClient { // Debug.Assert(_rpcArrayOf1[0] == rpc); - writeTask = _stateObj.Parser.TdsExecuteRPC(_rpcArrayOf1, timeout, inSchema, this.Notification, _stateObj, CommandType.StoredProcedure == CommandType, sync:!asyncWrite); + writeTask = _stateObj.Parser.TdsExecuteRPC(this, _rpcArrayOf1, timeout, inSchema, this.Notification, _stateObj, CommandType.StoredProcedure == CommandType, sync:!asyncWrite); } else { Debug.Assert(this.CommandType == System.Data.CommandType.StoredProcedure, "unknown command type!"); @@ -3010,7 +4055,7 @@ namespace System.Data.SqlClient { // execute sp Debug.Assert(_rpcArrayOf1[0] == rpc); - writeTask=_stateObj.Parser.TdsExecuteRPC(_rpcArrayOf1, timeout, inSchema, this.Notification, _stateObj, CommandType.StoredProcedure == CommandType, sync:!asyncWrite); + writeTask=_stateObj.Parser.TdsExecuteRPC(this, _rpcArrayOf1, timeout, inSchema, this.Notification, _stateObj, CommandType.StoredProcedure == CommandType, sync:!asyncWrite); } Debug.Assert(writeTask == null || async, "Returned task in sync mode"); @@ -3118,7 +4163,7 @@ namespace System.Data.SqlClient { } if (null != eventStream) { - eventStream.Close( EventSink ); // UNDONE: should cancel instead! + eventStream.Close( EventSink ); // } if (requestExecutor != null) { @@ -3259,6 +4304,16 @@ namespace System.Data.SqlClient { // Ensure that the connection is open and that the Parser is in the correct state SqlInternalConnectionTds tdsConnection = _activeConnection.InnerConnection as SqlInternalConnectionTds; + + // Ensure that if column encryption override was used then server supports its + if (((SqlCommandColumnEncryptionSetting.UseConnectionSetting == ColumnEncryptionSetting && _activeConnection.IsColumnEncryptionSettingEnabled) + || (ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled || ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.ResultSetOnly)) + && null != tdsConnection + && null != tdsConnection.Parser + && !tdsConnection.Parser.IsColumnEncryptionSupported) { + throw SQL.TceNotSupported (); + } + if (tdsConnection != null) { var parser = tdsConnection.Parser; if ((parser == null) || (parser.State == TdsParserState.Closed)) { @@ -3449,6 +4504,48 @@ namespace System.Data.SqlClient { } } + /// <summary> + /// IMPORTANT NOTE: This is created as a copy of OnDoneProc below for Transparent Column Encryption improvement + /// as there is not much time, to address regressions. Will revisit removing the duplication, when we have time again. + /// </summary> + internal void OnDoneDescribeParameterEncryptionProc(TdsParserStateObject stateObj) { + // called per rpc batch complete + if (BatchRPCMode) { + // track the records affected for the just completed rpc batch + // _rowsAffected is cumulative for ExecuteNonQuery across all rpc batches + _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].cumulativeRecordsAffected = _rowsAffected; + + _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].recordsAffected = + (((0 < _currentlyExecutingDescribeParameterEncryptionRPC) && (0 <= _rowsAffected)) + ? (_rowsAffected - Math.Max(_sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC - 1].cumulativeRecordsAffected, 0)) + : _rowsAffected); + + // track the error collection (not available from TdsParser after ExecuteNonQuery) + // and the which errors are associated with the just completed rpc batch + _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].errorsIndexStart = + ((0 < _currentlyExecutingDescribeParameterEncryptionRPC) + ? _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC - 1].errorsIndexEnd + : 0); + _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].errorsIndexEnd = stateObj.ErrorCount; + _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].errors = stateObj._errors; + + // track the warning collection (not available from TdsParser after ExecuteNonQuery) + // and the which warnings are associated with the just completed rpc batch + _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].warningsIndexStart = + ((0 < _currentlyExecutingDescribeParameterEncryptionRPC) + ? _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC - 1].warningsIndexEnd + : 0); + _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].warningsIndexEnd = stateObj.WarningCount; + _sqlRPCParameterEncryptionReqArray[_currentlyExecutingDescribeParameterEncryptionRPC].warnings = stateObj._warnings; + + _currentlyExecutingDescribeParameterEncryptionRPC++; + } + } + + /// <summary> + /// IMPORTANT NOTE: There is a copy of this function above in OnDoneDescribeParameterEncryptionProc. + /// Please consider the changes being done in this function for the above function as well. + /// </summary> internal void OnDoneProc() { // called per rpc batch complete if (BatchRPCMode) { @@ -3492,6 +4589,10 @@ namespace System.Data.SqlClient { if (_inPrepare) return; + // Don't set the return status if this is the status for sp_describe_parameter_encryption. + if (IsDescribeParameterEncryptionRPCCurrentlyInProgress) + return; + SqlParameterCollection parameters = _parameters; if (BatchRPCMode) { if (_parameterCollectionList.Count > _currentlyExecutingBatch) { @@ -3528,7 +4629,7 @@ namespace System.Data.SqlClient { // If named, match the parameter name, otherwise fill in based on ordinal position. // If the parameter is not bound, then ignore the return value. // - internal void OnReturnValue(SqlReturnValue rec) { + internal void OnReturnValue(SqlReturnValue rec, TdsParserStateObject stateObj) { if (_inPrepare) { if (!rec.value.IsNull) { @@ -3545,65 +4646,114 @@ namespace System.Data.SqlClient { SqlParameter thisParam = GetParameterForOutputValueExtraction(parameters, rec.parameter, count); if (null != thisParam) { - // copy over data + // If the parameter's direction is InputOutput, Output or ReturnValue and it needs to be transparently encrypted/decrypted + // then simply decrypt, deserialize and set the value. + if (rec.cipherMD != null && + thisParam.CipherMetadata != null && + (thisParam.Direction == ParameterDirection.Output || + thisParam.Direction == ParameterDirection.InputOutput || + thisParam.Direction == ParameterDirection.ReturnValue)) { + if(rec.tdsType != TdsEnums.SQLBIGVARBINARY) { + throw SQL.InvalidDataTypeForEncryptedParameter(thisParam.ParameterNameFixed, rec.tdsType, TdsEnums.SQLBIGVARBINARY); + } - // if the value user has supplied a SqlType class, then just copy over the SqlType, otherwise convert - // to the com type - object val = thisParam.Value; + // Decrypt the ciphertext + TdsParser parser = _activeConnection.Parser; + if ((parser == null) || (parser.State == TdsParserState.Closed) || (parser.State == TdsParserState.Broken)) { + throw ADP.ClosedConnectionError(); + } - //set the UDT value as typed object rather than bytes - if (SqlDbType.Udt == thisParam.SqlDbType) { - object data = null; - try { - Connection.CheckGetExtendedUDTInfo(rec, true); + if (!rec.value.IsNull) { + try { + Debug.Assert(_activeConnection != null, @"_activeConnection should not be null"); - //extract the byte array from the param value - if (rec.value.IsNull) - data = DBNull.Value; - else { - data = rec.value.ByteArray; //should work for both sql and non-sql values - } + // Get the key information from the parameter and decrypt the value. + rec.cipherMD.EncryptionInfo = thisParam.CipherMetadata.EncryptionInfo; + byte[] unencryptedBytes = SqlSecurityUtility.DecryptWithKey(rec.value.ByteArray, rec.cipherMD, _activeConnection.DataSource); - //call the connection to instantiate the UDT object - thisParam.Value = Connection.GetUdtValue(data, rec, false); - } - catch (FileNotFoundException e) { - // SQL BU DT 329981 - // Assign Assembly.Load failure in case where assembly not on client. - // This allows execution to complete and failure on SqlParameter.Value. - thisParam.SetUdtLoadError(e); + if (unencryptedBytes != null) { + // Denormalize the value and convert it to the parameter type. + SqlBuffer buffer = new SqlBuffer(); + parser.DeserializeUnencryptedValue(buffer, unencryptedBytes, rec, stateObj, rec.NormalizationRuleVersion); + thisParam.SetSqlBuffer(buffer); + } + } + catch (Exception e) { + throw SQL.ParamDecryptionFailed(thisParam.ParameterNameFixed, null, e); + } } - catch (FileLoadException e) { - // SQL BU DT 329981 - // Assign Assembly.Load failure in case where assembly cannot be loaded on client. - // This allows execution to complete and failure on SqlParameter.Value. - thisParam.SetUdtLoadError(e); + else { + // Create a new SqlBuffer and set it to null + // Note: We can't reuse the SqlBuffer in "rec" below since it's already been set (to varbinary) + // in previous call to TryProcessReturnValue(). + // Note 2: We will be coming down this code path only if the Command Setting is set to use TCE. + // We pass the command setting as TCE enabled in the below call for this reason. + SqlBuffer buff = new SqlBuffer(); + TdsParser.GetNullSqlValue(buff, rec, SqlCommandColumnEncryptionSetting.Enabled, parser.Connection); + thisParam.SetSqlBuffer(buff); } - - return; - } else { - thisParam.SetSqlBuffer(rec.value); } + else { + // copy over data - MetaType mt = MetaType.GetMetaTypeFromSqlDbType(rec.type, rec.isMultiValued); + // if the value user has supplied a SqlType class, then just copy over the SqlType, otherwise convert + // to the com type + object val = thisParam.Value; - if (rec.type == SqlDbType.Decimal) { - thisParam.ScaleInternal = rec.scale; - thisParam.PrecisionInternal = rec.precision; - } - else if (mt.IsVarTime) { - thisParam.ScaleInternal = rec.scale; - } - else if (rec.type == SqlDbType.Xml) { - SqlCachedBuffer cachedBuffer = (thisParam.Value as SqlCachedBuffer); - if (null != cachedBuffer) { - thisParam.Value = cachedBuffer.ToString(); + //set the UDT value as typed object rather than bytes + if (SqlDbType.Udt == thisParam.SqlDbType) { + object data = null; + try { + Connection.CheckGetExtendedUDTInfo(rec, true); + + //extract the byte array from the param value + if (rec.value.IsNull) + data = DBNull.Value; + else { + data = rec.value.ByteArray; //should work for both sql and non-sql values + } + + //call the connection to instantiate the UDT object + thisParam.Value = Connection.GetUdtValue(data, rec, false); + } + catch (FileNotFoundException e) { + // SQL BU DT 329981 + // Assign Assembly.Load failure in case where assembly not on client. + // This allows execution to complete and failure on SqlParameter.Value. + thisParam.SetUdtLoadError(e); + } + catch (FileLoadException e) { + // SQL BU DT 329981 + // Assign Assembly.Load failure in case where assembly cannot be loaded on client. + // This allows execution to complete and failure on SqlParameter.Value. + thisParam.SetUdtLoadError(e); + } + + return; + } else { + thisParam.SetSqlBuffer(rec.value); } - } - if (rec.collation != null) { - Debug.Assert(mt.IsCharType, "Invalid collation structure for non-char type"); - thisParam.Collation = rec.collation; + MetaType mt = MetaType.GetMetaTypeFromSqlDbType(rec.type, rec.isMultiValued); + + if (rec.type == SqlDbType.Decimal) { + thisParam.ScaleInternal = rec.scale; + thisParam.PrecisionInternal = rec.precision; + } + else if (mt.IsVarTime) { + thisParam.ScaleInternal = rec.scale; + } + else if (rec.type == SqlDbType.Xml) { + SqlCachedBuffer cachedBuffer = (thisParam.Value as SqlCachedBuffer); + if (null != cachedBuffer) { + thisParam.Value = cachedBuffer.ToString(); + } + } + + if (rec.collation != null) { + Debug.Assert(mt.IsCharType, "Invalid collation structure for non-char type"); + thisParam.Collation = rec.collation; + } } } @@ -3699,16 +4849,25 @@ namespace System.Data.SqlClient { return null; } - private void GetRPCObject(int paramCount, ref _SqlRPC rpc) { - + private void GetRPCObject(int paramCount, ref _SqlRPC rpc, bool forSpDescribeParameterEncryption = false) { // Designed to minimize necessary allocations int ii; if (rpc == null) { - if (_rpcArrayOf1 == null) { - _rpcArrayOf1 = new _SqlRPC[1]; - _rpcArrayOf1[0] = new _SqlRPC(); + if (!forSpDescribeParameterEncryption) { + if (_rpcArrayOf1 == null) { + _rpcArrayOf1 = new _SqlRPC[1]; + _rpcArrayOf1[0] = new _SqlRPC(); + } + + rpc = _rpcArrayOf1[0]; + } + else { + if (_rpcForEncryption == null) { + _rpcForEncryption = new _SqlRPC(); + } + + rpc = _rpcForEncryption; } - rpc = _rpcArrayOf1[0] ; } rpc.ProcID = 0; @@ -3725,6 +4884,7 @@ namespace System.Data.SqlClient { rpc.warningsIndexStart = 0; rpc.warningsIndexEnd = 0; rpc.warnings = null; + rpc.needsFetchParameterEncryptionMetadata = false; // Make sure there is enough space in the parameters and paramoptions arrays if(rpc.parameters == null || rpc.parameters.Length < paramCount) { @@ -3767,6 +4927,11 @@ namespace System.Data.SqlClient { parameter.Direction == ParameterDirection.Output) rpc.paramoptions[j] = TdsEnums.RPC_PARAM_BYREF; + // Set the encryped bit, if the parameter is to be encrypted. + if (parameter.CipherMetadata != null) { + rpc.paramoptions[j] |= TdsEnums.RPC_PARAM_ENCRYPTED; + } + // set default value bit if (parameter.Direction != ParameterDirection.Output) { // remember that null == Convert.IsEmpty, DBNull.Value is a database null! @@ -3832,11 +4997,11 @@ namespace System.Data.SqlClient { // returns true if the parameter is not a return value // and it's value is not DBNull (for a nullable parameter) // - private static bool ShouldSendParameter(SqlParameter p) { + private static bool ShouldSendParameter(SqlParameter p, bool includeReturnValue = false) { switch (p.Direction) { case ParameterDirection.ReturnValue: - // return value parameters are never sent - return false; + // return value parameters are not sent, except for the parameter list of sp_describe_parameter_encryption + return includeReturnValue; case ParameterDirection.Output: case ParameterDirection.InputOutput: case ParameterDirection.Input: @@ -3976,12 +5141,96 @@ namespace System.Data.SqlClient { } } + /// <summary> + /// This function constructs a string parameter containing the exec statement in the following format + /// N'EXEC sp_name @param1=@param1, @param1=@param2, ..., @paramN=@paramN' + /// + + + + + private SqlParameter BuildStoredProcedureStatementForColumnEncryption(string storedProcedureName, SqlParameter[] parameters) { + Debug.Assert(CommandType == CommandType.StoredProcedure, "BuildStoredProcedureStatementForColumnEncryption() should only be called for stored procedures"); + Debug.Assert(!string.IsNullOrWhiteSpace(storedProcedureName), "storedProcedureName cannot be null or empty in BuildStoredProcedureStatementForColumnEncryption"); + Debug.Assert(parameters != null, "parameters cannot be null in BuildStoredProcedureStatementForColumnEncryption"); + + StringBuilder execStatement = new StringBuilder(); + execStatement.Append(@"EXEC "); + + // Find the return value parameter (if any). + SqlParameter returnValueParameter = null; + foreach (SqlParameter parameter in parameters) { + if (parameter.Direction == ParameterDirection.ReturnValue) { + returnValueParameter = parameter; + break; + } + } + + // If there is a return value parameter we need to assign the result to it. + // EXEC @returnValue = moduleName [parameters] + if (returnValueParameter != null) { + execStatement.AppendFormat(@"{0}=", returnValueParameter.ParameterNameFixed); + } + + execStatement.Append(ParseAndQuoteIdentifier(storedProcedureName, false)); + + // Build parameter list in the format + // @param1=@param1, @param1=@param2, ..., @paramn=@paramn + + // Append the first parameter + int i = 0; + + if(parameters.Count() > 0) { + // Skip the return value parameters. + while (i < parameters.Count() && parameters[i].Direction == ParameterDirection.ReturnValue) { + i++; + } + + if (i < parameters.Count()) { + // Possibility of a SQL Injection issue through parameter names and how to construct valid identifier for parameters. + // Since the parameters comes from application itself, there should not be a security vulnerability. + // Also since the query is not executed, but only analyzed there is no possibility for elevation of priviledge, but only for + // incorrect results which would only affect the user that attempts the injection. + execStatement.AppendFormat(@" {0}={0}", parameters[i].ParameterNameFixed); + + // InputOutput and Output parameters need to be marked as such. + if (parameters[i].Direction == ParameterDirection.Output || + parameters[i].Direction == ParameterDirection.InputOutput) { + execStatement.AppendFormat(@" OUTPUT"); + } + } + } + + // Move to the next parameter. + i++; + + // Append the rest of parameters + for (; i < parameters.Count(); i++) { + if (parameters[i].Direction != ParameterDirection.ReturnValue) { + execStatement.AppendFormat(@", {0}={0}", parameters[i].ParameterNameFixed); + + // InputOutput and Output parameters need to be marked as such. + if (parameters[i].Direction == ParameterDirection.Output || + parameters[i].Direction == ParameterDirection.InputOutput) { + execStatement.AppendFormat(@" OUTPUT"); + } + } + } + + // Construct @tsql SqlParameter to be returned + SqlParameter tsqlParameter = new SqlParameter(null, ((execStatement.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT) ? SqlDbType.NVarChar : SqlDbType.NText, execStatement.Length); + tsqlParameter.Value = execStatement.ToString(); + + return tsqlParameter; + } + // paramList parameter for sp_executesql, sp_prepare, and sp_prepexec - internal string BuildParamList(TdsParser parser, SqlParameterCollection parameters) { + internal string BuildParamList(TdsParser parser, SqlParameterCollection parameters, bool includeReturnValue = false) { StringBuilder paramList = new StringBuilder(); bool fAddSeperator = false; bool yukonOrNewer = parser.IsYukonOrNewer; + int count = 0; count = parameters.Count; @@ -3989,7 +5238,7 @@ namespace System.Data.SqlClient { SqlParameter sqlParam = parameters[i]; sqlParam.Validate(i, CommandType.StoredProcedure == CommandType); // skip ReturnValue parameters; we never send them to the server - if (!ShouldSendParameter(sqlParam)) + if (!ShouldSendParameter(sqlParam, includeReturnValue)) continue; // add our separator for the ith parmeter @@ -4090,8 +5339,8 @@ namespace System.Data.SqlClient { } } - // bug 49497, if the user specifies a 0-sized parameter for a variable len field - // pass over max size (8000 bytes or 4000 characters for wide types) + // + if (0 == size) size = mt.IsSizeInCharacters ? (TdsEnums.MAXSIZE >> 1) : TdsEnums.MAXSIZE; @@ -4274,6 +5523,25 @@ namespace System.Data.SqlClient { } } + /// <summary> + /// Get or set the number of records affected by SpDescribeParameterEncryption. + /// The below line is used only for debug asserts and not exposed publicly or impacts functionality otherwise. + /// </summary> + internal int RowsAffectedByDescribeParameterEncryption + { + get { + return _rowsAffectedBySpDescribeParameterEncryption; + } + set { + if (-1 == _rowsAffectedBySpDescribeParameterEncryption) { + _rowsAffectedBySpDescribeParameterEncryption = value; + } + else if (0 < value) { + _rowsAffectedBySpDescribeParameterEncryption += value; + } + } + } + internal int InternalRecordsAffected { get { return _rowsAffected; @@ -4308,6 +5576,16 @@ namespace System.Data.SqlClient { } } + /// <summary> + /// Clear the state in sqlcommand related to describe parameter encryption RPC requests. + /// </summary> + private void ClearDescribeParameterEncryptionRequests() { + _sqlRPCParameterEncryptionReqArray = null; + _currentlyExecutingDescribeParameterEncryptionRPC = 0; + _isDescribeParameterEncryptionRPCCurrentlyInProgress = false; + _rowsAffectedBySpDescribeParameterEncryption = -1; + } + internal void ClearBatchCommand() { List<_SqlRPC> rpcList = _RPCList; if (null != rpcList) { @@ -4320,7 +5598,23 @@ namespace System.Data.SqlClient { _currentlyExecutingBatch = 0; } - internal void AddBatchCommand(string commandText, SqlParameterCollection parameters, CommandType cmdType) { + /// <summary> + /// Set the column encryption setting to the new one. + /// Do not allow conflicting column encryption settings. + /// </summary> + private void SetColumnEncryptionSetting(SqlCommandColumnEncryptionSetting newColumnEncryptionSetting) { + if (!this._wasBatchModeColumnEncryptionSettingSetOnce) { + this._columnEncryptionSetting = newColumnEncryptionSetting; + this._wasBatchModeColumnEncryptionSettingSetOnce = true; + } + else { + if (this._columnEncryptionSetting != newColumnEncryptionSetting) { + throw SQL.BatchedUpdateColumnEncryptionSettingMismatch(); + } + } + } + + internal void AddBatchCommand(string commandText, SqlParameterCollection parameters, CommandType cmdType, SqlCommandColumnEncryptionSetting columnEncryptionSetting) { Debug.Assert(BatchRPCMode, "Command is not in batch RPC Mode"); Debug.Assert(_RPCList != null); Debug.Assert(_parameterCollectionList != null); @@ -4329,6 +5623,10 @@ namespace System.Data.SqlClient { this.CommandText = commandText; this.CommandType = cmdType; + + // Set the column encryption setting. + SetColumnEncryptionSetting(columnEncryptionSetting); + GetStateObject(); if (cmdType == CommandType.StoredProcedure) { BuildRPC(false, parameters, ref rpc); @@ -4537,6 +5835,43 @@ namespace System.Data.SqlClient { return requestExecutor; } + private void WriteBeginExecuteEvent() + { + if (SqlEventSource.Log.IsEnabled() && Connection != null) + { + string commandText = CommandType == CommandType.StoredProcedure ? CommandText : string.Empty; + SqlEventSource.Log.BeginExecute(GetHashCode(), Connection.DataSource, Connection.Database, commandText); + } + } + + /// <summary> + /// Writes and end execute event in Event Source. + /// </summary> + /// <param name="success">True if SQL command finished successfully, otherwise false.</param> + /// <param name="sqlExceptionNumber">Gets a number that identifies the type of error.</param> + /// <param name="synchronous">True if SQL command was executed synchronously, otherwise false.</param> + private void WriteEndExecuteEvent(bool success, int? sqlExceptionNumber, bool synchronous) + { + if (SqlEventSource.Log.IsEnabled()) + { + // SqlEventSource.WriteEvent(int, int, int, int) is faster than provided overload SqlEventSource.WriteEvent(int, object[]). + // that's why trying to fit several booleans in one integer value + + // success state is stored the first bit in compositeState 0x01 + int successFlag = success ? 1 : 0; + + // isSqlException is stored in the 2nd bit in compositeState 0x100 + int isSqlExceptionFlag = sqlExceptionNumber.HasValue ? 2 : 0; + + // synchronous state is stored in the second bit in compositeState 0x10 + int synchronousFlag = synchronous ? 4 : 0; + + int compositeState = successFlag | isSqlExceptionFlag | synchronousFlag; + + SqlEventSource.Log.EndExecute(GetHashCode(), compositeState, sqlExceptionNumber.GetValueOrDefault()); + } + } + #if DEBUG internal void CompletePendingReadWithSuccess(bool resetForcePendingReadsToWait) { var stateObj = _stateObj; diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlCommandBuilder.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlCommandBuilder.cs index 476000e80a5..7563b0c082c 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlCommandBuilder.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlCommandBuilder.cs @@ -2,8 +2,8 @@ // <copyright file="SqlCommandBuilder.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlCommandSet.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlCommandSet.cs index eaf2f2a88a1..913a30f1c2a 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlCommandSet.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlCommandSet.cs @@ -2,8 +2,8 @@ // <copyright file="SqlBatchCommand.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { @@ -35,13 +35,15 @@ namespace System.Data.SqlClient { internal readonly SqlParameterCollection Parameters; internal readonly int ReturnParameterIndex; internal readonly CommandType CmdType; + internal readonly SqlCommandColumnEncryptionSetting ColumnEncryptionSetting; - internal LocalCommand(string commandText, SqlParameterCollection parameters, int returnParameterIndex, CommandType cmdType) { + internal LocalCommand(string commandText, SqlParameterCollection parameters, int returnParameterIndex, CommandType cmdType, SqlCommandColumnEncryptionSetting columnEncryptionSetting) { Debug.Assert(0 <= commandText.Length, "no text"); this.CommandText = commandText; this.Parameters = parameters; this.ReturnParameterIndex = returnParameterIndex; this.CmdType = cmdType; + this.ColumnEncryptionSetting = columnEncryptionSetting; } } @@ -197,7 +199,7 @@ namespace System.Data.SqlClient { } } } - LocalCommand cmd = new LocalCommand(cmdText, parameters, returnParameterIndex, command.CommandType); + LocalCommand cmd = new LocalCommand(cmdText, parameters, returnParameterIndex, command.CommandType, command.ColumnEncryptionSetting); CommandList.Add(cmd); } @@ -260,7 +262,7 @@ namespace System.Data.SqlClient { BatchCommand.Parameters.Clear(); for (int ii = 0 ; ii < _commandList.Count; ii++) { LocalCommand cmd = _commandList[ii]; - BatchCommand.AddBatchCommand(cmd.CommandText, cmd.Parameters, cmd.CmdType); + BatchCommand.AddBatchCommand(cmd.CommandText, cmd.Parameters, cmd.CmdType, cmd.ColumnEncryptionSetting); } return BatchCommand.ExecuteBatchRPCCommand(); } diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnection.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnection.cs index ac04c011fb5..f169a86ecd3 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnection.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnection.cs @@ -2,8 +2,8 @@ // <copyright file="SqlConnection.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("System.Data.DataSetExtensions, PublicKey="+AssemblyRef.EcmaPublicKeyFull)] // DevDiv Bugs 92166 @@ -12,6 +12,9 @@ namespace System.Data.SqlClient { using System; using System.Collections; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Collections.ObjectModel; using System.Configuration.Assemblies; using System.ComponentModel; using System.Data; @@ -22,6 +25,7 @@ namespace System.Data.SqlClient using System.Diagnostics; using System.Globalization; using System.IO; + using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; @@ -44,6 +48,163 @@ namespace System.Data.SqlClient static private readonly object EventInfoMessage = new object(); + // System column encryption key store providers are added by default + static private readonly Dictionary<string, SqlColumnEncryptionKeyStoreProvider> _SystemColumnEncryptionKeyStoreProviders + = new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>(capacity: 1, comparer: StringComparer.OrdinalIgnoreCase) + { + {SqlColumnEncryptionCertificateStoreProvider.ProviderName, new SqlColumnEncryptionCertificateStoreProvider()} + }; + + /// <summary> + /// Custom provider list should be provided by the user. We shallow copy the user supplied dictionary into a ReadOnlyDictionary. + /// Custom provider list can only supplied once per application. + /// </summary> + static private ReadOnlyDictionary<string, SqlColumnEncryptionKeyStoreProvider> _CustomColumnEncryptionKeyStoreProviders; + + // Lock to control setting of _CustomColumnEncryptionKeyStoreProviders + static private readonly Object _CustomColumnEncryptionKeyProvidersLock = new Object(); + + /// <summary> + /// Dictionary object holding trusted key paths for various SQL Servers. + /// Key to the dictionary is a SQL Server Name + /// IList contains a list of trusted key paths. + /// </summary> + static private readonly ConcurrentDictionary<string, IList<string>> _ColumnEncryptionTrustedMasterKeyPaths + = new ConcurrentDictionary<string, IList<string>>(concurrencyLevel: 4 * Environment.ProcessorCount /* default value in ConcurrentDictionary*/, + capacity: 1, + comparer: StringComparer.OrdinalIgnoreCase); + + [ + DefaultValue(null), + ResCategoryAttribute(Res.DataCategory_Data), + ResDescriptionAttribute(Res.TCE_SqlConnection_TrustedColumnMasterKeyPaths), + ] + static public IDictionary<string, IList<string>> ColumnEncryptionTrustedMasterKeyPaths + { + get + { + return _ColumnEncryptionTrustedMasterKeyPaths; + } + } + + /// <summary> + /// This function should only be called once in an app. This does shallow copying of the dictionary so that + /// the app cannot alter the custom provider list once it has been set. + /// + /// Example: + /// + /// Dictionary<string, SqlColumnEncryptionKeyStoreProvider> customKeyStoreProviders = new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>(); + /// MySqlClientHSMProvider myProvider = new MySqlClientHSMProvider(); + /// customKeyStoreProviders.Add(@"HSM Provider", myProvider); + /// SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customKeyStoreProviders); + /// </summary> + /// <param name="customProviders">Custom column encryption key provider dictionary</param> + static public void RegisterColumnEncryptionKeyStoreProviders(IDictionary<string, SqlColumnEncryptionKeyStoreProvider> customProviders) + { + + // Return when the provided dictionary is null. + if (customProviders == null) + { + throw SQL.NullCustomKeyStoreProviderDictionary(); + } + + // Validate that custom provider list doesn't contain any of system provider list + foreach (string key in customProviders.Keys) + { + // Validate the provider name + // + // Check for null or empty + if (string.IsNullOrWhiteSpace(key)) + { + throw SQL.EmptyProviderName(); + } + + // Check if the name starts with MSSQL_, since this is reserved namespace for system providers. + if (key.StartsWith(ADP.ColumnEncryptionSystemProviderNamePrefix, StringComparison.InvariantCultureIgnoreCase)) + { + throw SQL.InvalidCustomKeyStoreProviderName(key, ADP.ColumnEncryptionSystemProviderNamePrefix); + } + + // Validate the provider value + if (customProviders[key] == null) + { + throw SQL.NullProviderValue(key); + } + } + + lock (_CustomColumnEncryptionKeyProvidersLock) + { + // Provider list can only be set once + if (_CustomColumnEncryptionKeyStoreProviders != null) + { + throw SQL.CanOnlyCallOnce(); + } + + // Create a temporary dictionary and then add items from the provided dictionary. + // Dictionary constructor does shallow copying by simply copying the provider name and provider reference pairs + // in the provided customerProviders dictionary. + Dictionary<string, SqlColumnEncryptionKeyStoreProvider> customColumnEncryptionKeyStoreProviders = + new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>(customProviders, StringComparer.OrdinalIgnoreCase); + + // Set the dictionary to the ReadOnly dictionary. + _CustomColumnEncryptionKeyStoreProviders = new ReadOnlyDictionary<string, SqlColumnEncryptionKeyStoreProvider>(customColumnEncryptionKeyStoreProviders); + } + } + + /// <summary> + /// This function walks through both system and custom column encryption key store providers and returns an object if found. + /// </summary> + /// <param name="providerName">Provider Name to be searched in System Provider diction and Custom provider dictionary.</param> + /// <param name="columnKeyStoreProvider">If the provider is found, returns the corresponding SqlColumnEncryptionKeyStoreProvider instance.</param> + /// <returns>true if the provider is found, else returns false</returns> + static internal bool TryGetColumnEncryptionKeyStoreProvider(string providerName, out SqlColumnEncryptionKeyStoreProvider columnKeyStoreProvider) { + Debug.Assert(!string.IsNullOrWhiteSpace(providerName), "Provider name is invalid"); + + // Initialize the out parameter + columnKeyStoreProvider = null; + + // Search in the sytem provider list. + if (_SystemColumnEncryptionKeyStoreProviders.TryGetValue(providerName, out columnKeyStoreProvider)) + { + return true; + } + + lock (_CustomColumnEncryptionKeyProvidersLock) + { + // If custom provider is not set, then return false + if (_CustomColumnEncryptionKeyStoreProviders == null) + { + return false; + } + + // Search in the custom provider list + return _CustomColumnEncryptionKeyStoreProviders.TryGetValue(providerName, out columnKeyStoreProvider); + } + } + + /// <summary> + /// This function returns a list of system provider dictionary currently supported by this driver. + /// </summary> + /// <returns>Combined list of provider names</returns> + static internal List<string> GetColumnEncryptionSystemKeyStoreProviders() { + HashSet<string> providerNames = new HashSet<string>(_SystemColumnEncryptionKeyStoreProviders.Keys); + return providerNames.ToList(); + } + + /// <summary> + /// This function returns a list of custom provider dictionary currently registered. + /// </summary> + /// <returns>Combined list of provider names</returns> + static internal List<string> GetColumnEncryptionCustomKeyStoreProviders() { + if(_CustomColumnEncryptionKeyStoreProviders != null) + { + HashSet<string> providerNames = new HashSet<string>(_CustomColumnEncryptionKeyStoreProviders.Keys); + return providerNames.ToList(); + } + + return new List<string>(); + } + private SqlDebugContext _sdc; // SQL Debugging support private bool _AsyncCommandInProgress; @@ -59,7 +220,9 @@ namespace System.Data.SqlClient private SqlCredential _credential; // SQL authentication password stored in SecureString private string _connectionString; - private int _connectRetryCount; + private int _connectRetryCount; + + private string _accessToken; // Access Token to be used for token based authententication // connection resiliency private object _reconnectLock = new object(); @@ -98,6 +261,11 @@ namespace System.Data.SqlClient throw ADP.InvalidMixedArgumentOfSecureCredentialAndContextConnection(); } + if (UsesActiveDirectoryIntegrated(connectionOptions)) + { + throw SQL.SettingCredentialWithIntegratedArgument(); + } + Credential = credential; } // else @@ -116,6 +284,7 @@ namespace System.Data.SqlClient password.MakeReadOnly(); _credential = new SqlCredential(connection._credential.UserId, password); } + _accessToken = connection._accessToken; CacheConnectionStringProperties(); } @@ -206,19 +375,38 @@ namespace System.Data.SqlClient } } + /// <summary> + /// Is this connection using column encryption ? + /// </summary> + internal bool IsColumnEncryptionSettingEnabled { + get { + SqlConnectionString opt = (SqlConnectionString)ConnectionOptions; + return opt != null ? opt.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled : false; + } + } + // Is this connection is a Context Connection? private bool UsesContextConnection(SqlConnectionString opt) { return opt != null ? opt.ContextConnection : false; } + private bool UsesActiveDirectoryIntegrated(SqlConnectionString opt) + { + return opt != null ? opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated : false; + } + + private bool UsesAuthentication(SqlConnectionString opt) { + return opt != null ? opt.Authentication != SqlAuthenticationMethod.NotSpecified : false; + } + // Does this connection uses Integrated Security? private bool UsesIntegratedSecurity(SqlConnectionString opt) { - return opt != null ? opt.IntegratedSecurity : false; + return opt != null ? opt.IntegratedSecurity : false; } // Does this connection uses old style of clear userID or Password in connection string? - private bool UsesClearUserIdOrPassword(SqlConnectionString opt) { + private bool UsesClearUserIdOrPassword(SqlConnectionString opt) { bool result = false; if (null != opt) { result = (!ADP.IsEmpty(opt.UserID) || !ADP.IsEmpty(opt.Password)); @@ -256,6 +444,41 @@ namespace System.Data.SqlClient } } + // AccessToken: To be used for token based authentication + [ + Browsable(false), + DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), + ResDescriptionAttribute(Res.SqlConnection_AccessToken), + ] + public string AccessToken { + get { + string result = _accessToken; + // When a connection is connecting or is ever opened, make AccessToken available only if "Persist Security Info" is set to true + // otherwise, return null + SqlConnectionString connectionOptions = (SqlConnectionString)UserConnectionOptions; + if (InnerConnection.ShouldHidePassword && connectionOptions != null && !connectionOptions.PersistSecurityInfo) { + result = null; + } + + return result; + } + set { + // If a connection is connecting or is ever opened, AccessToken cannot be set + if (!InnerConnection.AllowSetConnectionString) { + throw ADP.OpenConnectionPropertySet("AccessToken", InnerConnection.State); + } + + if (value != null) { + // Check if the usage of AccessToken has any conflict with the keys used in connection string and credential + CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken((SqlConnectionString)ConnectionOptions); + } + + _accessToken = value; + // Need to call ConnectionString_Set to do proper pool group check + ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, credential: _credential, accessToken: _accessToken)); + } + } + [ DefaultValue(""), #pragma warning disable 618 // ignore obsolete warning about RecommendedAsConfigurable to use SettingsBindableAttribute @@ -272,13 +495,23 @@ namespace System.Data.SqlClient return ConnectionString_Get(); } set { - if (_credential != null) - { + if(_credential != null || _accessToken != null) { SqlConnectionString connectionOptions = new SqlConnectionString(value); - CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(connectionOptions); - } + if(_credential != null) { + // Check for Credential being used with Authentication=ActiveDirectoryIntegrated. Since a different error string is used + // for this case in ConnectionString setter vs in Credential setter, check for this error case before calling + // CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential, which is common to both setters. + if(UsesActiveDirectoryIntegrated(connectionOptions)) { + throw SQL.SettingIntegratedWithCredential(); + } - ConnectionString_Set(new SqlConnectionPoolKey(value, _credential)); + CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(connectionOptions); + } + else if(_accessToken != null) { + CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken(connectionOptions); + } + } + ConnectionString_Set(new SqlConnectionPoolKey(value, _credential, _accessToken)); _connectionString = value; // Change _connectionString value only after value is validated CacheConnectionStringProperties(); } @@ -482,13 +715,24 @@ namespace System.Data.SqlClient // check if the usage of credential has any conflict with the keys used in connection string if (value != null) { + // Check for Credential being used with Authentication=ActiveDirectoryIntegrated. Since a different error string is used + // for this case in ConnectionString setter vs in Credential setter, check for this error case before calling + // CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential, which is common to both setters. + if (UsesActiveDirectoryIntegrated((SqlConnectionString) ConnectionOptions)) { + throw SQL.SettingCredentialWithIntegratedInvalid(); + } + CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential((SqlConnectionString) ConnectionOptions); - } + if(_accessToken != null) { + throw ADP.InvalidMixedUsageOfCredentialAndAccessToken(); + } + } + _credential = value; // Need to call ConnectionString_Set to do proper pool group check - ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, _credential)); + ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, _credential, accessToken: _accessToken)); } } @@ -514,6 +758,33 @@ namespace System.Data.SqlClient } } + // CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken: check if the usage of AccessToken has any conflict + // with the keys used in connection string and credential + // If there is any conflict, it throws InvalidOperationException + // This is to be used setter of ConnectionString and AccessToken properties + private void CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken(SqlConnectionString connectionOptions) { + if (UsesClearUserIdOrPassword(connectionOptions)) { + throw ADP.InvalidMixedUsageOfAccessTokenAndUserIDPassword(); + } + + if (UsesIntegratedSecurity(connectionOptions)) { + throw ADP.InvalidMixedUsageOfAccessTokenAndIntegratedSecurity(); + } + + if (UsesContextConnection(connectionOptions)) { + throw ADP.InvalidMixedUsageOfAccessTokenAndContextConnection(); + } + + if (UsesAuthentication(connectionOptions)) { + throw ADP.InvalidMixedUsageOfAccessTokenAndAuthentication(); + } + + // Check if the usage of AccessToken has the conflict with credential + if (_credential != null) { + throw ADP.InvalidMixedUsageOfAccessTokenAndCredential(); + } + } + // // PUBLIC EVENTS // @@ -784,14 +1055,16 @@ namespace System.Data.SqlClient } private void DisposeMe(bool disposing) { // MDAC 65459 - _credential = null; // clear credential here rather than in IDisposable.Dispose as this is only specific to SqlConnection only - // IDisposable.Dispose is generated code from a template and used by other providers as well + // clear credential and AccessToken here rather than in IDisposable.Dispose as these are specific to SqlConnection only + // IDisposable.Dispose is generated code from a template and used by other providers as well + _credential = null; + _accessToken = null; if (!disposing) { - // DevDiv2 Bug 457934:SQLConnection leaks when not disposed - // http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/457934 - // For non-pooled connections we need to make sure that if the SqlConnection was not closed, then we release the GCHandle on the stateObject to allow it to be GCed - // For pooled connections, we will rely on the pool reclaiming the connection + // DevDiv2 + + + var innerConnection = (InnerConnection as SqlInternalConnectionTds); if ((innerConnection != null) && (!innerConnection.ConnectionOptions.Pooling)) { var parser = innerConnection.Parser; @@ -1151,7 +1424,16 @@ namespace System.Data.SqlClient } private bool TryOpen(TaskCompletionSource<DbConnectionInternal> retry) { - if (_impersonateIdentity != null) { + SqlConnectionString connectionOptions = (SqlConnectionString)ConnectionOptions; + + if (connectionOptions != null && + (connectionOptions.Authentication == SqlAuthenticationMethod.SqlPassword || connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryPassword) && + (!connectionOptions.HasUserIdKeyword || !connectionOptions.HasPasswordKeyword) && + _credential == null) { + throw SQL.CredentialsNotProvided(connectionOptions.Authentication); + } + + if (_impersonateIdentity != null) { if (_impersonateIdentity.User == DbConnectionPoolIdentity.GetCurrentWindowsIdentity().User) { return TryOpenInner(retry); } @@ -1162,7 +1444,7 @@ namespace System.Data.SqlClient } } else { - if (this.UsesIntegratedSecurity((SqlConnectionString)ConnectionOptions)) { + if (this.UsesIntegratedSecurity(connectionOptions) || this.UsesActiveDirectoryIntegrated(connectionOptions)) { _lastIdentity = DbConnectionPoolIdentity.GetCurrentWindowsIdentity(); } else { @@ -1353,13 +1635,13 @@ namespace System.Data.SqlClient internal void OnError(SqlException exception, bool breakConnection, Action<Action> wrapCloseInAction) { Debug.Assert(exception != null && exception.Errors.Count != 0, "SqlConnection: OnError called with null or empty exception!"); - // Bug fix - MDAC 49022 - connection open after failure... Problem was parser was passing - // Open as a state - because the parser's connection to the netlib was open. We would - // then set the connection state to the parser's state - which is not correct. The only - // time the connection state should change to what is passed in to this function is if - // the parser is broken, then we should be closed. Changed to passing in - // TdsParserState, not ConnectionState. - // fixed by [....] + // + + + + + + if (breakConnection && (ConnectionState.Open == State)) { @@ -1665,10 +1947,10 @@ namespace System.Data.SqlClient throw ADP.InvalidArgumentLength("newPassword", TdsEnums.MAXLEN_NEWPASSWORD); } - SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, null); + SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential: null, accessToken: null); SqlConnectionString connectionOptions = SqlConnectionFactory.FindSqlConnectionOptions(key); - if (connectionOptions.IntegratedSecurity) { + if (connectionOptions.IntegratedSecurity || connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { throw SQL.ChangePasswordConflictsWithSSPI(); } if (! ADP.IsEmpty(connectionOptions.AttachDBFilename)) { @@ -1714,7 +1996,7 @@ namespace System.Data.SqlClient throw ADP.InvalidArgumentLength("newSecurePassword", TdsEnums.MAXLEN_NEWPASSWORD); } - SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential); + SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential, accessToken: null); SqlConnectionString connectionOptions = SqlConnectionFactory.FindSqlConnectionOptions(key); @@ -1723,7 +2005,7 @@ namespace System.Data.SqlClient throw ADP.InvalidMixedArgumentOfSecureAndClearCredential(); } - if (connectionOptions.IntegratedSecurity) { + if (connectionOptions.IntegratedSecurity || connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { throw SQL.ChangePasswordConflictsWithSSPI(); } @@ -1755,7 +2037,7 @@ namespace System.Data.SqlClient throw SQL.ChangePasswordRequiresYukon(); } } - SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential); + SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential, accessToken: null); SqlConnectionFactory.SingletonInstance.ClearPool(key); } diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionFactory.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionFactory.cs index 37766e5f465..a80e9110217 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionFactory.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionFactory.cs @@ -2,8 +2,8 @@ // <copyright file="SqlConnectionFactory.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient @@ -63,7 +63,7 @@ namespace System.Data.SqlClient // Pass DbConnectionPoolIdentity to SqlInternalConnectionTds if using integrated security. // Used by notifications. - if (opt.IntegratedSecurity) { + if (opt.IntegratedSecurity || opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { if (pool != null) { identity = pool.Identity; } @@ -122,7 +122,7 @@ namespace System.Data.SqlClient opt = new SqlConnectionString(opt, instanceName, false /* user instance=false */, null /* do not modify the Enlist value */); poolGroupProviderInfo = null; // null so we do not pass to constructor below... } - result = new SqlInternalConnectionTds(identity, opt, key.Credential, poolGroupProviderInfo, "", null, redirectedUserInstance, userOpt, recoverySessionData); + result = new SqlInternalConnectionTds(identity, opt, key.Credential, poolGroupProviderInfo, "", null, redirectedUserInstance, userOpt, recoverySessionData, pool, key.AccessToken); } return result; } @@ -157,7 +157,7 @@ namespace System.Data.SqlClient connectionTimeout = Int32.MaxValue; poolingOptions = new DbConnectionPoolGroupOptions( - opt.IntegratedSecurity, + opt.IntegratedSecurity || opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated, opt.MinPoolSize, opt.MaxPoolSize, connectionTimeout, diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionPoolGroupProviderInfo.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionPoolGroupProviderInfo.cs index 044682c672c..157865b45ab 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionPoolGroupProviderInfo.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionPoolGroupProviderInfo.cs @@ -2,8 +2,8 @@ // <copyright file="SqlConnectionPoolGroupProviderInfo.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionPoolKey.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionPoolKey.cs index c589d8965a5..ccb98a2b8ea 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionPoolKey.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionPoolKey.cs @@ -2,8 +2,8 @@ // <copyright file="ConnectionPoolKey.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient @@ -12,6 +12,7 @@ namespace System.Data.SqlClient using System; using System.Collections; using System.Data.Common; + using System.Diagnostics; // SqlConnectionPoolKey: Implementation of a key to connection pool groups for specifically to be used for SqlConnection // Connection string and SqlCredential are used as a key @@ -19,16 +20,20 @@ namespace System.Data.SqlClient { private SqlCredential _credential; private int _hashValue; + private readonly string _accessToken; - internal SqlConnectionPoolKey(string connectionString, SqlCredential credential) : base(connectionString) + internal SqlConnectionPoolKey(string connectionString, SqlCredential credential, string accessToken) : base(connectionString) { + Debug.Assert(_credential == null || _accessToken == null, "Credential and AccessToken can't have the value at the same time."); _credential = credential; + _accessToken = accessToken; CalculateHashCode(); } private SqlConnectionPoolKey(SqlConnectionPoolKey key) : base (key) { _credential = key.Credential; + _accessToken = key.AccessToken; CalculateHashCode(); } @@ -59,12 +64,19 @@ namespace System.Data.SqlClient } } + internal string AccessToken + { + get + { + return _accessToken; + } + } public override bool Equals(object obj) { SqlConnectionPoolKey key = obj as SqlConnectionPoolKey; - return (key != null && _credential == key._credential && ConnectionString == key.ConnectionString); + return (key != null && _credential == key._credential && ConnectionString == key.ConnectionString && Object.ReferenceEquals(_accessToken, key._accessToken)); } public override int GetHashCode() @@ -83,6 +95,13 @@ namespace System.Data.SqlClient _hashValue = _hashValue * 17 + _credential.GetHashCode(); } } + else if (_accessToken != null) + { + unchecked + { + _hashValue = _hashValue * 17 + _accessToken.GetHashCode(); + } + } } } } diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionPoolProviderInfo.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionPoolProviderInfo.cs index d5ff005e671..3f9e5ccee0d 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionPoolProviderInfo.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionPoolProviderInfo.cs @@ -2,7 +2,7 @@ // <copyright file="SqlConnectionPoolProviderInfo.cs" company="Microsoft"> // Copyright (c) Microsoft Corporation. All rights reserved. // </copyright> -// <owner current="true" primary="true">[....]</owner> +// <owner current="true" primary="true">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionString.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionString.cs index 934f0f6ab50..794cc62baa2 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionString.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionString.cs @@ -2,8 +2,8 @@ // <copyright file="SqlConnectionString.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { @@ -57,6 +57,8 @@ namespace System.Data.SqlClient { internal const bool Replication = false; internal const int Connect_Retry_Count = 1; internal const int Connect_Retry_Interval = 10; + internal static readonly SqlAuthenticationMethod Authentication = SqlAuthenticationMethod.NotSpecified; + internal static readonly SqlConnectionColumnEncryptionSetting ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Disabled; } // SqlConnection ConnectionString Options @@ -66,6 +68,7 @@ namespace System.Data.SqlClient { internal const string Application_Name = "application name"; internal const string AsynchronousProcessing = "asynchronous processing"; internal const string AttachDBFilename = "attachdbfilename"; + internal const string ColumnEncryptionSetting = "column encryption setting"; internal const string Connect_Timeout = "connect timeout"; internal const string Connection_Reset = "connection reset"; internal const string Context_Connection = "context connection"; @@ -95,6 +98,7 @@ namespace System.Data.SqlClient { internal const string Replication = "replication"; internal const string Connect_Retry_Count = "connectretrycount"; internal const string Connect_Retry_Interval = "connectretryinterval"; + internal const string Authentication = "authentication"; } // Constant for the number of duplicate options in the connnection string @@ -194,6 +198,8 @@ namespace System.Data.SqlClient { private readonly bool _replication; private readonly bool _userInstance; private readonly bool _multiSubnetFailover; + private readonly SqlAuthenticationMethod _authType; + private readonly SqlConnectionColumnEncryptionSetting _columnEncryptionSetting; private readonly int _connectTimeout; private readonly int _loadBalanceTimeout; @@ -213,7 +219,6 @@ namespace System.Data.SqlClient { private readonly string _initialCatalog; private readonly string _password; private readonly string _userID; - private readonly string _networkLibrary; private readonly string _workstationId; @@ -240,7 +245,7 @@ namespace System.Data.SqlClient { // SQLPT 41700: Ignore ResetConnection=False (still validate the keyword/value) _connectionReset = ConvertValueToBoolean(KEY.Connection_Reset, DEFAULT.Connection_Reset); _contextConnection = ConvertValueToBoolean(KEY.Context_Connection, DEFAULT.Context_Connection); - _encrypt = ConvertValueToBoolean(KEY.Encrypt, DEFAULT.Encrypt); + _encrypt = ConvertValueToEncrypt(); _enlist = ConvertValueToBoolean(KEY.Enlist, ADP.IsWindowsNT); _mars = ConvertValueToBoolean(KEY.MARS, DEFAULT.MARS); _persistSecurityInfo = ConvertValueToBoolean(KEY.Persist_Security_Info, DEFAULT.Persist_Security_Info); @@ -268,6 +273,8 @@ namespace System.Data.SqlClient { _networkLibrary = ConvertValueToString(KEY.Network_Library, null); _password = ConvertValueToString(KEY.Password, DEFAULT.Password); _trustServerCertificate = ConvertValueToBoolean(KEY.TrustServerCertificate, DEFAULT.TrustServerCertificate); + _authType = ConvertValueToAuthenticationType(); + _columnEncryptionSetting = ConvertValueToColumnEncryptionSetting(); // Temporary string - this value is stored internally as an enum. string typeSystemVersionString = ConvertValueToString(KEY.Type_System_Version, null); @@ -446,6 +453,14 @@ namespace System.Data.SqlClient { if ((_connectRetryInterval < 1) || (_connectRetryInterval > 60)) { throw ADP.InvalidConnectRetryIntervalValue(); } + + if (Authentication != SqlAuthenticationMethod.NotSpecified && _integratedSecurity == true) { + throw SQL.AuthenticationAndIntegratedSecurity(); + } + + if (Authentication == SqlClient.SqlAuthenticationMethod.ActiveDirectoryIntegrated && (HasUserIdKeyword || HasPasswordKeyword)) { + throw SQL.IntegratedWithUserIDAndPassword(); + } } // This c-tor is used to create SSE and user instance connection strings when user instance is set to true @@ -492,7 +507,8 @@ namespace System.Data.SqlClient { _applicationIntent = connectionOptions._applicationIntent; _connectRetryCount = connectionOptions._connectRetryCount; _connectRetryInterval = connectionOptions._connectRetryInterval; - + _authType = connectionOptions._authType; + _columnEncryptionSetting = connectionOptions._columnEncryptionSetting; ValidateValueLength(_dataSource, TdsEnums.MAXLEN_SERVERNAME, KEY.Data_Source); } @@ -511,7 +527,8 @@ namespace System.Data.SqlClient { internal bool Enlist { get { return _enlist; } } internal bool MARS { get { return _mars; } } internal bool MultiSubnetFailover { get { return _multiSubnetFailover; } } - + internal SqlAuthenticationMethod Authentication { get { return _authType; } } + internal SqlConnectionColumnEncryptionSetting ColumnEncryptionSetting { get { return _columnEncryptionSetting; } } internal bool PersistSecurityInfo { get { return _persistSecurityInfo; } } internal bool Pooling { get { return _pooling; } } internal bool Replication { get { return _replication; } } @@ -619,11 +636,13 @@ namespace System.Data.SqlClient { hash.Add(KEY.TrustServerCertificate, KEY.TrustServerCertificate); hash.Add(KEY.TransactionBinding, KEY.TransactionBinding); hash.Add(KEY.Type_System_Version, KEY.Type_System_Version); + hash.Add(KEY.ColumnEncryptionSetting, KEY.ColumnEncryptionSetting); hash.Add(KEY.User_ID, KEY.User_ID); hash.Add(KEY.User_Instance, KEY.User_Instance); hash.Add(KEY.Workstation_Id, KEY.Workstation_Id); hash.Add(KEY.Connect_Retry_Count, KEY.Connect_Retry_Count); hash.Add(KEY.Connect_Retry_Interval, KEY.Connect_Retry_Interval); + hash.Add(KEY.Authentication, KEY.Authentication); hash.Add(SYNONYM.APP, KEY.Application_Name); hash.Add(SYNONYM.Async, KEY.AsynchronousProcessing); @@ -750,6 +769,56 @@ namespace System.Data.SqlClient { } // ArgumentException and other types are raised as is (no wrapping) } + + internal SqlAuthenticationMethod ConvertValueToAuthenticationType() { + object value = base.Parsetable[KEY.Authentication]; + + string valStr = value as string; + if (valStr == null) { + return DEFAULT.Authentication; + } + + try { + return DbConnectionStringBuilderUtil.ConvertToAuthenticationType(KEY.Authentication, valStr); + } + catch (FormatException e) { + throw ADP.InvalidConnectionOptionValue(KEY.Authentication, e); + } + catch (OverflowException e) { + throw ADP.InvalidConnectionOptionValue(KEY.Authentication, e); + } + } + + /// <summary> + /// Convert the value to SqlConnectionColumnEncryptionSetting. + /// </summary> + /// <returns></returns> + internal SqlConnectionColumnEncryptionSetting ConvertValueToColumnEncryptionSetting() { + object value = base.Parsetable[KEY.ColumnEncryptionSetting]; + + string valStr = value as string; + if (valStr == null) { + return DEFAULT.ColumnEncryptionSetting; + } + + try { + return DbConnectionStringBuilderUtil.ConvertToColumnEncryptionSetting(KEY.ColumnEncryptionSetting, valStr); + } + catch (FormatException e) { + throw ADP.InvalidConnectionOptionValue(KEY.ColumnEncryptionSetting, e); + } + catch (OverflowException e) { + throw ADP.InvalidConnectionOptionValue(KEY.ColumnEncryptionSetting, e); + } + } + + internal bool ConvertValueToEncrypt() { + // If the Authentication keyword is provided, default to Encrypt=true; + // otherwise keep old default for backwards compatibility + object authValue = base.Parsetable[KEY.Authentication]; + bool defaultEncryptValue = (authValue == null) ? DEFAULT.Encrypt : true; + return ConvertValueToBoolean(KEY.Encrypt, defaultEncryptValue); + } } } diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionStringBuilder.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionStringBuilder.cs index b5184a87ac9..9df61cecd01 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionStringBuilder.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionStringBuilder.cs @@ -2,8 +2,8 @@ // <copyright file="SqlConnectionStringBuilder.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ using System; @@ -55,6 +55,8 @@ namespace System.Data.SqlClient { PacketSize, TypeSystemVersion, + Authentication, + ApplicationName, CurrentLanguage, WorkstationID, @@ -73,6 +75,8 @@ namespace System.Data.SqlClient { ConnectRetryInterval, + ColumnEncryptionSetting, + // keep the count value last KeywordsCount } @@ -105,20 +109,21 @@ namespace System.Data.SqlClient { private int _connectRetryCount = DbConnectionStringDefaults.ConnectRetryCount; private int _connectRetryInterval = DbConnectionStringDefaults.ConnectRetryInterval; - private bool _asynchronousProcessing = DbConnectionStringDefaults.AsynchronousProcessing; - private bool _connectionReset = DbConnectionStringDefaults.ConnectionReset; - private bool _contextConnection = DbConnectionStringDefaults.ContextConnection; - private bool _encrypt = DbConnectionStringDefaults.Encrypt; - private bool _trustServerCertificate = DbConnectionStringDefaults.TrustServerCertificate; - private bool _enlist = DbConnectionStringDefaults.Enlist; - private bool _integratedSecurity = DbConnectionStringDefaults.IntegratedSecurity; - private bool _multipleActiveResultSets = DbConnectionStringDefaults.MultipleActiveResultSets; - private bool _multiSubnetFailover = DbConnectionStringDefaults.MultiSubnetFailover; - private bool _persistSecurityInfo = DbConnectionStringDefaults.PersistSecurityInfo; - private bool _pooling = DbConnectionStringDefaults.Pooling; - private bool _replication = DbConnectionStringDefaults.Replication; - private bool _userInstance = DbConnectionStringDefaults.UserInstance; - + private bool _asynchronousProcessing = DbConnectionStringDefaults.AsynchronousProcessing; + private bool _connectionReset = DbConnectionStringDefaults.ConnectionReset; + private bool _contextConnection = DbConnectionStringDefaults.ContextConnection; + private bool _encrypt = DbConnectionStringDefaults.Encrypt; + private bool _trustServerCertificate = DbConnectionStringDefaults.TrustServerCertificate; + private bool _enlist = DbConnectionStringDefaults.Enlist; + private bool _integratedSecurity = DbConnectionStringDefaults.IntegratedSecurity; + private bool _multipleActiveResultSets = DbConnectionStringDefaults.MultipleActiveResultSets; + private bool _multiSubnetFailover = DbConnectionStringDefaults.MultiSubnetFailover; + private bool _persistSecurityInfo = DbConnectionStringDefaults.PersistSecurityInfo; + private bool _pooling = DbConnectionStringDefaults.Pooling; + private bool _replication = DbConnectionStringDefaults.Replication; + private bool _userInstance = DbConnectionStringDefaults.UserInstance; + private SqlAuthenticationMethod _authentication = DbConnectionStringDefaults.Authentication; + private SqlConnectionColumnEncryptionSetting _columnEncryptionSetting = DbConnectionStringDefaults.ColumnEncryptionSetting; static SqlConnectionStringBuilder() { string[] validKeywords = new string[KeywordsCount]; @@ -156,7 +161,9 @@ namespace System.Data.SqlClient { validKeywords[(int)Keywords.WorkstationID] = DbConnectionStringKeywords.WorkstationID; validKeywords[(int)Keywords.ConnectRetryCount] = DbConnectionStringKeywords.ConnectRetryCount; validKeywords[(int)Keywords.ConnectRetryInterval] = DbConnectionStringKeywords.ConnectRetryInterval; - _validKeywords = validKeywords; + validKeywords[(int)Keywords.Authentication] = DbConnectionStringKeywords.Authentication; + validKeywords[(int)Keywords.ColumnEncryptionSetting] = DbConnectionStringKeywords.ColumnEncryptionSetting; + _validKeywords = validKeywords; Dictionary<string, Keywords> hash = new Dictionary<string, Keywords>(KeywordsCount + SqlConnectionString.SynonymCount, StringComparer.OrdinalIgnoreCase); hash.Add(DbConnectionStringKeywords.ApplicationIntent, Keywords.ApplicationIntent); @@ -193,7 +200,8 @@ namespace System.Data.SqlClient { hash.Add(DbConnectionStringKeywords.WorkstationID, Keywords.WorkstationID); hash.Add(DbConnectionStringKeywords.ConnectRetryCount, Keywords.ConnectRetryCount); hash.Add(DbConnectionStringKeywords.ConnectRetryInterval, Keywords.ConnectRetryInterval); - + hash.Add(DbConnectionStringKeywords.Authentication, Keywords.Authentication); + hash.Add(DbConnectionStringKeywords.ColumnEncryptionSetting, Keywords.ColumnEncryptionSetting); hash.Add(DbConnectionStringSynonyms.APP, Keywords.ApplicationName); hash.Add(DbConnectionStringSynonyms.Async, Keywords.AsynchronousProcessing); hash.Add(DbConnectionStringSynonyms.EXTENDEDPROPERTIES, Keywords.AttachDBFilename); @@ -261,6 +269,8 @@ namespace System.Data.SqlClient { case Keywords.IntegratedSecurity: IntegratedSecurity = ConvertToIntegratedSecurity(value); break; + case Keywords.Authentication: Authentication = ConvertToAuthenticationType(keyword, value); break; + case Keywords.ColumnEncryptionSetting: ColumnEncryptionSetting = ConvertToColumnEncryptionSetting(keyword, value); break; case Keywords.AsynchronousProcessing: AsynchronousProcessing = ConvertToBoolean(value); break; #pragma warning disable 618 // Obsolete ConnectionReset case Keywords.ConnectionReset: ConnectionReset = ConvertToBoolean(value); break; @@ -420,6 +430,22 @@ namespace System.Data.SqlClient { _encrypt = value; } } + + [DisplayName(DbConnectionStringKeywords.ColumnEncryptionSetting)] + [ResCategoryAttribute(Res.DataCategory_Security)] + [ResDescriptionAttribute(Res.TCE_DbConnectionString_ColumnEncryptionSetting)] + [RefreshPropertiesAttribute(RefreshProperties.All)] + public SqlConnectionColumnEncryptionSetting ColumnEncryptionSetting { + get { return _columnEncryptionSetting; } + set { + if (!DbConnectionStringBuilderUtil.IsValidColumnEncryptionSetting(value)) { + throw ADP.InvalidEnumerationValue(typeof(SqlConnectionColumnEncryptionSetting), (int)value); + } + + SetColumnEncryptionSettingValue(value); + _columnEncryptionSetting = value; + } + } [DisplayName(DbConnectionStringKeywords.TrustServerCertificate)] [ResCategoryAttribute(Res.DataCategory_Security)] @@ -483,6 +509,22 @@ namespace System.Data.SqlClient { } } + [DisplayName(DbConnectionStringKeywords.Authentication)] + [ResCategoryAttribute(Res.DataCategory_Security)] + [ResDescriptionAttribute(Res.DbConnectionString_Authentication)] + [RefreshPropertiesAttribute(RefreshProperties.All)] + public SqlAuthenticationMethod Authentication { + get { return _authentication; } + set { + if (!DbConnectionStringBuilderUtil.IsValidAuthenticationTypeValue(value)) { + throw ADP.InvalidEnumerationValue(typeof(SqlAuthenticationMethod), (int)value); + } + + SetAuthenticationValue(value); + _authentication = value; + } + } + [DisplayName(DbConnectionStringKeywords.LoadBalanceTimeout)] [ResCategoryAttribute(Res.DataCategory_Pooling)] [ResDescriptionAttribute(Res.DbConnectionString_LoadBalanceTimeout)] @@ -816,6 +858,27 @@ namespace System.Data.SqlClient { private static ApplicationIntent ConvertToApplicationIntent(string keyword, object value) { return DbConnectionStringBuilderUtil.ConvertToApplicationIntent(keyword, value); } + private static SqlAuthenticationMethod ConvertToAuthenticationType(string keyword, object value) { + return DbConnectionStringBuilderUtil.ConvertToAuthenticationType(keyword, value); + } + + /// <summary> + /// Convert to SqlConnectionColumnEncryptionSetting. + /// </summary> + /// <param name="keyword"></param> + /// <param name="value"></param> + private static SqlConnectionColumnEncryptionSetting ConvertToColumnEncryptionSetting(string keyword, object value) { + return DbConnectionStringBuilderUtil.ConvertToColumnEncryptionSetting(keyword, value); + } + + internal override string ConvertValueToString(object value) { + if (value is SqlAuthenticationMethod) { + return DbConnectionStringBuilderUtil.AuthenticationTypeToString((SqlAuthenticationMethod)value); + } + else { + return base.ConvertValueToString(value); + } + } private object GetAt(Keywords index) { switch(index) { @@ -855,7 +918,8 @@ namespace System.Data.SqlClient { case Keywords.WorkstationID: return WorkstationID; case Keywords.ConnectRetryCount: return ConnectRetryCount; case Keywords.ConnectRetryInterval: return ConnectRetryInterval; - + case Keywords.Authentication: return Authentication; + case Keywords.ColumnEncryptionSetting: return ColumnEncryptionSetting; default: Debug.Assert(false, "unexpected keyword"); throw ADP.KeywordNotSupported(_validKeywords[(int)index]); @@ -924,6 +988,9 @@ namespace System.Data.SqlClient { case Keywords.AttachDBFilename: _attachDBFilename = DbConnectionStringDefaults.AttachDBFilename; break; + case Keywords.Authentication: + _authentication = DbConnectionStringDefaults.Authentication; + break; case Keywords.ConnectTimeout: _connectTimeout = DbConnectionStringDefaults.ConnectTimeout; break; @@ -1014,6 +1081,9 @@ namespace System.Data.SqlClient { case Keywords.WorkstationID: _workstationID = DbConnectionStringDefaults.WorkstationID; break; + case Keywords.ColumnEncryptionSetting: + _columnEncryptionSetting = DbConnectionStringDefaults.ColumnEncryptionSetting; + break; default: Debug.Assert(false, "unexpected keyword"); throw ADP.KeywordNotSupported(_validKeywords[(int)index]); @@ -1031,9 +1101,17 @@ namespace System.Data.SqlClient { base[keyword] = value; } private void SetApplicationIntentValue(ApplicationIntent value) { - Debug.Assert(DbConnectionStringBuilderUtil.IsValidApplicationIntentValue(value), "invalid value"); + Debug.Assert(DbConnectionStringBuilderUtil.IsValidApplicationIntentValue(value), "Invalid value for ApplicationIntent"); base[DbConnectionStringKeywords.ApplicationIntent] = DbConnectionStringBuilderUtil.ApplicationIntentToString(value); } + private void SetAuthenticationValue(SqlAuthenticationMethod value) { + Debug.Assert(DbConnectionStringBuilderUtil.IsValidAuthenticationTypeValue(value), "Invalid value for AuthenticationType"); + base[DbConnectionStringKeywords.Authentication] = DbConnectionStringBuilderUtil.AuthenticationTypeToString(value); + } + private void SetColumnEncryptionSettingValue(SqlConnectionColumnEncryptionSetting value) { + Debug.Assert(DbConnectionStringBuilderUtil.IsValidColumnEncryptionSetting(value), "Invalid value for SqlConnectionColumnEncryptionSetting"); + base[DbConnectionStringKeywords.ColumnEncryptionSetting] = DbConnectionStringBuilderUtil.ColumnEncryptionSettingToString(value); + } public override bool ShouldSerialize(string keyword) { ADP.CheckArgumentNull(keyword, "keyword"); diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionTimeoutErrorInternal.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionTimeoutErrorInternal.cs index 879a86d61a8..c702b82b3a5 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionTimeoutErrorInternal.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlConnectionTimeoutErrorInternal.cs @@ -2,7 +2,7 @@ // <copyright file="SqlConnectionTimeoutErrorInternal.cs" company="Microsoft"> // Copyright (c) Microsoft Corporation. All rights reserved. // </copyright> -// <owner current="true" primary="false">[....]</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlCredential.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlCredential.cs index 874ac5cc355..091753c6f38 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlCredential.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlCredential.cs @@ -2,8 +2,8 @@ // <copyright file="SqlCredential.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDataAdapter.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDataAdapter.cs index 11be2219aea..b1f304cd795 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDataAdapter.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDataAdapter.cs @@ -2,8 +2,8 @@ // <copyright file="SqlDataAdapter.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDataReader.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDataReader.cs index ad91abda628..ed353411d0d 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDataReader.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDataReader.cs @@ -2,8 +2,8 @@ // <copyright file="SqlDataReader.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { @@ -44,7 +44,7 @@ namespace System.Data.SqlClient { internal SharedState _sharedState = new SharedState(); - private TdsParser _parser; // TODO: Probably don't need this, since it's on the stateObj + private TdsParser _parser; // private TdsParserStateObject _stateObj; private SqlCommand _command; private SqlConnection _connection; @@ -516,11 +516,17 @@ namespace System.Data.SqlClient { // col.length is always byte count so for unicode types, half the length // // For MAX and XML datatypes, we get 0x7fffffff from the server. Do not divide this. - schemaRow[Size] = (col.metaType.IsSizeInCharacters && (col.length != 0x7fffffff)) ? (col.length / 2) : col.length; + if (col.cipherMD != null) { + Debug.Assert(col.baseTI != null && col.baseTI.metaType != null, "col.baseTI and col.baseTI.metaType should not be null."); + schemaRow[Size] = (col.baseTI.metaType.IsSizeInCharacters && (col.baseTI.length != 0x7fffffff)) ? (col.baseTI.length / 2) : col.baseTI.length; + } + else { + schemaRow[Size] = (col.metaType.IsSizeInCharacters && (col.length != 0x7fffffff)) ? (col.length / 2) : col.length; + } schemaRow[DataType] = GetFieldTypeInternal(col); schemaRow[ProviderSpecificDataType] = GetProviderSpecificFieldTypeInternal(col); - schemaRow[NonVersionedProviderType] = (int) col.type; // SqlDbType enum value - does not change with TypeSystem. + schemaRow[NonVersionedProviderType] = (int) (col.cipherMD != null ? col.baseTI.type : col.type); // SqlDbType enum value - does not change with TypeSystem. schemaRow[DataTypeName] = GetDataTypeNameInternal(col); if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && col.IsNewKatmaiDateTimeType) { @@ -556,7 +562,7 @@ namespace System.Data.SqlClient { // TypeSystem.SQLServer2005 and above // SqlDbType enum value - always the actual type for SQLServer2005. - schemaRow[ProviderType] = (int) col.type; + schemaRow[ProviderType] = (int) (col.cipherMD != null ? col.baseTI.type : col.type); if (col.type == SqlDbType.Udt) { // Additional metadata for UDTs. Debug.Assert(Connection.IsYukonOrNewer, "Invalid Column type received from the server"); @@ -576,8 +582,16 @@ namespace System.Data.SqlClient { schemaRow[ProviderType] = GetVersionedMetaType(col.metaType).SqlDbType; } - - if (TdsEnums.UNKNOWN_PRECISION_SCALE != col.precision) { + if (col.cipherMD != null) { + Debug.Assert(col.baseTI != null, @"col.baseTI should not be null."); + if (TdsEnums.UNKNOWN_PRECISION_SCALE != col.baseTI.precision) { + schemaRow[Precision] = col.baseTI.precision; + } + else { + schemaRow[Precision] = col.baseTI.metaType.Precision; + } + } + else if (TdsEnums.UNKNOWN_PRECISION_SCALE != col.precision) { schemaRow[Precision] = col.precision; } else { @@ -587,6 +601,15 @@ namespace System.Data.SqlClient { if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && col.IsNewKatmaiDateTimeType) { schemaRow[Scale] = MetaType.MetaNVarChar.Scale; } + else if (col.cipherMD != null) { + Debug.Assert(col.baseTI != null, @"col.baseTI should not be null."); + if (TdsEnums.UNKNOWN_PRECISION_SCALE != col.baseTI.scale) { + schemaRow[Scale] = col.baseTI.scale; + } + else { + schemaRow[Scale] = col.baseTI.metaType.Scale; + } + } else if (TdsEnums.UNKNOWN_PRECISION_SCALE != col.scale) { schemaRow[Scale] = col.scale; } @@ -606,7 +629,15 @@ namespace System.Data.SqlClient { schemaRow[IsIdentity] = col.isIdentity; schemaRow[IsAutoIncrement] = col.isIdentity; - schemaRow[IsLong] = col.metaType.IsLong; + + if (col.cipherMD != null) { + Debug.Assert(col.baseTI != null, @"col.baseTI should not be null."); + Debug.Assert(col.baseTI.metaType != null, @"col.baseTI.metaType should not be null."); + schemaRow[IsLong] = col.baseTI.metaType.IsLong; + } + else { + schemaRow[IsLong] = col.metaType.IsLong; + } // mark unique for timestamp columns if (SqlDbType.Timestamp == col.type) { @@ -1074,7 +1105,7 @@ namespace System.Data.SqlClient { // CleanWire will do cleanup - so we don't really care about the snapshot CleanupAfterAsyncInvocationInternal(stateObj, resetNetworkPacketTaskSource: false); } - // Switch to [....] to prepare for cleanwire + // Switch to sync to prepare for cleanwire stateObj._syncOverAsync = true; // Remove owner (this will allow the stateObj to be disposed after the connection is closed) stateObj.RemoveOwner(); @@ -1163,14 +1194,20 @@ namespace System.Data.SqlClient { dataTypeName = metaData.udtDatabaseName + "." + metaData.udtSchemaName + "." + metaData.udtTypeName; } else { // For all other types, including Xml - use data in MetaType. - dataTypeName = metaData.metaType.TypeName; + if (metaData.cipherMD != null) { + Debug.Assert(metaData.baseTI != null && metaData.baseTI.metaType != null, "metaData.baseTI and metaData.baseTI.metaType should not be null."); + dataTypeName = metaData.baseTI.metaType.TypeName; + } + else { + dataTypeName = metaData.metaType.TypeName; + } } } else { // TypeSystem.SQLServer2000 - - dataTypeName = GetVersionedMetaType(metaData.metaType).TypeName; - } + + dataTypeName = GetVersionedMetaType(metaData.metaType).TypeName; + } return dataTypeName; } @@ -1183,7 +1220,7 @@ namespace System.Data.SqlClient { } override public IEnumerator GetEnumerator() { - return new DbEnumerator((IDataReader)this, IsCommandBehavior(CommandBehavior.CloseConnection)); + return new DbEnumerator(this, IsCommandBehavior(CommandBehavior.CloseConnection)); } override public Type GetFieldType(int i) { @@ -1224,7 +1261,13 @@ namespace System.Data.SqlClient { fieldType = metaData.udtType; } else { // For all other types, including Xml - use data in MetaType. - fieldType = metaData.metaType.ClassType; // Com+ type. + if (metaData.cipherMD != null) { + Debug.Assert(metaData.baseTI != null && metaData.baseTI.metaType != null, "metaData.baseTI and metaData.baseTI.metaType should not be null."); + fieldType = metaData.baseTI.metaType.ClassType; + } + else { + fieldType = metaData.metaType.ClassType; // Com+ type. + } } } else { @@ -1292,8 +1335,16 @@ namespace System.Data.SqlClient { Connection.CheckGetExtendedUDTInfo(metaData, false); providerSpecificFieldType = metaData.udtType; } - else { // For all other types, including Xml - use data in MetaType. - providerSpecificFieldType = metaData.metaType.SqlType; // SqlType type. + else { + // For all other types, including Xml - use data in MetaType. + if (metaData.cipherMD != null) { + Debug.Assert(metaData.baseTI != null && metaData.baseTI.metaType != null, + "metaData.baseTI and metaData.baseTI.metaType should not be null."); + providerSpecificFieldType = metaData.baseTI.metaType.SqlType; // SqlType type. + } + else { + providerSpecificFieldType = metaData.metaType.SqlType; // SqlType type. + } } } else { @@ -1395,6 +1446,11 @@ namespace System.Data.SqlClient { override public Stream GetStream(int i) { CheckDataIsReady(columnIndex: i, methodName: "GetStream"); + // Streaming is not supported on encrypted columns. + if (_metaData[i] != null && _metaData[i].cipherMD != null) { + throw SQL.StreamNotSupportOnEncryptedColumn(_metaData[i].column); + } + // Stream is only for Binary, Image, VarBinary, Udt and Xml types // NOTE: IsBinType also includes Timestamp for some reason... MetaType mt = _metaData[i].metaType; @@ -1490,6 +1546,10 @@ namespace System.Data.SqlClient { if (IsCommandBehavior(CommandBehavior.SequentialAccess)) { Debug.Assert(!HasActiveStreamOrTextReaderOnColumn(i), "Column has an active Stream or TextReader"); + if (_metaData[i] != null && _metaData[i].cipherMD != null) { + throw SQL.SequentialAccessNotSupportedOnEncryptedColumn(_metaData[i].column); + } + if (_sharedState._nextColumnHeaderToRead <= i) { if (!TryReadColumnHeader(i)) { return false; @@ -1798,13 +1858,28 @@ namespace System.Data.SqlClient { CheckDataIsReady(columnIndex: i, methodName: "GetTextReader"); // Xml type is not supported - MetaType mt = _metaData[i].metaType; + MetaType mt = null; + + if (_metaData[i].cipherMD != null) { + Debug.Assert(_metaData[i].baseTI != null, "_metaData[i].baseTI should not be null."); + mt = _metaData[i].baseTI.metaType; + } + else { + mt = _metaData[i].metaType; + } + + Debug.Assert(mt != null, @"mt should not be null."); + if (((!mt.IsCharType) && (mt.SqlDbType != SqlDbType.Variant)) || (mt.SqlDbType == SqlDbType.Xml)) { throw SQL.TextReaderNotSupportOnColumnType(_metaData[i].column); } // For non-variant types with sequential access, we support proper streaming if ((mt.SqlDbType != SqlDbType.Variant) && (IsCommandBehavior(CommandBehavior.SequentialAccess))) { + if (_metaData[i].cipherMD != null) { + throw SQL.SequentialAccessNotSupportedOnEncryptedColumn(_metaData[i].column); + } + System.Text.Encoding encoding; if (mt.IsNCharType) { @@ -1852,16 +1927,40 @@ namespace System.Data.SqlClient { if (_currentTask != null) { throw ADP.AsyncOperationPending(); } - + + MetaType mt = null; + if (_metaData[i].cipherMD != null) { + Debug.Assert(_metaData[i].baseTI != null, @"_metaData[i].baseTI should not be null."); + mt = _metaData[i].baseTI.metaType; + } + else { + mt = _metaData[i].metaType; + } + + Debug.Assert(mt != null, "mt should not be null."); + + SqlDbType sqlDbType; + if (_metaData[i].cipherMD != null) { + Debug.Assert(_metaData[i].baseTI != null, @"_metaData[i].baseTI should not be null."); + sqlDbType = _metaData[i].baseTI.type; + } + else { + sqlDbType = _metaData[i].type; + } + try { statistics = SqlStatistics.StartTimer(Statistics); SetTimeout(_defaultTimeoutMilliseconds); - if ((_metaData[i].metaType.IsPlp) && + if ((mt.IsPlp) && (IsCommandBehavior(CommandBehavior.SequentialAccess)) ) { if (length < 0) { throw ADP.InvalidDataLength(length); } + if (_metaData[i].cipherMD != null) { + throw SQL.SequentialAccessNotSupportedOnEncryptedColumn(_metaData[i].column); + } + // if bad buffer index, throw if ((bufferIndex < 0) || (buffer != null && bufferIndex >= buffer.Length)) { throw ADP.InvalidDestinationBufferIndex(buffer.Length, bufferIndex, "bufferIndex"); @@ -1872,13 +1971,13 @@ namespace System.Data.SqlClient { throw ADP.InvalidBufferSizeOrIndex(length, bufferIndex); } long charsRead = 0; - if ( _metaData[i].type == SqlDbType.Xml ) { + if ( sqlDbType == SqlDbType.Xml ) { try { CheckDataIsReady(columnIndex: i, allowPartiallyReadColumn: true, methodName: "GetChars"); } catch (Exception ex) { - // Dev11 Bug #315513: Exception type breaking change from 4.0 RTM when calling GetChars on null xml - // We need to wrap all exceptions inside a TargetInvocationException to simulate calling CreateSqlReader via MethodInfo.Invoke + // Dev11 + if (ADP.IsCatchableExceptionType(ex)) { throw new TargetInvocationException(ex); } @@ -2012,7 +2111,7 @@ namespace System.Data.SqlClient { // In both cases we will clean decoder if (dataIndex == 0) _stateObj._plpdecoder = null; - + bool isUnicode = _metaData[i].metaType.IsNCharType; // If there are an unknown (-1) number of bytes left for a PLP, read its size @@ -2314,8 +2413,8 @@ namespace System.Data.SqlClient { // NOTE: This method is called by the fast-paths in Async methods and, therefore, should be resilient to the DataReader being closed // Always make sure to take reference copies of anything set to null in TryCloseInternal() private object GetSqlValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData metaData) { - // Dev11 Bug #336820, Dev10 Bug #479607 (SqlClient: IsDBNull always returns false for timestamp datatype) - // Due to a bug in TdsParser.GetNullSqlValue, Timestamps' IsNull is not correctly set - so we need to bypass the following check + // Dev11 + Debug.Assert(!data.IsEmpty || data.IsNull || metaData.type == SqlDbType.Timestamp, "Data has been read, but the buffer is empty"); // Convert Katmai types to string @@ -2467,8 +2566,8 @@ namespace System.Data.SqlClient { // NOTE: This method is called by the fast-paths in Async methods and, therefore, should be resilient to the DataReader being closed // Always make sure to take reference copies of anything set to null in TryCloseInternal() private object GetValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData metaData) { - // Dev11 Bug #336820, Dev10 Bug #479607 (SqlClient: IsDBNull always returns false for timestamp datatype) - // Due to a bug in TdsParser.GetNullSqlValue, Timestamps' IsNull is not correctly set - so we need to bypass the following check + // Dev11 + Debug.Assert(!data.IsEmpty || data.IsNull || metaData.type == SqlDbType.Timestamp, "Data has been read, but the buffer is empty"); if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && metaData.IsNewKatmaiDateTimeType) { @@ -2673,9 +2772,9 @@ namespace System.Data.SqlClient { moreResults = true; return true; - // VSTFDEVDIV 926281: DONEINPROC case is missing here; we have decided to reject this bug as it would result in breaking change - // from Orcas RTM/SP1 and Dev10 RTM. See the bug for more details. - // case TdsEnums.DONEINPROC: + // VSTFDEVDIV 926281: DONEINPROC case is missing here; we have decided to reject this + + case TdsEnums.SQLDONE: Debug.Assert(_altRowStatus == ALTROWSTATUS.Done || _altRowStatus == ALTROWSTATUS.Null, "invalid AltRowStatus"); _altRowStatus = ALTROWSTATUS.Null; @@ -2688,10 +2787,10 @@ namespace System.Data.SqlClient { return true; } - // Dev11 Bug 316483:Hang on SqlDataReader::TryHasMoreResults using MARS - // http://vstfdevdiv:8080/web/wi.aspx?pcguid=22f9acc9-569a-41ff-b6ac-fac1b6370209&id=316483 - // TryRun() will immediately return if the TdsParser is closed\broken, causing us to enter an infinite loop - // Instead, we will throw a closed connection exception + // Dev11 + + + if (_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed) { throw ADP.ClosedConnectionError(); } @@ -2727,8 +2826,8 @@ namespace System.Data.SqlClient { } if (_stateObj._pendingData) { // Consume error's, info's, done's on HasMoreRows, so user obtains error on Read. - // Previous bug where Read() would return false with error on the wire in the case - // of metadata and error immediately following. See MDAC 78285 and 75225. + // Previous + // @@ -2763,10 +2862,10 @@ namespace System.Data.SqlClient { ParsedDoneToken = true; } - // Dev11 Bug 316483:Hang on SqlDataReader::TryHasMoreResults using MARS - // http://vstfdevdiv:8080/web/wi.aspx?pcguid=22f9acc9-569a-41ff-b6ac-fac1b6370209&id=316483 - // TryRun() will immediately return if the TdsParser is closed\broken, causing us to enter an infinite loop - // Instead, we will throw a closed connection exception + // Dev11 + + + if (_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed) { throw ADP.ClosedConnectionError(); } @@ -2804,10 +2903,10 @@ namespace System.Data.SqlClient { override public bool IsDBNull(int i) { if ((IsCommandBehavior(CommandBehavior.SequentialAccess)) && ((_sharedState._nextColumnHeaderToRead > i + 1) || (_lastColumnWithDataChunkRead > i))) { - // Bug 447026 : A breaking change in System.Data .NET 4.5 for calling IsDBNull on commands in SequentialAccess mode - // http://vstfdevdiv:8080/web/wi.aspx?pcguid=22f9acc9-569a-41ff-b6ac-fac1b6370209&id=447026 - // In .Net 4.0 and previous, it was possible to read a previous column using IsDBNull when in sequential mode - // However, since it had already gone past the column, the current IsNull value is simply returned + // + + + // To replicate this behavior we will skip CheckHeaderIsReady\ReadColumnHeader and instead just check that the reader is ready and the column is valid CheckMetaDataIsReady(columnIndex: i); @@ -3264,7 +3363,9 @@ namespace System.Data.SqlClient { if (!_data[_sharedState._nextColumnDataToRead].IsNull) { _SqlMetaData columnMetaData = _metaData[_sharedState._nextColumnDataToRead]; - if (!_parser.TryReadSqlValue(_data[_sharedState._nextColumnDataToRead], columnMetaData, (int)_sharedState._columnDataBytesRemaining, _stateObj)) { // will read UDTs as VARBINARY. + if (!_parser.TryReadSqlValue(_data[_sharedState._nextColumnDataToRead], columnMetaData, (int)_sharedState._columnDataBytesRemaining, _stateObj, + _command != null ? _command.ColumnEncryptionSetting : SqlCommandColumnEncryptionSetting.UseConnectionSetting, + columnMetaData.column)) { // will read UDTs as VARBINARY. return false; } _sharedState._columnDataBytesRemaining = 0; @@ -3338,8 +3439,8 @@ namespace System.Data.SqlClient { Debug.Assert(i == _sharedState._nextColumnDataToRead || // Either we haven't read the column yet ((i + 1 < _sharedState._nextColumnDataToRead) && (IsCommandBehavior(CommandBehavior.SequentialAccess))) || // Or we're in sequential mode and we've read way past the column (i.e. it was not the last column we read) (!_data[i].IsEmpty || _data[i].IsNull) || // Or we should have data stored for the column (unless the column was null) - (_metaData[i].type == SqlDbType.Timestamp), // Or Dev11 Bug #336820, Dev10 Bug #479607 (SqlClient: IsDBNull always returns false for timestamp datatype) - // Due to a bug in TdsParser.GetNullSqlValue, Timestamps' IsNull is not correctly set - so we need to bypass the check + (_metaData[i].type == SqlDbType.Timestamp), // Or Dev11 + // Due to a "Gone past column, be we have no data stored for it"); return true; } @@ -3396,9 +3497,12 @@ namespace System.Data.SqlClient { _sharedState._nextColumnDataToRead = _sharedState._nextColumnHeaderToRead; _sharedState._nextColumnHeaderToRead++; // We read this one - if (isNull && columnMetaData.type != SqlDbType.Timestamp /* Maintain behavior for known bug (Dev10 479607) rejected as breaking change - See comments in GetNullSqlValue for timestamp */) + if (isNull && columnMetaData.type != SqlDbType.Timestamp /* Maintain behavior for known */) { - _parser.GetNullSqlValue(_data[_sharedState._nextColumnDataToRead], columnMetaData); + TdsParser.GetNullSqlValue(_data[_sharedState._nextColumnDataToRead], + columnMetaData, + _command != null ? _command.ColumnEncryptionSetting : SqlCommandColumnEncryptionSetting.UseConnectionSetting, + _parser.Connection); if (!readHeaderOnly) { _sharedState._nextColumnDataToRead++; @@ -3409,7 +3513,9 @@ namespace System.Data.SqlClient { // If we're not in sequential access mode, we have to // save the data we skip over so that the consumer // can read it out of order - if (!_parser.TryReadSqlValue(_data[_sharedState._nextColumnDataToRead], columnMetaData, (int)dataLength, _stateObj)) { // will read UDTs as VARBINARY. + if (!_parser.TryReadSqlValue(_data[_sharedState._nextColumnDataToRead], columnMetaData, (int)dataLength, _stateObj, + _command != null ? _command.ColumnEncryptionSetting : SqlCommandColumnEncryptionSetting.UseConnectionSetting, + columnMetaData.column)) { // will read UDTs as VARBINARY. return false; } _sharedState._nextColumnDataToRead++; @@ -4236,7 +4342,7 @@ namespace System.Data.SqlClient { return ADP.CreatedTaskWithCancellation<bool>(); } - // Shortcut - if we have enough data, then run [....] + // Shortcut - if we have enough data, then run sync try { if (WillHaveEnoughData(i, headerOnly: true)) { #if DEBUG @@ -4338,7 +4444,7 @@ namespace System.Data.SqlClient { return ADP.CreatedTaskWithCancellation<T>(); } - // Shortcut - if we have enough data, then run [....] + // Shortcut - if we have enough data, then run sync try { if (WillHaveEnoughData(i)) { #if DEBUG @@ -4509,7 +4615,7 @@ namespace System.Data.SqlClient { } if (task.IsCompleted) { - // If we've completed [....], then don't bother handling the TaskCompletionSource - we'll just return the completed task + // If we've completed sync, then don't bother handling the TaskCompletionSource - we'll just return the completed task CompleteRetryable(task, source, objectToDispose); return task; } diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDataReaderSmi.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDataReaderSmi.cs index 4a46f64b62e..ded4479ff4a 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDataReaderSmi.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDataReaderSmi.cs @@ -2,8 +2,8 @@ // <copyright file="SqlDataReader.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDelegatedTransaction.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDelegatedTransaction.cs index 44fad3c95d7..dece801213c 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDelegatedTransaction.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDelegatedTransaction.cs @@ -2,8 +2,8 @@ // <copyright file="SqlDelegatedTransaction.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDependency.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDependency.cs index fe13f0878c0..c27b94cd082 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDependency.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDependency.cs @@ -2,9 +2,9 @@ // <copyright file="SqlDependency.cs" company="Microsoft"> // Copyright (c) Microsoft Corporation. All rights reserved. // </copyright> -// <owner current="true" primary="true">[....]</owner> -// <owner current="true" primary="true">[....]</owner> -// <owner current="false" primary="false">[....]</owner> +// <owner current="true" primary="true">Microsoft</owner> +// <owner current="true" primary="true">Microsoft</owner> +// <owner current="false" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDependencyListener.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDependencyListener.cs index 9a11b63cb68..656d0dd6db1 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDependencyListener.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDependencyListener.cs @@ -2,9 +2,9 @@ // <copyright file="SqlDependency.cs" company="Microsoft"> // Copyright (c) Microsoft Corporation. All rights reserved. // </copyright> -// <owner current="true" primary="true">[....]</owner> -// <owner current="true" primary="true">[....]</owner> -// <owner current="false" primary="false">[....]</owner> +// <owner current="true" primary="true">Microsoft</owner> +// <owner current="true" primary="true">Microsoft</owner> +// <owner current="false" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ using System; @@ -151,7 +151,7 @@ internal class SqlDependencyProcessDispatcher : MarshalByRefObject { // MBR sinc _conversationGuidParam = new SqlParameter("@p1", SqlDbType.UniqueIdentifier); _timeoutParam = new SqlParameter("@p2", SqlDbType.Int); - _timeoutParam.Value = 0; // Timeout set to 0 for initial [....] query. + _timeoutParam.Value = 0; // Timeout set to 0 for initial sync query. _com.Parameters.Add(_timeoutParam); setupCompleted = true; @@ -160,7 +160,7 @@ internal class SqlDependencyProcessDispatcher : MarshalByRefObject { // MBR sinc // Create standard query. _receiveQuery = "WAITFOR(RECEIVE TOP (1) message_type_name, conversation_handle, cast(message_body AS XML) as message_body from " + _escapedQueueName + "), TIMEOUT @p2;"; - // Create queue, service, [....] query, and async query on user thread to ensure proper + // Create queue, service, sync query, and async query on user thread to ensure proper // init prior to return. if (useDefaults) { // Only create if user did not specify service & database. @@ -179,7 +179,7 @@ internal class SqlDependencyProcessDispatcher : MarshalByRefObject { // MBR sinc // Query synchronously once to ensure everything is working correctly. // We want the exception to occur on start to immediately inform caller. SynchronouslyQueryServiceBrokerQueue(); - _timeoutParam.Value = _defaultWaitforTimeout; // [....] successful, extend timeout to 60 seconds. + _timeoutParam.Value = _defaultWaitforTimeout; // Sync successful, extend timeout to 60 seconds. AsynchronouslyQueryServiceBrokerQueue(); } catch (Exception e) { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDependencyUtils.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDependencyUtils.cs index ae91e9644a2..ccd6d84f7e4 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDependencyUtils.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlDependencyUtils.cs @@ -2,9 +2,9 @@ // <copyright file="SqlDependencyUtils.cs" company="Microsoft"> // Copyright (c) Microsoft Corporation. All rights reserved. // </copyright> -// <owner current="true" primary="true">[....]</owner> -// <owner current="true" primary="true">[....]</owner> -// <owner current="false" primary="false">[....]</owner> +// <owner current="true" primary="true">Microsoft</owner> +// <owner current="true" primary="true">Microsoft</owner> +// <owner current="false" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { @@ -179,7 +179,7 @@ namespace System.Data.SqlClient { { // this should not happen since _commandHashToNotificationId and _notificationIdToDependenciesHash are always // updated together - Debug.Assert(false, "_commandHashToNotificationId has entries that were removed from _notificationIdToDependenciesHash. Remember to keep them in [....]"); + Debug.Assert(false, "_commandHashToNotificationId has entries that were removed from _notificationIdToDependenciesHash. Remember to keep them in sync"); throw ADP.InternalError(ADP.InternalErrorCode.SqlDependencyCommandHashIsNotAssociatedWithNotification); } @@ -217,7 +217,7 @@ namespace System.Data.SqlClient { } - Debug.Assert(_notificationIdToDependenciesHash.Count == _commandHashToNotificationId.Count, "always keep these maps in [....]!"); + Debug.Assert(_notificationIdToDependenciesHash.Count == _commandHashToNotificationId.Count, "always keep these maps in sync!"); } } } @@ -402,7 +402,7 @@ namespace System.Data.SqlClient { Bid.NotificationsTrace("<sc.SqlDependencyPerAppDomainDispatcher.LookupDependencyEntriesWithRemove|DEP> Entries NOT found in hashtable.\n"); } - Debug.Assert(_notificationIdToDependenciesHash.Count == _commandHashToNotificationId.Count, "always keep these maps in [....]!"); + Debug.Assert(_notificationIdToDependenciesHash.Count == _commandHashToNotificationId.Count, "always keep these maps in sync!"); } return entry; // DependencyList inherits from List<SqlDependency> @@ -436,7 +436,7 @@ namespace System.Data.SqlClient { // same SqlDependency can be associated with more than one command, so we have to continue till the end... } - Debug.Assert(commandHashesToRemove.Count == notificationIdsToRemove.Count, "maps should be kept in [....]"); + Debug.Assert(commandHashesToRemove.Count == notificationIdsToRemove.Count, "maps should be kept in sync"); for (int i = 0; i < notificationIdsToRemove.Count; i++ ) { // cleanup the entry outside of foreach // do it inside finally block to avoid ThreadAbort exception interrupt this operation @@ -448,7 +448,7 @@ namespace System.Data.SqlClient { } } - Debug.Assert(_notificationIdToDependenciesHash.Count == _commandHashToNotificationId.Count, "always keep these maps in [....]!"); + Debug.Assert(_notificationIdToDependenciesHash.Count == _commandHashToNotificationId.Count, "always keep these maps in sync!"); } } finally { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlEnums.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlEnums.cs index da0013b801c..ea4a9a88bde 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlEnums.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlEnums.cs @@ -2,8 +2,8 @@ // <copyright file="SqlEnums.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlError.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlError.cs index 9b4d5ee36a5..4e65e159d58 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlError.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlError.cs @@ -2,8 +2,8 @@ // <copyright file="SqlError.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { @@ -15,8 +15,8 @@ namespace System.Data.SqlClient { [Serializable] public sealed class SqlError { - // bug fix - MDAC 48965 - missing source of exception - // fixed by [....] + // + private string source = TdsEnums.SQL_PROVIDER_NAME; private int number; private byte state; @@ -51,17 +51,17 @@ namespace System.Data.SqlClient { this.win32ErrorCode = 0; } - // bug fix - MDAC #49280 - SqlError does not implement ToString(); - // I did not include an exception stack because the correct exception stack is only available - // on SqlException, and to obtain that the SqlError would have to have backpointers all the - // way back to SqlException. If the user needs a call stack, they can obtain it on SqlException. + // + + + public override string ToString() { //return this.GetType().ToString() + ": " + this.message; return typeof(SqlError).ToString() + ": " + this.message; // since this is sealed so we can change GetType to typeof } - // bug fix - MDAC #48965 - missing source of exception - // fixed by [....] + // + public string Source { get { return this.source;} } diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlErrorCollection.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlErrorCollection.cs index 143be5fa399..18ea5d8081e 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlErrorCollection.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlErrorCollection.cs @@ -2,8 +2,8 @@ // <copyright file="SqlErrorCollection.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlException.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlException.cs index 2da09347a56..72dab1c0a59 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlException.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlException.cs @@ -2,8 +2,8 @@ // <copyright file="SqlException.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlInfoMessageEvent.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlInfoMessageEvent.cs index 51e69e9dd4d..1e3eadb40fd 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlInfoMessageEvent.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlInfoMessageEvent.cs @@ -2,8 +2,8 @@ // <copyright file="SqlInfoMessageEvent.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlInfoMessageEventHandler.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlInfoMessageEventHandler.cs index c299cee5106..d6c63875a81 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlInfoMessageEventHandler.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlInfoMessageEventHandler.cs @@ -2,8 +2,8 @@ // <copyright file="SqlInfoMessageEventHandler.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">blained</owner> +// <owner current="true" primary="false">laled</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlInternalConnection.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlInternalConnection.cs index 105f4432368..386a6cea58e 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlInternalConnection.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlInternalConnection.cs @@ -2,8 +2,8 @@ // <copyright file="SqlInternalConnection.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlInternalConnectionSmi.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlInternalConnectionSmi.cs index 16772773427..becb5962e50 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlInternalConnectionSmi.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlInternalConnectionSmi.cs @@ -2,8 +2,8 @@ // <copyright file="SqlInternalConnectionSmi.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlInternalConnectionTds.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlInternalConnectionTds.cs index 82e3138fd73..d8c893d6c82 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlInternalConnectionTds.cs @@ -2,8 +2,8 @@ // <copyright file="SqlInternalConnectionTds.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient @@ -104,6 +104,7 @@ namespace System.Data.SqlClient private TdsParser _parser; private SqlLoginAck _loginAck; private SqlCredential _credential; + private FederatedAuthenticationFeatureExtensionData? _fedAuthFeatureExtensionData; // Connection Resiliency private bool _sessionRecoveryRequested; @@ -111,6 +112,62 @@ namespace System.Data.SqlClient internal SessionData _currentSessionData; // internal for use from TdsParser only, otehr should use CurrentSessionData property that will fix database and language private SessionData _recoverySessionData; + // Federated Authentication + // Response obtained from the server for FEDAUTHREQUIRED prelogin option. + internal bool _fedAuthRequired; + + internal bool _federatedAuthenticationRequested; + internal bool _federatedAuthenticationAcknowledged; + internal bool _federatedAuthenticationInfoRequested; // Keep this distinct from _federatedAuthenticationRequested, since some fedauth library types may not need more info + internal bool _federatedAuthenticationInfoReceived; + + // TCE flags + internal byte _tceVersionSupported; + + internal byte[] _accessTokenInBytes; + + // The pool that this connection is associated with, if at all it is. + private DbConnectionPool _dbConnectionPool; + + // This is used to preserve the authentication context object if we decide to cache it for subsequent connections in the same pool. + // This will finally end up in _dbConnectionPool.AuthenticationContexts, but only after 1 successful login to SQL Server using this context. + // This variable is to persist the context after we have generated it, but before we have successfully completed the login with this new context. + // If this connection attempt ended up re-using the existing context and not create a new one, this will be null (since the context is not new). + private DbConnectionPoolAuthenticationContext _newDbConnectionPoolAuthenticationContext; + + // The key of the authentication context, built from information found in the FedAuthInfoToken. + private DbConnectionPoolAuthenticationContextKey _dbConnectionPoolAuthenticationContextKey; + +#if DEBUG + // This is a test hook to enable testing of the retry paths for ADAL get access token. + // Sample code to enable: + // + // Type type = typeof(SqlConnection).Assembly.GetType("System.Data.SqlClient.SqlInternalConnectionTds"); + // System.Reflection.FieldInfo field = type.GetField("_forceAdalRetry", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + // if (field != null) { + // field.SetValue(null, true); + // } + // + internal static bool _forceAdalRetry = false; + + // This is a test hook to simulate a token expiring within the next 45 minutes. + private static bool _forceExpiryLocked = false; + + // This is a test hook to simulate a token expiring within the next 10 minutes. + private static bool _forceExpiryUnLocked = false; +#endif //DEBUG + + // The timespan defining the amount of time the authentication context needs to be valid for at-least, to re-use the cached context, + // without making an attempt to refresh it. IF the context is expiring within the next 45 mins, then try to take a lock and refresh + // the context, if the lock is acquired. + private static readonly TimeSpan _dbAuthenticationContextLockedRefreshTimeSpan = new TimeSpan(hours: 0, minutes: 45, seconds: 00); + + // The timespan defining the minimum amount of time the authentication context needs to be valid for re-using the cached context. + // If the context is expiring within the next 10 mins, then create a new context, irrespective of if another thread is trying to do the same. + private static readonly TimeSpan _dbAuthenticationContextUnLockedRefreshTimeSpan = new TimeSpan(hours: 0, minutes: 10, seconds: 00); + + private readonly TimeoutTimer _timeout; + internal SessionData CurrentSessionData { get { if (_currentSessionData != null) { @@ -254,7 +311,9 @@ namespace System.Data.SqlClient SecureString newSecurePassword, bool redirectedUserInstance, SqlConnectionString userConnectionOptions = null, // NOTE: userConnectionOptions may be different to connectionOptions if the connection string has been expanded (see SqlConnectionString.Expand) - SessionData reconnectSessionData = null) : base(connectionOptions) { + SessionData reconnectSessionData = null, + DbConnectionPool pool = null, + string accessToken = null) : base(connectionOptions) { #if DEBUG if (reconnectSessionData != null) { @@ -275,6 +334,8 @@ namespace System.Data.SqlClient } #endif Debug.Assert(reconnectSessionData == null || connectionOptions.ConnectRetryCount > 0, "Reconnect data supplied with CR turned off"); + + _dbConnectionPool = pool; if (connectionOptions.ConnectRetryCount > 0) { _recoverySessionData = reconnectSessionData; @@ -287,11 +348,15 @@ namespace System.Data.SqlClient _originalLanguage = _recoverySessionData._initialLanguage; } } - + if (connectionOptions.UserInstance && InOutOfProcHelper.InProc) { throw SQL.UserInstanceNotAvailableInProc(); } + if (accessToken != null) { + _accessTokenInBytes = System.Text.Encoding.Unicode.GetBytes(accessToken); + } + _identity = identity; Debug.Assert(newSecurePassword != null || newPassword != null, "cannot have both new secure change password and string based change password to be null"); Debug.Assert(credential == null || (String.IsNullOrEmpty(connectionOptions.UserID) && String.IsNullOrEmpty(connectionOptions.Password)), "cannot mix the new secure password system and the connection string based password"); @@ -322,8 +387,8 @@ namespace System.Data.SqlClient #else { #endif //DEBUG - var timeout = TimeoutTimer.StartSecondsTimeout(connectionOptions.ConnectTimeout); - OpenLoginEnlist(timeout, connectionOptions, credential, newPassword, newSecurePassword, redirectedUserInstance); + _timeout = TimeoutTimer.StartSecondsTimeout(connectionOptions.ConnectTimeout); + OpenLoginEnlist(_timeout, connectionOptions, credential, newPassword, newSecurePassword, redirectedUserInstance); } #if DEBUG finally { @@ -1028,6 +1093,15 @@ namespace System.Data.SqlClient _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj); if (_routingInfo == null) { // ROR should not affect state of connection recovery + if (_federatedAuthenticationRequested && !_federatedAuthenticationAcknowledged) { + Bid.Trace("<sc.SqlInternalConnectionTds.CompleteLogin|ERR> %d#, Server did not acknowledge the federated authentication request\n", ObjectID); + throw SQL.ParsingError(ParsingErrorState.FedAuthNotAcknowledged); + } + if (_federatedAuthenticationInfoRequested && !_federatedAuthenticationInfoReceived) { + Bid.Trace("<sc.SqlInternalConnectionTds.CompleteLogin|ERR> %d#, Server never sent the requested federated authentication info\n", ObjectID); + throw SQL.ParsingError(ParsingErrorState.FedAuthInfoNotReceived); + } + if (!_sessionRecoveryAcknowledged) { _currentSessionData = null; if (_recoverySessionData != null) { @@ -1094,6 +1168,7 @@ namespace System.Data.SqlClient } } + login.authentication = ConnectionOptions.Authentication; login.timeout = timeoutInSeconds; login.userInstance = ConnectionOptions.UserInstance; login.hostName = ConnectionOptions.ObtainWorkstationId(); @@ -1113,7 +1188,8 @@ namespace System.Data.SqlClient login.serverName = server.UserServerName; login.useReplication = ConnectionOptions.Replication; - login.useSSPI = ConnectionOptions.IntegratedSecurity; + login.useSSPI = ConnectionOptions.IntegratedSecurity + || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && !_fedAuthRequired); login.packetSize = _currentPacketSize; login.newPassword = newPassword; login.readOnlyIntent = ConnectionOptions.ApplicationIntent == ApplicationIntent.ReadOnly; @@ -1128,7 +1204,34 @@ namespace System.Data.SqlClient _sessionRecoveryRequested = true; } - _parser.TdsLogin(login, requestedFeatures, _recoverySessionData); + // If the workflow being used is Active Directory Password or Active Directory Integrated and server's prelogin response + // for FEDAUTHREQUIRED option indicates Federated Authentication is required, we have to insert FedAuth Feature Extension + // in Login7, indicating the intent to use Active Directory Authentication Library for SQL Server. + if (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryPassword + || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired)) { + requestedFeatures |= TdsEnums.FeatureExtension.FedAuth; + _federatedAuthenticationInfoRequested = true; + _fedAuthFeatureExtensionData = + new FederatedAuthenticationFeatureExtensionData { + libraryType = TdsEnums.FedAuthLibrary.ADAL, + authentication = ConnectionOptions.Authentication, + fedAuthRequiredPreLoginResponse = _fedAuthRequired + }; + } + if (_accessTokenInBytes != null) { + requestedFeatures |= TdsEnums.FeatureExtension.FedAuth; + _fedAuthFeatureExtensionData = new FederatedAuthenticationFeatureExtensionData { + libraryType = TdsEnums.FedAuthLibrary.SecurityToken, + fedAuthRequiredPreLoginResponse = _fedAuthRequired, + accessToken = _accessTokenInBytes + }; + // No need any further info from the server for token based authentication. So set _federatedAuthenticationRequested to true + _federatedAuthenticationRequested = true; + } + + // The TCE feature is implicitly requested + requestedFeatures |= TdsEnums.FeatureExtension.Tce; + _parser.TdsLogin(login, requestedFeatures, _recoverySessionData, _fedAuthFeatureExtensionData); } private void LoginFailure() { @@ -1326,9 +1429,9 @@ namespace System.Data.SqlClient } catch (SqlException sqlex) { if (null == _parser - || TdsParserState.Closed != _parser.State - || IsDoNotRetryConnectError(sqlex) - || timeout.IsExpired) { // no more time to try again + || TdsParserState.Closed != _parser.State + || IsDoNotRetryConnectError(sqlex) + || timeout.IsExpired) { // no more time to try again throw; // Caller will call LoginFailure() } @@ -1518,7 +1621,7 @@ namespace System.Data.SqlClient } catch (SqlException sqlex) { if (IsDoNotRetryConnectError(sqlex) - || timeout.IsExpired) + || timeout.IsExpired) { // no more time to try again throw; // Caller will call LoginFailure() } @@ -1625,7 +1728,8 @@ namespace System.Data.SqlClient ConnectionOptions.Encrypt, ConnectionOptions.TrustServerCertificate, ConnectionOptions.IntegratedSecurity, - withFailover); + withFailover, + ConnectionOptions.Authentication); timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake); timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.LoginBegin); @@ -1821,6 +1925,9 @@ namespace System.Data.SqlClient break; case TdsEnums.ENV_ROUTING: + if (Bid.AdvancedOn) { + Bid.Trace("<sc.SqlInternalConnectionTds.OnEnvChange> %d#, Received routing info\n", ObjectID); + } if (string.IsNullOrEmpty(rec.newRoutingInfo.ServerName) || rec.newRoutingInfo.Protocol != 0 || rec.newRoutingInfo.Port == 0) { throw SQL.ROR_InvalidRoutingInfo(this); } @@ -1846,6 +1953,276 @@ namespace System.Data.SqlClient } } + /// <summary> + /// Generates (if appropriate) and sends a Federated Authentication Access token to the server, using the Federated Authentication Info. + /// </summary> + /// <param name="fedAuthInfo">Federated Authentication Info.</param> + internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) { + Debug.Assert((ConnectionOptions.HasUserIdKeyword && ConnectionOptions.HasPasswordKeyword) + || _credential != null + || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired), + "Credentials aren't provided for calling ADAL"); + Debug.Assert(fedAuthInfo != null, "info should not be null."); + Debug.Assert(_dbConnectionPoolAuthenticationContextKey == null, "_dbConnectionPoolAuthenticationContextKey should be null."); + + Bid.Trace("<sc.SqlInternalConnectionTds.OnFedAuthInfo> %d#, Generating federated authentication token\n", ObjectID); + + DbConnectionPoolAuthenticationContext dbConnectionPoolAuthenticationContext = null; + + // We want to refresh the token without taking a lock on the context, allowed when the access token is expiring within the next 10 mins. + bool attemptRefreshTokenUnLocked = false; + + // We want to refresh the token, if taking the lock on the authentication context is successful. + bool attemptRefreshTokenLocked = false; + + // The Federated Authentication returned by TryGetFedAuthTokenLocked or GetFedAuthToken. + SqlFedAuthToken fedAuthToken = null; + + if (_dbConnectionPool != null) { + Debug.Assert(_dbConnectionPool.AuthenticationContexts != null); + + // Construct the dbAuthenticationContextKey with information from FedAuthInfo and store for later use, when inserting in to the token cache. + _dbConnectionPoolAuthenticationContextKey = new DbConnectionPoolAuthenticationContextKey(fedAuthInfo.stsurl, fedAuthInfo.spn); + + // Try to retrieve the authentication context from the pool, if one does exist for this key. + if (_dbConnectionPool.AuthenticationContexts.TryGetValue(_dbConnectionPoolAuthenticationContextKey, out dbConnectionPoolAuthenticationContext)) { + Debug.Assert(dbConnectionPoolAuthenticationContext != null, "dbConnectionPoolAuthenticationContext should not be null."); + + // The timespan between UTCNow and the token expiry. + TimeSpan contextValidity = dbConnectionPoolAuthenticationContext.ExpirationTime.Subtract(DateTime.UtcNow); + + // If the authentication context is expiring within next 10 minutes, lets just re-create a token for this connection attempt. + // And on successful login, try to update the cache with the new token. + if (contextValidity <= _dbAuthenticationContextUnLockedRefreshTimeSpan) { + Bid.Trace("<sc.SqlInternalConnectionTds.OnFedAuthInfo> %d#, The expiration time is less than 10 mins, so trying to get new access token regardless of if an other thread is also trying to update it.The expiration time is %s. Current Time is %s.\n", ObjectID, dbConnectionPoolAuthenticationContext.ExpirationTime.ToLongTimeString(), DateTime.UtcNow.ToLongTimeString()); + + attemptRefreshTokenUnLocked = true; + } + +#if DEBUG + // Checking if any failpoints are enabled. + else if (_forceExpiryUnLocked) { + attemptRefreshTokenUnLocked = true; + } + else if (_forceExpiryLocked) { + attemptRefreshTokenLocked = TryGetFedAuthTokenLocked(fedAuthInfo, dbConnectionPoolAuthenticationContext, out fedAuthToken); + } +#endif + + // If the token is expiring within the next 45 mins, try to fetch a new token, if there is no thread already doing it. + // If a thread is already doing the refresh, just use the existing token in the cache and proceed. + else if (contextValidity <= _dbAuthenticationContextLockedRefreshTimeSpan) { + if (Bid.AdvancedOn) { + Bid.Trace("<sc.SqlInternalConnectionTds.OnFedAuthInfo> %d#, The authentication context needs a refresh.The expiration time is %s. Current Time is %s.\n", ObjectID, dbConnectionPoolAuthenticationContext.ExpirationTime.ToLongTimeString(), DateTime.UtcNow.ToLongTimeString()); + } + + // Call the function which tries to acquire a lock over the authentication context before trying to update. + // If the lock could not be obtained, it will return false, without attempting to fetch a new token. + attemptRefreshTokenLocked = TryGetFedAuthTokenLocked(fedAuthInfo, dbConnectionPoolAuthenticationContext, out fedAuthToken); + + // If TryGetFedAuthTokenLocked returns true, it means lock was obtained and fedAuthToken should not be null. + // If there was an exception in retrieving the new token, TryGetFedAuthTokenLocked should have thrown, so we won't be here. + Debug.Assert(!attemptRefreshTokenLocked || fedAuthToken != null, "Either Lock should not have been obtained or fedAuthToken should not be null."); + Debug.Assert(!attemptRefreshTokenLocked || _newDbConnectionPoolAuthenticationContext != null, "Either Lock should not have been obtained or _newDbConnectionPoolAuthenticationContext should not be null."); + + // Indicate in Bid Trace that we are successful with the update. + if (attemptRefreshTokenLocked) { + Bid.Trace("<sc.SqlInternalConnectionTds.OnFedAuthInfo> %d#, The attempt to get a new access token succeeded under the locked mode."); + } + + } + else if (Bid.AdvancedOn) { + Bid.Trace("<sc.SqlInternalConnectionTds.OnFedAuthInfo> %d#, Found an authentication context in the cache that does not need a refresh at this time. Re-using the cached token.\n", ObjectID); + } + } + } + + // dbConnectionPoolAuthenticationContext will be null if either this is the first connection attempt in the pool or pooling is disabled. + if (dbConnectionPoolAuthenticationContext == null || attemptRefreshTokenUnLocked) { + // Get the Federated Authentication Token. + fedAuthToken = GetFedAuthToken(fedAuthInfo); + Debug.Assert(fedAuthToken != null, "fedAuthToken should not be null."); + + if (_dbConnectionPool != null) { + // GetFedAuthToken should have updated _newDbConnectionPoolAuthenticationContext. + Debug.Assert(_newDbConnectionPoolAuthenticationContext != null, "_newDbConnectionPoolAuthenticationContext should not be null."); + } + } + else if (!attemptRefreshTokenLocked) { + Debug.Assert(dbConnectionPoolAuthenticationContext != null, "dbConnectionPoolAuthenticationContext should not be null."); + Debug.Assert(fedAuthToken == null, "fedAuthToken should be null in this case."); + Debug.Assert(_newDbConnectionPoolAuthenticationContext == null, "_newDbConnectionPoolAuthenticationContext should be null."); + + fedAuthToken = new SqlFedAuthToken(); + + // If the code flow is here, then we are re-using the context from the cache for this connection attempt and not + // generating a new access token on this thread. + fedAuthToken.accessToken = dbConnectionPoolAuthenticationContext.AccessToken; + } + + Debug.Assert(fedAuthToken != null && fedAuthToken.accessToken != null, "fedAuthToken and fedAuthToken.accessToken cannot be null."); + _parser.SendFedAuthToken(fedAuthToken); + } + + /// <summary> + /// Tries to acquire a lock on the authentication context. If successful in acquiring the lock, gets a new token and assigns it in the out parameter. Else returns false. + /// </summary> + /// <param name="fedAuthInfo">Federated Authentication Info</param> + /// <param name="dbConnectionPoolAuthenticationContext">Authentication Context cached in the connection pool.</param> + /// <param name="fedAuthToken">Out parameter, carrying the token if we acquired a lock and got the token.</param> + /// <returns></returns> + internal bool TryGetFedAuthTokenLocked(SqlFedAuthInfo fedAuthInfo, DbConnectionPoolAuthenticationContext dbConnectionPoolAuthenticationContext, out SqlFedAuthToken fedAuthToken) { + + Debug.Assert(fedAuthInfo != null, "fedAuthInfo should not be null."); + Debug.Assert(dbConnectionPoolAuthenticationContext != null, "dbConnectionPoolAuthenticationContext should not be null."); + + fedAuthToken = null; + + // Variable which indicates if we did indeed manage to acquire the lock on the authentication context, to try update it. + bool authenticationContextLocked = false; + + // Prepare CER to ensure the lock on authentication context is released. + RuntimeHelpers.PrepareConstrainedRegions(); + try { + // Try to obtain a lock on the context. If acquired, this thread got the opportunity to update. + // Else some other thread is already updating it, so just proceed forward with the existing token in the cache. + if (dbConnectionPoolAuthenticationContext.LockToUpdate()) { + Bid.Trace("<sc.SqlInternalConnectionTds.TryGetFedAuthTokenLocked> %d#, Acquired the lock to update the authentication context.The expiration time is %s. Current Time is %s.\n", ObjectID, dbConnectionPoolAuthenticationContext.ExpirationTime.ToLongTimeString(), DateTime.UtcNow.ToLongTimeString()); + + authenticationContextLocked = true; + } + else { + Bid.Trace("<sc.SqlInternalConnectionTds.TryGetFedAuthTokenLocked> %d#, Refreshing the context is already in progress by another thread.\n", ObjectID); + } + + if (authenticationContextLocked) { + // Get the Federated Authentication Token. + fedAuthToken = GetFedAuthToken(fedAuthInfo); + Debug.Assert(fedAuthToken != null, "fedAuthToken should not be null."); + } + } + finally { + if (authenticationContextLocked) { + // Release the lock we took on the authentication context, even if we have not yet updated the cache with the new context. Login process can fail at several places after this step and so there is no guarantee that the new context will make it to the cache. So we shouldn't miss resetting the flag. With the reset, at-least another thread may have a chance to update it. + dbConnectionPoolAuthenticationContext.ReleaseLockToUpdate(); + } + } + + return authenticationContextLocked; + } + + /// <summary> + /// Get the Federated Authentication Token. + /// </summary> + /// <param name="fedAuthInfo">Information obtained from server as Federated Authentication Info.</param> + /// <returns>SqlFedAuthToken</returns> + internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) { + + Debug.Assert(fedAuthInfo != null, "fedAuthInfo should not be null."); + + // No:of milliseconds to sleep for the inital back off. + int sleepInterval = 100; + + // No:of attempts, for tracing purposes, if we underwent retries. + int numberOfAttempts = 0; + + // Object that will be returned to the caller, containing all required data about the token. + SqlFedAuthToken fedAuthToken = new SqlFedAuthToken(); + + // Username to use in error messages. + string username = null; + + while (true) { + numberOfAttempts++; + try { + if (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { + username = TdsEnums.NTAUTHORITYANONYMOUSLOGON; + fedAuthToken.accessToken = ADALNativeWrapper.ADALGetAccessTokenForWindowsIntegrated(fedAuthInfo.stsurl, + fedAuthInfo.spn, + _clientConnectionId, ActiveDirectoryAuthentication.AdoClientId, + ref fedAuthToken.expirationFileTime); + } + else if (_credential != null) { + username = _credential.UserId; + fedAuthToken.accessToken = ADALNativeWrapper.ADALGetAccessToken(_credential.UserId, + _credential.Password, + fedAuthInfo.stsurl, + fedAuthInfo.spn, + _clientConnectionId, + ActiveDirectoryAuthentication.AdoClientId, + ref fedAuthToken.expirationFileTime); + } + else { + username = ConnectionOptions.UserID; + fedAuthToken.accessToken = ADALNativeWrapper.ADALGetAccessToken(ConnectionOptions.UserID, + ConnectionOptions.Password, + fedAuthInfo.stsurl, + fedAuthInfo.spn, + _clientConnectionId, + ActiveDirectoryAuthentication.AdoClientId, + ref fedAuthToken.expirationFileTime); + } + + Debug.Assert(fedAuthToken.accessToken != null, "AccessToken should not be null."); +#if DEBUG + if (_forceAdalRetry) { + // 3399614468 is 0xCAA20004L just for testing. + throw new AdalException("Force retry in GetFedAuthToken", ActiveDirectoryAuthentication.GetAccessTokenTansisentError, 3399614468, 6); + } +#endif + // Break out of the retry loop in successful case. + break; + } + catch (AdalException adalException) { + + uint errorCategory = adalException.GetCategory(); + + if (ActiveDirectoryAuthentication.GetAccessTokenTansisentError != errorCategory + || _timeout.IsExpired + || _timeout.MillisecondsRemaining <= sleepInterval) { + + string errorStatus = adalException.GetStatus().ToString("X"); + + Bid.Trace("<sc.SqlInternalConnectionTds.GetFedAuthToken.ADALException category:> %d# <error:> %s#\n", (int)errorCategory, errorStatus); + + // Error[0] + SqlErrorCollection sqlErs = new SqlErrorCollection(); + sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, Res.GetString(Res.SQL_ADALFailure, username, ConnectionOptions.Authentication.ToString("G")), ActiveDirectoryAuthentication.AdalGetAccessTokenFunctionName, 0)); + + // Error[1] + string errorMessage1 = Res.GetString(Res.SQL_ADALInnerException, errorStatus, adalException.GetState()); + sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, errorMessage1, ActiveDirectoryAuthentication.AdalGetAccessTokenFunctionName, 0)); + + // Error[2] + if (!string.IsNullOrEmpty(adalException.Message)) { + sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, adalException.Message, ActiveDirectoryAuthentication.AdalGetAccessTokenFunctionName, 0)); + } + SqlException exc = SqlException.CreateException(sqlErs, "", this); + throw exc; + } + + Bid.Trace("<sc.SqlInternalConnectionTds.GetFedAuthToken|ADV> %d#, sleeping %d{Milliseconds}\n", ObjectID, sleepInterval); + Bid.Trace("<sc.SqlInternalConnectionTds.GetFedAuthToken|ADV> %d#, remaining %d{Milliseconds}\n", ObjectID, _timeout.MillisecondsRemaining); + + Thread.Sleep(sleepInterval); + sleepInterval *= 2; + } + } + + Debug.Assert(fedAuthToken != null, "fedAuthToken should not be null."); + Debug.Assert(fedAuthToken.accessToken != null && fedAuthToken.accessToken.Length > 0, "fedAuthToken.accessToken should not be null or empty."); + + // Store the newly generated token in _newDbConnectionPoolAuthenticationContext, only if using pooling. + if (_dbConnectionPool != null) { + DateTime expirationTime = DateTime.FromFileTimeUtc(fedAuthToken.expirationFileTime); + _newDbConnectionPoolAuthenticationContext = new DbConnectionPoolAuthenticationContext(fedAuthToken.accessToken, expirationTime); + } + + Bid.Trace("<sc.SqlInternalConnectionTds.GetFedAuthToken> %d#, Finished generating federated authentication token.\n", ObjectID); + + return fedAuthToken; + } + internal void OnFeatureExtAck(int featureId, byte[] data) { if (_routingInfo != null) { return; @@ -1854,7 +2231,7 @@ namespace System.Data.SqlClient case TdsEnums.FEATUREEXT_SRECOVERY: { // Session recovery not requested if (!_sessionRecoveryRequested) { - throw SQL.ParsingError(); + throw SQL.ParsingErrorFeatureId(ParsingErrorState.UnrequestedFeatureAckReceived, featureId); } _sessionRecoveryAcknowledged = true; @@ -1888,9 +2265,82 @@ namespace System.Data.SqlClient } break; } + case TdsEnums.FEATUREEXT_FEDAUTH: { + if (Bid.AdvancedOn) { + Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck> %d#, Received feature extension acknowledgement for federated authentication\n", ObjectID); + } + if (!_federatedAuthenticationRequested) { + Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, Did not request federated authentication\n", ObjectID); + throw SQL.ParsingErrorFeatureId(ParsingErrorState.UnrequestedFeatureAckReceived, featureId); + } + + Debug.Assert(_fedAuthFeatureExtensionData != null, "_fedAuthFeatureExtensionData must not be null when _federatedAuthenticatonRequested == true"); + + switch (_fedAuthFeatureExtensionData.Value.libraryType) { + case TdsEnums.FedAuthLibrary.ADAL: + case TdsEnums.FedAuthLibrary.SecurityToken: + // The server shouldn't have sent any additional data with the ack (like a nonce) + if (data.Length != 0) { + Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, Federated authentication feature extension ack for ADAL and Security Token includes extra data\n", ObjectID); + throw SQL.ParsingError(ParsingErrorState.FedAuthFeatureAckContainsExtraData); + } + break; + + default: + Debug.Assert(false, "Unknown _fedAuthLibrary type"); + Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, Attempting to use unknown federated authentication library\n", ObjectID); + throw SQL.ParsingErrorLibraryType(ParsingErrorState.FedAuthFeatureAckUnknownLibraryType, (int)_fedAuthFeatureExtensionData.Value.libraryType); + } + _federatedAuthenticationAcknowledged = true; + + // If a new authentication context was used as part of this login attempt, try to update the new context in the cache, i.e.dbConnectionPool.AuthenticationContexts. + // ChooseAuthenticationContextToUpdate will take care that only the context which has more validity will remain in the cache, based on the Update logic. + if (_newDbConnectionPoolAuthenticationContext != null) + { + Debug.Assert(_dbConnectionPool != null, "_dbConnectionPool should not be null when _newDbConnectionPoolAuthenticationContext != null."); + + DbConnectionPoolAuthenticationContext newAuthenticationContextInCacheAfterAddOrUpdate = _dbConnectionPool.AuthenticationContexts.AddOrUpdate(_dbConnectionPoolAuthenticationContextKey, _newDbConnectionPoolAuthenticationContext, + (key, oldValue) => DbConnectionPoolAuthenticationContext.ChooseAuthenticationContextToUpdate(oldValue, _newDbConnectionPoolAuthenticationContext)); + + Debug.Assert(newAuthenticationContextInCacheAfterAddOrUpdate != null, "newAuthenticationContextInCacheAfterAddOrUpdate should not be null."); +#if DEBUG + // For debug purposes, assert and trace if we ended up updating the cache with the new one or some other thread's context won the expiration ----. + if (newAuthenticationContextInCacheAfterAddOrUpdate == _newDbConnectionPoolAuthenticationContext) { + Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, Updated the new dbAuthenticationContext in the _dbConnectionPool.AuthenticationContexts. \n", ObjectID); + } + else { + Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, AddOrUpdate attempted on _dbConnectionPool.AuthenticationContexts, but it did not update the new value. \n", ObjectID); + } +#endif + } + + break; + } + case TdsEnums.FEATUREEXT_TCE: { + if (Bid.AdvancedOn) { + Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck> %d#, Received feature extension acknowledgement for TCE\n", ObjectID); + } + + if (data.Length < 1) { + Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, Unknown version number for TCE\n", ObjectID); + throw SQL.ParsingError(ParsingErrorState.TceUnknownVersion); + } + + byte supportedTceVersion = data[0]; + if (0 == supportedTceVersion || supportedTceVersion > TdsEnums.MAX_SUPPORTED_TCE_VERSION) { + Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, Invalid version number for TCE\n", ObjectID); + throw SQL.ParsingErrorValue(ParsingErrorState.TceInvalidVersion, supportedTceVersion); + } + + _tceVersionSupported = supportedTceVersion; + Debug.Assert (_tceVersionSupported == TdsEnums.MAX_SUPPORTED_TCE_VERSION, "Client support TCE version 1"); + _parser.IsColumnEncryptionSupported = true; + break; + } + default: { // Unknown feature ack - throw SQL.ParsingError(); + throw SQL.ParsingErrorFeatureId(ParsingErrorState.UnknownFeatureAck, featureId); } } } diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlNotificationEventArgs.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlNotificationEventArgs.cs index 89498fc18ec..590e7bfe329 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlNotificationEventArgs.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlNotificationEventArgs.cs @@ -2,9 +2,9 @@ // <copyright file="SqlNotificationEventArgs.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="false" primary="true">[....]</owner> +// <owner current="true" primary="true">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> +// <owner current="false" primary="true">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlNotificationInfo.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlNotificationInfo.cs index 97826a6c96b..bb13ddb37c3 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlNotificationInfo.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlNotificationInfo.cs @@ -2,9 +2,9 @@ // <copyright file="SqlNotificationInfo.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="false" primary="true">[....]</owner> +// <owner current="true" primary="true">mithomas</owner> +// <owner current="true" primary="false">blained</owner> +// <owner current="false" primary="true">ramp</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlNotificationSource.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlNotificationSource.cs index 6fa06cf29ec..cc787d352fc 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlNotificationSource.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlNotificationSource.cs @@ -2,9 +2,9 @@ // <copyright file="SqlNotificationSource.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="false" primary="false">[....]</owner> +// <owner current="true" primary="true">mithomas</owner> +// <owner current="true" primary="false">blained</owner> +// <owner current="false" primary="false">ramp</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlNotificationType.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlNotificationType.cs index 17f31f43c07..6cf8747e9ac 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlNotificationType.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlNotificationType.cs @@ -2,9 +2,9 @@ // <copyright file="SqlNotificationType.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="false" primary="false">[....]</owner> +// <owner current="true" primary="true">mithomas</owner> +// <owner current="true" primary="false">blained</owner> +// <owner current="false" primary="false">ramp</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlParameter.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlParameter.cs index 66614199c47..155094a3fa8 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlParameter.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlParameter.cs @@ -2,8 +2,8 @@ // <copyright file="SqlParameter.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { @@ -84,6 +84,48 @@ namespace System.Data.SqlClient { private bool _coercedValueIsDataFeed; private int _actualSize = -1; + /// <summary> + /// Column Encryption Cipher Related Metadata. + /// </summary> + private SqlCipherMetadata _columnEncryptionCipherMetadata; + + /// <summary> + /// Get or set the encryption related metadata of this SqlParameter. + /// Should be set to a non-null value only once. + /// </summary> + internal SqlCipherMetadata CipherMetadata { + get { + return _columnEncryptionCipherMetadata; + } + + set { + Debug.Assert(_columnEncryptionCipherMetadata == null || value == null, + "_columnEncryptionCipherMetadata should be set to a non-null value only once."); + + _columnEncryptionCipherMetadata = value; + } + } + + /// <summary> + /// Indicates if the parameter encryption metadata received by sp_describe_parameter_encryption. + /// For unencrypted parameters, the encryption metadata should still be sent (and will indicate + /// that no encryption is needed). + /// </summary> + internal bool HasReceivedMetadata { get; set; } + + /// <summary> + /// Returns the normalization rule version number as a byte + /// </summary> + internal byte NormalizationRuleVersion { + get { + if (_columnEncryptionCipherMetadata != null) { + return _columnEncryptionCipherMetadata.NormalizationRuleVersion; + } + + return 0x00; + } + } + public SqlParameter() : base() { } @@ -231,6 +273,16 @@ namespace System.Data.SqlClient { } } + [ + DefaultValue(false), + ResCategoryAttribute(Res.DataCategory_Data), + ResDescriptionAttribute(Res.TCE_SqlParameter_ForceColumnEncryption), + ] + public bool ForceColumnEncryption { + get; + set; + } + override public DbType DbType { get { return GetMetaTypeOnly().DbType; @@ -293,7 +345,7 @@ namespace System.Data.SqlClient { maxlen = (long)mt.FixedLength; } else if (Size > 0 || Size < 0) { - maxlen = Size; // Bug Fix: 302768, 302695, 302694, 302693 + maxlen = Size; // } else { maxlen = MSS.SmiMetaData.GetDefaultForType( mt.SqlDbType ).MaxLength; @@ -309,6 +361,22 @@ namespace System.Data.SqlClient { } } + /// <summary> + /// Get SMI Metadata to write out type_info stream. + /// </summary> + /// <returns></returns> + internal MSS.SmiParameterMetaData GetMetadataForTypeInfo() { + ParameterPeekAheadValue peekAhead = null; + + if (_internalMetaType == null) { + _internalMetaType = GetMetaTypeOnly(); + } + + return MetaDataForSmi(out peekAhead); + } + + // IMPORTANT DEVNOTE: This method is being used for parameter encryption functionality, to get the type_info TDS object from SqlParameter. + // Please consider impact to that when changing this method. Refer to the callers of SqlParameter.GetMetadataForTypeInfo(). internal MSS.SmiParameterMetaData MetaDataForSmi(out ParameterPeekAheadValue peekAhead) { peekAhead = null; MetaType mt = ValidateTypeLengths( true /* Yukon or newer */ ); @@ -448,7 +516,7 @@ namespace System.Data.SqlClient { internal bool ParamaterIsSqlType { get { return _isSqlParameterSqlType; - } + } set { _isSqlParameterSqlType = value; } @@ -998,6 +1066,7 @@ namespace System.Data.SqlClient { destination._coercedValueIsDataFeed = _coercedValueIsDataFeed; destination._coercedValueIsSqlType = _coercedValueIsSqlType; destination._actualSize = _actualSize; + destination.ForceColumnEncryption = ForceColumnEncryption; } internal byte GetActualPrecision() { @@ -1463,32 +1532,32 @@ namespace System.Data.SqlClient { // byte length and a parameter length > than that expressable in 2 bytes internal MetaType ValidateTypeLengths(bool yukonOrNewer) { MetaType mt = InternalMetaType; - // MDAC bug #50839 + #52829 : Since the server will automatically reject any - // char, varchar, binary, varbinary, nchar, or nvarchar parameter that has a - // byte sizeInCharacters > 8000 bytes, we promote the parameter to image, text, or ntext. This - // allows the user to specify a parameter type using a COM+ datatype and be able to - // use that parameter against a BLOB column. + // MDAC + + + + if ((SqlDbType.Udt != mt.SqlDbType) && (false == mt.IsFixed) && (false == mt.IsLong)) { // if type has 2 byte length long actualSizeInBytes = this.GetActualSize(); long sizeInCharacters = this.Size; - // Bug: VSTFDevDiv #636867 - // Notes: - // 'actualSizeInBytes' is the size of value passed; - // 'sizeInCharacters' is the parameter size; - // 'actualSizeInBytes' is in bytes; - // 'this.Size' is in charaters; - // 'sizeInCharacters' is in characters; - // 'TdsEnums.TYPE_SIZE_LIMIT' is in bytes; - // For Non-NCharType and for non-Yukon or greater variables, size should be maintained; - // Reverting changes from bug VSTFDevDiv # 479739 as it caused an regression; - // Modifed variable names from 'size' to 'sizeInCharacters', 'actualSize' to 'actualSizeInBytes', and - // 'maxSize' to 'maxSizeInBytes' - // The idea is to - // 1) revert the regression from bug 479739 - // 2) fix as many scenarios as possible including bug 636867 - // 3) cause no additional regression from 3.5 sp1 - // Keeping these goals in mind - the following are the changes we are making + // + + + + + + + + + + + + + + + + long maxSizeInBytes = 0; if ((mt.IsNCharType) && (yukonOrNewer)) diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlParameterCollection.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlParameterCollection.cs index 4367bc7a67c..a8951459130 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlParameterCollection.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlParameterCollection.cs @@ -2,8 +2,8 @@ // <copyright file="SqlParameterCollection.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlReferenceCollection.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlReferenceCollection.cs index e26069f2dac..c50e7ea4f77 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlReferenceCollection.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlReferenceCollection.cs @@ -2,8 +2,8 @@ // <copyright file="SqlReferenceCollection.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ using System; diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlRowUpdatedEvent.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlRowUpdatedEvent.cs index 75d37032331..a0ff53b1fad 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlRowUpdatedEvent.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlRowUpdatedEvent.cs @@ -2,8 +2,8 @@ // <copyright file="SqlRowUpdatedEvent.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlRowUpdatedEventHandler.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlRowUpdatedEventHandler.cs index 832883eccb4..b047910e735 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlRowUpdatedEventHandler.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlRowUpdatedEventHandler.cs @@ -2,8 +2,8 @@ // <copyright file="SqlRowUpdatedEventHandler.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">blained</owner> +// <owner current="true" primary="false">laled</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlRowUpdatingEvent.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlRowUpdatingEvent.cs index 6d3bcd921b4..2233e9dff94 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlRowUpdatingEvent.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlRowUpdatingEvent.cs @@ -2,8 +2,8 @@ // <copyright file="SqlRowUpdatingEvent.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlRowUpdatingEventHandler.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlRowUpdatingEventHandler.cs index ad38aaf49af..cffa552e77e 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlRowUpdatingEventHandler.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlRowUpdatingEventHandler.cs @@ -2,8 +2,8 @@ // <copyright file="SqlRowUpdatingEventHandler.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">blained</owner> +// <owner current="true" primary="false">laled</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlSecurityUtility.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlSecurityUtility.cs new file mode 100644 index 00000000000..a4b18c52525 --- /dev/null +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlSecurityUtility.cs @@ -0,0 +1,263 @@ +//------------------------------------------------------------------------------ +// <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.Diagnostics; + using System.Reflection; + using System.Security; + using System.Security.Cryptography; + using System.Text; + + internal static class SqlSecurityUtility { + + /// <summary> + /// Computes a keyed hash of a given text and returns. It fills the buffer "hash" with computed hash value. + /// </summary> + /// <param name="plainText">Plain text bytes whose hash has to be computed.</param> + /// <param name="key">key used for the HMAC</param> + /// <param name="hash">Output buffer where the computed hash value is stored. If its less that 64 bytes, the hash is truncated</param> + /// <returns>HMAC value</returns> + internal static void GetHMACWithSHA256(byte[] plainText, byte[] key, byte[] hash) { + const int MaxSHA256HashBytes = 32; + + Debug.Assert(key != null && plainText != null); + Debug.Assert(hash.Length != 0 && hash.Length <= MaxSHA256HashBytes); + + using (HMACSHA256 hmac = new HMACSHA256(key)) { + byte[] computedHash = hmac.ComputeHash(plainText); + + // Truncate the hash if needed + Buffer.BlockCopy (computedHash, 0, hash, 0, hash.Length); + } + } + + /// <summary> + /// Computes SHA256 hash of a given input + /// </summary> + /// <param name="input">input byte array which needs to be hashed</param> + /// <returns>Returns SHA256 hash in a string form</returns> + internal static string GetSHA256Hash(byte[] input) { + Debug.Assert(input != null); + + using (SHA256 sha256 = SHA256Cng.Create()) { + byte[] hashValue = sha256.ComputeHash(input); + return GetHexString(hashValue); + } + } + + /// <summary> + /// Generates cryptographicall random bytes + /// </summary> + /// <param name="length">No of cryptographically random bytes to be generated</param> + /// <returns>A byte array containing cryptographically generated random bytes</returns> + internal static void GenerateRandomBytes(byte[] randomBytes) { + // Generate random bytes cryptographically. + RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider(); + rngCsp.GetBytes(randomBytes); + } + + /// <summary> + /// Compares two byte arrays and returns true if all bytes are equal + /// </summary> + /// <param name="buffer1">input buffer</param> + /// <param name="buffer2">another buffer to be compared against</param> + /// <returns>returns true if both the arrays have the same byte values else returns false</returns> + internal static bool CompareBytes(byte[] buffer1, byte[] buffer2, int buffer2Index, int lengthToCompare) { + if (null == buffer1 || null == buffer2) { + return false; + } + + Debug.Assert (buffer2Index > -1 && buffer2Index < buffer2.Length, "invalid index");// bounds on buffer2Index + if ((buffer2.Length -buffer2Index) < lengthToCompare) { + return false; + } + + for (int index = 0; index < buffer1.Length && index < lengthToCompare; ++index) { + if (buffer1[index] != buffer2[buffer2Index + index]) { + return false; + } + } + + return true; + } + + /// <summary> + /// Gets hex representation of byte array. + /// <param name="input">input byte array</param> + /// </summary> + internal static string GetHexString(byte[] input) { + Debug.Assert(input != null); + + StringBuilder str = new StringBuilder(); + foreach (byte b in input) { + str.AppendFormat(b.ToString(@"X2")); + } + + return str.ToString(); + } + + /// <summary> + /// Returns the caller's function name in the format of [ClassName].[FunctionName] + /// </summary> + internal static string GetCurrentFunctionName() { + StackTrace stackTrace = new StackTrace(); + StackFrame stackFrame = stackTrace.GetFrame(1); + MethodBase methodBase = stackFrame.GetMethod(); + return string.Format(@"{0}.{1}", methodBase.DeclaringType.Name, methodBase.Name); + } + + /// <summary> + /// Return the algorithm name mapped to an Id. + /// </summary> + /// <param name="cipherAlgorithmId"></param> + /// <returns></returns> + private static string ValidateAndGetEncryptionAlgorithmName (byte cipherAlgorithmId, string cipherAlgorithmName) { + if (TdsEnums.CustomCipherAlgorithmId == cipherAlgorithmId) { + if (null == cipherAlgorithmName) { + throw SQL.NullColumnEncryptionAlgorithm(SqlClientEncryptionAlgorithmFactoryList.GetInstance().GetRegisteredCipherAlgorithmNames()); + } + + return cipherAlgorithmName; + } + else if (TdsEnums.AEAD_AES_256_CBC_HMAC_SHA256 == cipherAlgorithmId) { + return SqlAeadAes256CbcHmac256Algorithm.AlgorithmName; + } + else if (TdsEnums.AES_256_CBC == cipherAlgorithmId) { + return SqlAes256CbcAlgorithm.AlgorithmName; + } + else { + throw SQL.UnknownColumnEncryptionAlgorithmId(cipherAlgorithmId, GetRegisteredCipherAlgorithmIds()); + } + } + + /// <summary> + /// Retrieves a string with comma separated list of registered algorithm Ids (enclosed in quotes). + /// </summary> + private static string GetRegisteredCipherAlgorithmIds () { + return @"'1', '2'"; + } + + /// <summary> + /// Encrypts the plaintext. + /// </summary> + internal static byte[] EncryptWithKey (byte[] plainText, SqlCipherMetadata md, string serverName) { + Debug.Assert(serverName != null, @"serverName should not be null in EncryptWithKey."); + + // Initialize cipherAlgo if not already done. + if (!md.IsAlgorithmInitialized()) { + SqlSecurityUtility.DecryptSymmetricKey(md, serverName); + } + + Debug.Assert(md.IsAlgorithmInitialized(), "Encryption Algorithm is not initialized"); + byte[] cipherText = md.CipherAlgorithm.EncryptData(plainText); // this call succeeds or throws. + if (null == cipherText || 0 == cipherText.Length) { + SQL.NullCipherText(); + } + + return cipherText; + } + + /// <summary> + /// Gets a string with first/last 10 bytes in the buff (useful for exception handling). + /// </summary> + internal static string GetBytesAsString(byte[] buff, bool fLast, int countOfBytes) { + int count = (buff.Length > countOfBytes) ? countOfBytes : buff.Length; + int startIndex = 0; + if (fLast) { + startIndex = buff.Length - count; + Debug.Assert(startIndex >= 0); + } + + return BitConverter.ToString(buff, startIndex, count); + } + + /// <summary> + /// Decrypts the ciphertext. + /// </summary> + internal static byte[] DecryptWithKey(byte[] cipherText, SqlCipherMetadata md, string serverName) { + Debug.Assert(serverName != null, @"serverName should not be null in DecryptWithKey."); + + // Initialize cipherAlgo if not already done. + if (!md.IsAlgorithmInitialized()) { + SqlSecurityUtility.DecryptSymmetricKey(md, serverName); + } + + Debug.Assert(md.IsAlgorithmInitialized(), "Decryption Algorithm is not initialized"); + try { + byte[] plainText = md.CipherAlgorithm.DecryptData(cipherText); // this call succeeds or throws. + if (null == plainText) { + throw SQL.NullPlainText (); + } + + return plainText; + } + catch (Exception e) { + // compute the strings to pass + string keyStr = GetBytesAsString(md.EncryptionKeyInfo.Value.encryptedKey, fLast:true, countOfBytes:10); + string valStr = GetBytesAsString(cipherText, fLast:false, countOfBytes:10); + throw SQL.ThrowDecryptionFailed(keyStr, valStr, e); + } + } + + /// <summary> + /// <para> Decrypts the symmetric key and saves it in metadata. In addition, intializes + /// the SqlClientEncryptionAlgorithm for rapid decryption.</para> + /// </summary> + internal static void DecryptSymmetricKey(SqlCipherMetadata md, string serverName) { + Debug.Assert(serverName != null, @"serverName should not be null in DecryptSymmetricKey."); + Debug.Assert(md != null, "md should not be null in DecryptSymmetricKey."); + Debug.Assert(md.EncryptionInfo.HasValue, "md.EncryptionInfo should not be null in DecryptSymmetricKey."); + Debug.Assert(md.EncryptionInfo.Value.ColumnEncryptionKeyValues != null, "md.EncryptionInfo.ColumnEncryptionKeyValues should not be null in DecryptSymmetricKey."); + + SqlClientSymmetricKey symKey = null; + SqlEncryptionKeyInfo? encryptionkeyInfoChosen = null; + SqlSymmetricKeyCache cache = SqlSymmetricKeyCache.GetInstance(); + Exception lastException = null; + foreach (SqlEncryptionKeyInfo keyInfo in md.EncryptionInfo.Value.ColumnEncryptionKeyValues) { + try { + if (cache.GetKey(keyInfo, serverName, out symKey)) { + encryptionkeyInfoChosen = keyInfo; + break; + } + } catch (Exception e) { + lastException = e; + } + } + + if (null == symKey) { + Debug.Assert (null != lastException, "CEK decryption failed without raising exceptions"); + throw lastException; + } + + Debug.Assert(encryptionkeyInfoChosen.HasValue, "encryptionkeyInfoChosen must have a value."); + + // Given the symmetric key instantiate a SqlClientEncryptionAlgorithm object and cache it in metadata + md.CipherAlgorithm = null; + SqlClientEncryptionAlgorithm cipherAlgorithm = null; + string algorithmName = ValidateAndGetEncryptionAlgorithmName(md.CipherAlgorithmId, md.CipherAlgorithmName); // may throw + SqlClientEncryptionAlgorithmFactoryList.GetInstance().GetAlgorithm(symKey, md.EncryptionType, algorithmName, out cipherAlgorithm); // will validate algorithm name and type + Debug.Assert(cipherAlgorithm != null); + md.CipherAlgorithm = cipherAlgorithm; + md.EncryptionKeyInfo = encryptionkeyInfoChosen; + return; + } + + /// <summary> + /// Calculates the length of the Base64 string used to represent a byte[] with the specified length. + /// </summary> + /// <param name="byteLength"></param> + /// <returns></returns> + internal static int GetBase64LengthFromByteLength(int byteLength) { + Debug.Assert(byteLength <= UInt16.MaxValue, @"Encrypted column encryption key cannot be larger than 65536 bytes"); + + // Base64 encoding uses 1 character to encode 6 bits which means 4 characters for 3 bytes and pads to 4 byte multiples. + return (int)((double)byteLength * 4 / 3) + 4; + } + } +} diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlSequentialTextReader.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlSequentialTextReader.cs index d5e60712a24..8236a519bb1 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlSequentialTextReader.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlSequentialTextReader.cs @@ -180,7 +180,7 @@ namespace System.Data.SqlClient byteBufferUsed += bytesRead; } else { - // We need more data - setup the callback, and mark this as not completed [....] + // We need more data - setup the callback, and mark this as not completed sync completedSynchronously = false; getBytesTask.ContinueWith((t) => { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlStatistics.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlStatistics.cs index be518346a2e..688fe4f3de0 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlStatistics.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlStatistics.cs @@ -2,8 +2,8 @@ // <copyright file="SqlStatistics.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlStream.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlStream.cs index 6fa1900911e..e80bd516e01 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlStream.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlStream.cs @@ -2,8 +2,8 @@ // <copyright file="SqlStream.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { @@ -244,8 +244,8 @@ namespace System.Data.SqlClient { } internal XmlReader ToXmlReader() { - // Dev11 Bug #315513: Exception type breaking change from 4.0 RTM when calling GetChars on null xml - // We need to wrap all exceptions inside a TargetInvocationException to simulate calling CreateSqlReader via MethodInfo.Invoke + // Dev11 + return SqlTypes.SqlXml.CreateSqlXmlReader(this, closeInput: true, throwTargetInvocationExceptions: true); } diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlSymmetricKeyCache.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlSymmetricKeyCache.cs new file mode 100644 index 00000000000..fa371fb7222 --- /dev/null +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlSymmetricKeyCache.cs @@ -0,0 +1,107 @@ +//------------------------------------------------------------------------------ +// <copyright file="SqlSymmetricKeyCache.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.Collections.Generic; + using System.Collections.Concurrent; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Text; + + /// <summary> + /// <para> Implements a cache of Symmetric Keys (once they are decrypted).Useful for rapidly decrypting multiple data values.</para> + /// </summary> + sealed internal class SqlSymmetricKeyCache { + private readonly ConcurrentDictionary<string,SqlClientSymmetricKey> _cache; + private static readonly SqlSymmetricKeyCache _singletonInstance = new SqlSymmetricKeyCache(); + + + private SqlSymmetricKeyCache () { + _cache = new ConcurrentDictionary<string, SqlClientSymmetricKey>(concurrencyLevel: 4 * Environment.ProcessorCount /* default value in ConcurrentDictionary*/, capacity: 2); + } + + internal static SqlSymmetricKeyCache GetInstance () { + return _singletonInstance; + } + + /// <summary> + /// <para> Retrieves Symmetric Key (in plaintext) given the encryption material.</para> + /// </summary> + internal bool GetKey (SqlEncryptionKeyInfo keyInfo, string serverName, out SqlClientSymmetricKey encryptionKey) { + Debug.Assert(serverName != null, @"serverName should not be null."); + + StringBuilder cacheLookupKeyBuilder = new StringBuilder(serverName, capacity: serverName.Length + SqlSecurityUtility.GetBase64LengthFromByteLength(keyInfo.encryptedKey.Length) + keyInfo.keyStoreName.Length + 2/*separators*/); + +#if DEBUG + int capacity = cacheLookupKeyBuilder.Capacity; +#endif //DEBUG + + cacheLookupKeyBuilder.Append(":"); + cacheLookupKeyBuilder.Append(Convert.ToBase64String(keyInfo.encryptedKey)); + cacheLookupKeyBuilder.Append(":"); + cacheLookupKeyBuilder.Append(keyInfo.keyStoreName); + + string cacheLookupKey = cacheLookupKeyBuilder.ToString(); + +#if DEBUG + Debug.Assert(cacheLookupKey.Length <= capacity, "We needed to allocate a larger array"); +#endif //DEBUG + + encryptionKey = null; + + // Lookup the key in cache + if (!_cache.TryGetValue(cacheLookupKey, out encryptionKey)) { + Debug.Assert(SqlConnection.ColumnEncryptionTrustedMasterKeyPaths != null, @"SqlConnection.ColumnEncryptionTrustedMasterKeyPaths should not be null"); + + // Check against the trusted key paths + // + // Get the List corresponding to the connected server + IList<string> trustedKeyPaths; + if (SqlConnection.ColumnEncryptionTrustedMasterKeyPaths.TryGetValue(serverName, out trustedKeyPaths)) { + // If the list is null or is empty or if the keyPath doesn't exist in the trusted key paths, then throw an exception. + if ((trustedKeyPaths == null) || (trustedKeyPaths.Count() == 0) || + // (trustedKeyPaths.Where(s => s.Equals(keyInfo.keyPath, StringComparison.InvariantCultureIgnoreCase)).Count() == 0)) { + (trustedKeyPaths.Any(s => s.Equals(keyInfo.keyPath, StringComparison.InvariantCultureIgnoreCase)) == false)) { + // throw an exception since the key path is not in the trusted key paths list for this server + throw SQL.UntrustedKeyPath(keyInfo.keyPath, serverName); + } + } + + // Key Not found, attempt to look up the provider and decrypt CEK + SqlColumnEncryptionKeyStoreProvider provider; + if (!SqlConnection.TryGetColumnEncryptionKeyStoreProvider(keyInfo.keyStoreName, out provider)) { + throw SQL.UnrecognizedKeyStoreProviderName(keyInfo.keyStoreName, + SqlConnection.GetColumnEncryptionSystemKeyStoreProviders(), + SqlConnection.GetColumnEncryptionCustomKeyStoreProviders()); + } + + // Decrypt the CEK + // We will simply bubble up the exception from the DecryptColumnEncryptionKey function. + byte[] plaintextKey; + try { + plaintextKey = provider.DecryptColumnEncryptionKey(keyInfo.keyPath, keyInfo.algorithmName, keyInfo.encryptedKey); + } + catch (Exception e) { + // Generate a new exception and throw. + string keyHex = SqlSecurityUtility.GetBytesAsString(keyInfo.encryptedKey, fLast:true, countOfBytes:10); + throw SQL.KeyDecryptionFailed (keyInfo.keyStoreName, keyHex, e); + } + + encryptionKey = new SqlClientSymmetricKey (plaintextKey); + + // In case multiple threads reach here at the same time, the first one wins. + // The allocated memory will be reclaimed by Garbage Collector. + _cache.TryAdd(cacheLookupKey, encryptionKey); + } + + return true; + } + } +} diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlTransaction.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlTransaction.cs index ff02ce923a3..e7927edf1a4 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlTransaction.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlTransaction.cs @@ -2,8 +2,8 @@ // <copyright file="SqlTransaction.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlUdtInfo.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlUdtInfo.cs index cfde73a6f2b..a97f766bfc5 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlUdtInfo.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlUdtInfo.cs @@ -3,8 +3,8 @@ // Copyright (c) Microsoft Corporation. All Rights Reserved. // Information Contained Herein is Proprietary and Confidential. // </copyright> -// <owner current="true" primary="true">[....]</owner> -// <owner current="true" primary="false">[....]</owner> +// <owner current="true" primary="true">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlUtil.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlUtil.cs index 736fbb9af31..171855fc211 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlUtil.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/SqlUtil.cs @@ -2,17 +2,19 @@ // <copyright file="SqlUtil.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { using System; + using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Data.Common; using System.Diagnostics; using System.Globalization; + using System.Linq; using System.Reflection; using System.Runtime.Serialization.Formatters; using System.Security; @@ -259,6 +261,24 @@ namespace System.Data.SqlClient { static internal Exception UserInstanceFailoverNotCompatible() { return ADP.Argument(Res.GetString(Res.SQL_UserInstanceFailoverNotCompatible)); } + static internal Exception CredentialsNotProvided(SqlAuthenticationMethod auth) { + return ADP.InvalidOperation(Res.GetString(Res.SQL_CredentialsNotProvided, DbConnectionStringBuilderUtil.AuthenticationTypeToString(auth))); + } + static internal Exception AuthenticationAndIntegratedSecurity() { + return ADP.Argument(Res.GetString(Res.SQL_AuthenticationAndIntegratedSecurity)); + } + static internal Exception IntegratedWithUserIDAndPassword() { + return ADP.Argument(Res.GetString(Res.SQL_IntegratedWithUserIDAndPassword)); + } + static internal Exception SettingIntegratedWithCredential() { + return ADP.InvalidOperation(Res.GetString(Res.SQL_SettingIntegratedWithCredential)); + } + static internal Exception SettingCredentialWithIntegratedArgument() { + return ADP.Argument(Res.GetString(Res.SQL_SettingCredentialWithIntegrated)); + } + static internal Exception SettingCredentialWithIntegratedInvalid() { + return ADP.InvalidOperation(Res.GetString(Res.SQL_SettingCredentialWithIntegrated)); + } static internal Exception InvalidSQLServerVersionUnknown() { return ADP.DataAdapter(Res.GetString(Res.SQL_InvalidSQLServerVersionUnknown)); } @@ -295,6 +315,9 @@ namespace System.Data.SqlClient { static internal Exception InvalidPartnerConfiguration (string server, string database) { return ADP.InvalidOperation(Res.GetString(Res.SQL_InvalidPartnerConfiguration, server, database)); } + static internal Exception BatchedUpdateColumnEncryptionSettingMismatch () { + return ADP.InvalidOperation(Res.GetString(Res.TCE_BatchedUpdateColumnEncryptionSettingMismatch, "SqlCommandColumnEncryptionSetting", "SelectCommand", "InsertCommand", "UpdateCommand", "DeleteCommand")); + } static internal Exception MARSUnspportedOnConnection() { return ADP.InvalidOperation(Res.GetString(Res.SQL_MarsUnsupportedOnConnection)); } @@ -440,9 +463,34 @@ namespace System.Data.SqlClient { static internal Exception InvalidTDSVersion() { return ADP.InvalidOperation(Res.GetString(Res.SQL_InvalidTDSVersion)); } - static internal Exception ParsingError() { - return ADP.InvalidOperation(Res.GetString(Res.SQL_ParsingError)); + static internal Exception ParsingError(ParsingErrorState state) { + return ADP.InvalidOperation(Res.GetString(Res.SQL_ParsingErrorWithState, ((int)state).ToString(CultureInfo.InvariantCulture))); + } + static internal Exception ParsingError(ParsingErrorState state, Exception innerException) { + return ADP.InvalidOperation(Res.GetString(Res.SQL_ParsingErrorWithState, ((int)state).ToString(CultureInfo.InvariantCulture)), innerException); + } + static internal Exception ParsingErrorValue(ParsingErrorState state, int value) { + return ADP.InvalidOperation(Res.GetString(Res.SQL_ParsingErrorValue, ((int)state).ToString(CultureInfo.InvariantCulture), value)); + } + static internal Exception ParsingErrorOffset(ParsingErrorState state, int offset) { + return ADP.InvalidOperation(Res.GetString(Res.SQL_ParsingErrorOffset, ((int)state).ToString(CultureInfo.InvariantCulture), offset)); + } + static internal Exception ParsingErrorFeatureId(ParsingErrorState state, int featureId) { + return ADP.InvalidOperation(Res.GetString(Res.SQL_ParsingErrorFeatureId, ((int)state).ToString(CultureInfo.InvariantCulture), featureId)); + } + static internal Exception ParsingErrorToken(ParsingErrorState state, int token) { + return ADP.InvalidOperation(Res.GetString(Res.SQL_ParsingErrorToken, ((int)state).ToString(CultureInfo.InvariantCulture), token)); } + static internal Exception ParsingErrorLength(ParsingErrorState state, int length) { + return ADP.InvalidOperation(Res.GetString(Res.SQL_ParsingErrorLength, ((int)state).ToString(CultureInfo.InvariantCulture), length)); + } + static internal Exception ParsingErrorStatus(ParsingErrorState state, int status) { + return ADP.InvalidOperation(Res.GetString(Res.SQL_ParsingErrorStatus, ((int)state).ToString(CultureInfo.InvariantCulture), status)); + } + static internal Exception ParsingErrorLibraryType(ParsingErrorState state, int libraryType) { + return ADP.InvalidOperation(Res.GetString(Res.SQL_ParsingErrorAuthLibraryType, ((int)state).ToString(CultureInfo.InvariantCulture), libraryType)); + } + static internal Exception MoneyOverflow(string moneyValue) { return ADP.Overflow(Res.GetString(Res.SQL_MoneyOverflow, moneyValue)); } @@ -475,6 +523,14 @@ namespace System.Data.SqlClient { return ADP.InvalidCast(Res.GetString(Res.SQL_StreamNotSupportOnColumnType, columnName)); } + static internal Exception StreamNotSupportOnEncryptedColumn(string columnName) { + return ADP.InvalidOperation(Res.GetString(Res.TCE_StreamNotSupportOnEncryptedColumn, columnName, "Stream")); + } + + static internal Exception SequentialAccessNotSupportedOnEncryptedColumn(string columnName) { + return ADP.InvalidOperation(Res.GetString(Res.TCE_SequentialAccessNotSupportedOnEncryptedColumn, columnName, "CommandBehavior=SequentialAccess")); + } + static internal Exception TextReaderNotSupportOnColumnType(string columnName) { return ADP.InvalidCast(Res.GetString(Res.SQL_TextReaderNotSupportOnColumnType, columnName)); } @@ -484,7 +540,7 @@ namespace System.Data.SqlClient { } static internal Exception UDTUnexpectedResult(string exceptionText){ - return ADP.TypeLoad(Res.GetString(Res.SQLUDT_Unexpected,exceptionText)); + return ADP.TypeLoad(Res.GetString(Res.SQLUDT_Unexpected,exceptionText)); } @@ -729,6 +785,325 @@ namespace System.Data.SqlClient { } // + // TCE - Certificate Store Provider Errors. + // + static internal Exception InvalidKeyEncryptionAlgorithm(string encryptionAlgorithm, string validEncryptionAlgorithm, bool isSystemOp) { + if (isSystemOp) { + return ADP.Argument(Res.GetString(Res.TCE_InvalidKeyEncryptionAlgorithmSysErr, encryptionAlgorithm, validEncryptionAlgorithm), TdsEnums.TCE_PARAM_ENCRYPTION_ALGORITHM); + } + else { + return ADP.Argument(Res.GetString(Res.TCE_InvalidKeyEncryptionAlgorithm, encryptionAlgorithm, validEncryptionAlgorithm), TdsEnums.TCE_PARAM_ENCRYPTION_ALGORITHM); + } + } + + static internal Exception NullKeyEncryptionAlgorithm(bool isSystemOp) { + if (isSystemOp) { + return ADP.ArgumentNull (TdsEnums.TCE_PARAM_ENCRYPTION_ALGORITHM, Res.GetString(Res.TCE_NullKeyEncryptionAlgorithmSysErr)); + } + else { + return ADP.ArgumentNull (TdsEnums.TCE_PARAM_ENCRYPTION_ALGORITHM, Res.GetString(Res.TCE_NullKeyEncryptionAlgorithm)); + } + } + + static internal Exception EmptyColumnEncryptionKey() { + return ADP.Argument(Res.GetString(Res.TCE_EmptyColumnEncryptionKey), TdsEnums.TCE_PARAM_COLUMNENCRYPTION_KEY); + } + + static internal Exception NullColumnEncryptionKey() { + return ADP.ArgumentNull(TdsEnums.TCE_PARAM_COLUMNENCRYPTION_KEY, Res.GetString(Res.TCE_NullColumnEncryptionKey)); + } + + static internal Exception EmptyEncryptedColumnEncryptionKey() { + return ADP.Argument(Res.GetString(Res.TCE_EmptyEncryptedColumnEncryptionKey), TdsEnums.TCE_PARAM_ENCRYPTED_CEK); + } + + static internal Exception NullEncryptedColumnEncryptionKey() { + return ADP.ArgumentNull(TdsEnums.TCE_PARAM_ENCRYPTED_CEK, Res.GetString(Res.TCE_NullEncryptedColumnEncryptionKey)); + } + + static internal Exception LargeCertificatePathLength(int actualLength, int maxLength, bool isSystemOp) { + if (isSystemOp) { + return ADP.Argument(Res.GetString(Res.TCE_LargeCertificatePathLengthSysErr, actualLength, maxLength), TdsEnums.TCE_PARAM_MASTERKEY_PATH); + } + else { + return ADP.Argument(Res.GetString(Res.TCE_LargeCertificatePathLength, actualLength, maxLength), TdsEnums.TCE_PARAM_MASTERKEY_PATH); + } + } + + static internal Exception NullCertificatePath(string[] validLocations, bool isSystemOp) { + Debug.Assert(2 == validLocations.Length); + if (isSystemOp) { + return ADP.ArgumentNull(TdsEnums.TCE_PARAM_MASTERKEY_PATH, Res.GetString(Res.TCE_NullCertificatePathSysErr, validLocations[0], validLocations[1], @"/")); + } + else { + return ADP.ArgumentNull(TdsEnums.TCE_PARAM_MASTERKEY_PATH, Res.GetString(Res.TCE_NullCertificatePath, validLocations[0], validLocations[1], @"/")); + } + } + + static internal Exception InvalidCertificatePath(string actualCertificatePath, string[] validLocations, bool isSystemOp) { + Debug.Assert(2 == validLocations.Length); + if (isSystemOp) { + return ADP.Argument(Res.GetString(Res.TCE_InvalidCertificatePathSysErr, actualCertificatePath, validLocations[0], validLocations[1], @"/"), TdsEnums.TCE_PARAM_MASTERKEY_PATH); + } + else { + return ADP.Argument(Res.GetString(Res.TCE_InvalidCertificatePath, actualCertificatePath, validLocations[0], validLocations[1], @"/"), TdsEnums.TCE_PARAM_MASTERKEY_PATH); + } + } + + static internal Exception InvalidCertificateLocation(string certificateLocation, string certificatePath, string[] validLocations, bool isSystemOp) { + Debug.Assert(2 == validLocations.Length); + if (isSystemOp) { + return ADP.Argument(Res.GetString(Res.TCE_InvalidCertificateLocationSysErr, certificateLocation, certificatePath, validLocations[0], validLocations[1], @"/"), TdsEnums.TCE_PARAM_MASTERKEY_PATH); + } + else { + return ADP.Argument(Res.GetString(Res.TCE_InvalidCertificateLocation, certificateLocation, certificatePath, validLocations[0], validLocations[1], @"/"), TdsEnums.TCE_PARAM_MASTERKEY_PATH); + } + } + + static internal Exception InvalidCertificateStore(string certificateStore, string certificatePath, string validCertificateStore, bool isSystemOp) { + if (isSystemOp) { + return ADP.Argument(Res.GetString(Res.TCE_InvalidCertificateStoreSysErr, certificateStore, certificatePath, validCertificateStore), TdsEnums.TCE_PARAM_MASTERKEY_PATH); + } + else { + return ADP.Argument(Res.GetString(Res.TCE_InvalidCertificateStore, certificateStore, certificatePath, validCertificateStore), TdsEnums.TCE_PARAM_MASTERKEY_PATH); + } + } + + static internal Exception EmptyCertificateThumbprint(string certificatePath, bool isSystemOp) { + if (isSystemOp) { + return ADP.Argument(Res.GetString(Res.TCE_EmptyCertificateThumbprintSysErr, certificatePath), TdsEnums.TCE_PARAM_MASTERKEY_PATH); + } + else { + return ADP.Argument(Res.GetString(Res.TCE_EmptyCertificateThumbprint, certificatePath), TdsEnums.TCE_PARAM_MASTERKEY_PATH); + } + } + + static internal Exception CertificateNotFound(string thumbprint, string certificateLocation, string certificateStore, bool isSystemOp) { + if (isSystemOp) { + return ADP.Argument(Res.GetString(Res.TCE_CertificateNotFoundSysErr, thumbprint, certificateLocation, certificateStore), TdsEnums.TCE_PARAM_MASTERKEY_PATH); + } + else { + return ADP.Argument(Res.GetString(Res.TCE_CertificateNotFound, thumbprint, certificateLocation, certificateStore), TdsEnums.TCE_PARAM_MASTERKEY_PATH); + } + } + + static internal Exception InvalidAlgorithmVersionInEncryptedCEK(byte actual, byte expected) { + return ADP.Argument(Res.GetString(Res.TCE_InvalidAlgorithmVersionInEncryptedCEK, actual.ToString(@"X2"), expected.ToString(@"X2")), TdsEnums.TCE_PARAM_ENCRYPTED_CEK); + } + + static internal Exception InvalidCiphertextLengthInEncryptedCEK(int actual, int expected, string certificateName) { + return ADP.Argument(Res.GetString(Res.TCE_InvalidCiphertextLengthInEncryptedCEK, actual, expected, certificateName), TdsEnums.TCE_PARAM_ENCRYPTED_CEK); + } + + static internal Exception InvalidSignatureInEncryptedCEK(int actual, int expected, string masterKeyPath) { + return ADP.Argument(Res.GetString(Res.TCE_InvalidSignatureInEncryptedCEK, actual, expected, masterKeyPath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK); + } + + static internal Exception InvalidCertificateSignature(string certificatePath) { + return ADP.Argument(Res.GetString(Res.TCE_InvalidCertificateSignature, certificatePath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK); + } + + static internal Exception CertificateWithNoPrivateKey(string keyPath, bool isSystemOp) { + if (isSystemOp) { + return ADP.Argument(Res.GetString(Res.TCE_CertificateWithNoPrivateKeySysErr, keyPath), TdsEnums.TCE_PARAM_MASTERKEY_PATH); + } + else { + return ADP.Argument(Res.GetString(Res.TCE_CertificateWithNoPrivateKey, keyPath), TdsEnums.TCE_PARAM_MASTERKEY_PATH); + } + } + + // + // TCE - Cryptographic Algorithms Error messages + // + static internal Exception NullColumnEncryptionKeySysErr() { + return ADP.ArgumentNull(TdsEnums.TCE_PARAM_ENCRYPTIONKEY,Res.GetString(Res.TCE_NullColumnEncryptionKeySysErr)); + } + + static internal Exception InvalidKeySize(string algorithmName, int actualKeylength, int expectedLength) { + return ADP.Argument(Res.GetString( + Res.TCE_InvalidKeySize, + algorithmName, + actualKeylength, + expectedLength), TdsEnums.TCE_PARAM_ENCRYPTIONKEY); + } + + static internal Exception InvalidEncryptionType(string algorithmName, SqlClientEncryptionType encryptionType, params SqlClientEncryptionType[] validEncryptionTypes) { + const string valueSeparator = @", "; + return ADP.Argument(Res.GetString( + Res.TCE_InvalidEncryptionType, + algorithmName, + encryptionType.ToString(), + string.Join(valueSeparator, validEncryptionTypes.Select((validEncryptionType => @"'" + validEncryptionType + @"'")))), TdsEnums.TCE_PARAM_ENCRYPTIONTYPE); + } + + static internal Exception NullPlainText () { + return ADP.ArgumentNull (Res.GetString(Res.TCE_NullPlainText)); + } + + static internal Exception VeryLargeCiphertext(long cipherTextLength, long maxCipherTextSize, long plainTextLength) { + return ADP.Argument(Res.GetString(Res.TCE_VeryLargeCiphertext, cipherTextLength, maxCipherTextSize, plainTextLength)); + } + + static internal Exception NullCipherText () { + return ADP.ArgumentNull (Res.GetString(Res.TCE_NullCipherText)); + } + + static internal Exception InvalidCipherTextSize(int actualSize, int minimumSize) { + return ADP.Argument(Res.GetString(Res.TCE_InvalidCipherTextSize, actualSize, minimumSize), TdsEnums.TCE_PARAM_CIPHERTEXT); + } + + static internal Exception InvalidAlgorithmVersion(byte actual, byte expected) { + return ADP.Argument(Res.GetString(Res.TCE_InvalidAlgorithmVersion, actual.ToString(@"X2"), expected.ToString(@"X2")), TdsEnums.TCE_PARAM_CIPHERTEXT); + } + + static internal Exception InvalidAuthenticationTag() { + return ADP.Argument(Res.GetString(Res.TCE_InvalidAuthenticationTag), TdsEnums.TCE_PARAM_CIPHERTEXT); + } + + static internal Exception NullColumnEncryptionAlgorithm (string supportedAlgorithms) { + return ADP.ArgumentNull (TdsEnums.TCE_PARAM_ENCRYPTION_ALGORITHM, Res.GetString(Res.TCE_NullColumnEncryptionAlgorithm, supportedAlgorithms)); + } + + // + // TCE - Errors from sp_describe_parameter_encryption + // + static internal Exception UnexpectedDescribeParamFormat () { + return ADP.Argument (Res.GetString(Res.TCE_UnexpectedDescribeParamFormat, "sp_describe_parameter_encryption")); + } + + static internal Exception InvalidEncryptionKeyOrdinal (int ordinal, int maxOrdinal) { + return ADP.InvalidOperation(Res.GetString(Res.TCE_InvalidEncryptionKeyOrdinal, "sp_describe_parameter_encryption", ordinal, maxOrdinal)); + } + + static internal Exception ParamEncryptionMetadataMissing (string paramName, string procedureName) { + return ADP.Argument (Res.GetString(Res.TCE_ParamEncryptionMetaDataMissing, "sp_describe_parameter_encryption", paramName, procedureName)); + } + + static internal Exception ParamInvalidForceColumnEncryptionSetting (string paramName, string procedureName) { + return ADP.InvalidOperation(Res.GetString(Res.TCE_ParamInvalidForceColumnEncryptionSetting, TdsEnums.TCE_PARAM_FORCE_COLUMN_ENCRYPTION, paramName, procedureName, "SqlParameter")); + } + + static internal Exception ParamUnExpectedEncryptionMetadata (string paramName, string procedureName) { + return ADP.InvalidOperation(Res.GetString(Res.TCE_ParamUnExpectedEncryptionMetadata, paramName, procedureName, TdsEnums.TCE_PARAM_FORCE_COLUMN_ENCRYPTION, "SqlParameter")); + } + + static internal Exception ProcEncryptionMetadataMissing (string procedureName) { + return ADP.Argument (Res.GetString(Res.TCE_ProcEncryptionMetaDataMissing, "sp_describe_parameter_encryption", procedureName)); + } + + // + // TCE- Generic toplevel failures. + // + static internal Exception GetExceptionArray (string serverName, string errorMessage, Exception e) { + // Create and throw an exception array + SqlErrorCollection sqlErs = new SqlErrorCollection(); + Exception exceptionToInclude = (null != e.InnerException) ? e.InnerException : e; + sqlErs.Add (new SqlError(infoNumber:0, errorState:(byte)0x00, errorClass:(byte)TdsEnums.MIN_ERROR_CLASS, server:serverName, errorMessage:errorMessage, procedure:null, lineNumber:0)); + + if (e is SqlException) { + SqlException exThrown = (SqlException)e; + SqlErrorCollection errorList = exThrown.Errors; + for (int i =0; i < exThrown.Errors.Count; i++) { + sqlErs.Add(errorList[i]); + } + } + else { + sqlErs.Add (new SqlError(infoNumber:0, errorState:(byte)0x00, errorClass:(byte)TdsEnums.MIN_ERROR_CLASS, server:serverName, errorMessage:e.Message, procedure:null, lineNumber:0)); + } + + return SqlException.CreateException(sqlErs, "", null, exceptionToInclude); + } + + static internal Exception ParamEncryptionFailed (string paramName, string serverName, Exception e){ + return GetExceptionArray (serverName, Res.GetString(Res.TCE_ParamEncryptionFailed, paramName), e); + } + + static internal Exception ParamDecryptionFailed (string paramName, string serverName, Exception e) { + return GetExceptionArray (serverName, Res.GetString(Res.TCE_ParamDecryptionFailed, paramName), e); + } + + static internal Exception ColumnDecryptionFailed (string columnName, string serverName, Exception e) { + return GetExceptionArray (serverName, Res.GetString(Res.TCE_ColumnDecryptionFailed, columnName), e); + } + + // + // TCE- Client side query processing errors. + // + static internal Exception UnknownColumnEncryptionAlgorithm (string algorithmName, string supportedAlgorithms) { + return ADP.Argument(Res.GetString(Res.TCE_UnknownColumnEncryptionAlgorithm, algorithmName, supportedAlgorithms)); + } + + static internal Exception UnknownColumnEncryptionAlgorithmId (int algoId, string supportAlgorithmIds) { + return ADP.Argument(Res.GetString(Res.TCE_UnknownColumnEncryptionAlgorithmId, algoId, supportAlgorithmIds), TdsEnums.TCE_PARAM_CIPHER_ALGORITHM_ID); + } + + static internal Exception UnsupportedNormalizationVersion (byte version) { + return ADP.Argument(Res.GetString(Res.TCE_UnsupportedNormalizationVersion, version, "'1'", "SQL Server")); + } + + static internal Exception UnrecognizedKeyStoreProviderName(string providerName, List<string> systemProviders, List<string> customProviders) { + const string valueSeparator = @", "; + string systemProviderStr = string.Join (valueSeparator, systemProviders.Select(provider => @"'" + provider + @"'")); + string customProviderStr = string.Join (valueSeparator, customProviders.Select(provider => @"'" + provider + @"'")); + return ADP.Argument(Res.GetString(Res.TCE_UnrecognizedKeyStoreProviderName, providerName, systemProviderStr, customProviderStr)); + } + + static internal Exception InvalidDataTypeForEncryptedParameter(string parameterName, int actualDataType, int expectedDataType) { + return ADP.Argument(Res.GetString(Res.TCE_NullProviderValue, parameterName, actualDataType, expectedDataType)); + } + + static internal Exception KeyDecryptionFailed (string providerName, string keyHex, Exception e) { + if (providerName.Equals(SqlColumnEncryptionCertificateStoreProvider.ProviderName)) { + return GetExceptionArray(null, Res.GetString(Res.TCE_KeyDecryptionFailedCertStore, providerName, keyHex), e); + } + else { + return GetExceptionArray(null, Res.GetString(Res.TCE_KeyDecryptionFailed, providerName, keyHex), e); + } + } + + static internal Exception UntrustedKeyPath(string keyPath, string serverName) { + return ADP.Argument(Res.GetString(Res.TCE_UntrustedKeyPath, keyPath, serverName)); + } + + static internal Exception UnsupportedDatatypeEncryption (string dataType) { + return ADP.Argument(Res.GetString(Res.TCE_UnsupportedDatatype, dataType)); + } + + static internal Exception ThrowDecryptionFailed (string keyStr, string valStr, Exception e) { + return GetExceptionArray (null, Res.GetString(Res.TCE_DecryptionFailed, keyStr, valStr), e); + } + + // + // TCE- SQL connection related error messages + // + static internal Exception TceNotSupported() { + return ADP.InvalidOperation (Res.GetString (Res.TCE_NotSupportedByServer, "SQL Server")); + } + + // + // TCE- Extensibility related error messages + // + static internal Exception CanOnlyCallOnce() { + return ADP.InvalidOperation(Res.GetString(Res.TCE_CanOnlyCallOnce)); + } + + static internal Exception NullCustomKeyStoreProviderDictionary() { + return ADP.ArgumentNull(TdsEnums.TCE_PARAM_CLIENT_KEYSTORE_PROVIDERS, Res.GetString(Res.TCE_NullCustomKeyStoreProviderDictionary)); + } + + static internal Exception InvalidCustomKeyStoreProviderName(string providerName, string prefix) { + return ADP.Argument(Res.GetString(Res.TCE_InvalidCustomKeyStoreProviderName, providerName, prefix), TdsEnums.TCE_PARAM_CLIENT_KEYSTORE_PROVIDERS); + } + + static internal Exception NullProviderValue(string providerName) { + return ADP.ArgumentNull(TdsEnums.TCE_PARAM_CLIENT_KEYSTORE_PROVIDERS, Res.GetString(Res.TCE_NullProviderValue, providerName)); + } + + static internal Exception EmptyProviderName() { + return ADP.ArgumentNull(TdsEnums.TCE_PARAM_CLIENT_KEYSTORE_PROVIDERS, Res.GetString(Res.TCE_EmptyProviderName)); + } + + // // transactions. // static internal Exception ConnectionDoomed() { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsEnums.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsEnums.cs index 6cdc17706dc..7f9efe37d1d 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsEnums.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsEnums.cs @@ -2,8 +2,8 @@ // <copyright file="TdsEnums.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { @@ -57,6 +57,7 @@ namespace System.Data.SqlClient { // network function string contants public const string INIT_SSPI_PACKAGE = "InitSSPIPackage"; + public const string INIT_ADAL_PACKAGE = "InitADALPackage"; public const string INIT_SESSION = "InitSession"; public const string CONNECTION_GET_SVR_USER = "ConnectionGetSvrUser"; public const string GEN_CLIENT_CONTEXT = "GenClientContext"; @@ -104,7 +105,7 @@ namespace System.Data.SqlClient { public const byte MT_BINARY = 5; // Unformatted binary response data (UNUSED) public const byte MT_ATTN = 6; // Attention (break) signal public const byte MT_BULK = 7; // Bulk load data - public const byte MT_OPEN = 8; // Set up subchannel (UNUSED) + public const byte MT_FEDAUTH = 8; // Authentication token for federated authentication public const byte MT_CLOSE = 9; // Close subchannel (UNUSED) public const byte MT_ERROR = 10; // Protocol error detected public const byte MT_ACK = 11; // Protocol acknowledgement (UNUSED) @@ -156,6 +157,7 @@ namespace System.Data.SqlClient { public const byte SQLCOLMETADATA = 0x81; // Column metadata including name public const byte SQLALTMETADATA = 0x88; // Alt column metadata including name public const byte SQLSSPI = 0xed; // SSPI data + public const byte SQLFEDAUTHINFO = 0xee; // Info for client to generate fed auth token // Environment change notification streams // TYPE on TDS ENVCHANGE token stream (from sql\ntdbms\include\odsapi.h) @@ -196,13 +198,39 @@ namespace System.Data.SqlClient { // Feature Extension public const byte FEATUREEXT_TERMINATOR = 0xFF; public const byte FEATUREEXT_SRECOVERY = 0x01; + public const byte FEATUREEXT_FEDAUTH = 0x02; + public const byte FEATUREEXT_TCE = 0x04; [Flags] public enum FeatureExtension:uint { None=0, SessionRecovery=1, + FedAuth=2, + Tce=4, } + public const byte FEDAUTHLIB_LIVEID = 0X00; + public const byte FEDAUTHLIB_SECURITYTOKEN = 0x01; + public const byte FEDAUTHLIB_ADAL = 0x02; + public const byte FEDAUTHLIB_RESERVED = 0X7F; + + public enum FedAuthLibrary:byte { + LiveId=FEDAUTHLIB_LIVEID, + SecurityToken=FEDAUTHLIB_SECURITYTOKEN, + ADAL=FEDAUTHLIB_ADAL, + Default=FEDAUTHLIB_RESERVED + } + + public const byte ADALWORKFLOW_ACTIVEDIRECTORYPASSWORD = 0x01; + public const byte ADALWORKFLOW_ACTIVEDIRECTORYINTEGRATED = 0x02; + + public enum ActiveDirectoryWorkflow : byte { + Password=ADALWORKFLOW_ACTIVEDIRECTORYPASSWORD, + Integrated=ADALWORKFLOW_ACTIVEDIRECTORYINTEGRATED, + } + + // The string used for username in the error message when Authentication = Active Directory Integrated with FedAuth is used, if authentication fails. + public const string NTAUTHORITYANONYMOUSLOGON = @"NT Authority\Anonymous Logon"; // Loginrec defines public const byte MAX_LOG_NAME = 30; // TDS 4.2 login rec max name length @@ -313,6 +341,7 @@ namespace System.Data.SqlClient { // second byte public const byte ClrFixedLen = 0x1; // Fixed length CLR type public const byte IsColumnSet = 0x4; // Column is an XML representation of an aggregation of other columns + public const byte IsEncrypted = 0x8; // Column is encrypted using TCE // null values public const uint VARLONGNULL = 0xffffffff; // null value for text and image types @@ -478,7 +507,7 @@ namespace System.Data.SqlClient { // RPC parameter class public const byte RPC_PARAM_BYREF = 0x1; public const byte RPC_PARAM_DEFAULT = 0x2; - public const byte RPC_PARAM_IS_LOB_COOKIE = 0x8; + public const byte RPC_PARAM_ENCRYPTED = 0x8; // SQL parameter list text public const string PARAM_OUTPUT = "output"; @@ -511,7 +540,7 @@ namespace System.Data.SqlClient { public const int LOGON_FAILED = 18456; public const int PASSWORD_EXPIRED = 18488; public const int IMPERSONATION_FAILED = 1346; - public const int P_TOKENTOOLONG = 103; + public const int P_TOKENTOOLONG = 103; // SNI\Win32 error values // NOTE: these are simply windows system error codes, not SNI specific @@ -862,10 +891,66 @@ namespace System.Data.SqlClient { 1, }; + internal const int MAX_TIME_SCALE = 7; // Maximum scale for time-related types + internal const int MAX_TIME_LENGTH = 5; // Maximum length for time + internal const int MAX_DATETIME2_LENGTH = 8; // Maximum length for datetime2 internal const int WHIDBEY_DATE_LENGTH = 10; internal static readonly int[] WHIDBEY_TIME_LENGTH = { 8, 10, 11, 12, 13, 14, 15, 16 }; internal static readonly int[] WHIDBEY_DATETIME2_LENGTH = { 19, 21, 22, 23, 24, 25, 26, 27 }; internal static readonly int[] WHIDBEY_DATETIMEOFFSET_LENGTH = {26, 28, 29, 30, 31, 32, 33, 34 }; + + internal enum FedAuthInfoId:byte { + Stsurl = 0x01, // FedAuthInfoData is token endpoint URL from which to acquire fed auth token + Spn = 0x02, // FedAuthInfoData is the SPN to use for acquiring fed auth token + } + + // TCE Related constants + internal const byte MAX_SUPPORTED_TCE_VERSION = 0x01; // max version + internal const ushort MAX_TCE_CIPHERINFO_SIZE = 2048; // max size of cipherinfo blob + internal const long MAX_TCE_CIPHERTEXT_SIZE = 2147483648; // max size of encrypted blob- currently 2GB. + internal const byte CustomCipherAlgorithmId = 0; // Id used for custom encryption algorithm. + + internal const int AES_256_CBC = 1; + internal const int AEAD_AES_256_CBC_HMAC_SHA256 = 2; + + // TCE Param names for exec handling + internal const string TCE_PARAM_CIPHERTEXT = "cipherText"; + internal const string TCE_PARAM_CIPHER_ALGORITHM_ID="cipherAlgorithmId"; + internal const string TCE_PARAM_COLUMNENCRYPTION_KEY="columnEncryptionKey"; + internal const string TCE_PARAM_ENCRYPTION_ALGORITHM="encryptionAlgorithm"; + internal const string TCE_PARAM_ENCRYPTIONTYPE = "encryptionType"; + internal const string TCE_PARAM_ENCRYPTIONKEY = "encryptionKey"; + internal const string TCE_PARAM_MASTERKEY_PATH = "masterKeyPath"; + internal const string TCE_PARAM_ENCRYPTED_CEK = "encryptedColumnEncryptionKey"; + internal const string TCE_PARAM_CLIENT_KEYSTORE_PROVIDERS="clientKeyStoreProviders"; + internal const string TCE_PARAM_FORCE_COLUMN_ENCRYPTION="ForceColumnEncryption(true)"; + } + + internal enum ParsingErrorState { + Undefined = 0, + FedAuthInfoLengthTooShortForCountOfInfoIds = 1, + FedAuthInfoLengthTooShortForData = 2, + FedAuthInfoFailedToReadCountOfInfoIds = 3, + FedAuthInfoFailedToReadTokenStream = 4, + FedAuthInfoInvalidOffset = 5, + FedAuthInfoFailedToReadData = 6, + FedAuthInfoDataNotUnicode = 7, + FedAuthInfoDoesNotContainStsurlAndSpn = 8, + FedAuthInfoNotReceived = 9, + FedAuthNotAcknowledged = 10, + FedAuthFeatureAckContainsExtraData = 11, + FedAuthFeatureAckUnknownLibraryType = 12, + UnrequestedFeatureAckReceived = 13, + UnknownFeatureAck = 14, + InvalidTdsTokenReceived = 15, + SessionStateLengthTooShort = 16, + SessionStateInvalidStatus = 17, + CorruptedTdsStream = 18, + ProcessSniPacketFailed = 19, + FedAuthRequiredPreLoginResponseInvalidValue = 20, + TceUnknownVersion = 21, + TceInvalidVersion = 22, + TceInvalidOrdinalIntoCipherInfoTable = 23, } internal enum SniContext { @@ -884,5 +969,89 @@ namespace System.Data.SqlClient { Snix_Close, Snix_SendRows, } -} + /// <summary> + /// Column Encryption Setting to be used for the SqlConnection. + /// </summary> + public enum SqlConnectionColumnEncryptionSetting { + /// <summary> + /// Disables column encryption by default on all commands on this connection. + /// </summary> + Disabled = 0, + + /// <summary> + /// Enables column encryption by default on all commands on this connection. + /// </summary> + Enabled, + } + + /// <summary> + /// Column Encryption Setting to be used for the SqlCommand. + /// </summary> + public enum SqlCommandColumnEncryptionSetting { + /// <summary> + /// if “Column Encryption Setting=Enabled” in the connection string, use Enabled. Otherwise, maps to Disabled. + /// </summary> + UseConnectionSetting = 0, + + /// <summary> + /// Enables TCE for the command. Overrides the connection level setting for this command. + /// </summary> + Enabled, + + /// <summary> + /// Parameters will not be encrypted, only the ResultSet will be decrypted. This is an optimization for queries that do not pass any encrypted input parameters. + /// Overrides the connection level setting for this command. + /// </summary> + ResultSetOnly, + + /// <summary> + /// Disables TCE for the command.Overrides the connection level setting for this command. + /// </summary> + Disabled, + } + + public enum SqlAuthenticationMethod { + NotSpecified = 0, + SqlPassword, + ActiveDirectoryPassword, + ActiveDirectoryIntegrated, + } + + internal class ActiveDirectoryAuthentication + { + internal const string AdoClientId = "4d079b4c-cab7-4b7c-a115-8fd51b6f8239"; + internal const string AdalGetAccessTokenFunctionName = "ADALGetAccessToken"; + internal const int GetAccessTokenSuccess = 0; + internal const int GetAccessTokenInvalidGrant = 1; + internal const int GetAccessTokenTansisentError = 2; + internal const int GetAccessTokenOtherError = 3; + } + + // Fields in the first resultset of "sp_describe_parameter_encryption". + // We expect the server to return the fields in the resultset in the same order as mentioned below. + // If the server changes the below order, then transparent parameter encryption will break. + internal enum DescribeParameterEncryptionResultSet1 { + KeyOrdinal = 0, + DbId, + KeyId, + KeyVersion, + KeyMdVersion, + EncryptedKey, + ProviderName, + KeyPath, + KeyEncryptionAlgorithm, + } + + // Fields in the second resultset of "sp_describe_parameter_encryption" + // We expect the server to return the fields in the resultset in the same order as mentioned below. + // If the server changes the below order, then transparent parameter encryption will break. + internal enum DescribeParameterEncryptionResultSet2 { + ParameterOrdinal = 0, + ParameterName, + ColumnEncryptionAlgorithm, + ColumnEncrytionType, + ColumnEncryptionKeyOrdinal, + NormalizationRuleVersion, + } +} diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParameterSetter.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParameterSetter.cs index 56a661fe1ae..18da7f15354 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParameterSetter.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParameterSetter.cs @@ -2,9 +2,9 @@ // <copyright file="TdsParameterSetter.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParser.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParser.cs index a0d429ddb1b..74ddcf726a6 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParser.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParser.cs @@ -2,8 +2,8 @@ // <copyright file="TdsParser.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { @@ -211,6 +211,9 @@ namespace System.Data.SqlClient { private volatile static UInt32 s_maxSSPILength = 0; // variable to hold max SSPI data size, keep for token from server + // ADAL variables + private static bool s_fADALLoaded = false; // bool to indicate whether ADAL library has been loaded + // textptr sequence private static readonly byte[] s_longDataHeader = { 0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; @@ -228,6 +231,22 @@ namespace System.Data.SqlClient { // NOTE: You must take the internal connection's _parserLock before modifying this internal bool _asyncWrite = false; + // TCE supported flag, used to determine if new TDS fields are present. This is + // useful when talking to downlevel/uplevel server. + private bool _serverSupportsColumnEncryption = false; + + /// <summary> + /// Get or set if column encryption is supported by the server. + /// </summary> + internal bool IsColumnEncryptionSupported { + get { + return _serverSupportsColumnEncryption; + } + set { + _serverSupportsColumnEncryption = value; + } + } + internal TdsParser(bool MARS, bool fAsynchronous) { _fMARS = MARS; // may change during Connect to pre Yukon servers _physicalStateObj = new TdsParserStateObject(this); @@ -358,7 +377,15 @@ namespace System.Data.SqlClient { } } - internal void Connect(ServerInfo serverInfo, SqlInternalConnectionTds connHandler, bool ignoreSniOpenTimeout, long timerExpire, bool encrypt, bool trustServerCert, bool integratedSecurity, bool withFailover) { + internal void Connect(ServerInfo serverInfo, + SqlInternalConnectionTds connHandler, + bool ignoreSniOpenTimeout, + long timerExpire, + bool encrypt, + bool trustServerCert, + bool integratedSecurity, + bool withFailover, + SqlAuthenticationMethod authType) { if (_state != TdsParserState.Closed) { Debug.Assert(false, "TdsParser.Connect called on non-closed connection!"); return; @@ -379,16 +406,23 @@ namespace System.Data.SqlClient { if (connHandler.ConnectionOptions.LocalDBInstance != null) LocalDBAPI.CreateLocalDBInstance(connHandler.ConnectionOptions.LocalDBInstance); - - if (integratedSecurity) { + if (integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { LoadSSPILibrary(); // now allocate proper length of buffer _sniSpnBuffer = new byte[SNINativeMethodWrapper.SniMaxComposedSpnLength]; - Bid.Trace("<sc.TdsParser.Connect|SEC> SSPI authentication\n"); + Bid.Trace("<sc.TdsParser.Connect|SEC> SSPI or Active Directory Authentication Library for SQL Server based integrated authentication\n"); } else { _sniSpnBuffer = null; - Bid.Trace("<sc.TdsParser.Connect|SEC> SQL authentication\n"); + if (authType == SqlAuthenticationMethod.ActiveDirectoryPassword) { + Bid.Trace("<sc.TdsParser.Connect|SEC> Active Directory Password authentication\n"); + } + else if (authType == SqlAuthenticationMethod.SqlPassword) { + Bid.Trace("<sc.TdsParser.Connect|SEC> SQL Password authentication\n"); + } + else{ + Bid.Trace("<sc.TdsParser.Connect|SEC> SQL authentication\n"); + } } byte[] instanceName = null; @@ -439,6 +473,7 @@ namespace System.Data.SqlClient { Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionId"); // + Bid.Trace("<sc.TdsParser.Connect|SEC> Sending prelogin handshake\n"); SendPreLoginHandshake(instanceName, encrypt); _connHandler.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake); @@ -446,9 +481,12 @@ namespace System.Data.SqlClient { _physicalStateObj.SniContext = SniContext.Snix_PreLogin; - PreLoginHandshakeStatus status = ConsumePreLoginHandshake(encrypt, trustServerCert, integratedSecurity, out marsCapable); + Bid.Trace("<sc.TdsParser.Connect|SEC> Consuming prelogin handshake\n"); + PreLoginHandshakeStatus status = ConsumePreLoginHandshake(authType, encrypt, trustServerCert, integratedSecurity, out marsCapable, + out _connHandler._fedAuthRequired); if (status == PreLoginHandshakeStatus.InstanceFailure) { + Bid.Trace("<sc.TdsParser.Connect|SEC> Prelogin handshake unsuccessful. Reattempting prelogin handshake\n"); _physicalStateObj.Dispose(); // Close previous connection // On Instance failure re-connect and flush SNI named instance cache. @@ -464,16 +502,19 @@ namespace System.Data.SqlClient { UInt32 retCode = SNINativeMethodWrapper.SniGetConnectionId(_physicalStateObj.Handle, ref _connHandler._clientConnectionId); Debug.Assert(retCode == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionId"); + Bid.Trace("<sc.TdsParser.Connect|SEC> Sending prelogin handshake\n"); SendPreLoginHandshake(instanceName, encrypt); - status = ConsumePreLoginHandshake(encrypt, trustServerCert, integratedSecurity, out marsCapable); + status = ConsumePreLoginHandshake(authType, encrypt, trustServerCert, integratedSecurity, out marsCapable, + out _connHandler._fedAuthRequired); // Don't need to check for Sphinx failure, since we've already consumed // one pre-login packet and know we are connecting to Shiloh. if (status == PreLoginHandshakeStatus.InstanceFailure) { - Bid.Trace("<sc.TdsParser.Connect|ERR|SEC> Login failure\n"); + Bid.Trace("<sc.TdsParser.Connect|ERR|SEC> Prelogin handshake unsuccessful. Login failure\n"); throw SQL.InstanceFailure(); } } + Bid.Trace("<sc.TdsParser.Connect|SEC> Prelogin handshake successful\n"); if (_fMARS && marsCapable) { // if user explictly disables mars or mars not supported, don't create the session pool @@ -482,6 +523,16 @@ namespace System.Data.SqlClient { else { _fMARS = false; } + + if (authType == SqlAuthenticationMethod.ActiveDirectoryPassword || (authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _connHandler._fedAuthRequired)) { + Debug.Assert(!integratedSecurity, "The legacy Integrated Security connection string option cannot be true when using Active Directory Authentication Library for SQL Server Based workflows."); + + LoadADALLibrary(); + if (Bid.AdvancedOn) { + Bid.Trace("<sc.TdsParser.Connect|SEC> Active directory authentication.Loaded Active Directory Authentication Library for SQL Server\n"); + } + } + return; } @@ -496,7 +547,7 @@ namespace System.Data.SqlClient { _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); ThrowExceptionAndWarning(_physicalStateObj); } - // create a new packet encryption changes the internal packet size Bug# 228403 + // create a new packet encryption changes the internal packet size try {} // EmptyTry/Finally to avoid FXCop violation finally { _physicalStateObj.ClearAllWritePackets(); @@ -756,6 +807,12 @@ namespace System.Data.SqlClient { Bid.Trace("<sc.TdsParser.SendPreLoginHandshake|INFO> ClientConnectionID %ls, ActivityID %ls\n", _connHandler._clientConnectionId.ToString(), actId.ToString()); break; + case (int)PreLoginOptions.FEDAUTHREQUIRED: + payload[payloadLength++] = 0x01; + offset += 1; + optionDataSize += 1; + break; + default: Debug.Assert(false, "UNKNOWN option in SendPreLoginHandshake"); break; @@ -776,8 +833,11 @@ namespace System.Data.SqlClient { _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH); } - private PreLoginHandshakeStatus ConsumePreLoginHandshake(bool encrypt, bool trustServerCert, bool integratedSecurity, out bool marsCapable) { - marsCapable = _fMARS; // Assign default value + private PreLoginHandshakeStatus ConsumePreLoginHandshake(SqlAuthenticationMethod authType, bool encrypt, bool trustServerCert, bool integratedSecurity, out bool marsCapable, out bool fedAuthRequired) { + + // Assign default values + marsCapable = _fMARS; + fedAuthRequired = false; bool isYukonOrLater = false; @@ -883,7 +943,13 @@ namespace System.Data.SqlClient { if (_encryptionOption == EncryptionOptions.ON || _encryptionOption == EncryptionOptions.LOGIN) { UInt32 error = 0; - UInt32 info = ((encrypt && !trustServerCert) ? TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE : 0) + + // If we're using legacy server certificate validation behavior (Authentication keyword not provided and not using access token), then validate if + // Encrypt=true and Trust Sever Certificate = false. + // If using Authentication keyword or access token, validate if Trust Server Certificate=false. + bool shouldValidateServerCert = (encrypt && !trustServerCert) || ((authType != SqlAuthenticationMethod.NotSpecified || _connHandler._accessTokenInBytes != null) && !trustServerCert); + + UInt32 info = (shouldValidateServerCert ? TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE : 0) | (isYukonOrLater ? TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE : 0); if (encrypt && !integratedSecurity) { @@ -911,7 +977,7 @@ namespace System.Data.SqlClient { ThrowExceptionAndWarning(_physicalStateObj); } - // create a new packet encryption changes the internal packet size Bug# 228403 + // create a new packet encryption changes the internal packet size try {} // EmptyTry/Finally to avoid FXCop violation finally { _physicalStateObj.ClearAllWritePackets(); @@ -955,6 +1021,27 @@ namespace System.Data.SqlClient { offset += 4; break; + case (int)PreLoginOptions.FEDAUTHREQUIRED: + payloadOffset = payload[offset++] << 8 | payload[offset++]; + payloadLength = payload[offset++] << 8 | payload[offset++]; + + // Only 0x00 and 0x01 are accepted values from the server. + if (payload[payloadOffset] != 0x00 && payload[payloadOffset] != 0x01) { + Bid.Trace("<sc.TdsParser.ConsumePreLoginHandshake|ERR> %d#, Server sent an unexpected value for FedAuthRequired PreLogin Option. Value was %d.\n", ObjectID, (int)payload[payloadOffset]); + throw SQL.ParsingErrorValue(ParsingErrorState.FedAuthRequiredPreLoginResponseInvalidValue, (int)payload[payloadOffset]); + } + + // We must NOT use the response for the FEDAUTHREQUIRED PreLogin option, if the connection string option + // was not using the new Authentication keyword or in other words, if Authentication=NotSpecified + // Or AccessToken is not null, mean token based authentication is used. + if ((_connHandler.ConnectionOptions != null + && _connHandler.ConnectionOptions.Authentication != SqlAuthenticationMethod.NotSpecified) + || _connHandler._accessTokenInBytes != null) + { + fedAuthRequired = payload[payloadOffset] == 0x01 ? true : false; + } + break; + default: Debug.Assert(false, "UNKNOWN option in ConsumePreLoginHandshake, option:" + option); @@ -1127,10 +1214,10 @@ namespace System.Data.SqlClient { breakConnection &= (TdsParserState.Closed != _state); if (breakConnection) { if ((_state == TdsParserState.OpenNotLoggedIn) && (_connHandler.ConnectionOptions.MultiSubnetFailover || _loginWithFailover) && (temp.Count == 1) && ((temp[0].Number == TdsEnums.TIMEOUT_EXPIRED) || (temp[0].Number == TdsEnums.SNI_WAIT_TIMEOUT))) { - // DevDiv2 Bug 459546: With "MultiSubnetFailover=yes" in the Connection String, SQLClient incorrectly throws a Timeout using shorter time slice (3-4 seconds), not honoring the actual 'Connect Timeout' - // http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/459546 - // For Multisubnet Failover we slice the timeout to make reconnecting faster (with the assumption that the server will not failover instantaneously) - // However, when timeout occurs we need to not doom the internal connection and also to mark the TdsParser as closed such that the login will be will retried + // DevDiv2 + + + breakConnection = false; Disconnect(); } @@ -1147,6 +1234,9 @@ namespace System.Data.SqlClient { serverVersion = _connHandler.ServerVersion; } exception = SqlException.CreateException(temp, serverVersion, _connHandler); + if (exception.Procedure == TdsEnums.INIT_ADAL_PACKAGE || exception.Procedure == TdsEnums.INIT_SSPI_PACKAGE) { + exception._doNotReconnect = true; + } } // call OnError outside of _ErrorCollectionLock to avoid deadlock @@ -1274,10 +1364,10 @@ namespace System.Data.SqlClient { len-=iColon; /* The error message should come back in the following format: "TCP Provider: MESSAGE TEXT" - Fix Bug 370686, if the message is recieved on a Win9x OS, the error message will not contain MESSAGE TEXT - per Bug: 269574. If we get a errormessage with no message text, just return the entire message otherwise - return just the message text. - */ + Fix + + +*/ if (len > 0) { errorMessage = errorMessage.Substring(iColon, len); } @@ -1385,6 +1475,21 @@ namespace System.Data.SqlClient { // // Takes a 16 bit short and writes it. // + internal byte[] SerializeShort(int v, TdsParserStateObject stateObj) { + if (null == stateObj._bShortBytes) { + stateObj._bShortBytes = new byte[2]; + } + else { + Debug.Assert(2 == stateObj._bShortBytes.Length); + } + + byte[] bytes = stateObj._bShortBytes; + int current = 0; + bytes[current++] = (byte)(v & 0xff); + bytes[current++] = (byte)((v >> 8) & 0xff); + return bytes; + } + internal void WriteShort(int v, TdsParserStateObject stateObj) { ReliabilitySection.Assert("unreliable call to WriteShort"); // you need to setup for a thread abort somewhere before you call this method @@ -1408,6 +1513,10 @@ namespace System.Data.SqlClient { // // Takes a long and writes out an unsigned int // + internal byte[] SerializeUnsignedInt(uint i, TdsParserStateObject stateObj) { + return SerializeInt((int)i, stateObj); + } + internal void WriteUnsignedInt(uint i, TdsParserStateObject stateObj) { WriteInt((int)i, stateObj); } @@ -1415,6 +1524,23 @@ namespace System.Data.SqlClient { // // Takes an int and writes it as an int. // + internal byte[] SerializeInt(int v, TdsParserStateObject stateObj) { + if (null == stateObj._bIntBytes) { + stateObj._bIntBytes = new byte[4]; + } + else { + Debug.Assert (4 == stateObj._bIntBytes.Length); + } + + int current = 0; + byte[] bytes = stateObj._bIntBytes; + bytes[current++] = (byte)(v & 0xff); + bytes[current++] = (byte)((v >> 8) & 0xff); + bytes[current++] = (byte)((v >> 16) & 0xff); + bytes[current++] = (byte)((v >> 24) & 0xff); + return bytes; + } + internal void WriteInt(int v, TdsParserStateObject stateObj) { ReliabilitySection.Assert("unreliable call to WriteInt"); // you need to setup for a thread abort somewhere before you call this method @@ -1438,6 +1564,14 @@ namespace System.Data.SqlClient { // // Takes a float and writes it as a 32 bit float. // + internal byte[] SerializeFloat(float v) { + if (Single.IsInfinity(v) || Single.IsNaN(v)) { + throw ADP.ParameterValueOutOfRange(v.ToString()); + } + + return BitConverter.GetBytes(v); + } + internal void WriteFloat(float v, TdsParserStateObject stateObj) { byte[] bytes = BitConverter.GetBytes(v); @@ -1447,6 +1581,27 @@ namespace System.Data.SqlClient { // // Takes a long and writes it as a long. // + internal byte[] SerializeLong(long v, TdsParserStateObject stateObj) { + int current = 0; + if (null == stateObj._bLongBytes) { + stateObj._bLongBytes = new byte[8]; + } + + byte[] bytes = stateObj._bLongBytes; + Debug.Assert (8 == bytes.Length, "Cached buffer has wrong size"); + + bytes[current++] = (byte)(v & 0xff); + bytes[current++] = (byte)((v >> 8) & 0xff); + bytes[current++] = (byte)((v >> 16) & 0xff); + bytes[current++] = (byte)((v >> 24) & 0xff); + bytes[current++] = (byte)((v >> 32) & 0xff); + bytes[current++] = (byte)((v >> 40) & 0xff); + bytes[current++] = (byte)((v >> 48) & 0xff); + bytes[current++] = (byte)((v >> 56) & 0xff); + + return bytes; + } + internal void WriteLong(long v, TdsParserStateObject stateObj) { ReliabilitySection.Assert("unreliable call to WriteLong"); // you need to setup for a thread abort somewhere before you call this method @@ -1474,6 +1629,20 @@ namespace System.Data.SqlClient { // // Takes a long and writes part of it // + internal byte[] SerializePartialLong(long v, int length) { + Debug.Assert(length <= 8, "Length specified is longer than the size of a long"); + Debug.Assert(length >= 0, "Length should not be negative"); + + byte[] bytes = new byte[length]; + + // all of the long fits into the buffer + for (int index = 0; index < length; index++) { + bytes[index] = (byte)((v >> (index * 8)) & 0xff); + } + + return bytes; + } + internal void WritePartialLong(long v, int length, TdsParserStateObject stateObj) { ReliabilitySection.Assert("unreliable call to WritePartialLong"); // you need to setup for a thread abort somewhere before you call this method Debug.Assert(length <= 8, "Length specified is longer than the size of a long"); @@ -1504,6 +1673,14 @@ namespace System.Data.SqlClient { // // Takes a double and writes it as a 64 bit double. // + internal byte[] SerializeDouble(double v) { + if (Double.IsInfinity(v) || Double.IsNaN(v)) { + throw ADP.ParameterValueOutOfRange(v.ToString()); + } + + return BitConverter.GetBytes(v); + } + internal void WriteDouble(double v, TdsParserStateObject stateObj) { byte[] bytes = BitConverter.GetBytes(v); @@ -1597,7 +1774,8 @@ namespace System.Data.SqlClient { token == TdsEnums.SQLOFFSET || token == TdsEnums.SQLSSPI || token == TdsEnums.SQLFEATUREEXTACK || - token == TdsEnums.SQLSESSIONSTATE); + token == TdsEnums.SQLSESSIONSTATE || + token == TdsEnums.SQLFEDAUTHINFO); } // Main parse loop for the top-level tds tokens, calls back into the I*Handler interfaces @@ -1656,7 +1834,7 @@ namespace System.Data.SqlClient { _state = TdsParserState.Broken; _connHandler.BreakConnection(); Bid.Trace("<sc.TdsParser.Run|ERR> Potential multi-threaded misuse of connection, unexpected TDS token found %d#\n", ObjectID); - throw SQL.ParsingError(); // MDAC 82443 + throw SQL.ParsingErrorToken(ParsingErrorState.InvalidTdsTokenReceived, token); // MDAC 82443 } int tokenLength; @@ -1716,8 +1894,8 @@ namespace System.Data.SqlClient { // anyways so we need to consume all errors. This is not the case // if we have already given out a reader. If we have already given out // a reader we need to throw the error but not halt further processing. We used to - // halt processing and that was a bug preventing the user from - // processing subsequent results. + // halt processing and that was a + if (null != dataStream) { // Webdata 104560 if (!dataStream.IsInitialized) { @@ -1770,7 +1948,15 @@ namespace System.Data.SqlClient { return false; } if ((token == TdsEnums.SQLDONEPROC) && (cmdHandler != null)) { - cmdHandler.OnDoneProc(); + // If the current parse/read is for the results of describe parameter encryption RPC requests, + // call a different handler which will update the describe parameter encryption RPC structures + // with the results, instead of the actual user RPC requests. + if (cmdHandler.IsDescribeParameterEncryptionRPCCurrentlyInProgress) { + cmdHandler.OnDoneDescribeParameterEncryptionProc(stateObj); + } + else { + cmdHandler.OnDoneProc(); + } } break; @@ -1923,6 +2109,7 @@ namespace System.Data.SqlClient { } case TdsEnums.SQLLOGINACK: { + Bid.Trace("<sc.TdsParser.TryRun|SEC> Received login acknowledgement token\n"); SqlLoginAck ack; if (!TryProcessLoginAck(stateObj, out ack)) { return false; @@ -1938,6 +2125,17 @@ namespace System.Data.SqlClient { } break; } + case TdsEnums.SQLFEDAUTHINFO: + { + _connHandler._federatedAuthenticationInfoReceived = true; + SqlFedAuthInfo info; + Bid.Trace("<sc.TdsParser.TryRun|SEC> Received federated authentication info token\n"); + if (!TryProcessFedAuthInfo(stateObj, tokenLength, out info)) { + return false; + } + _connHandler.OnFedAuthInfo(info); + break; + } case TdsEnums.SQLSESSIONSTATE: { if (!TryProcessSessionState(stateObj, tokenLength, _connHandler._currentSessionData)) { @@ -1949,7 +2147,8 @@ namespace System.Data.SqlClient { { if (tokenLength != TdsEnums.VARNULL) { _SqlMetaDataSet metadata; - if (!TryProcessMetaData(tokenLength, stateObj, out metadata)) { + if (!TryProcessMetaData(tokenLength, stateObj, out metadata, + cmdHandler != null ? cmdHandler.ColumnEncryptionSetting : SqlCommandColumnEncryptionSetting.UseConnectionSetting)) { return false; } stateObj._cleanupMetaData = metadata; @@ -2023,11 +2222,12 @@ namespace System.Data.SqlClient { case TdsEnums.SQLRETURNVALUE: { SqlReturnValue returnValue; - if (!TryProcessReturnValue(tokenLength, stateObj, out returnValue)) { + if (!TryProcessReturnValue(tokenLength, stateObj, out returnValue, + cmdHandler != null ? cmdHandler.ColumnEncryptionSetting : SqlCommandColumnEncryptionSetting.UseConnectionSetting)) { return false; } if (cmdHandler != null) { - cmdHandler.OnReturnValue(returnValue); + cmdHandler.OnReturnValue(returnValue, stateObj); } break; } @@ -2494,7 +2694,13 @@ namespace System.Data.SqlClient { } if ((null != cmd) && (TdsEnums.DONE_COUNT == (status & TdsEnums.DONE_COUNT))) { if (curCmd != TdsEnums.SELECT) { - cmd.InternalRecordsAffected = count; + if (cmd.IsDescribeParameterEncryptionRPCCurrentlyInProgress) { + // The below line is used only for debug asserts and not exposed publicly or impacts functionality otherwise. + cmd.RowsAffectedByDescribeParameterEncryption = count; + } + else { + cmd.InternalRecordsAffected = count; + } } // Skip the bogus DONE counts sent by the server if (stateObj._receivedColMetaData || (curCmd != TdsEnums.SELECT)) { @@ -2648,12 +2854,18 @@ namespace System.Data.SqlClient { _connHandler.OnFeatureExtAck(featureId, data); } } while (featureId != TdsEnums.FEATUREEXT_TERMINATOR); + + // Check if column encryption was on and feature wasn't acknowledged. + if (_connHandler.ConnectionOptions.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled && !IsColumnEncryptionSupported) { + throw SQL.TceNotSupported (); + } + return true; } private bool TryProcessSessionState(TdsParserStateObject stateObj, int length, SessionData sdata) { if (length < 5) { - throw SQL.ParsingError(); + throw SQL.ParsingErrorLength(ParsingErrorState.SessionStateLengthTooShort, length); } UInt32 seqNum; if (!stateObj.TryReadUInt32(out seqNum)) { @@ -2667,7 +2879,7 @@ namespace System.Data.SqlClient { return false; } if (status > 1) { - throw SQL.ParsingError(); + throw SQL.ParsingErrorStatus(ParsingErrorState.SessionStateInvalidStatus, status); } bool recoverable = status != 0; length -= 5; @@ -2855,6 +3067,133 @@ namespace System.Data.SqlClient { return true; } + private bool TryProcessFedAuthInfo(TdsParserStateObject stateObj, int tokenLen, out SqlFedAuthInfo sqlFedAuthInfo) { + sqlFedAuthInfo = null; + SqlFedAuthInfo tempFedAuthInfo = new SqlFedAuthInfo(); + + // Skip reading token length, since it has already been read in caller + + if (Bid.AdvancedOn) { + Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo> FEDAUTHINFO token stream length = {0}\n", tokenLen); + } + + if (tokenLen < sizeof(uint)) { + // the token must at least contain a DWORD indicating the number of info IDs + Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> FEDAUTHINFO token stream length too short for CountOfInfoIDs.\n"); + throw SQL.ParsingErrorLength(ParsingErrorState.FedAuthInfoLengthTooShortForCountOfInfoIds, tokenLen); + } + + // read how many FedAuthInfo options there are + uint optionsCount; + if (!stateObj.TryReadUInt32(out optionsCount)) { + Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> Failed to read CountOfInfoIDs in FEDAUTHINFO token stream.\n"); + throw SQL.ParsingError(ParsingErrorState.FedAuthInfoFailedToReadCountOfInfoIds); + } + tokenLen -= sizeof(uint); // remaining length is shortened since we read optCount + + if (Bid.AdvancedOn) { + Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo> CountOfInfoIDs = {0}\n", optionsCount.ToString(CultureInfo.InvariantCulture)); + } + + if (tokenLen > 0) { + // read the rest of the token + byte[] tokenData = new byte[tokenLen]; + int totalRead = 0; + bool successfulRead = stateObj.TryReadByteArray(tokenData, 0, tokenLen, out totalRead); + + if (Bid.AdvancedOn) { + Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo> Read rest of FEDAUTHINFO token stream: {0}\n", BitConverter.ToString(tokenData, 0, totalRead)); + } + + if (!successfulRead || totalRead != tokenLen) { + Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> Failed to read FEDAUTHINFO token stream. Attempted to read {0} bytes, actually read {1}\n", tokenLen, totalRead); + throw SQL.ParsingError(ParsingErrorState.FedAuthInfoFailedToReadTokenStream); + } + + // each FedAuthInfoOpt is 9 bytes: + // 1 byte for FedAuthInfoID + // 4 bytes for FedAuthInfoDataLen + // 4 bytes for FedAuthInfoDataOffset + // So this is the index in tokenData for the i-th option + const uint optionSize = 9; + + // the total number of bytes for all FedAuthInfoOpts together + uint totalOptionsSize = checked(optionsCount * optionSize); + + for (uint i = 0; i < optionsCount; i++) { + uint currentOptionOffset = checked(i * optionSize); + + byte id = tokenData[currentOptionOffset]; + uint dataLen = BitConverter.ToUInt32(tokenData, checked((int)(currentOptionOffset + 1))); + uint dataOffset = BitConverter.ToUInt32(tokenData, checked((int)(currentOptionOffset + 5))); + + if (Bid.AdvancedOn) { + Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo> FedAuthInfoOpt: ID={0}, DataLen={1}, Offset={2}\n", id, dataLen.ToString(CultureInfo.InvariantCulture), dataOffset.ToString(CultureInfo.InvariantCulture)); + } + + // offset is measured from optCount, so subtract to make offset measured + // from the beginning of tokenData + checked { + dataOffset -= sizeof(uint); + } + + // if dataOffset points to a region within FedAuthInfoOpt or after the end of the token, throw + if (dataOffset < totalOptionsSize || dataOffset >= tokenLen) { + Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> FedAuthInfoDataOffset points to an invalid location.\n"); + throw SQL.ParsingErrorOffset(ParsingErrorState.FedAuthInfoInvalidOffset, unchecked((int)dataOffset)); + } + + // try to read data and throw if the arguments are bad, meaning the server sent us a bad token + string data; + try { + data = System.Text.Encoding.Unicode.GetString(tokenData, checked((int)dataOffset), checked((int)dataLen)); + } + catch (ArgumentOutOfRangeException e) { + Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> Failed to read FedAuthInfoData.\n"); + throw SQL.ParsingError(ParsingErrorState.FedAuthInfoFailedToReadData, e); + } + catch (ArgumentException e) { + Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> FedAuthInfoData is not in unicode format.\n"); + throw SQL.ParsingError(ParsingErrorState.FedAuthInfoDataNotUnicode, e); + } + + if (Bid.AdvancedOn) { + Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo> FedAuthInfoData: {0}\n", data); + } + + // store data in tempFedAuthInfo + switch ((TdsEnums.FedAuthInfoId)id) { + case TdsEnums.FedAuthInfoId.Spn: + tempFedAuthInfo.spn = data; + break; + case TdsEnums.FedAuthInfoId.Stsurl: + tempFedAuthInfo.stsurl = data; + break; + default: + if (Bid.AdvancedOn) { + Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo> Ignoring unknown federated authentication info option: {0}\n", id); + } + break; + } + } + } + else { + Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> FEDAUTHINFO token stream is not long enough to contain the data it claims to.\n"); + throw SQL.ParsingErrorLength(ParsingErrorState.FedAuthInfoLengthTooShortForData, tokenLen); + } + + Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo> Processed FEDAUTHINFO token stream: {0}\n", tempFedAuthInfo.ToString()); + + if (String.IsNullOrWhiteSpace(tempFedAuthInfo.stsurl) || String.IsNullOrWhiteSpace(tempFedAuthInfo.spn)) { + // We should be receiving both stsurl and spn + Bid.Trace("<sc.TdsParser.TryProcessFedAuthInfo|ERR> FEDAUTHINFO token stream does not contain both STSURL and SPN.\n"); + throw SQL.ParsingError(ParsingErrorState.FedAuthInfoDoesNotContainStsurlAndSpn); + } + + sqlFedAuthInfo = tempFedAuthInfo; + return true; + } + internal bool TryProcessError(byte token, TdsParserStateObject stateObj, out SqlError error) { ushort shortLen; byte byteLen; @@ -2891,8 +3230,8 @@ namespace System.Data.SqlClient { string server; - // MDAC bug #49307 - server sometimes does not send over server field! In those cases - // we will use our locally cached value. + // MDAC + if (byteLen == 0) { server = _server; } @@ -2946,7 +3285,10 @@ namespace System.Data.SqlClient { } - internal bool TryProcessReturnValue(int length, TdsParserStateObject stateObj, out SqlReturnValue returnValue) { + internal bool TryProcessReturnValue(int length, + TdsParserStateObject stateObj, + out SqlReturnValue returnValue, + SqlCommandColumnEncryptionSetting columnEncryptionSetting) { returnValue = null; SqlReturnValue rec = new SqlReturnValue(); rec.length = length; // In Yukon this length is -1 @@ -2960,6 +3302,7 @@ namespace System.Data.SqlClient { return false; } + rec.parameter = null; if (len > 0) { if (!stateObj.TryReadString(len, out rec.parameter)) { return false; @@ -2988,12 +3331,22 @@ namespace System.Data.SqlClient { userType = userTypeShort; } - // read off the flags - ushort ignoredFlags; - if (!stateObj.TryReadUInt16(out ignoredFlags)) { + // Read off the flags. + // The first byte is ignored since it doesn't contain any interesting information. + byte flags; + if (!stateObj.TryReadByte(out flags)) { + return false; + } + + if (!stateObj.TryReadByte(out flags)) { return false; } + // Check if the column is encrypted. + if (_serverSupportsColumnEncryption) { + rec.isEncrypted = (TdsEnums.IsEncrypted == (flags & TdsEnums.IsEncrypted)); + } + // read the type byte tdsType; if (!stateObj.TryReadByte(out tdsType)) { @@ -3117,6 +3470,13 @@ namespace System.Data.SqlClient { } } + // For encrypted parameters, read the unencrypted type and encryption information. + if (_serverSupportsColumnEncryption && rec.isEncrypted) { + if (!TryProcessTceCryptoMetadata(stateObj, rec, cipherTable: null, columnEncryptionSetting: columnEncryptionSetting, isReturnValue: true)) { + return false; + } + } + // for now we coerce return values into a SQLVariant, not good... bool isNull = false; ulong valLen; @@ -3134,10 +3494,12 @@ namespace System.Data.SqlClient { } if (isNull) { - GetNullSqlValue(rec.value, rec); + GetNullSqlValue(rec.value, rec, SqlCommandColumnEncryptionSetting.Disabled, _connHandler); } else { - if (!TryReadSqlValue(rec.value, rec, intlen, stateObj)) { + // We should never do any decryption here, so pass disabled as the command encryption override. + // We only read the binary value and decryption will be performed by OnReturnValue(). + if (!TryReadSqlValue(rec.value, rec, intlen, stateObj, SqlCommandColumnEncryptionSetting.Disabled, columnName:null /*Not used*/)) { return false; } } @@ -3146,6 +3508,97 @@ namespace System.Data.SqlClient { return true; } + internal bool TryProcessTceCryptoMetadata (TdsParserStateObject stateObj, + SqlMetaDataPriv col, + SqlTceCipherInfoTable? cipherTable, + SqlCommandColumnEncryptionSetting columnEncryptionSetting, + bool isReturnValue) { + Debug.Assert(isReturnValue == (cipherTable == null), "Ciphertable is not set iff this is a return value"); + + // Read the ordinal into cipher table + ushort index = 0; + UInt32 userType; + + // For return values there is not cipher table and no ordinal. + if (cipherTable.HasValue) { + if (!stateObj.TryReadUInt16(out index)) { + return false; + } + + // validate the index (ordinal passed) + if (index >= cipherTable.Value.Size) { + Bid.Trace("<sc.TdsParser.TryProcessTceCryptoMetadata|TCE> Incorrect ordinal received %d, max tab size: %d\n", index, cipherTable.Value.Size); + throw SQL.ParsingErrorValue(ParsingErrorState.TceInvalidOrdinalIntoCipherInfoTable, index); + } + } + + // Read the user type + if (!stateObj.TryReadUInt32(out userType)) { + return false; + } + + // Read the base TypeInfo + col.baseTI = new SqlMetaDataPriv(); + if (!TryProcessTypeInfo(stateObj, col.baseTI, userType)) { + return false; + } + + // Read the cipher algorithm Id + byte cipherAlgorithmId; + if (!stateObj.TryReadByte(out cipherAlgorithmId)) { + return false; + } + + string cipherAlgorithmName = null; + if (TdsEnums.CustomCipherAlgorithmId == cipherAlgorithmId) { + // Custom encryption algorithm, read the name + byte nameSize; + if (!stateObj.TryReadByte(out nameSize)) { + return false; + } + + if (!stateObj.TryReadString(nameSize, out cipherAlgorithmName)) { + return false; + } + } + + // Read Encryption Type. + byte encryptionType; + if (!stateObj.TryReadByte(out encryptionType)) { + return false; + } + + // Read Normalization Rule Version. + byte normalizationRuleVersion; + if (!stateObj.TryReadByte(out normalizationRuleVersion)) { + return false; + } + + Debug.Assert(col.cipherMD == null, "col.cipherMD should be null in TryProcessTceCryptoMetadata."); + + // Check if TCE is enable and if it is set the crypto MD for the column. + // TCE is enabled if the command is set to enabled or to resultset only and this is not a return value + // or if it is set to use connection setting and the connection has TCE enabled. + if ((columnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled || + (columnEncryptionSetting == SqlCommandColumnEncryptionSetting.ResultSetOnly && !isReturnValue)) || + (columnEncryptionSetting == SqlCommandColumnEncryptionSetting.UseConnectionSetting && + _connHandler != null && _connHandler.ConnectionOptions != null && + _connHandler.ConnectionOptions.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled)) { + col.cipherMD = new SqlCipherMetadata(cipherTable.HasValue ? (SqlTceCipherInfoEntry?)cipherTable.Value[index] : null, + index, + cipherAlgorithmId: cipherAlgorithmId, + cipherAlgorithmName: cipherAlgorithmName, + encryptionType: encryptionType, + normalizationRuleVersion: normalizationRuleVersion); + } + else { + // If TCE is disabled mark the MD as not encrypted. + col.isEncrypted = false; + } + + return true; + } + internal bool TryProcessCollation(TdsParserStateObject stateObj, out SqlCollation collation) { SqlCollation newCollation = new SqlCollation(); @@ -3357,8 +3810,7 @@ namespace System.Data.SqlClient { Debug.Assert(cColumns > 0, "should have at least 1 column in altMetaData!"); metaData = null; - - _SqlMetaDataSet altMetaDataSet = new _SqlMetaDataSet(cColumns); + _SqlMetaDataSet altMetaDataSet = new _SqlMetaDataSet(cColumns, null); int[] indexMap = new int[cColumns]; if (!stateObj.TryReadUInt16(out altMetaDataSet.id)) { @@ -3389,7 +3841,8 @@ namespace System.Data.SqlClient { return false; } - if (!TryCommonProcessMetaData(stateObj, col)) { + // TCE is not applicable to AltMetadata. + if (!TryCommonProcessMetaData(stateObj, col, null, fColMD: false, columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Disabled)) { return false; } @@ -3455,61 +3908,178 @@ namespace System.Data.SqlClient { return true; } + /// <summary> + /// <para> Parses the TDS message to read single CIPHER_INFO entry.</para> + /// </summary> + internal bool TryReadCipherInfoEntry (TdsParserStateObject stateObj, out SqlTceCipherInfoEntry entry) { + byte cekValueCount = 0; + entry = new SqlTceCipherInfoEntry(ordinal: 0); - internal bool TryProcessMetaData(int cColumns, TdsParserStateObject stateObj, out _SqlMetaDataSet metaData) { - Debug.Assert(cColumns > 0, "should have at least 1 column in metadata!"); + // Read the DB ID + int dbId; + if (!stateObj.TryReadInt32(out dbId)) { + return false; + } - _SqlMetaDataSet newMetaData = new _SqlMetaDataSet(cColumns); - for (int i = 0; i < cColumns; i++) { - if (!TryCommonProcessMetaData(stateObj, newMetaData[i])) { - metaData = null; + // Read the keyID + int keyId; + if (!stateObj.TryReadInt32(out keyId)) { + return false; + } + + // Read the key version + int keyVersion; + if (!stateObj.TryReadInt32(out keyVersion)) { + return false; + } + + // Read the key MD Version + byte[] keyMDVersion = new byte[8]; + if (!stateObj.TryReadByteArray(keyMDVersion, 0, 8)) { + return false; + } + + // Read the value count + if (!stateObj.TryReadByte (out cekValueCount)) { + return false; + } + + for (int i = 0; i < cekValueCount; i++) { + // Read individual CEK values + byte[] encryptedCek; + string keyPath; + string keyStoreName; + byte algorithmLength; + string algorithmName; + ushort shortValue; + byte byteValue; + int length; + + // Read the length of encrypted CEK + if (!stateObj.TryReadUInt16 (out shortValue)) { + return false; + } + + length = shortValue; + encryptedCek = new byte[length]; + + // Read the actual encrypted CEK + if (!stateObj.TryReadByteArray (encryptedCek, 0, length)) { + return false; + } + + // Read the length of key store name + if (!stateObj.TryReadByte (out byteValue)) { + return false; + } + + length = byteValue; + + // And read the key store name now + if (!stateObj.TryReadString(length, out keyStoreName)) { + return false; + } + + // Read the length of key Path + if (!stateObj.TryReadUInt16 (out shortValue)) { + return false; + } + + length = shortValue; + + // Read the key path string + if (!stateObj.TryReadString(length, out keyPath)) { + return false; + } + + // Read the length of the string carrying the encryption algo + if (!stateObj.TryReadByte(out algorithmLength)) { + return false; + } + + length = (int)algorithmLength; + + // Read the string carrying the encryption algo (eg. RSA_PKCS_OAEP) + if (!stateObj.TryReadString(length, out algorithmName)) { return false; } + + // Add this encrypted CEK blob to our list of encrypted values for the CEK + entry.Add(encryptedCek, + databaseId: dbId, + cekId: keyId, + cekVersion: keyVersion, + cekMdVersion: keyMDVersion, + keyPath: keyPath, + keyStoreName: keyStoreName, + algorithmName: algorithmName); } - metaData = newMetaData; return true; } - private bool IsVarTimeTds(byte tdsType) { - return tdsType == TdsEnums.SQLTIME || tdsType == TdsEnums.SQLDATETIME2 || tdsType == TdsEnums.SQLDATETIMEOFFSET; - } + /// <summary> + /// <para> Parses the TDS message to read a single CIPHER_INFO table.</para> + /// </summary> + internal bool TryProcessCipherInfoTable (TdsParserStateObject stateObj, out SqlTceCipherInfoTable? cipherTable) { + // Read count + short tableSize = 0; + cipherTable = null; + if (!stateObj.TryReadInt16(out tableSize)) { + return false; + } - private bool TryCommonProcessMetaData(TdsParserStateObject stateObj, _SqlMetaData col) { - byte byteLen; - UInt32 userType; + if (0 != tableSize) { + SqlTceCipherInfoTable tempTable = new SqlTceCipherInfoTable(tableSize); - // read user type - 4 bytes Yukon, 2 backwards - if (IsYukonOrNewer) { - if (!stateObj.TryReadUInt32(out userType)) { - return false; + // Read individual entries + for (int i = 0; i < tableSize; i++) { + SqlTceCipherInfoEntry entry; + if (!TryReadCipherInfoEntry (stateObj, out entry)) { + return false; + } + + tempTable[i] = entry; } + + cipherTable = tempTable; } - else { - ushort userTypeShort; - if (!stateObj.TryReadUInt16(out userTypeShort)) { + + return true; + } + + internal bool TryProcessMetaData(int cColumns, TdsParserStateObject stateObj, out _SqlMetaDataSet metaData, SqlCommandColumnEncryptionSetting columnEncryptionSetting) { + Debug.Assert(cColumns > 0, "should have at least 1 column in metadata!"); + + // Read the cipher info table first + SqlTceCipherInfoTable? cipherTable = null; + if (_serverSupportsColumnEncryption) { + if (!TryProcessCipherInfoTable (stateObj, out cipherTable)) { + metaData = null; return false; } - userType = userTypeShort; } - // read flags and set appropriate flags in structure - byte flags; - if (!stateObj.TryReadByte(out flags)) { - return false; + // Read the ColumnData fields + _SqlMetaDataSet newMetaData = new _SqlMetaDataSet(cColumns, cipherTable); + for (int i = 0; i < cColumns; i++) { + if (!TryCommonProcessMetaData(stateObj, newMetaData[i], cipherTable, fColMD: true, columnEncryptionSetting: columnEncryptionSetting)) { + metaData = null; + return false; + } } - col.updatability = (byte)((flags & TdsEnums.Updatability) >> 2); - col.isNullable = (TdsEnums.Nullable == (flags & TdsEnums.Nullable)); - col.isIdentity = (TdsEnums.Identity == (flags & TdsEnums.Identity)); + // DEVNOTE: cipherTable is discarded at this point since its no longer needed. + metaData = newMetaData; + return true; + } - // read second byte of column metadata flags - if (!stateObj.TryReadByte(out flags)) { - return false; - } - - col.isColumnSet = (TdsEnums.IsColumnSet == (flags & TdsEnums.IsColumnSet)); + private bool IsVarTimeTds(byte tdsType) { + return tdsType == TdsEnums.SQLTIME || tdsType == TdsEnums.SQLDATETIME2 || tdsType == TdsEnums.SQLDATETIMEOFFSET; + } + private bool TryProcessTypeInfo (TdsParserStateObject stateObj, SqlMetaDataPriv col, UInt32 userType) { + byte byteLen; byte tdsType; if (!stateObj.TryReadByte(out tdsType)) { return false; @@ -3649,6 +4219,53 @@ namespace System.Data.SqlClient { } } + return true; + } + + private bool TryCommonProcessMetaData(TdsParserStateObject stateObj, _SqlMetaData col, SqlTceCipherInfoTable? cipherTable, bool fColMD, SqlCommandColumnEncryptionSetting columnEncryptionSetting) { + byte byteLen; + UInt32 userType; + + // read user type - 4 bytes Yukon, 2 backwards + if (IsYukonOrNewer) { + if (!stateObj.TryReadUInt32(out userType)) { + return false; + } + } + else { + ushort userTypeShort; + if (!stateObj.TryReadUInt16(out userTypeShort)) { + return false; + } + userType = userTypeShort; + } + + // read flags and set appropriate flags in structure + byte flags; + if (!stateObj.TryReadByte(out flags)) { + return false; + } + + col.updatability = (byte)((flags & TdsEnums.Updatability) >> 2); + col.isNullable = (TdsEnums.Nullable == (flags & TdsEnums.Nullable)); + col.isIdentity = (TdsEnums.Identity == (flags & TdsEnums.Identity)); + + // read second byte of column metadata flags + if (!stateObj.TryReadByte(out flags)) { + return false; + } + + col.isColumnSet = (TdsEnums.IsColumnSet == (flags & TdsEnums.IsColumnSet)); + if (fColMD && _serverSupportsColumnEncryption) { + col.isEncrypted = (TdsEnums.IsEncrypted == (flags & TdsEnums.IsEncrypted)); + } + + // Read TypeInfo + if (!TryProcessTypeInfo (stateObj, col, userType)) { + return false; + } + + // Read tablename if present if (col.metaType.IsLong && !col.metaType.IsPlp) { if (_isYukon) { int unusedLen = 0xFFFF; //We ignore this value @@ -3672,6 +4289,15 @@ namespace System.Data.SqlClient { } } + // Read the TCE column cryptoinfo + if (fColMD && _serverSupportsColumnEncryption && col.isEncrypted) { + // If the column is encrypted, we should have a valid cipherTable + if (cipherTable.HasValue && !TryProcessTceCryptoMetadata (stateObj, col, cipherTable.Value, columnEncryptionSetting, isReturnValue: false)) { + return false; + } + } + + // Read the column name if (!stateObj.TryReadByte(out byteLen)) { return false; } @@ -4045,14 +4671,16 @@ namespace System.Data.SqlClient { } if (isNull) { - GetNullSqlValue(data, md); + GetNullSqlValue(data, md, SqlCommandColumnEncryptionSetting.Disabled /*Column Encryption Disabled for Bulk Copy*/, _connHandler); buffer[map[i]] = data.SqlValue; } else { // We only read up to 2Gb. Throw if data is larger. Very large data // should be read in chunks in sequential read mode // For Plp columns, we may have gotten only the length of the first chunk - if (!TryReadSqlValue(data, md, md.metaType.IsPlp ? (Int32.MaxValue) : (int)len, stateObj)) { + if (!TryReadSqlValue(data, md, md.metaType.IsPlp ? (Int32.MaxValue) : (int)len, stateObj, + SqlCommandColumnEncryptionSetting.Disabled /*Column Encryption Disabled for Bulk Copy*/, + md.column)) { return false; } buffer[map[i]] = data.SqlValue; @@ -4066,8 +4694,44 @@ namespace System.Data.SqlClient { return true; } - internal object GetNullSqlValue(SqlBuffer nullVal, SqlMetaDataPriv md) { - switch (md.type) { + /// <summary> + /// Determines if a column value should be transparently decrypted (based on SqlCommand and Connection String settings). + /// </summary> + /// <returns>true if the value should be transparently decrypted, false otherwise</returns> + internal static bool ShouldHonorTceForRead (SqlCommandColumnEncryptionSetting columnEncryptionSetting, + SqlInternalConnectionTds connection) { + + // Command leve setting trumps all + switch (columnEncryptionSetting) { + case SqlCommandColumnEncryptionSetting.Disabled: + return false; + case SqlCommandColumnEncryptionSetting.Enabled: + return true; + case SqlCommandColumnEncryptionSetting.ResultSetOnly: + return true; + default: + // Check connection level setting! + Debug.Assert(SqlCommandColumnEncryptionSetting.UseConnectionSetting == columnEncryptionSetting, + "Unexpected value for command level override"); + return (connection != null && connection.ConnectionOptions != null && + connection.ConnectionOptions.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled); + } + } + + internal static object GetNullSqlValue( + SqlBuffer nullVal, + SqlMetaDataPriv md, + SqlCommandColumnEncryptionSetting columnEncryptionSetting, + SqlInternalConnectionTds connection) { + SqlDbType type = md.type; + + if (type == SqlDbType.VarBinary && // if its a varbinary + md.isEncrypted &&// and encrypted + ShouldHonorTceForRead(columnEncryptionSetting, connection)){ + type = md.baseTI.type; // the use the actual (plaintext) type + } + + switch (type) { case SqlDbType.Real: nullVal.SetToNullOfType(SqlBuffer.StorageType.Single); break; @@ -4156,8 +4820,8 @@ namespace System.Data.SqlClient { break; case SqlDbType.Timestamp: - // Dev10 Bug #479607 - this should have been the same as SqlDbType.Binary, but it's a rejected breaking change - // Dev10 Bug #752790 - don't assert when it does happen + // Dev10 + break; default: @@ -4312,7 +4976,269 @@ namespace System.Data.SqlClient { return true; } - internal bool TryReadSqlValue(SqlBuffer value, SqlMetaDataPriv md, int length, TdsParserStateObject stateObj) { + /// <summary> + /// Deserializes the unencrypted bytes into a value based on the target type info. + /// </summary> + internal bool DeserializeUnencryptedValue (SqlBuffer value, byte[] unencryptedBytes, SqlMetaDataPriv md, TdsParserStateObject stateObj, byte normalizationVersion) { + if (normalizationVersion != 0x01) { + throw SQL.UnsupportedNormalizationVersion(normalizationVersion); + } + + byte tdsType = md.baseTI.tdsType; + int length = unencryptedBytes.Length; + + // For normalized types, the length and scale of the actual type might be different than the value's. + int denormalizedLength = md.baseTI.length; + byte denormalizedScale = md.baseTI.scale; + + Debug.Assert (false == md.baseTI.isEncrypted, "Double encryption detected"); + switch (tdsType) { + // We normalize to allow conversion across data types. All data types below are serialized into a BIGINT. + case TdsEnums.SQLBIT: + case TdsEnums.SQLBITN: + case TdsEnums.SQLINTN: + case TdsEnums.SQLINT1: + case TdsEnums.SQLINT2: + case TdsEnums.SQLINT4: + case TdsEnums.SQLINT8: + Debug.Assert(length == 8, "invalid length for SqlInt64 type!"); + byte byteValue; + long longValue; + + if (unencryptedBytes.Length != 8) { + return false; + } + + longValue = BitConverter.ToInt64(unencryptedBytes, 0); + + if (tdsType == TdsEnums.SQLBIT || + tdsType == TdsEnums.SQLBITN) { + value.Boolean = (longValue != 0); + break; + } + + if (tdsType == TdsEnums.SQLINT1 || denormalizedLength == 1) + value.Byte = (byte)longValue; + else if (tdsType == TdsEnums.SQLINT2 || denormalizedLength == 2) + value.Int16 = (Int16)longValue; + else if (tdsType == TdsEnums.SQLINT4 || denormalizedLength == 4) + value.Int32 = (Int32)longValue; + else + value.Int64 = longValue; + + break; + + case TdsEnums.SQLFLTN: + if (length == 4) { + goto case TdsEnums.SQLFLT4; + } + else { + goto case TdsEnums.SQLFLT8; + } + + case TdsEnums.SQLFLT4: + Debug.Assert(length == 4, "invalid length for SqlSingle type!"); + float singleValue; + if (unencryptedBytes.Length != 4) { + return false; + } + + singleValue = BitConverter.ToSingle(unencryptedBytes, 0); + value.Single = singleValue; + break; + + case TdsEnums.SQLFLT8: + double doubleValue; + if (unencryptedBytes.Length != 8) { + return false; + } + + doubleValue = BitConverter.ToDouble(unencryptedBytes, 0); + value.Double = doubleValue; + break; + + // We normalize to allow conversion across data types. SMALLMONEY is serialized into a MONEY. + case TdsEnums.SQLMONEYN: + case TdsEnums.SQLMONEY4: + case TdsEnums.SQLMONEY: + { + int mid; + uint lo; + + if (unencryptedBytes.Length != 8) { + return false; + } + + mid = BitConverter.ToInt32(unencryptedBytes, 0); + lo = BitConverter.ToUInt32(unencryptedBytes, 4); + + long l = (((long)mid) << 0x20) + ((long)lo); + value.SetToMoney(l); + break; + } + + case TdsEnums.SQLDATETIMN: + if (length == 4) { + goto case TdsEnums.SQLDATETIM4; + } + else { + goto case TdsEnums.SQLDATETIME; + } + + case TdsEnums.SQLDATETIM4: + ushort daypartShort, timepartShort; + if (unencryptedBytes.Length != 4) { + return false; + } + + daypartShort = (UInt16)((unencryptedBytes[1] << 8) + unencryptedBytes[0]); + timepartShort = (UInt16)((unencryptedBytes[3] << 8) + unencryptedBytes[2]); + value.SetToDateTime(daypartShort, timepartShort * SqlDateTime.SQLTicksPerMinute); + break; + + case TdsEnums.SQLDATETIME: + int daypart; + uint timepart; + if (unencryptedBytes.Length != 8) { + return false; + } + + daypart = BitConverter.ToInt32(unencryptedBytes, 0); + timepart = BitConverter.ToUInt32(unencryptedBytes, 4); + value.SetToDateTime(daypart, (int)timepart); + break; + + case TdsEnums.SQLUNIQUEID: + { + Debug.Assert(length == 16, "invalid length for SqlGuid type!"); + value.SqlGuid = new SqlGuid(unencryptedBytes, true); // doesn't copy the byte array + break; + } + + case TdsEnums.SQLBINARY: + case TdsEnums.SQLBIGBINARY: + case TdsEnums.SQLBIGVARBINARY: + case TdsEnums.SQLVARBINARY: + case TdsEnums.SQLIMAGE: + { + // Note: Better not come here with plp data!! + Debug.Assert(length <= TdsEnums.MAXSIZE, "Plp data decryption attempted"); + + // If this is a fixed length type, pad with zeros to get to the fixed length size. + if (tdsType == TdsEnums.SQLBINARY || tdsType == TdsEnums.SQLBIGBINARY) { + byte[] bytes = new byte[md.baseTI.length]; + Buffer.BlockCopy(unencryptedBytes, 0, bytes, 0, unencryptedBytes.Length); + unencryptedBytes = bytes; + } + + value.SqlBinary = new SqlBinary(unencryptedBytes, true); // doesn't copy the byte array + break; + } + + case TdsEnums.SQLDECIMALN: + case TdsEnums.SQLNUMERICN: + // Check the sign from the first byte. + int index = 0; + byteValue = unencryptedBytes[index++]; + bool fPositive = (1 == byteValue); + + // Now read the 4 next integers which contain the actual value. + length = checked((int)length-1); + int[] bits = new int[4]; + int decLength = length>>2; + for (int i = 0; i < decLength; i++) { + // up to 16 bytes of data following the sign byte + bits[i] = BitConverter.ToInt32(unencryptedBytes, index); + index += 4; + } + value.SetToDecimal (md.baseTI.precision, md.baseTI.scale, fPositive, bits); + break; + + case TdsEnums.SQLCHAR: + case TdsEnums.SQLBIGCHAR: + case TdsEnums.SQLVARCHAR: + case TdsEnums.SQLBIGVARCHAR: + case TdsEnums.SQLTEXT: + { + System.Text.Encoding encoding = md.baseTI.encoding; + + if (null == encoding) { + encoding = _defaultEncoding; + } + + if (null == encoding) { + ThrowUnsupportedCollationEncountered(stateObj); + } + + string strValue = encoding.GetString(unencryptedBytes, 0, length); + + // If this is a fixed length type, pad with spaces to get to the fixed length size. + if (tdsType == TdsEnums.SQLCHAR || tdsType == TdsEnums.SQLBIGCHAR) { + strValue = strValue.PadRight(md.baseTI.length); + } + + value.SetToString(strValue); + break; + } + + case TdsEnums.SQLNCHAR: + case TdsEnums.SQLNVARCHAR: + case TdsEnums.SQLNTEXT: + { + string strValue = System.Text.Encoding.Unicode.GetString(unencryptedBytes, 0, length); + + // If this is a fixed length type, pad with spaces to get to the fixed length size. + if (tdsType == TdsEnums.SQLNCHAR) { + strValue = strValue.PadRight(md.baseTI.length / ADP.CharSize); + } + + value.SetToString(strValue); + break; + } + + case TdsEnums.SQLDATE: + Debug.Assert(length == 3, "invalid length for date type!"); + value.SetToDate(unencryptedBytes); + break; + + case TdsEnums.SQLTIME: + // We normalize to maximum precision to allow conversion across different precisions. + Debug.Assert(length == 5, "invalid length for time type!"); + value.SetToTime(unencryptedBytes, length, TdsEnums.MAX_TIME_SCALE, denormalizedScale); + break; + + case TdsEnums.SQLDATETIME2: + // We normalize to maximum precision to allow conversion across different precisions. + Debug.Assert(length == 8, "invalid length for datetime2 type!"); + value.SetToDateTime2(unencryptedBytes, length, TdsEnums.MAX_TIME_SCALE, denormalizedScale); + break; + + case TdsEnums.SQLDATETIMEOFFSET: + // We normalize to maximum precision to allow conversion across different precisions. + Debug.Assert(length == 10, "invalid length for datetimeoffset type!"); + value.SetToDateTimeOffset(unencryptedBytes, length, TdsEnums.MAX_TIME_SCALE, denormalizedScale); + break; + + default: + MetaType metaType = md.baseTI.metaType; + + // If we don't have a metatype already, construct one to get the proper type name. + if (metaType == null) { + metaType = MetaType.GetSqlDataType(tdsType, userType:0, length:length); + } + + throw SQL.UnsupportedDatatypeEncryption(metaType.TypeName); + } + + return true; + } + + internal bool TryReadSqlValue(SqlBuffer value, + SqlMetaDataPriv md, + int length, + TdsParserStateObject stateObj, + SqlCommandColumnEncryptionSetting columnEncryptionOverride, + string columnName) { bool isPlp = md.metaType.IsPlp; byte tdsType = md.tdsType; @@ -4321,6 +5247,9 @@ namespace System.Data.SqlClient { // We must read the column value completely, no matter what length is passed in length = Int32.MaxValue; } + + //DEVNOTE: When modifying the following routines (for deserialization) please pay attention to + // deserialization code in DecryptWithKey () method and modify it accordingly. switch (tdsType) { case TdsEnums.SQLDECIMALN: case TdsEnums.SQLNUMERICN: @@ -4354,8 +5283,27 @@ namespace System.Data.SqlClient { } } - value.SqlBinary = new SqlBinary(b, true); // doesn't copy the byte array + if (md.isEncrypted + && ((columnEncryptionOverride == SqlCommandColumnEncryptionSetting.Enabled + || columnEncryptionOverride == SqlCommandColumnEncryptionSetting.ResultSetOnly) + || (columnEncryptionOverride == SqlCommandColumnEncryptionSetting.UseConnectionSetting + && _connHandler != null && _connHandler.ConnectionOptions != null + && _connHandler.ConnectionOptions.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled))) { + try { + // CipherInfo is present, decrypt and read + byte[] unencryptedBytes = SqlSecurityUtility.DecryptWithKey(b, md.cipherMD, _connHandler.ConnectionOptions.DataSource); + if (unencryptedBytes != null) { + DeserializeUnencryptedValue(value, unencryptedBytes, md, stateObj, md.NormalizationRuleVersion); + } + } + catch (Exception e) { + throw SQL.ColumnDecryptionFailed(columnName, null, e); + } + } + else { + value.SqlBinary = new SqlBinary(b, true); // doesn't copy the byte array + } break; case TdsEnums.SQLCHAR: @@ -4417,17 +5365,17 @@ namespace System.Data.SqlClient { case TdsEnums.SQLTIME: Debug.Assert(3 <= length && length <= 5, "invalid length for time type!"); - value.SetToTime(datetimeBuffer, length, scale); + value.SetToTime(datetimeBuffer, length, scale, scale); break; case TdsEnums.SQLDATETIME2: Debug.Assert(6 <= length && length <= 8, "invalid length for datetime2 type!"); - value.SetToDateTime2(datetimeBuffer, length, scale); + value.SetToDateTime2(datetimeBuffer, length, scale, scale); break; case TdsEnums.SQLDATETIMEOFFSET: Debug.Assert(8 <= length && length <= 10, "invalid length for datetimeoffset type!"); - value.SetToDateTimeOffset(datetimeBuffer, length, scale); + value.SetToDateTimeOffset(datetimeBuffer, length, scale, scale); break; default: @@ -5146,6 +6094,10 @@ namespace System.Data.SqlClient { WriteDate(value, stateObj); } + private byte[] SerializeSqlMoney(SqlMoney value, int length, TdsParserStateObject stateObj) { + return SerializeCurrency(value.Value, length, stateObj); + } + private void WriteSqlMoney(SqlMoney value, int length, TdsParserStateObject stateObj) { // int[] bits = Decimal.GetBits(value.Value); @@ -5173,6 +6125,45 @@ namespace System.Data.SqlClient { } } + private byte[] SerializeCurrency(Decimal value, int length, TdsParserStateObject stateObj) { + SqlMoney m = new SqlMoney(value); + int[] bits = Decimal.GetBits(m.Value); + + // this decimal should be scaled by 10000 (regardless of what the incoming decimal was scaled by) + bool isNeg = (0 != (bits[3] & unchecked((int)0x80000000))); + long l = ((long)(uint)bits[1]) << 0x20 | (uint)bits[0]; + + if (isNeg) + l = -l; + + if (length == 4) { + // validate the value can be represented as a small money + if (value < TdsEnums.SQL_SMALL_MONEY_MIN || value > TdsEnums.SQL_SMALL_MONEY_MAX) { + throw SQL.MoneyOverflow(value.ToString(CultureInfo.InvariantCulture)); + } + + // We normalize to allow conversion across data types. SMALLMONEY is serialized into a MONEY. + length = 8; + } + + Debug.Assert (8 == length, "invalid length in SerializeCurrency"); + if (null == stateObj._bLongBytes) { + stateObj._bLongBytes = new byte[8]; + } + + byte[] bytes = stateObj._bLongBytes; + int current = 0; + + byte[] bytesPart = SerializeInt((int)(l >> 0x20), stateObj); + Buffer.BlockCopy(bytesPart, 0, bytes, current, 4); + current += 4; + + bytesPart = SerializeInt((int)l, stateObj); + Buffer.BlockCopy(bytesPart, 0, bytes, current, 4); + + return bytes; + } + private void WriteCurrency(Decimal value, int length, TdsParserStateObject stateObj) { SqlMoney m = new SqlMoney(value); int[] bits = Decimal.GetBits(m.Value); @@ -5198,11 +6189,30 @@ namespace System.Data.SqlClient { } } + private byte[] SerializeDate(DateTime value) { + long days = value.Subtract(DateTime.MinValue).Days; + return SerializePartialLong(days, 3); + } + private void WriteDate(DateTime value, TdsParserStateObject stateObj) { long days = value.Subtract(DateTime.MinValue).Days; WritePartialLong(days, 3, stateObj); } + private byte[] SerializeTime(TimeSpan value, byte scale, int length) { + if (0 > value.Ticks || value.Ticks >= TimeSpan.TicksPerDay) { + throw SQL.TimeOverflow(value.ToString()); + } + + long time = value.Ticks / TdsEnums.TICKS_FROM_SCALE[scale]; + + // We normalize to maximum precision to allow conversion across different precisions. + time = time * TdsEnums.TICKS_FROM_SCALE[scale]; + length = TdsEnums.MAX_TIME_LENGTH; + + return SerializePartialLong(time, length); + } + private void WriteTime(TimeSpan value, byte scale, int length, TdsParserStateObject stateObj) { if (0 > value.Ticks || value.Ticks >= TimeSpan.TicksPerDay) { throw SQL.TimeOverflow(value.ToString()); @@ -5211,12 +6221,54 @@ namespace System.Data.SqlClient { WritePartialLong(time, length, stateObj); } + private byte[] SerializeDateTime2(DateTime value, byte scale, int length) { + long time = value.TimeOfDay.Ticks / TdsEnums.TICKS_FROM_SCALE[scale]; // DateTime.TimeOfDay always returns a valid TimeSpan for Time + + // We normalize to maximum precision to allow conversion across different precisions. + time = time * TdsEnums.TICKS_FROM_SCALE[scale]; + length = TdsEnums.MAX_DATETIME2_LENGTH; + + byte[] bytes = new byte[length]; + byte[] bytesPart; + int current = 0; + + bytesPart = SerializePartialLong(time, length - 3); + Buffer.BlockCopy(bytesPart, 0, bytes, current, length - 3); + current += length - 3; + + bytesPart = SerializeDate(value); + Buffer.BlockCopy(bytesPart, 0, bytes, current, 3); + + return bytes; + } + private void WriteDateTime2(DateTime value, byte scale, int length, TdsParserStateObject stateObj) { long time = value.TimeOfDay.Ticks / TdsEnums.TICKS_FROM_SCALE[scale]; // DateTime.TimeOfDay always returns a valid TimeSpan for Time WritePartialLong(time, length - 3, stateObj); WriteDate(value, stateObj); } + private byte[] SerializeDateTimeOffset(DateTimeOffset value, byte scale, int length) { + byte[] bytesPart; + int current = 0; + + bytesPart = SerializeDateTime2(value.UtcDateTime, scale, length - 2); + + // We need to allocate the array after we have received the length of the serialized value + // since it might be higher due to normalization. + length = bytesPart.Length + 2; + byte[] bytes = new byte[length]; + + Buffer.BlockCopy(bytesPart, 0, bytes, current, length - 2); + current += length - 2; + + Int16 offset = (Int16)value.Offset.TotalMinutes; + bytes[current++] = (byte)(offset & 0xff); + bytes[current++] = (byte)((offset >> 8) & 0xff); + + return bytes; + } + private void WriteDateTimeOffset(DateTimeOffset value, byte scale, int length, TdsParserStateObject stateObj) { WriteDateTime2(value.UtcDateTime, scale, length - 2, stateObj); Int16 offset = (Int16)value.Offset.TotalMinutes; @@ -5292,6 +6344,35 @@ namespace System.Data.SqlClient { return value; } + internal byte[] SerializeSqlDecimal(SqlDecimal d, TdsParserStateObject stateObj) { + if (null == stateObj._bDecimalBytes) { + stateObj._bDecimalBytes = new byte[17]; + } + + byte[] bytes = stateObj._bDecimalBytes; + int current = 0; + + // sign + if (d.IsPositive) + bytes[current++] = 1; + else + bytes[current++] = 0; + + byte[] bytesPart = SerializeUnsignedInt(d.m_data1, stateObj); + Buffer.BlockCopy(bytesPart, 0, bytes, current, 4); + current += 4; + bytesPart = SerializeUnsignedInt(d.m_data2, stateObj); + Buffer.BlockCopy(bytesPart, 0, bytes, current, 4); + current += 4; + bytesPart = SerializeUnsignedInt(d.m_data3, stateObj); + Buffer.BlockCopy(bytesPart, 0, bytes, current, 4); + current += 4; + bytesPart = SerializeUnsignedInt(d.m_data4, stateObj); + Buffer.BlockCopy(bytesPart, 0, bytes, current, 4); + + return bytes; + } + internal void WriteSqlDecimal(SqlDecimal d, TdsParserStateObject stateObj) { // sign if (d.IsPositive) @@ -5305,6 +6386,55 @@ namespace System.Data.SqlClient { WriteUnsignedInt(d.m_data4, stateObj); } + private byte[] SerializeDecimal(decimal value, TdsParserStateObject stateObj) { + int[] decimalBits = Decimal.GetBits(value); + if (null == stateObj._bDecimalBytes) { + stateObj._bDecimalBytes = new byte[17]; + } + + byte[] bytes = stateObj._bDecimalBytes; + int current = 0; + + /* + Returns a binary representation of a Decimal. The return value is an integer + array with four elements. Elements 0, 1, and 2 contain the low, middle, and + high 32 bits of the 96-bit integer part of the Decimal. Element 3 contains + the scale factor and sign of the Decimal: bits 0-15 (the lower word) are + unused; bits 16-23 contain a value between 0 and 28, indicating the power of + 10 to divide the 96-bit integer part by to produce the Decimal value; bits 24- + 30 are unused; and finally bit 31 indicates the sign of the Decimal value, 0 + meaning positive and 1 meaning negative. + + SQLDECIMAL/SQLNUMERIC has a byte stream of: + struct { + BYTE sign; // 1 if positive, 0 if negative + BYTE data[]; + } + + For TDS 7.0 and above, there are always 17 bytes of data + */ + + // write the sign (note that COM and SQL are opposite) + if (0x80000000 == (decimalBits[3] & 0x80000000)) + bytes[current++] = 0; + else + bytes[current++] = 1; + + byte[] bytesPart = SerializeInt(decimalBits[0], stateObj); + Buffer.BlockCopy(bytesPart, 0, bytes, current, 4); + current += 4; + bytesPart = SerializeInt(decimalBits[1], stateObj); + Buffer.BlockCopy(bytesPart, 0, bytes, current, 4); + current += 4; + bytesPart = SerializeInt(decimalBits[2], stateObj); + Buffer.BlockCopy(bytesPart, 0, bytes, current, 4); + current += 4; + bytesPart = SerializeInt(0, stateObj); + Buffer.BlockCopy(bytesPart, 0, bytes, current, 4); + + return bytes; + } + private void WriteDecimal(decimal value, TdsParserStateObject stateObj) { stateObj._decimalBits = Decimal.GetBits(value); Debug.Assert(null != stateObj._decimalBits, "decimalBits should be filled in at TdsExecuteRPC time"); @@ -5364,6 +6494,14 @@ namespace System.Data.SqlClient { return WriteString(s, s.Length, 0, stateObj, canAccumulate); } + internal byte[] SerializeCharArray(char[] carr, int length, int offset) { + int cBytes = ADP.CharSize * length; + byte[] bytes = new byte[cBytes]; + + CopyCharsToBytes(carr, offset, bytes, 0, length); + return bytes; + } + internal Task WriteCharArray(char[] carr, int length, int offset, TdsParserStateObject stateObj, bool canAccumulate = true) { int cBytes = ADP.CharSize * length; @@ -5383,6 +6521,14 @@ namespace System.Data.SqlClient { } } + internal byte[] SerializeString(string s, int length, int offset) { + int cBytes = ADP.CharSize * length; + byte[] bytes = new byte[cBytes]; + + CopyStringToBytes(s, offset, bytes, 0, length); + return bytes; + } + internal Task WriteString(string s, int length, int offset, TdsParserStateObject stateObj, bool canAccumulate = true) { int cBytes = ADP.CharSize * length; @@ -5467,6 +6613,23 @@ namespace System.Data.SqlClient { return WriteEncodingChar(s, s.Length, 0, encoding, stateObj, canAccumulate); } + private byte[] SerializeEncodingChar(string s, int numChars, int offset, Encoding encoding) { + char[] charData; + byte[] byteData = null; + + // if hitting 7.0 server, encoding will be null in metadata for columns or return values since + // 7.0 has no support for multiple code pages in data - single code page support only + if (encoding == null) + encoding = _defaultEncoding; + + charData = s.ToCharArray(offset, numChars); + + byteData = new byte[encoding.GetByteCount(charData, 0, charData.Length)]; + encoding.GetBytes(charData, 0, charData.Length, byteData, 0); + + return byteData; + } + private Task WriteEncodingChar(string s, int numChars, int offset, Encoding encoding, TdsParserStateObject stateObj, bool canAccumulate = true) { char[] charData; byte[] byteData; @@ -5555,6 +6718,7 @@ namespace System.Data.SqlClient { tokenLength = -1; return true; case TdsEnums.SQLSESSIONSTATE: + case TdsEnums.SQLFEDAUTHINFO: return stateObj.TryReadInt32(out tokenLength); } @@ -5751,12 +6915,116 @@ namespace System.Data.SqlClient { return len; } - internal void TdsLogin(SqlLogin rec, TdsEnums.FeatureExtension requestedFeatures, SessionData recoverySessionData) { + internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionData fedAuthFeatureData, + bool write /* if false just calculates the length */) { + Debug.Assert(fedAuthFeatureData.libraryType == TdsEnums.FedAuthLibrary.ADAL || fedAuthFeatureData.libraryType == TdsEnums.FedAuthLibrary.SecurityToken, + "only fed auth library type ADAL and Security Token are supported in writing feature request"); + + int dataLen = 0; + int totalLen = 0; + + // set dataLen and totalLen + switch (fedAuthFeatureData.libraryType) { + case TdsEnums.FedAuthLibrary.ADAL: + dataLen = 2; // length of feature data = 1 byte for library and echo + 1 byte for workflow + break; + case TdsEnums.FedAuthLibrary.SecurityToken: + Debug.Assert(fedAuthFeatureData.accessToken != null, "AccessToken should not be null."); + dataLen = 1 + sizeof(int) + fedAuthFeatureData.accessToken.Length; // length of feature data = 1 byte for library and echo, security token length and sizeof(int) for token lengh itself + break; + default: + Debug.Assert(false, "Unrecognized library type for fedauth feature extension request"); + break; + } + + totalLen = dataLen + 5; // length of feature id (1 byte), data length field (4 bytes), and feature data (dataLen) + + // write feature id + if (write) { + _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_FEDAUTH); + + // set options + byte options = 0x00; + + // set upper 7 bits of options to indicate fed auth library type + switch (fedAuthFeatureData.libraryType) { + case TdsEnums.FedAuthLibrary.ADAL: + Debug.Assert(_connHandler._federatedAuthenticationInfoRequested == true, "_federatedAuthenticationInfoRequested field should be true"); + options |= TdsEnums.FEDAUTHLIB_ADAL << 1; + break; + case TdsEnums.FedAuthLibrary.SecurityToken: + Debug.Assert(_connHandler._federatedAuthenticationRequested == true, "_federatedAuthenticationRequested field should be true"); + options |= TdsEnums.FEDAUTHLIB_SECURITYTOKEN << 1; + break; + default: + Debug.Assert(false, "Unrecognized FedAuthLibrary type for feature extension request"); + break; + } + + options |= (byte)(fedAuthFeatureData.fedAuthRequiredPreLoginResponse == true ? 0x01 : 0x00); + + // write dataLen and options + WriteInt(dataLen, _physicalStateObj); + _physicalStateObj.WriteByte(options); + + // write workflow for FedAuthLibrary.ADAL + // write accessToken for FedAuthLibrary.SecurityToken + switch (fedAuthFeatureData.libraryType) { + case TdsEnums.FedAuthLibrary.ADAL: + byte workflow = 0x00; + switch (fedAuthFeatureData.authentication) { + case SqlAuthenticationMethod.ActiveDirectoryPassword: + workflow = TdsEnums.ADALWORKFLOW_ACTIVEDIRECTORYPASSWORD; + break; + case SqlAuthenticationMethod.ActiveDirectoryIntegrated: + workflow = TdsEnums.ADALWORKFLOW_ACTIVEDIRECTORYINTEGRATED; + break; + default: + Debug.Assert(false, "Unrecognized Authentication type for fedauth ADAL request"); + break; + } + + _physicalStateObj.WriteByte(workflow); + break; + case TdsEnums.FedAuthLibrary.SecurityToken: + WriteInt(fedAuthFeatureData.accessToken.Length, _physicalStateObj); + _physicalStateObj.WriteByteArray(fedAuthFeatureData.accessToken, fedAuthFeatureData.accessToken.Length, 0); + break; + default: + Debug.Assert(false, "Unrecognized FedAuthLibrary type for feature extension request"); + break; + } + } + return totalLen; + } + + internal int WriteTceFeatureRequest (bool write /* if false just calculates the length */) { + int len = 6; // (1byte = featureID, 4bytes = featureData length, 1 bytes = Version + + if (write) { + // Write Feature ID, legth of the version# field and TCE Version# + _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_TCE); + WriteInt (1, _physicalStateObj); + _physicalStateObj.WriteByte(TdsEnums.MAX_SUPPORTED_TCE_VERSION); + } + + return len; // size of data written + } + + internal void TdsLogin(SqlLogin rec, + TdsEnums.FeatureExtension requestedFeatures, + SessionData recoverySessionData, + FederatedAuthenticationFeatureExtensionData? fedAuthFeatureExtensionData) { _physicalStateObj.SetTimeoutSeconds(rec.timeout); - Debug.Assert(recoverySessionData == null || (requestedFeatures | TdsEnums.FeatureExtension.SessionRecovery) != 0, "Recovery session data without session recovery feature request"); + Debug.Assert(recoverySessionData == null || (requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) != 0, "Recovery session data without session recovery feature request"); Debug.Assert(TdsEnums.MAXLEN_HOSTNAME>=rec.hostName.Length, "_workstationId.Length exceeds the max length for this value"); + Debug.Assert(!(rec.useSSPI && _connHandler._fedAuthRequired), "Cannot use SSPI when server has responded 0x01 for FedAuthRequired PreLogin Option."); + Debug.Assert(!rec.useSSPI || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) == 0, "Cannot use both SSPI and FedAuth"); + Debug.Assert(fedAuthFeatureExtensionData == null || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) != 0, "fedAuthFeatureExtensionData provided without fed auth feature request"); + Debug.Assert(fedAuthFeatureExtensionData != null || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) == 0, "Fed Auth feature requested without specifying fedAuthFeatureExtensionData."); + Debug.Assert(rec.userName == null || (rec.userName != null && TdsEnums.MAXLEN_USERNAME >= rec.userName.Length), "_userID.Length exceeds the max length for this value"); Debug.Assert(rec.credential == null || (rec.credential != null && TdsEnums.MAXLEN_USERNAME >= rec.credential.UserId.Length), "_credential.UserId.Length exceeds the max length for this value"); @@ -5828,8 +7096,8 @@ namespace System.Data.SqlClient { byte[] outSSPIBuff = null; UInt32 outSSPILength = 0; - // only add lengths of password and username if not using SSPI - if (!rec.useSSPI) { + // only add lengths of password and username if not using SSPI or requesting federated authentication info + if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) { checked { length += (userName.Length * 2) + encryptedPasswordLengthInBytes + encryptedChangePasswordLengthInBytes; @@ -5861,10 +7129,19 @@ namespace System.Data.SqlClient { int feOffset = length; if (useFeatureExt) { - if ((requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) !=0) { - length += WriteSessionRecoveryFeatureRequest(recoverySessionData, false); - }; - length++; // for terminator + checked { + if ((requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) != 0) { + length += WriteSessionRecoveryFeatureRequest(recoverySessionData, false); + }; + if ((requestedFeatures & TdsEnums.FeatureExtension.FedAuth) != 0) { + Debug.Assert(fedAuthFeatureExtensionData != null, "fedAuthFeatureExtensionData should not null."); + length += WriteFedAuthFeatureRequest(fedAuthFeatureExtensionData.Value, write:false); + } + if ((requestedFeatures & TdsEnums.FeatureExtension.Tce) != 0) { + length += WriteTceFeatureRequest (false); + } + length++; // for terminator + } } try { @@ -5936,6 +7213,7 @@ namespace System.Data.SqlClient { if (rec.userInstance) { log7Flags |= 1 << 26; } + if (useFeatureExt) { log7Flags |= 1 << 28; } @@ -5958,9 +7236,9 @@ namespace System.Data.SqlClient { WriteShort(rec.hostName.Length, _physicalStateObj); offset += rec.hostName.Length * 2; - // Only send user/password over if not fSSPI... If both user/password and SSPI are in login + // Only send user/password over if not fSSPI or fed auth ADAL... If both user/password and SSPI are in login // rec, only SSPI is used. Confirmed same bahavior as in luxor. - if (rec.useSSPI == false) { + if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) { WriteShort(offset, _physicalStateObj); // userName offset WriteShort(userName.Length, _physicalStateObj); offset += userName.Length * 2; @@ -5992,7 +7270,7 @@ namespace System.Data.SqlClient { offset += 4; } else { - WriteShort(0, _physicalStateObj); // ununsed (was remote password ?) + WriteShort(0, _physicalStateObj); // unused (was remote password ?) } WriteShort(offset, _physicalStateObj); // client interface name offset @@ -6035,9 +7313,9 @@ namespace System.Data.SqlClient { // write variable length portion WriteString(rec.hostName, _physicalStateObj); - // if we are using SSPI, do not send over username/password, since we will use SSPI instead + // if we are using SSPI or fed auth ADAL, do not send over username/password, since we will use SSPI instead // same behavior as Luxor - if (!rec.useSSPI) { + if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) { WriteString(userName, _physicalStateObj); // Cache offset in packet for tracing. @@ -6069,7 +7347,7 @@ namespace System.Data.SqlClient { _physicalStateObj.WriteByteArray(outSSPIBuff, (int)outSSPILength, 0); WriteString(rec.attachDBFilename, _physicalStateObj); - if (!rec.useSSPI) { + if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) { // Cache offset in packet for tracing. _physicalStateObj._traceChangePasswordOffset = _physicalStateObj._outBytesUsed; _physicalStateObj._traceChangePasswordLength = encryptedChangePasswordLengthInBytes; @@ -6080,13 +7358,21 @@ namespace System.Data.SqlClient { _physicalStateObj.WriteByteArray(encryptedChangePassword, encryptedChangePasswordLengthInBytes, 0); } } + if (useFeatureExt) { if ((requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) != 0) { - length += WriteSessionRecoveryFeatureRequest(recoverySessionData, true); + WriteSessionRecoveryFeatureRequest(recoverySessionData, true); + }; + if ((requestedFeatures & TdsEnums.FeatureExtension.FedAuth) != 0) { + Bid.Trace("<sc.TdsParser.TdsLogin|SEC> Sending federated authentication feature request\n"); + Debug.Assert(fedAuthFeatureExtensionData != null, "fedAuthFeatureExtensionData should not null."); + WriteFedAuthFeatureRequest(fedAuthFeatureExtensionData.Value, write: true); + }; + if ((requestedFeatures & TdsEnums.FeatureExtension.Tce) != 0) { + WriteTceFeatureRequest (true); }; _physicalStateObj.WriteByte(0xFF); // terminator } - } // try catch (Exception e) { // @@ -6105,6 +7391,38 @@ namespace System.Data.SqlClient { _physicalStateObj._messageStatus = 0; }// tdsLogin + /// <summary> + /// Send the access token to the server. + /// </summary> + /// <param name="fedAuthToken">Type encapuslating a Federated Authentication access token.</param> + internal void SendFedAuthToken(SqlFedAuthToken fedAuthToken) { + Debug.Assert(fedAuthToken != null, "fedAuthToken cannot be null"); + Debug.Assert(fedAuthToken.accessToken != null, "fedAuthToken.accessToken cannot be null"); + + + Bid.Trace("<sc.TdsParser.SendFedAuthToken|SEC> Sending federated authentication token\n"); + + _physicalStateObj._outputMessageType = TdsEnums.MT_FEDAUTH; + + byte[] accessToken = fedAuthToken.accessToken; + + // Send total length (length of token plus 4 bytes for the token length field) + // If we were sending a nonce, this would include that length as well + WriteUnsignedInt((uint)accessToken.Length + sizeof(uint), _physicalStateObj); + + // Send length of token + WriteUnsignedInt((uint)accessToken.Length, _physicalStateObj); + + // Send federated authentication access token + _physicalStateObj.WriteByteArray(accessToken, accessToken.Length, 0); + + _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH); + _physicalStateObj._pendingData = true; + _physicalStateObj._messageStatus = 0; + + _connHandler._federatedAuthenticationRequested = true; + } + private void SSPIData(byte[] receivedBuff, UInt32 receivedLength, byte[] sendBuff, ref UInt32 sendLength) { SNISSPIData(receivedBuff, receivedLength, sendBuff, ref sendLength); } @@ -6184,6 +7502,39 @@ namespace System.Data.SqlClient { } } + private void LoadADALLibrary() { + // Outer check so we don't acquire lock once once it's loaded. + if (!s_fADALLoaded) { + lock (s_tdsParserLock) { + // re-check inside lock + if (!s_fADALLoaded) { + int result = ADALNativeWrapper.ADALInitialize(); + + if (0 == result) { + s_fADALLoaded = true; + } + else { + s_fADALLoaded = false; + + SqlAuthenticationMethod authentication = SqlAuthenticationMethod.NotSpecified; + + if (_connHandler.ConnectionOptions != null) + { + authentication = _connHandler.ConnectionOptions.Authentication; + + // Only the below connection string options should have ended up calling this function. + Debug.Assert(authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated || authentication == SqlAuthenticationMethod.ActiveDirectoryPassword); + } + + _physicalStateObj.AddError(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, _server, Res.GetString(Res.SQL_ADALInitializeError, authentication.ToString("G"), result.ToString("X")), TdsEnums.INIT_ADAL_PACKAGE, 0)); + + ThrowExceptionAndWarning(_physicalStateObj); + } + } + } + } + } + internal byte[] GetDTCAddress(int timeout, TdsParserStateObject stateObj) { // If this fails, the server will return a server error - Sameet Agarwal confirmed. // Success: DTCAddress returned. Failure: SqlError returned. @@ -6458,8 +7809,8 @@ namespace System.Data.SqlClient { bool originalThreadHasParserLock = _connHandler.ThreadHasParserLockForClose; try { - // Dev11 Bug 385286 : ExecuteNonQueryAsync hangs when trying to write a parameter which generates ArgumentException and while handling that exception the server disconnects the connection - // Need to set this to true such that if we have an error sending\processing the attention, we won't deadlock ourselves + // Dev11 + _connHandler.ThreadHasParserLockForClose = true; // If _outputPacketNumber prior to ResetBuffer was not equal to 1, a packet was already @@ -6565,7 +7916,7 @@ namespace System.Data.SqlClient { }, TaskScheduler.Default); } - // Finished [....] + // Finished sync return null; } catch (Exception e) { @@ -6586,11 +7937,12 @@ namespace System.Data.SqlClient { } } - internal Task TdsExecuteRPC(_SqlRPC[] rpcArray, int timeout, bool inSchema, SqlNotificationRequest notificationRequest, TdsParserStateObject stateObj, bool isCommandProc, bool sync = true, + internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, bool inSchema, SqlNotificationRequest notificationRequest, TdsParserStateObject stateObj, bool isCommandProc, bool sync = true, TaskCompletionSource<object> completion = null, int startRpc = 0, int startParam = 0) { bool firstCall = (completion == null); bool releaseConnectionLock = false; + Debug.Assert(cmd != null, @"cmd cannot be null inside TdsExecuteRPC"); Debug.Assert(!firstCall || startRpc == 0, "startRpc is not 0 on first call"); Debug.Assert(!firstCall || startParam == 0, "startParam is not 0 on first call"); Debug.Assert(!firstCall || !_connHandler.ThreadHasParserLockForClose, "Thread should not already have connection lock"); @@ -6674,6 +8026,20 @@ namespace System.Data.SqlClient { if (param == null) break; // End of parameters for this execute + // Throw an exception if ForceColumnEncryption is set on a parameter and the ColumnEncryption is not enabled on SqlConnection or SqlCommand + if (param.ForceColumnEncryption && + !(cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled || + (cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.UseConnectionSetting && cmd.Connection.IsColumnEncryptionSettingEnabled))) { + throw SQL.ParamInvalidForceColumnEncryptionSetting(param.ParameterName, rpcext.GetCommandTextOrRpcName()); + } + + // Check if the applications wants to force column encryption to avoid sending sensitive data to server + if (param.ForceColumnEncryption && param.CipherMetadata == null + && (param.Direction == ParameterDirection.Input || param.Direction == ParameterDirection.InputOutput)) { + // Application wants a parameter to be encrypted before sending it to server, however server doesnt think this parameter needs encryption. + throw SQL.ParamUnExpectedEncryptionMetadata(param.ParameterName, rpcext.GetCommandTextOrRpcName()); + } + // Validate parameters are not variable length without size and with null value. MDAC 66522 param.Validate(i, isCommandProc); @@ -6714,6 +8080,120 @@ namespace System.Data.SqlClient { // Write parameter status stateObj.WriteByte(rpcext.paramoptions[i]); + // MaxLen field is only written out for non-fixed length data types + // use the greater of the two sizes for maxLen + int actualSize; + int size = mt.IsSizeInCharacters ? param.GetParameterSize() * 2 : param.GetParameterSize(); + + //for UDTs, we calculate the length later when we get the bytes. This is a really expensive operation + if (mt.TDSType != TdsEnums.SQLUDT) + // getting the actualSize is expensive, cache here and use below + actualSize = param.GetActualSize(); + else + actualSize = 0; //get this later + + byte precision = 0; + byte scale = 0; + + // scale and precision are only relevant for numeric and decimal types + // adjust the actual value scale and precision to match the user specified + if (mt.SqlDbType == SqlDbType.Decimal) { + precision = param.GetActualPrecision(); + scale = param.GetActualScale(); + + if (precision > TdsEnums.MAX_NUMERIC_PRECISION) { + throw SQL.PrecisionValueOutOfRange(precision); + } + + // + if (!isNull) { + if (isSqlVal) { + value = AdjustSqlDecimalScale((SqlDecimal)value, scale); + + // If Precision is specified, verify value precision vs param precision + if (precision != 0) { + if (precision < ((SqlDecimal)value).Precision) { + throw ADP.ParameterValueOutOfRange((SqlDecimal)value); + } + } + } + else { + value = AdjustDecimalScale((Decimal)value, scale); + + SqlDecimal sqlValue = new SqlDecimal((Decimal)value); + + // If Precision is specified, verify value precision vs param precision + if (precision != 0) { + if (precision < sqlValue.Precision) { + throw ADP.ParameterValueOutOfRange((Decimal)value); + } + } + } + } + } + + bool isParameterEncrypted = 0 != (rpcext.paramoptions[i] & TdsEnums.RPC_PARAM_ENCRYPTED); + + // Additional information we need to send over wire to the server when writing encrypted parameters. + SqlColumnEncryptionInputParameterInfo encryptedParameterInfoToWrite = null; + + // If the parameter is encrypted, we need to encrypt the value. + if (isParameterEncrypted) { + Debug.Assert(mt.TDSType != TdsEnums.SQLVARIANT && + mt.TDSType != TdsEnums.SQLUDT && + mt.TDSType != TdsEnums.SQLXMLTYPE && + mt.TDSType != TdsEnums.SQLIMAGE && + mt.TDSType != TdsEnums.SQLTEXT && + mt.TDSType != TdsEnums.SQLNTEXT, "Type unsupported for encryption"); + + byte[] serializedValue = null; + byte[] encryptedValue = null; + + if (!isNull) { + try { + if (isSqlVal) { + serializedValue = SerializeUnencryptedSqlValue(value, mt, actualSize, param.Offset, param.NormalizationRuleVersion, stateObj); + } + else { + // for codePageEncoded types, WriteValue simply expects the number of characters + // For plp types, we also need the encoded byte size + serializedValue = SerializeUnencryptedValue(value, mt, param.GetActualScale(), actualSize, param.Offset, isDataFeed, param.NormalizationRuleVersion, stateObj); + } + + Debug.Assert(serializedValue != null, "serializedValue should not be null in TdsExecuteRPC."); + encryptedValue = SqlSecurityUtility.EncryptWithKey(serializedValue, param.CipherMetadata, _connHandler.ConnectionOptions.DataSource); + } + catch (Exception e) { + throw SQL.ParamEncryptionFailed(param.ParameterName, null, e); + } + + Debug.Assert(encryptedValue != null && encryptedValue.Length > 0, + "encryptedValue should not be null or empty in TdsExecuteRPC."); + } + else { + encryptedValue = null; + } + + // Change the datatype to varbinary(max). + // Since we don't know the size of the encrypted parameter on the server side, always set to (max). + // + mt = MetaType.MetaMaxVarBinary; + size = -1; + actualSize = (encryptedValue == null) ? 0 : encryptedValue.Length; + + encryptedParameterInfoToWrite = new SqlColumnEncryptionInputParameterInfo(param.GetMetadataForTypeInfo(), + param.CipherMetadata); + + // Set the value to the encrypted value and mark isSqlVal as false for VARBINARY encrypted value. + value = encryptedValue; + isSqlVal = false; + } + + Debug.Assert(isParameterEncrypted == (encryptedParameterInfoToWrite != null), + "encryptedParameterInfoToWrite can be not null if and only if isParameterEncrypted is true."); + + Debug.Assert(!isSqlVal || !isParameterEncrypted, "isParameterEncrypted can be true only if isSqlVal is false."); + // // fixup the types by using the NullableType property of the MetaType class // @@ -6732,18 +8212,6 @@ namespace System.Data.SqlClient { continue; } - // MaxLen field is only written out for non-fixed length data types - // use the greater of the two sizes for maxLen - int actualSize; - int size = mt.IsSizeInCharacters ? param.GetParameterSize() * 2 : param.GetParameterSize(); - - //for UDTs, we calculate the length later when we get the bytes. This is a really expensive operation - if (mt.TDSType != TdsEnums.SQLUDT) - // getting the actualSize is expensive, cache here and use below - actualSize = param.GetActualSize(); - else - actualSize = 0; //get this later - int codePageByteSize = 0; int maxsize = 0; @@ -6868,39 +8336,6 @@ namespace System.Data.SqlClient { // scale and precision are only relevant for numeric and decimal types if (mt.SqlDbType == SqlDbType.Decimal) { - byte precision = param.GetActualPrecision(); - byte scale = param.GetActualScale(); - - if (precision > TdsEnums.MAX_NUMERIC_PRECISION) { - throw SQL.PrecisionValueOutOfRange(precision); - } - - // bug 49512, make sure the value matches the scale the user enters - if (!isNull) { - if (isSqlVal) { - value = AdjustSqlDecimalScale((SqlDecimal)value, scale); - - // If Precision is specified, verify value precision vs param precision - if (precision != 0) { - if (precision < ((SqlDecimal)value).Precision) { - throw ADP.ParameterValueOutOfRange((SqlDecimal)value); - } - } - } - else { - value = AdjustDecimalScale((Decimal)value, scale); - - SqlDecimal sqlValue = new SqlDecimal((Decimal)value); - - // If Precision is specified, verify value precision vs param precision - if (precision != 0) { - if (precision < sqlValue.Precision) { - throw ADP.ParameterValueOutOfRange((Decimal)value); - } - } - } - } - if (0 == precision) { if (_isShiloh) stateObj.WriteByte(TdsEnums.DEFAULT_NUMERIC_PRECISION); @@ -6978,10 +8413,15 @@ namespace System.Data.SqlClient { else { // for codePageEncoded types, WriteValue simply expects the number of characters // For plp types, we also need the encoded byte size - writeParamTask = WriteValue(value, mt, param.GetActualScale(), actualSize, codePageByteSize, param.Offset, stateObj, param.Size, isDataFeed); + writeParamTask = WriteValue(value, mt, isParameterEncrypted ? (byte)0 : param.GetActualScale(), actualSize, codePageByteSize, isParameterEncrypted ? 0 : param.Offset, stateObj, isParameterEncrypted ? 0 : param.Size, isDataFeed); } } + // Send encryption metadata for encrypted parameters. + if (isParameterEncrypted) { + writeParamTask = WriteEncryptionMetadata(writeParamTask, encryptedParameterInfoToWrite, stateObj); + } + if (!sync) { if (writeParamTask == null) { writeParamTask = stateObj.WaitForAccumulatedWrites(); @@ -6995,7 +8435,7 @@ namespace System.Data.SqlClient { } AsyncHelper.ContinueTask(writeParamTask, completion, - () => TdsExecuteRPC(rpcArray, timeout, inSchema, notificationRequest, stateObj, isCommandProc, sync, completion, + () => TdsExecuteRPC(cmd, rpcArray, timeout, inSchema, notificationRequest, stateObj, isCommandProc, sync, completion, startRpc: ii, startParam: i + 1), connectionToDoom: _connHandler, onFailure: exc => TdsExecuteRPC_OnFailure(exc, stateObj)); @@ -7301,7 +8741,7 @@ namespace System.Data.SqlClient { case SqlDbType.Char: stateObj.WriteByte(TdsEnums.SQLBIGCHAR); WriteUnsignedShort(checked((ushort)(metaData.MaxLength)), stateObj); - WriteUnsignedInt(_defaultCollation.info, stateObj); // TODO: Use metadata's collation?? + WriteUnsignedInt(_defaultCollation.info, stateObj); // stateObj.WriteByte(_defaultCollation.sortId); break; case SqlDbType.DateTime: @@ -7333,13 +8773,13 @@ namespace System.Data.SqlClient { case SqlDbType.NChar: stateObj.WriteByte(TdsEnums.SQLNCHAR); WriteUnsignedShort(checked((ushort)(metaData.MaxLength*2)), stateObj); - WriteUnsignedInt(_defaultCollation.info, stateObj); // TODO: Use metadata's collation?? + WriteUnsignedInt(_defaultCollation.info, stateObj); // stateObj.WriteByte(_defaultCollation.sortId); break; case SqlDbType.NText: stateObj.WriteByte(TdsEnums.SQLNVARCHAR); WriteUnsignedShort(unchecked((ushort)MSS.SmiMetaData.UnlimitedMaxLengthIndicator), stateObj); - WriteUnsignedInt(_defaultCollation.info, stateObj); // TODO: Use metadata's collation?? + WriteUnsignedInt(_defaultCollation.info, stateObj); // stateObj.WriteByte(_defaultCollation.sortId); break; case SqlDbType.NVarChar: @@ -7350,7 +8790,7 @@ namespace System.Data.SqlClient { else { WriteUnsignedShort(checked((ushort)(metaData.MaxLength*2)), stateObj); } - WriteUnsignedInt(_defaultCollation.info, stateObj); // TODO: Use metadata's collation?? + WriteUnsignedInt(_defaultCollation.info, stateObj); // stateObj.WriteByte(_defaultCollation.sortId); break; case SqlDbType.Real: @@ -7376,7 +8816,7 @@ namespace System.Data.SqlClient { case SqlDbType.Text: stateObj.WriteByte(TdsEnums.SQLBIGVARCHAR); WriteUnsignedShort(unchecked((ushort)MSS.SmiMetaData.UnlimitedMaxLengthIndicator), stateObj); - WriteUnsignedInt(_defaultCollation.info, stateObj); // TODO: Use metadata's collation?? + WriteUnsignedInt(_defaultCollation.info, stateObj); // stateObj.WriteByte(_defaultCollation.sortId); break; case SqlDbType.Timestamp: @@ -7394,7 +8834,7 @@ namespace System.Data.SqlClient { case SqlDbType.VarChar: stateObj.WriteByte(TdsEnums.SQLBIGVARCHAR); WriteUnsignedShort(unchecked((ushort)metaData.MaxLength), stateObj); - WriteUnsignedInt(_defaultCollation.info, stateObj); // TODO: Use metadata's collation?? + WriteUnsignedInt(_defaultCollation.info, stateObj); // stateObj.WriteByte(_defaultCollation.sortId); break; case SqlDbType.Variant: @@ -7580,6 +9020,146 @@ namespace System.Data.SqlClient { return stateObj.WritePacket(TdsEnums.HARDFLUSH); } + /// <summary> + /// Loads the column encryptions keys into cache. This will read the master key info, + /// decrypt the CEK and keep it ready for encryption. + /// </summary> + /// <returns></returns> + internal void LoadColumnEncryptionKeys (_SqlMetaDataSet metadataCollection, string serverName) { + if (_serverSupportsColumnEncryption && ShouldEncryptValuesForBulkCopy()) { + for (int col = 0; col < metadataCollection.Length; col++) { + if (null != metadataCollection[col]) { + _SqlMetaData md = metadataCollection[col]; + if (md.isEncrypted) { + SqlSecurityUtility.DecryptSymmetricKey(md.cipherMD, serverName); + } + } + } + } + } + + /// <summary> + /// Writes a single entry of CEK Table into TDS Stream (for bulk copy). + /// </summary> + /// <returns></returns> + internal void WriteEncryptionEntries (ref SqlTceCipherInfoTable cekTable, TdsParserStateObject stateObj) { + for (int i =0; i < cekTable.Size; i++) { + // Write Db ID + WriteInt(cekTable[i].DatabaseId, stateObj); + + // Write Key ID + WriteInt(cekTable[i].CekId, stateObj); + + // Write Key Version + WriteInt(cekTable[i].CekVersion, stateObj); + + // Write 8 bytes of key MD Version + Debug.Assert (8 == cekTable[i].CekMdVersion.Length); + stateObj.WriteByteArray (cekTable[i].CekMdVersion, 8, 0); + + // We don't really need to send the keys + stateObj.WriteByte(0x00); + } + } + + /// <summary> + /// Writes a CEK Table (as part of COLMETADATA token) for bulk copy. + /// </summary> + /// <returns></returns> + internal void WriteCekTable (_SqlMetaDataSet metadataCollection, TdsParserStateObject stateObj) { + if (!_serverSupportsColumnEncryption) { + return; + } + + // If no cek table is present, send a count of 0 for table size + // Note- Cek table (with 0 entries) will be present if TCE + // was enabled and server supports it! + // OR if encryption was disabled in connection options + if (!metadataCollection.cekTable.HasValue || + !ShouldEncryptValuesForBulkCopy()) { + WriteShort(0x00, stateObj); + return; + } + + SqlTceCipherInfoTable cekTable = metadataCollection.cekTable.Value; + ushort count = (ushort)cekTable.Size; + + WriteShort(count, stateObj); + + WriteEncryptionEntries(ref cekTable, stateObj); + } + + /// <summary> + /// Writes the UserType and TYPE_INFO values for CryptoMetadata (for bulk copy). + /// </summary> + /// <returns></returns> + internal void WriteTceUserTypeAndTypeInfo(SqlMetaDataPriv mdPriv, TdsParserStateObject stateObj) { + // Write the UserType (4 byte value) + WriteInt(0x0, stateObj); // + + Debug.Assert(SqlDbType.Xml != mdPriv.type); + Debug.Assert(SqlDbType.Udt != mdPriv.type); + + stateObj.WriteByte(mdPriv.tdsType); + + switch (mdPriv.type) { + case SqlDbType.Decimal: + WriteTokenLength(mdPriv.tdsType, mdPriv.length, stateObj); + stateObj.WriteByte(mdPriv.precision); + stateObj.WriteByte(mdPriv.scale); + break; + case SqlDbType.Date: + // Nothing more to write! + break; + case SqlDbType.Time: + case SqlDbType.DateTime2: + case SqlDbType.DateTimeOffset: + stateObj.WriteByte(mdPriv.scale); + break; + default: + WriteTokenLength(mdPriv.tdsType, mdPriv.length, stateObj); + if (mdPriv.metaType.IsCharType && _isShiloh) { + WriteUnsignedInt(mdPriv.collation.info, stateObj); + stateObj.WriteByte(mdPriv.collation.sortId); + } + break; + } + } + + /// <summary> + /// Writes the crypto metadata (as part of COLMETADATA token) for encrypted columns. + /// </summary> + /// <returns></returns> + internal void WriteCryptoMetadata(_SqlMetaData md, TdsParserStateObject stateObj) { + if (!_serverSupportsColumnEncryption || // TCE Feature supported + !md.isEncrypted || // Column is not encrypted + !ShouldEncryptValuesForBulkCopy()) { // TCE disabled on connection string + return; + } + + // Write the ordinal + WriteShort (md.cipherMD.CekTableOrdinal, stateObj); + + // Write UserType and TYPEINFO + WriteTceUserTypeAndTypeInfo(md.baseTI, stateObj); + + // Write Encryption Algo + stateObj.WriteByte(md.cipherMD.CipherAlgorithmId); + + if (TdsEnums.CustomCipherAlgorithmId == md.cipherMD.CipherAlgorithmId) { + // Write the algorithm name + Debug.Assert (md.cipherMD.CipherAlgorithmName.Length < 256); + stateObj.WriteByte((byte)md.cipherMD.CipherAlgorithmName.Length); + WriteString(md.cipherMD.CipherAlgorithmName, stateObj); + } + + // Write Encryption Algo Type + stateObj.WriteByte(md.cipherMD.EncryptionType); + + // Write Normalization Version + stateObj.WriteByte(md.cipherMD.NormalizationRuleVersion); + } + internal void WriteBulkCopyMetaData(_SqlMetaDataSet metadataCollection, int count, TdsParserStateObject stateObj) { if (!(State == TdsParserState.OpenNotLoggedIn || State == TdsParserState.OpenLoggedIn)) { throw ADP.ClosedConnectionError(); @@ -7588,6 +9168,9 @@ namespace System.Data.SqlClient { stateObj.WriteByte(TdsEnums.SQLCOLMETADATA); WriteShort(count, stateObj); + // Write CEK table - 0 count + WriteCekTable(metadataCollection, stateObj); + for (int i = 0; i < metadataCollection.Length; i++) { if (metadataCollection[i] != null) { _SqlMetaData md = metadataCollection[i]; @@ -7600,18 +9183,25 @@ namespace System.Data.SqlClient { WriteShort(0x0000, stateObj); } + // Write the flags UInt16 flags; - flags = (UInt16)(md.updatability << 2); flags |= (UInt16)(md.isNullable ? (UInt16)TdsEnums.Nullable : (UInt16)0); flags |= (UInt16)(md.isIdentity ? (UInt16)TdsEnums.Identity : (UInt16)0); - WriteShort(flags, stateObj); // write the flags + // Write the next byte of flags + if (_serverSupportsColumnEncryption) { // TCE Supported + if (ShouldEncryptValuesForBulkCopy()) { // TCE enabled on connection options + flags |= (UInt16)(md.isEncrypted ? (UInt16)(TdsEnums.IsEncrypted << 8) : (UInt16)0); + } + } + + WriteShort(flags, stateObj);// write the flags // todo: // for xml WriteTokenLength results in a no-op // discuss this with blaine ... - // ([....]) xml datatype does not have token length in its metadata. So it should be a noop. + // (Microsoft) xml datatype does not have token length in its metadata. So it should be a noop. switch (md.type) { case SqlDbType.Decimal: @@ -7652,12 +9242,127 @@ namespace System.Data.SqlClient { WriteString(md.tableName, stateObj); } + WriteCryptoMetadata(md, stateObj); + stateObj.WriteByte((byte)md.column.Length); WriteString(md.column, stateObj); } } // end for loop } + /// <summary> + /// Determines if a column value should be encrypted when using BulkCopy (based on connectionstring setting). + /// </summary> + /// <returns></returns> + internal bool ShouldEncryptValuesForBulkCopy () { + if (null != _connHandler && + null != _connHandler.ConnectionOptions && + SqlConnectionColumnEncryptionSetting.Enabled == _connHandler.ConnectionOptions.ColumnEncryptionSetting) { + return true; + } + + return false; + } + + /// <summary> + /// Encrypts a column value (for SqlBulkCopy) + /// </summary> + /// <returns></returns> + internal object EncryptColumnValue (object value, SqlMetaDataPriv metadata, string column, TdsParserStateObject stateObj, bool isDataFeed, bool isSqlType) { + Debug.Assert (_serverSupportsColumnEncryption, "Server doesn't support encryption, yet we received encryption metadata"); + Debug.Assert (ShouldEncryptValuesForBulkCopy(), "Encryption attempted when not requested"); + + if (isDataFeed) { // can't encrypt a stream column + SQL.StreamNotSupportOnEncryptedColumn(column); + } + + int actualLengthInBytes; + switch(metadata.baseTI.metaType.NullableType) { + case TdsEnums.SQLBIGBINARY: + case TdsEnums.SQLBIGVARBINARY: + case TdsEnums.SQLIMAGE: + // For some datatypes, engine does truncation before storing the value. (For example, when + // trying to insert a varbinary(7000) into a varbinary(3000) column). Since we encrypt the + // column values, engine has no way to tell the size of the plaintext datatype. Therefore, + // we truncate the values based on target column sizes here before encrypting them. This + // truncation is only needed if we exceed the max column length or if the target column is + // not a blob type (eg. varbinary(max)). The actual work of truncating the column happens + // when we normalize and serialize the data buffers. The serialization routine expects us + // to report the size of data to be copied out (for serialization). If we underreport the + // size, truncation will happen for us! + actualLengthInBytes = (isSqlType) ? ((SqlBinary)value).Length : ((byte[])value).Length; + if (metadata.baseTI.length > 0 && + actualLengthInBytes > metadata.baseTI.length) { // see comments agove + actualLengthInBytes = metadata.baseTI.length; + } + break; + + case TdsEnums.SQLUNIQUEID: + actualLengthInBytes = GUID_SIZE; // that's a constant for guid + break; + case TdsEnums.SQLBIGCHAR: + case TdsEnums.SQLBIGVARCHAR: + case TdsEnums.SQLTEXT: + if (null == _defaultEncoding) + { + ThrowUnsupportedCollationEncountered(null); // stateObject only when reading + } + + string stringValue = (isSqlType) ? ((SqlString)value).Value : (string)value; + actualLengthInBytes = _defaultEncoding.GetByteCount(stringValue); + + // If the string length is > max length, then use the max length (see comments above) + if (metadata.baseTI.length > 0 && + actualLengthInBytes > metadata.baseTI.length) { + actualLengthInBytes = metadata.baseTI.length; // this ensure truncation! + } + + break; + case TdsEnums.SQLNCHAR: + case TdsEnums.SQLNVARCHAR: + case TdsEnums.SQLNTEXT: + actualLengthInBytes = ((isSqlType) ? ((SqlString)value).Value.Length : ((string)value).Length) * 2; + + if (metadata.baseTI.length > 0 && + actualLengthInBytes > metadata.baseTI.length) { // see comments above + actualLengthInBytes = metadata.baseTI.length; + } + + break; + + default: + actualLengthInBytes = metadata.baseTI.length; + break; + } + + byte[] serializedValue; + if (isSqlType) { + // SqlType + serializedValue = SerializeUnencryptedSqlValue (value, + metadata.baseTI.metaType, + actualLengthInBytes, + offset : 0, + normalizationVersion: metadata.cipherMD.NormalizationRuleVersion, + stateObj: stateObj); + } + else { + serializedValue = SerializeUnencryptedValue (value, + metadata.baseTI.metaType, + metadata.baseTI.scale, + actualLengthInBytes, + offset: 0, + isDataFeed: isDataFeed, + normalizationVersion: metadata.cipherMD.NormalizationRuleVersion, + stateObj: stateObj); + } + + Debug.Assert(serializedValue != null, "serializedValue should not be null in TdsExecuteRPC."); + return SqlSecurityUtility.EncryptWithKey( + serializedValue, + metadata.cipherMD, + _connHandler.ConnectionOptions.DataSource); + } + internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsParserStateObject stateObj, bool isSqlType, bool isDataFeed, bool isNull) { Debug.Assert(!isSqlType || value is INullable, "isSqlType is true, but value can not be type cast to an INullable"); Debug.Assert(!isDataFeed ^ value is DataFeed, "Incorrect value for isDataFeed"); @@ -8681,7 +10386,8 @@ namespace System.Data.SqlClient { // For MAX types, this method can only write everything in one big chunk. If multiple // chunk writes needed, please use WritePlpBytes/WritePlpChars - private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int actualLength, int encodingByteSize, int offset, TdsParserStateObject stateObj, int paramSize, bool isDataFeed) { + private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int actualLength, int encodingByteSize, int offset, TdsParserStateObject stateObj, int paramSize, bool isDataFeed) + { Debug.Assert((null != value) && (DBNull.Value != value), "unexpected missing or empty object"); // parameters are always sent over as BIG or N types @@ -8712,6 +10418,7 @@ namespace System.Data.SqlClient { if (type.IsPlp) { WriteInt(actualLength, stateObj); // chunk length } + return stateObj.WriteByteArray((byte[])value, actualLength, offset, canAccumulate: false); } } @@ -8875,6 +10582,360 @@ namespace System.Data.SqlClient { // Debug.WriteLine("value: " + value.ToString(CultureInfo.InvariantCulture)); } + /// <summary> + /// Write parameter encryption metadata and returns a task if necessary. + /// </summary> + private Task WriteEncryptionMetadata(Task terminatedWriteTask, SqlColumnEncryptionInputParameterInfo columnEncryptionParameterInfo, TdsParserStateObject stateObj) { + Debug.Assert(columnEncryptionParameterInfo != null, @"columnEncryptionParameterInfo cannot be null"); + Debug.Assert(stateObj != null, @"stateObj cannot be null"); + + // If there is not task already, simply write the encryption metadata synchronously. + if (terminatedWriteTask == null) { + WriteEncryptionMetadata(columnEncryptionParameterInfo, stateObj); + return null; + } + else { + // Otherwise, create a continuation task to write the encryption metadata after the previous write completes. + return AsyncHelper.CreateContinuationTask<SqlColumnEncryptionInputParameterInfo, TdsParserStateObject>(terminatedWriteTask, + WriteEncryptionMetadata, columnEncryptionParameterInfo, stateObj, + connectionToDoom: _connHandler); + } + } + + /// <summary> + /// Write parameter encryption metadata. + /// </summary> + private void WriteEncryptionMetadata(SqlColumnEncryptionInputParameterInfo columnEncryptionParameterInfo, TdsParserStateObject stateObj) { + Debug.Assert(columnEncryptionParameterInfo != null, @"columnEncryptionParameterInfo cannot be null"); + Debug.Assert(stateObj != null, @"stateObj cannot be null"); + + // Write the TypeInfo. + WriteSmiTypeInfo(columnEncryptionParameterInfo.ParameterMetadata, stateObj); + + // Write the serialized array in columnEncryptionParameterInfo. + stateObj.WriteByteArray(columnEncryptionParameterInfo.SerializedWireFormat, + columnEncryptionParameterInfo.SerializedWireFormat.Length, + offsetBuffer: 0); + } + + // For MAX types, this method can only write everything in one big chunk. If multiple + // chunk writes needed, please use WritePlpBytes/WritePlpChars + private byte[] SerializeUnencryptedValue(object value, MetaType type, byte scale, int actualLength, int offset, bool isDataFeed, byte normalizationVersion, TdsParserStateObject stateObj) { + Debug.Assert((null != value) && (DBNull.Value != value), "unexpected missing or empty object"); + + if (normalizationVersion != 0x01) { + throw SQL.UnsupportedNormalizationVersion(normalizationVersion); + } + + // parameters are always sent over as BIG or N types + switch (type.NullableType) { + case TdsEnums.SQLFLTN: + if (type.FixedLength == 4) + return SerializeFloat((Single)value); + else { + Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!"); + return SerializeDouble((Double)value); + } + + case TdsEnums.SQLBIGBINARY: + case TdsEnums.SQLBIGVARBINARY: + case TdsEnums.SQLIMAGE: + case TdsEnums.SQLUDT: { + Debug.Assert(!isDataFeed, "We cannot seriliaze streams"); + Debug.Assert(value is byte[], "Value should be an array of bytes"); + + byte[] b = new byte[actualLength]; + Buffer.BlockCopy((byte[])value, offset, b, 0, actualLength); + return b; + } + + case TdsEnums.SQLUNIQUEID: { + System.Guid guid = (System.Guid)value; + byte[] b = guid.ToByteArray(); + + Debug.Assert((actualLength == b.Length) && (actualLength == 16), "Invalid length for guid type in com+ object"); + return b; + } + + case TdsEnums.SQLBITN: { + Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type"); + + // We normalize to allow conversion across data types. BIT is serialized into a BIGINT. + return SerializeLong((bool)value == true ? 1 : 0, stateObj); + } + + case TdsEnums.SQLINTN: + if (type.FixedLength == 1) + return SerializeLong((byte)value, stateObj); + + if (type.FixedLength == 2) + return SerializeLong((Int16)value, stateObj); + + if (type.FixedLength == 4) + return SerializeLong((Int32)value, stateObj); + + Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture)); + return SerializeLong((Int64)value, stateObj); + + case TdsEnums.SQLBIGCHAR: + case TdsEnums.SQLBIGVARCHAR: + case TdsEnums.SQLTEXT: { + Debug.Assert(!isDataFeed, "We cannot seriliaze streams"); + Debug.Assert((value is string || value is byte[]), "Value is a byte array or string"); + + if (value is byte[]) { // If LazyMat non-filled blob, send cookie rather than value + byte[] b = new byte[actualLength]; + Buffer.BlockCopy((byte[])value, 0, b, 0, actualLength); + return b; + } + else { + return SerializeEncodingChar((string)value, actualLength, offset, _defaultEncoding); + } + } + case TdsEnums.SQLNCHAR: + case TdsEnums.SQLNVARCHAR: + case TdsEnums.SQLNTEXT: + case TdsEnums.SQLXMLTYPE: { + Debug.Assert(!isDataFeed, "We cannot seriliaze streams"); + Debug.Assert((value is string || value is byte[]), "Value is a byte array or string"); + + if (value is byte[]) { // If LazyMat non-filled blob, send cookie rather than value + byte[] b = new byte[actualLength]; + Buffer.BlockCopy((byte[])value, 0, b, 0, actualLength); + return b; + } + else { // convert to cchars instead of cbytes + actualLength >>= 1; + return SerializeString((string)value, actualLength, offset); + } + } + case TdsEnums.SQLNUMERICN: + Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes"); + return SerializeDecimal((Decimal)value, stateObj); + + case TdsEnums.SQLDATETIMN: + Debug.Assert(type.FixedLength <= 0xff, "Invalid Fixed Length"); + + TdsDateTime dt = MetaType.FromDateTime((DateTime)value, (byte)type.FixedLength); + + if (type.FixedLength == 4) { + if (0 > dt.days || dt.days > UInt16.MaxValue) + throw SQL.SmallDateTimeOverflow(MetaType.ToDateTime(dt.days, dt.time, 4).ToString(CultureInfo.InvariantCulture)); + + if (null == stateObj._bIntBytes) { + stateObj._bIntBytes = new byte[4]; + } + + byte[] b = stateObj._bIntBytes; + int current = 0; + + byte[] bPart = SerializeShort(dt.days, stateObj); + Buffer.BlockCopy(bPart, 0, b, current, 2); + current += 2; + + bPart = SerializeShort(dt.time, stateObj); + Buffer.BlockCopy(bPart, 0, b, current, 2); + + return b; + } + else { + if (null == stateObj._bLongBytes) { + stateObj._bLongBytes = new byte[8]; + } + byte[] b = stateObj._bLongBytes; + int current = 0; + + byte[] bPart = SerializeInt(dt.days, stateObj); + Buffer.BlockCopy(bPart, 0, b, current, 4); + current += 4; + + bPart = SerializeInt(dt.time, stateObj); + Buffer.BlockCopy(bPart, 0, b, current, 4); + + return b; + } + + case TdsEnums.SQLMONEYN: { + return SerializeCurrency((Decimal)value, type.FixedLength, stateObj); + } + + case TdsEnums.SQLDATE: { + return SerializeDate((DateTime)value); + } + + case TdsEnums.SQLTIME: + if (scale > TdsEnums.DEFAULT_VARTIME_SCALE) { + throw SQL.TimeScaleValueOutOfRange(scale); + } + return SerializeTime((TimeSpan)value, scale, actualLength); + + case TdsEnums.SQLDATETIME2: + if (scale > TdsEnums.DEFAULT_VARTIME_SCALE) { + throw SQL.TimeScaleValueOutOfRange(scale); + } + return SerializeDateTime2((DateTime)value, scale, actualLength); + + case TdsEnums.SQLDATETIMEOFFSET: + if (scale > TdsEnums.DEFAULT_VARTIME_SCALE) { + throw SQL.TimeScaleValueOutOfRange(scale); + } + return SerializeDateTimeOffset((DateTimeOffset)value, scale, actualLength); + + default: + throw SQL.UnsupportedDatatypeEncryption(type.TypeName); + } // switch + // Debug.WriteLine("value: " + value.ToString(CultureInfo.InvariantCulture)); + } + + // For MAX types, this method can only write everything in one big chunk. If multiple + // chunk writes needed, please use WritePlpBytes/WritePlpChars + private byte[] SerializeUnencryptedSqlValue(object value, MetaType type, int actualLength, int offset, byte normalizationVersion, TdsParserStateObject stateObj) { + Debug.Assert(((type.NullableType == TdsEnums.SQLXMLTYPE) || + (value is INullable && !((INullable)value).IsNull)), + "unexpected null SqlType!"); + + if (normalizationVersion != 0x01) { + throw SQL.UnsupportedNormalizationVersion(normalizationVersion); + } + + // parameters are always sent over as BIG or N types + switch (type.NullableType) { + case TdsEnums.SQLFLTN: + if (type.FixedLength == 4) + return SerializeFloat(((SqlSingle)value).Value); + else { + Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!"); + return SerializeDouble(((SqlDouble)value).Value); + } + + case TdsEnums.SQLBIGBINARY: + case TdsEnums.SQLBIGVARBINARY: + case TdsEnums.SQLIMAGE: { + byte[] b = new byte[actualLength]; + + if (value is SqlBinary) { + Buffer.BlockCopy(((SqlBinary)value).Value, offset, b, 0, actualLength); + } + else { + Debug.Assert(value is SqlBytes); + Buffer.BlockCopy(((SqlBytes)value).Value, offset, b, 0, actualLength); + } + return b; + } + + case TdsEnums.SQLUNIQUEID: { + byte[] b = ((SqlGuid)value).ToByteArray(); + + Debug.Assert((actualLength == b.Length) && (actualLength == 16), "Invalid length for guid type in com+ object"); + return b; + } + + case TdsEnums.SQLBITN: { + Debug.Assert(type.FixedLength == 1, "Invalid length for SqlBoolean type"); + + // We normalize to allow conversion across data types. BIT is serialized into a BIGINT. + return SerializeLong(((SqlBoolean)value).Value == true ? 1 : 0, stateObj); + } + + case TdsEnums.SQLINTN: + // We normalize to allow conversion across data types. All data types below are serialized into a BIGINT. + if (type.FixedLength == 1) + return SerializeLong(((SqlByte)value).Value, stateObj); + + if (type.FixedLength == 2) + return SerializeLong(((SqlInt16)value).Value, stateObj); + + if (type.FixedLength == 4) + return SerializeLong(((SqlInt32)value).Value, stateObj); + else { + Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture)); + return SerializeLong(((SqlInt64)value).Value, stateObj); + } + + case TdsEnums.SQLBIGCHAR: + case TdsEnums.SQLBIGVARCHAR: + case TdsEnums.SQLTEXT: + if (value is SqlChars) { + String sch = new String(((SqlChars)value).Value); + return SerializeEncodingChar(sch, actualLength, offset, _defaultEncoding); + } + else { + Debug.Assert(value is SqlString); + return SerializeEncodingChar(((SqlString)value).Value, actualLength, offset, _defaultEncoding); + } + + + case TdsEnums.SQLNCHAR: + case TdsEnums.SQLNVARCHAR: + case TdsEnums.SQLNTEXT: + case TdsEnums.SQLXMLTYPE: + // convert to cchars instead of cbytes + // Xml type is already converted to string through GetCoercedValue + if (actualLength != 0) + actualLength >>= 1; + + if (value is SqlChars) { + return SerializeCharArray(((SqlChars)value).Value, actualLength, offset); + } + else { + Debug.Assert(value is SqlString); + return SerializeString(((SqlString)value).Value, actualLength, offset); + } + + case TdsEnums.SQLNUMERICN: + Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes"); + return SerializeSqlDecimal((SqlDecimal)value, stateObj); + + case TdsEnums.SQLDATETIMN: + SqlDateTime dt = (SqlDateTime)value; + + if (type.FixedLength == 4) { + if (0 > dt.DayTicks || dt.DayTicks > UInt16.MaxValue) + throw SQL.SmallDateTimeOverflow(dt.ToString()); + + if (null == stateObj._bIntBytes) { + stateObj._bIntBytes = new byte[4]; + } + + byte[] b = stateObj._bIntBytes; + int current = 0; + + byte[] bPart = SerializeShort(dt.DayTicks, stateObj); + Buffer.BlockCopy(bPart, 0, b, current, 2); + current += 2; + + bPart = SerializeShort(dt.TimeTicks / SqlDateTime.SQLTicksPerMinute, stateObj); + Buffer.BlockCopy(bPart, 0, b, current, 2); + + return b; + } + else { + if (null == stateObj._bLongBytes) { + stateObj._bLongBytes = new byte[8]; + } + + byte[] b = stateObj._bLongBytes; + int current = 0; + + byte[] bPart = SerializeInt(dt.DayTicks, stateObj); + Buffer.BlockCopy(bPart, 0, b, current, 4); + current += 4; + + bPart = SerializeInt(dt.TimeTicks, stateObj); + Buffer.BlockCopy(bPart, 0, b, current, 4); + + return b; + } + + case TdsEnums.SQLMONEYN: { + return SerializeSqlMoney((SqlMoney)value, type.FixedLength, stateObj); + } + + default: + throw SQL.UnsupportedDatatypeEncryption(type.TypeName); + } // switch + } + // // we always send over nullable types for parameters so we always write the varlen fields // diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParserHelperClasses.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParserHelperClasses.cs index fe5d91e1be7..7d2408a9042 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParserHelperClasses.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParserHelperClasses.cs @@ -2,8 +2,8 @@ // <copyright file="TdsParserHelperClasses.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { @@ -19,8 +19,9 @@ namespace System.Data.SqlClient { using System.Text; using System.Threading; using System.Security; + using System.Globalization; - using Microsoft.SqlServer.Server; // for SMI metadata + using Microsoft.SqlServer.Server; // for SMI metadata internal enum CallbackType { Read = 0, @@ -47,6 +48,7 @@ namespace System.Data.SqlClient { THREADID, MARS, TRACEID, + FEDAUTHREQUIRED, NUMOPT, LASTOPT = 255 } @@ -65,6 +67,210 @@ namespace System.Data.SqlClient { Broken, } + /// <summary> + /// Struct encapsulating the data to be sent to the server as part of Federated Authentication Feature Extension. + /// </summary> + internal struct FederatedAuthenticationFeatureExtensionData + { + internal TdsEnums.FedAuthLibrary libraryType; + internal bool fedAuthRequiredPreLoginResponse; + internal SqlAuthenticationMethod authentication; + internal byte[] accessToken; + } + + /// <summary> + /// <para> Represents a single encrypted value for a CEK. It contains the encrypted CEK, + /// the store type, name,the key path and encryption algorithm.</para> + /// </summary> + internal struct SqlEncryptionKeyInfo { + internal byte[] encryptedKey; // the encrypted "column encryption key" + internal int databaseId; + internal int cekId; + internal int cekVersion; + internal byte[] cekMdVersion; + internal string keyPath; + internal string keyStoreName; + internal string algorithmName; + internal byte normalizationRuleVersion; + } + + /// <summary> + /// <para> Encapsulates one entry in the CipherInfo table sent as part of Colmetadata. + /// The same CEK is encrypted multiple times with different master keys (for master key + /// rotation scenario) We need to keep all these around until we can resolve the CEK + /// using the correct master key.</para> + /// </summary> + internal struct SqlTceCipherInfoEntry { + + /// <summary> + /// List of Column Encryption Key Information. + /// </summary> + private readonly List<SqlEncryptionKeyInfo> _columnEncryptionKeyValues; + + /// <summary> + /// Key Ordinal. + /// </summary> + private readonly int _ordinal; + + /// <summary> + /// Database ID + /// </summary> + private int _databaseId; + + /// <summary> + /// Cek ID + /// </summary> + private int _cekId; + + /// <summary> + /// Cek Version + /// </summary> + private int _cekVersion; + + /// <summary> + /// Cek MD Version + /// </summary> + private byte[] _cekMdVersion; + + /// <summary> + /// Return the ordinal. + /// </summary> + internal int Ordinal { + get { + return _ordinal; + } + } + + /// <summary> + /// Return the DatabaseID. + /// </summary> + internal int DatabaseId { + get { + return _databaseId; + } + } + + /// <summary> + /// Return the CEK ID. + /// </summary> + internal int CekId { + get { + return _cekId; + } + } + + /// <summary> + /// Return the CEK Version. + /// </summary> + internal int CekVersion { + get { + return _cekVersion; + } + } + + /// <summary> + /// Return the CEK MD Version. + /// </summary> + internal byte[] CekMdVersion { + get { + return _cekMdVersion; + } + } + + /// <summary> + /// Return the list of Column Encryption Key Values. + /// </summary> + internal List<SqlEncryptionKeyInfo> ColumnEncryptionKeyValues { + get { + return _columnEncryptionKeyValues; + } + } + + /// <summary> + /// Add an entry to the list of ColumnEncryptionKeyValues. + /// </summary> + /// <param name="encryptedKey"></param> + /// <param name="databaseId"></param> + /// <param name="cekId"></param> + /// <param name="cekVersion"></param> + /// <param name="cekMdVersion"></param> + /// <param name="keyPath"></param> + /// <param name="keyStoreName"></param> + /// <param name="algorithmName"></param> + internal void Add(byte[] encryptedKey, int databaseId, int cekId, int cekVersion, byte[] cekMdVersion, string keyPath, string keyStoreName, string algorithmName) { + + Debug.Assert(_columnEncryptionKeyValues != null, "_columnEncryptionKeyValues should already be initialized."); + + SqlEncryptionKeyInfo encryptionKey = new SqlEncryptionKeyInfo(); + encryptionKey.encryptedKey = encryptedKey; + encryptionKey.databaseId = databaseId; + encryptionKey.cekId = cekId; + encryptionKey.cekVersion = cekVersion; + encryptionKey.cekMdVersion = cekMdVersion; + encryptionKey.keyPath = keyPath; + encryptionKey.keyStoreName = keyStoreName; + encryptionKey.algorithmName = algorithmName; + _columnEncryptionKeyValues.Add(encryptionKey); + + if (0 == _databaseId) { + _databaseId = databaseId; + _cekId = cekId; + _cekVersion = cekVersion; + _cekMdVersion = cekMdVersion; + } + else { + Debug.Assert(_databaseId == databaseId); + Debug.Assert(_cekId == cekId); + Debug.Assert(_cekVersion == cekVersion); + Debug.Assert (_cekMdVersion != null && cekMdVersion != null && _cekMdVersion.Length == _cekMdVersion.Length); + } + } + + /// <summary> + /// Constructor. + /// </summary> + /// <param name="ordinal"></param> + internal SqlTceCipherInfoEntry(int ordinal = 0) : this() { + _ordinal = ordinal; + _databaseId = 0; + _cekId = 0; + _cekVersion = 0; + _cekMdVersion = null; + _columnEncryptionKeyValues = new List<SqlEncryptionKeyInfo>(); + } + } + + /// <summary> + /// <para> Represents a table with various CEKs used in a resultset. Each entry corresponds to one (unique) CEK. The CEK + /// may have been encrypted using multiple master keys (giving us multiple CEK values). All these values form one single + /// entry in this table.</para> + ///</summary> + internal struct SqlTceCipherInfoTable { + private readonly SqlTceCipherInfoEntry [] keyList; + + internal SqlTceCipherInfoTable (int tabSize) { + Debug.Assert (0 < tabSize, "Invalid Table Size"); + keyList = new SqlTceCipherInfoEntry[tabSize]; + } + + internal SqlTceCipherInfoEntry this [int index] { + get { + Debug.Assert (index < keyList.Length, "Invalid index specified."); + return keyList[index]; + } + set { + Debug.Assert (index < keyList.Length, "Invalid index specified."); + keyList[index] = value; + } + } + + internal int Size { + get { + return keyList.Length; + } + } + } + sealed internal class SqlCollation { // First 20 bits of info field represent the lcid, bits 21-25 are compare options private const uint IgnoreCase = 1 << 20; // bit 21 - IgnoreCase @@ -230,6 +436,7 @@ namespace System.Data.SqlClient { } sealed internal class SqlLogin { + internal SqlAuthenticationMethod authentication = SqlAuthenticationMethod.NotSpecified; // Authentication type internal int timeout; // login timeout internal bool userInstance = false; // user instance internal string hostName = ""; // client machine name @@ -258,6 +465,20 @@ namespace System.Data.SqlClient { internal UInt32 tdsVersion; } + sealed internal class SqlFedAuthInfo { + internal string spn; + internal string stsurl; + public override string ToString() { + return String.Format(CultureInfo.InvariantCulture, "STSURL: {0}, SPN: {1}", stsurl ?? String.Empty, spn ?? String.Empty); + } + } + + sealed internal class SqlFedAuthToken { + internal UInt32 dataLen; + internal byte[] accessToken; + internal long expirationFileTime; + } + sealed internal class _SqlMetaData : SqlMetaDataPriv, ICloneable { internal string column; @@ -337,9 +558,11 @@ namespace System.Data.SqlClient { internal int[] indexMap; internal int visibleColumns; internal DataTable schemaTable; - private readonly _SqlMetaData[] metaDataArray; + internal readonly SqlTceCipherInfoTable? cekTable; // table of "column encryption keys" used for this metadataset + internal readonly _SqlMetaData[] metaDataArray; - internal _SqlMetaDataSet(int count) { + internal _SqlMetaDataSet(int count, SqlTceCipherInfoTable? cipherTable) { + cekTable = cipherTable; metaDataArray = new _SqlMetaData[count]; for(int i = 0; i < metaDataArray.Length; ++i) { metaDataArray[i] = new _SqlMetaData(i); @@ -428,6 +651,170 @@ namespace System.Data.SqlClient { } } + /// <summary> + /// Represents Encryption related information of the cipher data. + /// </summary> + internal class SqlCipherMetadata { + + /// <summary> + /// Cipher Info Entry. + /// </summary> + private SqlTceCipherInfoEntry? _sqlTceCipherInfoEntry; + + /// <summary> + /// Encryption Algorithm Id. + /// </summary> + private readonly byte _cipherAlgorithmId; + + /// <summary> + /// Encryption Algorithm Name. + /// </summary> + private readonly string _cipherAlgorithmName; + + /// <summary> + /// Encryption Type. + /// </summary> + private readonly byte _encryptionType; + + /// <summary> + /// Normalization Rule Version. + /// </summary> + private readonly byte _normalizationRuleVersion; + + /// <summary> + /// Encryption Algorithm Handle. + /// </summary> + private SqlClientEncryptionAlgorithm _sqlClientEncryptionAlgorithm; + + /// <summary> + /// Sql Encryption Key Info. + /// </summary> + private SqlEncryptionKeyInfo? _sqlEncryptionKeyInfo; + + /// <summary> + /// Ordinal (into the Cek Table). + /// </summary> + private readonly ushort _ordinal; + + /// <summary> + /// Return the Encryption Info Entry. + /// </summary> + internal SqlTceCipherInfoEntry? EncryptionInfo { + get { + return _sqlTceCipherInfoEntry; + } + set { + Debug.Assert(!_sqlTceCipherInfoEntry.HasValue, "We can only set the EncryptionInfo once."); + _sqlTceCipherInfoEntry = value; + } + } + + /// <summary> + /// Return the cipher's encryption algorithm id. + /// </summary> + internal byte CipherAlgorithmId { + get { + return _cipherAlgorithmId; + } + } + + /// <summary> + /// Return the cipher's encryption algorithm name (could be null). + /// </summary> + internal string CipherAlgorithmName { + get { + return _cipherAlgorithmName; + } + } + + /// <summary> + /// Return EncryptionType (Deterministic, Randomized, etc.) + /// </summary> + internal byte EncryptionType { + get { + return _encryptionType; + } + } + + /// <summary> + /// Return normalization rule version. + /// </summary> + internal byte NormalizationRuleVersion { + get { + return _normalizationRuleVersion; + } + } + + /// <summary> + /// Return the cipher encyrption algorithm handle. + /// </summary> + internal SqlClientEncryptionAlgorithm CipherAlgorithm { + get { + return _sqlClientEncryptionAlgorithm; + } + set { + Debug.Assert(_sqlClientEncryptionAlgorithm == null, "_sqlClientEncryptionAlgorithm should not be set more than once."); + _sqlClientEncryptionAlgorithm = value; + } + } + + /// <summary> + /// Return Encryption Key Info. + /// </summary> + internal SqlEncryptionKeyInfo? EncryptionKeyInfo { + get { + return _sqlEncryptionKeyInfo; + } + + set { + Debug.Assert(!_sqlEncryptionKeyInfo.HasValue, "_sqlEncryptionKeyInfo should not be set more than once."); + _sqlEncryptionKeyInfo = value; + } + } + + /// <summary> + /// Return Ordinal into Cek Table. + /// </summary> + internal ushort CekTableOrdinal { + get { + return _ordinal; + } + } + + /// <summary> + /// Constructor. + /// </summary> + /// <param name="sqlTceCipherInfoEntry"></param> + /// <param name="sqlClientEncryptionAlgorithm"></param> + /// <param name="cipherAlgorithmId"></param> + /// <param name="encryptionType"></param> + /// <param name="normalizationRuleVersion"></param> + internal SqlCipherMetadata (SqlTceCipherInfoEntry? sqlTceCipherInfoEntry, + ushort ordinal, + byte cipherAlgorithmId, + string cipherAlgorithmName, + byte encryptionType, + byte normalizationRuleVersion) { + Debug.Assert(!sqlTceCipherInfoEntry.Equals(default(SqlTceCipherInfoEntry)), "sqlTceCipherInfoEntry should not be un-initialized."); + + _sqlTceCipherInfoEntry = sqlTceCipherInfoEntry; + _ordinal = ordinal; + _cipherAlgorithmId = cipherAlgorithmId; + _cipherAlgorithmName = cipherAlgorithmName; + _encryptionType = encryptionType; + _normalizationRuleVersion = normalizationRuleVersion; + _sqlEncryptionKeyInfo = null; + } + + /// <summary> + /// Do we have an handle to the cipher encryption algorithm already ? + /// </summary> + /// <returns></returns> + internal bool IsAlgorithmInitialized() { + return (null != _sqlClientEncryptionAlgorithm) ? true : false; + } + } + internal class SqlMetaDataPriv { internal SqlDbType type; // SqlDbType enum value internal byte tdsType; // underlying tds type @@ -462,6 +849,9 @@ namespace System.Data.SqlClient { internal string structuredTypeName; internal IList<SmiMetaData> structuredFields; + internal bool isEncrypted; // TCE encrypted? + internal SqlMetaDataPriv baseTI; // for encrypted columns, represents the TYPE_INFO for plaintext value + internal SqlCipherMetadata cipherMD; // Cipher related metadata for encrypted columns. internal SqlMetaDataPriv() { } @@ -493,6 +883,157 @@ namespace System.Data.SqlClient { this.structuredTypeName = original.structuredTypeName; this.structuredFields = original.structuredFields; } + + /// <summary> + /// Is the algorithm handle for the cipher encryption initialized ? + /// </summary> + /// <returns></returns> + internal bool IsAlgorithmInitialized() { + if (null != cipherMD) { + return cipherMD.IsAlgorithmInitialized(); + } + + return false; + } + + /// <summary> + /// Returns the normalization rule version byte. + /// </summary> + /// <returns></returns> + internal byte NormalizationRuleVersion { + get { + if (null != cipherMD){ + return cipherMD.NormalizationRuleVersion; + } + + return 0x00; + } + } + } + + /// <summary> + /// Class encapsulating additional information when sending encrypted input parameters. + /// </summary> + sealed internal class SqlColumnEncryptionInputParameterInfo + { + /// <summary> + /// Metadata of the parameter to write the TYPE_INFO of the unencrypted column data type. + /// </summary> + private readonly SmiParameterMetaData _smiParameterMetadata; + + /// <summary> + /// Column encryption related metadata. + /// </summary> + private readonly SqlCipherMetadata _cipherMetadata; + + /// <summary> + /// Serialized format for a subset of members. + /// Does not include _smiParameterMetadata's serialization. + /// </summary> + private readonly byte[] _serializedWireFormat; + + /// <summary> + /// Return the SMI Parameter Metadata. + /// </summary> + internal SmiParameterMetaData ParameterMetadata { + get { + return _smiParameterMetadata; + } + } + + /// <summary> + /// Return the serialized format for some members. + /// This is pre-calculated and cached since members are immutable. + /// Does not include _smiParameterMetadata's serialization. + /// </summary> + internal byte[] SerializedWireFormat + { + get { + return _serializedWireFormat; + } + } + + /// <summary> + /// Constructor. + /// </summary> + /// <param name="smiParameterMetadata"></param> + /// <param name="cipherMetadata"></param> + internal SqlColumnEncryptionInputParameterInfo(SmiParameterMetaData smiParameterMetadata, SqlCipherMetadata cipherMetadata) { + Debug.Assert(smiParameterMetadata != null, "smiParameterMetadata should not be null."); + Debug.Assert(cipherMetadata != null, "cipherMetadata should not be null"); + Debug.Assert(cipherMetadata.EncryptionKeyInfo.HasValue, "cipherMetadata.EncryptionKeyInfo.HasValue should be true."); + + _smiParameterMetadata = smiParameterMetadata; + _cipherMetadata = cipherMetadata; + _serializedWireFormat = SerializeToWriteFormat(); + } + + /// <summary> + /// Serializes some data members to wire format. + /// </summary> + private byte[] SerializeToWriteFormat() { + int totalLength = 0; + + // CipherAlgorithmId. + totalLength += sizeof(byte); + + // Encryption Type. + totalLength += sizeof(byte); + + // Database id of the encryption key. + totalLength += sizeof(int); + + // Id of the encryption key. + totalLength += sizeof(int); + + // Version of the encryption key. + totalLength += sizeof(int); + + // Metadata version of the encryption key. + totalLength += _cipherMetadata.EncryptionKeyInfo.Value.cekMdVersion.Length; + + // Normalization Rule Version. + totalLength += sizeof(byte); + + byte[] serializedWireFormat = new byte[totalLength]; + + // No:of bytes consumed till now. Running variable. + int consumedBytes = 0; + + // 1 - Write Cipher Algorithm Id. + serializedWireFormat[consumedBytes++] = _cipherMetadata.CipherAlgorithmId; + + // 2 - Write Encryption Type. + serializedWireFormat[consumedBytes++] = _cipherMetadata.EncryptionType; + + // 3 - Write the database id of the encryption key. + SerializeIntIntoBuffer(_cipherMetadata.EncryptionKeyInfo.Value.databaseId, serializedWireFormat, ref consumedBytes); + + // 4 - Write the id of the encryption key. + SerializeIntIntoBuffer(_cipherMetadata.EncryptionKeyInfo.Value.cekId, serializedWireFormat, ref consumedBytes); + + // 5 - Write the version of the encryption key. + SerializeIntIntoBuffer(_cipherMetadata.EncryptionKeyInfo.Value.cekVersion, serializedWireFormat, ref consumedBytes); + + // 6 - Write the metadata version of the encryption key. + Buffer.BlockCopy(_cipherMetadata.EncryptionKeyInfo.Value.cekMdVersion, 0, serializedWireFormat, consumedBytes, _cipherMetadata.EncryptionKeyInfo.Value.cekMdVersion.Length); + consumedBytes += _cipherMetadata.EncryptionKeyInfo.Value.cekMdVersion.Length; + + // 7 - Write Normalization Rule Version. + serializedWireFormat[consumedBytes++] = _cipherMetadata.NormalizationRuleVersion; + + return serializedWireFormat; + } + + /// <summary> + /// Serializes an int into the provided buffer and offset. + /// </summary> + private void SerializeIntIntoBuffer(int value, byte[] buffer, ref int offset) { + buffer[offset++] = (byte)(value & 0xff); + buffer[offset++] = (byte)((value >> 8) & 0xff); + buffer[offset++] = (byte)((value >> 16) & 0xff); + buffer[offset++] = (byte)((value >> 24) & 0xff); + } } sealed internal class _SqlRPC { @@ -513,6 +1054,16 @@ namespace System.Data.SqlClient { internal int warningsIndexStart; internal int warningsIndexEnd; internal SqlErrorCollection warnings; + internal bool needsFetchParameterEncryptionMetadata; + internal string GetCommandTextOrRpcName() { + if (TdsEnums.RPC_PROCID_EXECUTESQL == ProcID) { + // Param 0 is the actual sql executing + return (string)parameters[0].Value; + } + else { + return rpcName; + } + } } sealed internal class SqlReturnValue : SqlMetaDataPriv { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParserSafeHandles.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParserSafeHandles.cs index 1bc1b7398ad..6ec933e0578 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParserSafeHandles.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParserSafeHandles.cs @@ -2,8 +2,8 @@ // <copyright file="TdsParserSafeHandles.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParserSessionPool.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParserSessionPool.cs index d60e9ecdc77..cb4d9b10da7 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParserSessionPool.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParserSessionPool.cs @@ -2,8 +2,8 @@ // <copyright file="TdsParserSessionPool.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParserStateObject.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParserStateObject.cs index 81941166389..ed189942b10 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParserStateObject.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParserStateObject.cs @@ -2,8 +2,8 @@ // <copyright file="TdsParserStateObject.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { @@ -151,6 +151,10 @@ namespace System.Data.SqlClient { internal Decoder _plpdecoder = null; // Decoder object to process plp character data internal bool _accumulateInfoEvents= false; // TRUE - accumulate info messages during TdsParser.Run, FALSE - fire them internal List<SqlError> _pendingInfoEvents = null; + internal byte[] _bLongBytes = null; // scratch buffer to serialize Long values (8 bytes). + internal byte[] _bIntBytes = null; // scratch buffer to serialize Int values (4 bytes). + internal byte[] _bShortBytes = null; // scratch buffer to serialize Short values (2 bytes). + internal byte[] _bDecimalBytes = null; // scratch buffer to serialize decimal values (17 bytes). // // DO NOT USE THIS BUFFER FOR OTHER THINGS. @@ -642,7 +646,7 @@ namespace System.Data.SqlClient { // Should only be called for MARS - that is the only time we need to take // the ResetConnection lock! - // SQL BU DT 333026 - it was raised in a security review by [....] questioning whether + // SQL BU DT 333026 - it was raised in a security review by Microsoft questioning whether // we need to actually process the resulting packet (sp_reset ack or error) to know if the // reset actually succeeded. There was a concern that if the reset failed and we proceeded // there might be a security issue present. We have been assured by the server that if @@ -1034,7 +1038,7 @@ namespace System.Data.SqlClient { // either TDS stream is corrupted or there is multithreaded misuse of connection // NOTE: usually we do not proactively apply checks to TDS data, but this situation happened several times // and caused infinite loop in CleanWire (VSTFDEVDIV\DEVDIV2:149937) - throw SQL.ParsingError(); + throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); } return true; @@ -1607,9 +1611,9 @@ namespace System.Data.SqlClient { Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); if (null == encoding) { - // Bug 462435:CR: TdsParser.DrainData(stateObj) hitting timeout exception after Connection Resiliency change - // http://vstfdevdiv:8080/web/wi.aspx?pcguid=22f9acc9-569a-41ff-b6ac-fac1b6370209&id=462435 - // Need to skip the current column before throwing the error - this ensures that the state shared between this and the data reader is consistent when calling DrainData + // + + if (isPlp) { ulong ignored; if (!_parser.TrySkipPlpValue((ulong)length, this, out ignored)) { @@ -2042,8 +2046,8 @@ namespace System.Data.SqlClient { TaskCompletionSource<object> source = _networkPacketTaskSource; if (_parser.Connection.IsInPool) { - // Dev11 Bug 390048 : Timing issue between OnTimeout and ReadAsyncCallback results in SqlClient's packet parsing going out of [....] - // We should never timeout if the connection is currently in the pool: the safest thing to do here is to doom the connection to avoid corruption + // Dev11 + Debug.Assert(_parser.Connection.IsConnectionDoomed, "Timeout occurred while the connection is in the pool"); _parser.State = TdsParserState.Broken; _parser.Connection.BreakConnection(); @@ -2146,7 +2150,7 @@ namespace System.Data.SqlClient { } // -1 == Infinite - // 0 == Already timed out (NOTE: To simulate the same behavior as [....] we will only timeout on 0 if we receive an IO Pending from SNI) + // 0 == Already timed out (NOTE: To simulate the same behavior as sync we will only timeout on 0 if we receive an IO Pending from SNI) // >0 == Actual timeout remaining int msecsRemaining = GetTimeoutRemaining(); if (msecsRemaining > 0) { @@ -2202,7 +2206,7 @@ namespace System.Data.SqlClient { ChangeNetworkPacketTimeout(0, Timeout.Infinite); } // DO NOT HANDLE PENDING READ HERE - which is TdsEnums.SNI_SUCCESS_IO_PENDING state. - // That is handled by user who initiated async read, or by ReadNetworkPacket which is [....] over async. + // That is handled by user who initiated async read, or by ReadNetworkPacket which is sync over async. } finally { if (readPacket != IntPtr.Zero) { @@ -2368,7 +2372,7 @@ namespace System.Data.SqlClient { else { if (_parser._loginWithFailover) { - // For DbMirroring Failover during login, never break the connection, just close the TdsParser + // For DB Mirroring Failover during login, never break the connection, just close the TdsParser (Devdiv 846298) _parser.Disconnect(); } else if ((_parser.State == TdsParserState.OpenNotLoggedIn) && (_parser.Connection.ConnectionOptions.MultiSubnetFailover)) @@ -2441,7 +2445,7 @@ namespace System.Data.SqlClient { AssertValidState(); } else { - throw SQL.ParsingError(); + throw SQL.ParsingError(ParsingErrorState.ProcessSniPacketFailed); } } } @@ -3080,7 +3084,7 @@ namespace System.Data.SqlClient { Debug.Assert(Parser.Connection._parserLock.ThreadMayHaveLock(), "Thread is writing without taking the connection lock"); Task task = SNIWritePacket(Handle, packet, out sniError, canAccumulate, callerHasConnectionLock: true); - // Check to see if the timeout has occured. This time out code is special case code to allow BCP writes to timeout to fix bug 350558, eventually we should make all writes timeout. + // Check to see if the timeout has occured. This time out code is special case code to allow BCP writes to timeout to fix if (_bulkCopyOpperationInProgress && 0 == GetTimeoutRemaining()) { _parser.Connection.ThreadHasParserLockForClose = true; try { @@ -3274,7 +3278,7 @@ namespace System.Data.SqlClient { internal void AddError(SqlError error) { Debug.Assert(error != null, "Trying to add a null error"); - // Switch to [....] once we see an error + // Switch to sync once we see an error _syncOverAsync = true; lock (_errorAndWarningsLock) { @@ -3308,7 +3312,7 @@ namespace System.Data.SqlClient { internal void AddWarning(SqlError error) { Debug.Assert(error != null, "Trying to add a null error"); - // Switch to [....] once we see a warning + // Switch to sync once we see a warning _syncOverAsync = true; lock (_errorAndWarningsLock){ diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParserStaticMethods.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParserStaticMethods.cs index 3b85484e02c..853e76fc23f 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParserStaticMethods.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsParserStaticMethods.cs @@ -2,8 +2,8 @@ // <copyright file="TdsParserStaticFunctionality.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { @@ -67,7 +67,7 @@ namespace System.Data.SqlClient { if (index+1 < aliasLookup.Length) { string parsedAliasName = aliasLookup.Substring(index+1); - // Fix bug 298286 + // Fix if ("dbnetlib" == parsedProtocol) { index = parsedAliasName.IndexOf(':'); if (-1 != index && index + 1 < parsedAliasName.Length) { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsRecordBufferSetter.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsRecordBufferSetter.cs index 406e2b7b614..7b856bb8500 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsRecordBufferSetter.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsRecordBufferSetter.cs @@ -2,8 +2,8 @@ // <copyright file="TdsRecordBufferSetter.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsValueSetter.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsValueSetter.cs index aef315de517..63e1dd4ed68 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsValueSetter.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/TdsValueSetter.cs @@ -2,8 +2,8 @@ // <copyright file="TdsValueSetter.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/assemblycache.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/assemblycache.cs index b843d552997..5fd69f0547e 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/assemblycache.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/assemblycache.cs @@ -2,8 +2,8 @@ // <copyright file="assemblycache.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/sqlinternaltransaction.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/sqlinternaltransaction.cs index c717a8efc77..e09e10f5aa8 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/sqlinternaltransaction.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/sqlinternaltransaction.cs @@ -2,8 +2,8 @@ // <copyright file="SqlInternalTransaction.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">Microsoft</owner> +// <owner current="true" primary="false">Microsoft</owner> //------------------------------------------------------------------------------ namespace System.Data.SqlClient { diff --git a/mcs/class/referencesource/System.Data/System/Data/SqlClient/sqlmetadatafactory.cs b/mcs/class/referencesource/System.Data/System/Data/SqlClient/sqlmetadatafactory.cs index 24cfe1c3ee8..f17f069a00f 100644 --- a/mcs/class/referencesource/System.Data/System/Data/SqlClient/sqlmetadatafactory.cs +++ b/mcs/class/referencesource/System.Data/System/Data/SqlClient/sqlmetadatafactory.cs @@ -3,7 +3,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // </copyright> // -// <owner current="true" primary="true">[....]</owner> +// <owner current="true" primary="true">Microsoft</owner> // <owner current="true" primary="false">Mugunm</owner> // //------------------------------------------------------------------------------ |