private static SqlClientSymmetricKey GetKeyFromLocalProviders(SqlEncryptionKeyInfo keyInfo, SqlConnection connection, SqlCommand command) { string serverName = connection.DataSource; Debug.Assert(serverName is not null, @"serverName should not be null."); Debug.Assert(SqlConnection.ColumnEncryptionTrustedMasterKeyPaths is not null, @"SqlConnection.ColumnEncryptionTrustedMasterKeyPaths should not be null"); ThrowIfKeyPathIsNotTrustedForServer(serverName, keyInfo.keyPath); if (!TryGetColumnEncryptionKeyStoreProvider(keyInfo.keyStoreName, out SqlColumnEncryptionKeyStoreProvider provider, connection, command)) { throw SQL.UnrecognizedKeyStoreProviderName(keyInfo.keyStoreName, SqlConnection.GetColumnEncryptionSystemKeyStoreProvidersNames(), GetListOfProviderNamesThatWereSearched(connection, command)); } // 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 = GetBytesAsString(keyInfo.encryptedKey, fLast: true, countOfBytes: 10); throw SQL.KeyDecryptionFailed(keyInfo.keyStoreName, keyHex, e); } return(new SqlClientSymmetricKey(plaintextKey)); }
/// <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 { encryptedKey = encryptedKey, databaseId = databaseId, cekId = cekId, cekVersion = cekVersion, cekMdVersion = cekMdVersion, keyPath = keyPath, keyStoreName = keyStoreName, 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="sqlTceCipherInfoEntry"></param> /// <param name="ordinal"></param> /// <param name="cipherAlgorithmId"></param> /// <param name="cipherAlgorithmName"></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> /// <para> Decrypts the symmetric key and saves it in metadata. In addition, initializes /// the SqlClientEncryptionAlgorithm for rapid decryption.</para> /// </summary> internal static void DecryptSymmetricKey(SqlCipherMetadata md, SqlConnection connection, SqlCommand command) { Debug.Assert(md is not null, "md should not be null in DecryptSymmetricKey."); SqlClientSymmetricKey symKey = null; SqlEncryptionKeyInfo encryptionkeyInfoChosen = null; DecryptSymmetricKey(md.EncryptionInfo, out symKey, out encryptionkeyInfoChosen, connection, command); // 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 is not null); md.CipherAlgorithm = cipherAlgorithm; md.EncryptionKeyInfo = encryptionkeyInfoChosen; return; }
/// <summary> /// Decrypts the symmetric key and saves it in metadata. /// </summary> internal static void DecryptSymmetricKey(SqlTceCipherInfoEntry sqlTceCipherInfoEntry, out SqlClientSymmetricKey sqlClientSymmetricKey, out SqlEncryptionKeyInfo encryptionkeyInfoChosen, SqlConnection connection, SqlCommand command) { Debug.Assert(sqlTceCipherInfoEntry is not null, "sqlTceCipherInfoEntry should not be null in DecryptSymmetricKey."); Debug.Assert(sqlTceCipherInfoEntry.ColumnEncryptionKeyValues is not null, "sqlTceCipherInfoEntry.ColumnEncryptionKeyValues should not be null in DecryptSymmetricKey."); sqlClientSymmetricKey = null; encryptionkeyInfoChosen = null; Exception lastException = null; SqlSymmetricKeyCache globalCekCache = SqlSymmetricKeyCache.GetInstance(); foreach (SqlEncryptionKeyInfo keyInfo in sqlTceCipherInfoEntry.ColumnEncryptionKeyValues) { try { sqlClientSymmetricKey = ShouldUseInstanceLevelProviderFlow(keyInfo.keyStoreName, connection, command) ? GetKeyFromLocalProviders(keyInfo, connection, command) : globalCekCache.GetKey(keyInfo, connection, command); encryptionkeyInfoChosen = keyInfo; break; } catch (Exception e) { lastException = e; } } if (sqlClientSymmetricKey is null) { Debug.Assert(lastException is not null, "CEK decryption failed without raising exceptions"); throw lastException; } Debug.Assert(encryptionkeyInfoChosen is not null, "encryptionkeyInfoChosen must have a value."); }
/// <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); }
/// <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); }