Esempio n. 1
0
        /// <summary>
        /// Decrypt the keys that need to be sent to the enclave
        /// </summary>
        /// <param name="keysTobeSentToEnclave">Keys that need to sent to the enclave</param>
        /// <param name="serverName"></param>
        /// <returns></returns>
        private List <ColumnEncryptionKeyInfo> GetDecryptedKeysToBeSentToEnclave(Dictionary <int, SqlTceCipherInfoEntry> keysTobeSentToEnclave, string serverName)
        {
            List <ColumnEncryptionKeyInfo> decryptedKeysToBeSentToEnclave = new List <ColumnEncryptionKeyInfo>();

            foreach (SqlTceCipherInfoEntry cipherInfo in keysTobeSentToEnclave.Values)
            {
                SqlClientSymmetricKey sqlClientSymmetricKey   = null;
                SqlEncryptionKeyInfo? encryptionkeyInfoChosen = null;
                SqlSecurityUtility.DecryptSymmetricKey(cipherInfo, serverName, out sqlClientSymmetricKey,
                                                       out encryptionkeyInfoChosen);

                if (sqlClientSymmetricKey == null)
                {
                    throw SQL.NullArgumentInternal("sqlClientSymmetricKey", ClassName, GetDecryptedKeysToBeSentToEnclaveName);
                }
                if (cipherInfo.ColumnEncryptionKeyValues == null)
                {
                    throw SQL.NullArgumentInternal("ColumnEncryptionKeyValues", ClassName, GetDecryptedKeysToBeSentToEnclaveName);
                }
                if (!(cipherInfo.ColumnEncryptionKeyValues.Count > 0))
                {
                    throw SQL.ColumnEncryptionKeysNotFound();
                }

                //cipherInfo.CekId is always 0, hence used cipherInfo.ColumnEncryptionKeyValues[0].cekId. Even when cek has multiple ColumnEncryptionKeyValues
                //the cekid and the plaintext value will remain the same, what varies is the encrypted cek value, since the cek can be encrypted by
                //multiple CMKs
                decryptedKeysToBeSentToEnclave.Add(new ColumnEncryptionKeyInfo(sqlClientSymmetricKey.RootKey,
                                                                               cipherInfo.ColumnEncryptionKeyValues[0].databaseId,
                                                                               cipherInfo.ColumnEncryptionKeyValues[0].cekMdVersion, cipherInfo.ColumnEncryptionKeyValues[0].cekId));
            }
            return(decryptedKeysToBeSentToEnclave);
        }
Esempio n. 2
0
        /// <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>
        /// 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));
        }
Esempio n. 4
0
        /// <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)
            {
                throw SQL.NullCipherText();
            }

            return(cipherText);
        }
Esempio n. 5
0
        private string GetCacheLookupKey(string masterKeyPath, bool allowEnclaveComputations, byte[] signature, string keyStoreName)
        {
            StringBuilder cacheLookupKeyBuilder = new StringBuilder(keyStoreName,
                                                                    capacity:
                                                                    keyStoreName.Length +
                                                                    masterKeyPath.Length +
                                                                    SqlSecurityUtility.GetBase64LengthFromByteLength(signature.Length) +
                                                                    3 /*separators*/ +
                                                                    10 /*boolean value + somebuffer*/);

            cacheLookupKeyBuilder.Append(_cacheLookupKeySeparator);
            cacheLookupKeyBuilder.Append(masterKeyPath);
            cacheLookupKeyBuilder.Append(_cacheLookupKeySeparator);
            cacheLookupKeyBuilder.Append(allowEnclaveComputations);
            cacheLookupKeyBuilder.Append(_cacheLookupKeySeparator);
            cacheLookupKeyBuilder.Append(Convert.ToBase64String(signature));
            cacheLookupKeyBuilder.Append(_cacheLookupKeySeparator);
            string cacheLookupKey = cacheLookupKeyBuilder.ToString();

            return(cacheLookupKey);
        }
Esempio n. 6
0
        /// <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>
        /// <para> Retrieves the query metadata for a specific query from the cache.</para>
        /// </summary>
        internal bool GetQueryMetadataIfExists(SqlCommand sqlCommand)
        {
            // Return immediately if caching is disabled.
            if (!SqlConnection.ColumnEncryptionQueryMetadataCacheEnabled)
            {
                return(false);
            }

            // Check the cache to see if we have the MD for this query cached.
            string cacheLookupKey = GetCacheLookupKeyFromSqlCommand(sqlCommand);

            if (cacheLookupKey == null)
            {
                IncrementCacheMisses();
                return(false);
            }

            Dictionary <string, SqlCipherMetadata> ciperMetadataDictionary = _cache.Get(cacheLookupKey) as Dictionary <string, SqlCipherMetadata>;

            // If we had a cache miss just return false.
            if (ciperMetadataDictionary == null)
            {
                IncrementCacheMisses();
                return(false);
            }

            // Iterate over all the parameters and try to get their cipher MD.
            foreach (SqlParameter param in sqlCommand.Parameters)
            {
                SqlCipherMetadata paramCiperMetadata;
                bool found = ciperMetadataDictionary.TryGetValue(param.ParameterNameFixed, out paramCiperMetadata);

                // If we failed to identify the encryption for a specific parameter, clear up the cipher MD of all parameters and exit.
                if (!found)
                {
                    foreach (SqlParameter paramToCleanup in sqlCommand.Parameters)
                    {
                        paramToCleanup.CipherMetadata = null;
                    }

                    IncrementCacheMisses();
                    return(false);
                }

                // Cached cipher MD should never have an initialized algorithm since this would contain the key.
                Debug.Assert(paramCiperMetadata == null || !paramCiperMetadata.IsAlgorithmInitialized());

                // We were able to identify the cipher MD for this parameter, so set it on the param.
                param.CipherMetadata = paramCiperMetadata;
            }

            // Create a copy of the cipherMD in order to load the key.
            // The key shouldn't be loaded in the cached version for security reasons.
            foreach (SqlParameter param in sqlCommand.Parameters)
            {
                SqlCipherMetadata cipherMdCopy = null;

                if (param.CipherMetadata != null)
                {
                    cipherMdCopy = new SqlCipherMetadata(
                        param.CipherMetadata.EncryptionInfo,
                        0,
                        param.CipherMetadata.CipherAlgorithmId,
                        param.CipherMetadata.CipherAlgorithmName,
                        param.CipherMetadata.EncryptionType,
                        param.CipherMetadata.NormalizationRuleVersion);
                }

                param.CipherMetadata = cipherMdCopy;

                if (cipherMdCopy != null)
                {
                    // Try to get the encryption key. If the key information is stale, this might fail.
                    // In this case, just fail the cache lookup.
                    try
                    {
                        SqlSecurityUtility.DecryptSymmetricKey(cipherMdCopy, sqlCommand.Connection.DataSource);
                    }
                    catch (Exception ex)
                    {
                        // Invalidate the cache entry.
                        InvalidateCacheEntry(sqlCommand);

                        // If we get one of the expected exceptions, just fail the cache lookup, otherwise throw.
                        if (ex is SqlException || ex is ArgumentException || ex is ArgumentNullException)
                        {
                            foreach (SqlParameter paramToCleanup in sqlCommand.Parameters)
                            {
                                paramToCleanup.CipherMetadata = null;
                            }

                            IncrementCacheMisses();
                            return(false);
                        }

                        throw;
                    }
                }
            }

            IncrementCacheHits();
            return(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);
        }
Esempio n. 9
0
        /// <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);
        }
        /// <summary>
        /// <para> Retrieves Symmetric Key (in plaintext) given the encryption material.</para>
        /// </summary>
        internal SqlClientSymmetricKey GetKey(SqlEncryptionKeyInfo keyInfo, SqlConnection connection, SqlCommand command)
        {
            string serverName = connection.DataSource;

            Debug.Assert(serverName is not 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

            // Lookup the key in cache
            if (!(_cache.Get(cacheLookupKey) is SqlClientSymmetricKey encryptionKey))
            {
                Debug.Assert(SqlConnection.ColumnEncryptionTrustedMasterKeyPaths is not null, @"SqlConnection.ColumnEncryptionTrustedMasterKeyPaths should not be null");

                SqlSecurityUtility.ThrowIfKeyPathIsNotTrustedForServer(serverName, keyInfo.keyPath);

                // Key Not found, attempt to look up the provider and decrypt CEK
                if (!SqlSecurityUtility.TryGetColumnEncryptionKeyStoreProvider(keyInfo.keyStoreName, out SqlColumnEncryptionKeyStoreProvider provider, connection, command))
                {
                    throw SQL.UnrecognizedKeyStoreProviderName(keyInfo.keyStoreName,
                                                               SqlConnection.GetColumnEncryptionSystemKeyStoreProvidersNames(),
                                                               SqlSecurityUtility.GetListOfProviderNamesThatWereSearched(connection, command));
                }

                // Decrypt the CEK
                // We will simply bubble up the exception from the DecryptColumnEncryptionKey function.
                byte[] plaintextKey;
                try
                {
                    // to prevent conflicts between CEK caches, global providers should not use their own CEK caches
                    provider.ColumnEncryptionKeyCacheTtl = new TimeSpan(0);
                    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);

                // If the cache TTL is zero, don't even bother inserting to the cache.
                if (SqlConnection.ColumnEncryptionKeyCacheTtl != TimeSpan.Zero)
                {
                    // In case multiple threads reach here at the same time, the first one wins.
                    // The allocated memory will be reclaimed by Garbage Collector.
                    DateTimeOffset expirationTime = DateTimeOffset.UtcNow.Add(SqlConnection.ColumnEncryptionKeyCacheTtl);
                    _cache.Add(cacheLookupKey, encryptionKey, expirationTime);
                }
            }

            return(encryptionKey);
        }
Esempio n. 11
0
        /// <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

            // Lookup the key in cache
            encryptionKey = _cache.Get(cacheLookupKey) as SqlClientSymmetricKey;

            if (encryptionKey == null)
            {
                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);

                // If the cache TTL is zero, don't even bother inserting to the cache.
                if (SqlConnection.ColumnEncryptionKeyCacheTtl != TimeSpan.Zero)
                {
                    // In case multiple threads reach here at the same time, the first one wins.
                    // The allocated memory will be reclaimed by Garbage Collector.
                    DateTimeOffset expirationTime = DateTimeOffset.UtcNow.Add(SqlConnection.ColumnEncryptionKeyCacheTtl);
                    _cache.Add(cacheLookupKey, encryptionKey, expirationTime);
                }
            }

            return(true);
        }