SqlSymmetricKeyCache.cs 5.4 KB

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