| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107 |
- //------------------------------------------------------------------------------
- // <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;
- }
- }
- }
|