1 //------------------------------------------------------------------------------
2 // <copyright file="SqlSymmetricKeyCache.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">krishnib</owner>
6 // <owner current="true" primary="false">balnee</owner>
7 //------------------------------------------------------------------------------
9 namespace System.Data.SqlClient {
11 using System.Collections.Generic;
12 using System.Collections.Concurrent;
13 using System.Diagnostics;
14 using System.Globalization;
19 /// <para> Implements a cache of Symmetric Keys (once they are decrypted).Useful for rapidly decrypting multiple data values.</para>
21 sealed internal class SqlSymmetricKeyCache {
22 private readonly ConcurrentDictionary<string,SqlClientSymmetricKey> _cache;
23 private static readonly SqlSymmetricKeyCache _singletonInstance = new SqlSymmetricKeyCache();
26 private SqlSymmetricKeyCache () {
27 _cache = new ConcurrentDictionary<string, SqlClientSymmetricKey>(concurrencyLevel: 4 * Environment.ProcessorCount /* default value in ConcurrentDictionary*/, capacity: 2);
30 internal static SqlSymmetricKeyCache GetInstance () {
31 return _singletonInstance;
35 /// <para> Retrieves Symmetric Key (in plaintext) given the encryption material.</para>
37 internal bool GetKey (SqlEncryptionKeyInfo keyInfo, string serverName, out SqlClientSymmetricKey encryptionKey) {
38 Debug.Assert(serverName != null, @"serverName should not be null.");
40 StringBuilder cacheLookupKeyBuilder = new StringBuilder(serverName, capacity: serverName.Length + SqlSecurityUtility.GetBase64LengthFromByteLength(keyInfo.encryptedKey.Length) + keyInfo.keyStoreName.Length + 2/*separators*/);
43 int capacity = cacheLookupKeyBuilder.Capacity;
46 cacheLookupKeyBuilder.Append(":");
47 cacheLookupKeyBuilder.Append(Convert.ToBase64String(keyInfo.encryptedKey));
48 cacheLookupKeyBuilder.Append(":");
49 cacheLookupKeyBuilder.Append(keyInfo.keyStoreName);
51 string cacheLookupKey = cacheLookupKeyBuilder.ToString();
54 Debug.Assert(cacheLookupKey.Length <= capacity, "We needed to allocate a larger array");
59 // Lookup the key in cache
60 if (!_cache.TryGetValue(cacheLookupKey, out encryptionKey)) {
61 Debug.Assert(SqlConnection.ColumnEncryptionTrustedMasterKeyPaths != null, @"SqlConnection.ColumnEncryptionTrustedMasterKeyPaths should not be null");
63 // Check against the trusted key paths
65 // Get the List corresponding to the connected server
66 IList<string> trustedKeyPaths;
67 if (SqlConnection.ColumnEncryptionTrustedMasterKeyPaths.TryGetValue(serverName, out trustedKeyPaths)) {
68 // If the list is null or is empty or if the keyPath doesn't exist in the trusted key paths, then throw an exception.
69 if ((trustedKeyPaths == null) || (trustedKeyPaths.Count() == 0) ||
70 // (trustedKeyPaths.Where(s => s.Equals(keyInfo.keyPath, StringComparison.InvariantCultureIgnoreCase)).Count() == 0)) {
71 (trustedKeyPaths.Any(s => s.Equals(keyInfo.keyPath, StringComparison.InvariantCultureIgnoreCase)) == false)) {
72 // throw an exception since the key path is not in the trusted key paths list for this server
73 throw SQL.UntrustedKeyPath(keyInfo.keyPath, serverName);
77 // Key Not found, attempt to look up the provider and decrypt CEK
78 SqlColumnEncryptionKeyStoreProvider provider;
79 if (!SqlConnection.TryGetColumnEncryptionKeyStoreProvider(keyInfo.keyStoreName, out provider)) {
80 throw SQL.UnrecognizedKeyStoreProviderName(keyInfo.keyStoreName,
81 SqlConnection.GetColumnEncryptionSystemKeyStoreProviders(),
82 SqlConnection.GetColumnEncryptionCustomKeyStoreProviders());
86 // We will simply bubble up the exception from the DecryptColumnEncryptionKey function.
89 plaintextKey = provider.DecryptColumnEncryptionKey(keyInfo.keyPath, keyInfo.algorithmName, keyInfo.encryptedKey);
92 // Generate a new exception and throw.
93 string keyHex = SqlSecurityUtility.GetBytesAsString(keyInfo.encryptedKey, fLast:true, countOfBytes:10);
94 throw SQL.KeyDecryptionFailed (keyInfo.keyStoreName, keyHex, e);
97 encryptionKey = new SqlClientSymmetricKey (plaintextKey);
99 // In case multiple threads reach here at the same time, the first one wins.
100 // The allocated memory will be reclaimed by Garbage Collector.
101 _cache.TryAdd(cacheLookupKey, encryptionKey);