/// <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); }
/// <summary> /// Encrypt the byte package containing keys with the session key /// </summary> /// <param name="bytePackage">byte package containing keys</param> /// <param name="sessionKey">session key used to encrypt the package</param> /// <param name="serverName">server hosting the enclave</param> /// <returns></returns> private byte[] EncryptBytePackage(byte[] bytePackage, byte[] sessionKey, string serverName) { if (sessionKey == null) { throw SQL.NullArgumentInternal("sessionKey", ClassName, "EncryptBytePackage"); } if (sessionKey.Length == 0) { throw SQL.EmptyArgumentInternal("sessionKey", ClassName, "EncryptBytePackage"); } //bytePackage is created internally in this class and is guaranteed to be non null and non empty try { SqlClientSymmetricKey symmetricKey = new SqlClientSymmetricKey(sessionKey); SqlClientEncryptionAlgorithm sqlClientEncryptionAlgorithm = SqlAeadAes256CbcHmac256Factory.Create(symmetricKey, SqlClientEncryptionType.Randomized, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName); return(sqlClientEncryptionAlgorithm.EncryptData(bytePackage)); } catch (Exception e) { throw SQL.FailedToEncryptRegisterRulesBytePackage(e); } }
/// <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> /// 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); }
/// <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> /// 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> /// 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> /// Decrypts the symmetric key and saves it in metadata. /// </summary> internal static void DecryptSymmetricKey(SqlTceCipherInfoEntry?sqlTceCipherInfoEntry, string serverName, out SqlClientSymmetricKey sqlClientSymmetricKey, out SqlEncryptionKeyInfo?encryptionkeyInfoChosen) { Debug.Assert(serverName != null, @"serverName should not be null in DecryptSymmetricKey."); Debug.Assert(sqlTceCipherInfoEntry.HasValue, "sqlTceCipherInfoEntry should not be null in DecryptSymmetricKey."); Debug.Assert(sqlTceCipherInfoEntry.Value.ColumnEncryptionKeyValues != null, "sqlTceCipherInfoEntry.ColumnEncryptionKeyValues should not be null in DecryptSymmetricKey."); sqlClientSymmetricKey = null; encryptionkeyInfoChosen = null; Exception lastException = null; SqlSymmetricKeyCache cache = SqlSymmetricKeyCache.GetInstance(); foreach (SqlEncryptionKeyInfo keyInfo in sqlTceCipherInfoEntry.Value.ColumnEncryptionKeyValues) { try { if (cache.GetKey(keyInfo, serverName, out sqlClientSymmetricKey)) { encryptionkeyInfoChosen = keyInfo; break; } } catch (Exception e) { lastException = e; } } if (null == sqlClientSymmetricKey) { Debug.Assert(null != lastException, "CEK decryption failed without raising exceptions"); throw lastException; } Debug.Assert(encryptionkeyInfoChosen.HasValue, "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); }