3
3
// See the LICENSE file in the project root for more information.
4
4
5
5
using System ;
6
+ using System . Collections . Concurrent ;
6
7
using System . Collections . Generic ;
7
8
using System . Data ;
8
9
using System . Diagnostics ;
@@ -22,7 +23,7 @@ sealed internal class SqlQueryMetadataCache
22
23
const int CacheTrimThreshold = 300 ; // Threshold above the cache size when we start trimming.
23
24
24
25
private readonly MemoryCache _cache ;
25
- private static readonly SqlQueryMetadataCache _singletonInstance = new SqlQueryMetadataCache ( ) ;
26
+ private static readonly SqlQueryMetadataCache _singletonInstance = new ( ) ;
26
27
private int _inTrim = 0 ;
27
28
private long _cacheHits = 0 ;
28
29
private long _cacheMisses = 0 ;
@@ -53,17 +54,17 @@ internal bool GetQueryMetadataIfExists(SqlCommand sqlCommand)
53
54
}
54
55
55
56
// Check the cache to see if we have the MD for this query cached.
56
- string cacheLookupKey = GetCacheLookupKeyFromSqlCommand ( sqlCommand ) ;
57
- if ( cacheLookupKey == null )
57
+ ( string cacheLookupKey , string enclaveLookupKey ) = GetCacheLookupKeysFromSqlCommand ( sqlCommand ) ;
58
+ if ( cacheLookupKey is null )
58
59
{
59
60
IncrementCacheMisses ( ) ;
60
61
return false ;
61
62
}
62
63
63
- Dictionary < string , SqlCipherMetadata > ciperMetadataDictionary = _cache . Get ( cacheLookupKey ) as Dictionary < string , SqlCipherMetadata > ;
64
+ Dictionary < string , SqlCipherMetadata > cipherMetadataDictionary = _cache . Get ( cacheLookupKey ) as Dictionary < string , SqlCipherMetadata > ;
64
65
65
66
// If we had a cache miss just return false.
66
- if ( ciperMetadataDictionary == null )
67
+ if ( cipherMetadataDictionary is null )
67
68
{
68
69
IncrementCacheMisses ( ) ;
69
70
return false ;
@@ -73,7 +74,7 @@ internal bool GetQueryMetadataIfExists(SqlCommand sqlCommand)
73
74
foreach ( SqlParameter param in sqlCommand . Parameters )
74
75
{
75
76
SqlCipherMetadata paramCiperMetadata ;
76
- bool found = ciperMetadataDictionary . TryGetValue ( param . ParameterNameFixed , out paramCiperMetadata ) ;
77
+ bool found = cipherMetadataDictionary . TryGetValue ( param . ParameterNameFixed , out paramCiperMetadata ) ;
77
78
78
79
// If we failed to identify the encryption for a specific parameter, clear up the cipher MD of all parameters and exit.
79
80
if ( ! found )
@@ -88,7 +89,7 @@ internal bool GetQueryMetadataIfExists(SqlCommand sqlCommand)
88
89
}
89
90
90
91
// Cached cipher MD should never have an initialized algorithm since this would contain the key.
91
- Debug . Assert ( paramCiperMetadata == null || ! paramCiperMetadata . IsAlgorithmInitialized ( ) ) ;
92
+ Debug . Assert ( paramCiperMetadata is null || ! paramCiperMetadata . IsAlgorithmInitialized ( ) ) ;
92
93
93
94
// We were able to identify the cipher MD for this parameter, so set it on the param.
94
95
param . CipherMetadata = paramCiperMetadata ;
@@ -100,7 +101,7 @@ internal bool GetQueryMetadataIfExists(SqlCommand sqlCommand)
100
101
{
101
102
SqlCipherMetadata cipherMdCopy = null ;
102
103
103
- if ( param . CipherMetadata != null )
104
+ if ( param . CipherMetadata is not null )
104
105
{
105
106
cipherMdCopy = new SqlCipherMetadata (
106
107
param . CipherMetadata . EncryptionInfo ,
@@ -113,7 +114,7 @@ internal bool GetQueryMetadataIfExists(SqlCommand sqlCommand)
113
114
114
115
param . CipherMetadata = cipherMdCopy ;
115
116
116
- if ( cipherMdCopy != null )
117
+ if ( cipherMdCopy is not null )
117
118
{
118
119
// Try to get the encryption key. If the key information is stale, this might fail.
119
120
// In this case, just fail the cache lookup.
@@ -143,6 +144,13 @@ internal bool GetQueryMetadataIfExists(SqlCommand sqlCommand)
143
144
}
144
145
}
145
146
147
+ ConcurrentDictionary < int , SqlTceCipherInfoEntry > enclaveKeys =
148
+ _cache . Get ( enclaveLookupKey ) as ConcurrentDictionary < int , SqlTceCipherInfoEntry > ;
149
+ if ( enclaveKeys is not null )
150
+ {
151
+ sqlCommand . keysToBeSentToEnclave = CreateCopyOfEnclaveKeys ( enclaveKeys ) ;
152
+ }
153
+
146
154
IncrementCacheHits ( ) ;
147
155
return true ;
148
156
}
@@ -178,19 +186,19 @@ internal void AddQueryMetadata(SqlCommand sqlCommand, bool ignoreQueriesWithRetu
178
186
}
179
187
180
188
// Construct the entry and put it in the cache.
181
- string cacheLookupKey = GetCacheLookupKeyFromSqlCommand ( sqlCommand ) ;
182
- if ( cacheLookupKey == null )
189
+ ( string cacheLookupKey , string enclaveLookupKey ) = GetCacheLookupKeysFromSqlCommand ( sqlCommand ) ;
190
+ if ( cacheLookupKey is null )
183
191
{
184
192
return ;
185
193
}
186
194
187
- Dictionary < string , SqlCipherMetadata > ciperMetadataDictionary = new Dictionary < string , SqlCipherMetadata > ( sqlCommand . Parameters . Count ) ;
195
+ Dictionary < string , SqlCipherMetadata > cipherMetadataDictionary = new ( sqlCommand . Parameters . Count ) ;
188
196
189
197
// Create a copy of the cipherMD that doesn't have the algorithm and put it in the cache.
190
198
foreach ( SqlParameter param in sqlCommand . Parameters )
191
199
{
192
200
SqlCipherMetadata cipherMdCopy = null ;
193
- if ( param . CipherMetadata != null )
201
+ if ( param . CipherMetadata is not null )
194
202
{
195
203
cipherMdCopy = new SqlCipherMetadata (
196
204
param . CipherMetadata . EncryptionInfo ,
@@ -202,9 +210,9 @@ internal void AddQueryMetadata(SqlCommand sqlCommand, bool ignoreQueriesWithRetu
202
210
}
203
211
204
212
// Cached cipher MD should never have an initialized algorithm since this would contain the key.
205
- Debug . Assert ( cipherMdCopy == null || ! cipherMdCopy . IsAlgorithmInitialized ( ) ) ;
213
+ Debug . Assert ( cipherMdCopy is null || ! cipherMdCopy . IsAlgorithmInitialized ( ) ) ;
206
214
207
- ciperMetadataDictionary . Add ( param . ParameterNameFixed , cipherMdCopy ) ;
215
+ cipherMetadataDictionary . Add ( param . ParameterNameFixed , cipherMdCopy ) ;
208
216
}
209
217
210
218
// If the size of the cache exceeds the threshold, set that we are in trimming and trim the cache accordingly.
@@ -228,21 +236,27 @@ internal void AddQueryMetadata(SqlCommand sqlCommand, bool ignoreQueriesWithRetu
228
236
}
229
237
230
238
// By default evict after 10 hours.
231
- _cache . Set ( cacheLookupKey , ciperMetadataDictionary , DateTimeOffset . UtcNow . AddHours ( 10 ) ) ;
239
+ _cache . Set ( cacheLookupKey , cipherMetadataDictionary , DateTimeOffset . UtcNow . AddHours ( 10 ) ) ;
240
+ if ( sqlCommand . requiresEnclaveComputations )
241
+ {
242
+ ConcurrentDictionary < int , SqlTceCipherInfoEntry > keysToBeCached = CreateCopyOfEnclaveKeys ( sqlCommand . keysToBeSentToEnclave ) ;
243
+ _cache . Set ( enclaveLookupKey , keysToBeCached , DateTimeOffset . UtcNow . AddHours ( 10 ) ) ;
244
+ }
232
245
}
233
246
234
247
/// <summary>
235
248
/// <para> Remove the metadata for a specific query from the cache.</para>
236
249
/// </summary>
237
250
internal void InvalidateCacheEntry ( SqlCommand sqlCommand )
238
251
{
239
- string cacheLookupKey = GetCacheLookupKeyFromSqlCommand ( sqlCommand ) ;
240
- if ( cacheLookupKey == null )
252
+ ( string cacheLookupKey , string enclaveLookupKey ) = GetCacheLookupKeysFromSqlCommand ( sqlCommand ) ;
253
+ if ( cacheLookupKey is null )
241
254
{
242
255
return ;
243
256
}
244
257
245
258
_cache . Remove ( cacheLookupKey ) ;
259
+ _cache . Remove ( enclaveLookupKey ) ;
246
260
}
247
261
248
262
@@ -271,26 +285,46 @@ private void ResetCacheCounts()
271
285
_cacheMisses = 0 ;
272
286
}
273
287
274
- private String GetCacheLookupKeyFromSqlCommand ( SqlCommand sqlCommand )
288
+ private ( string , string ) GetCacheLookupKeysFromSqlCommand ( SqlCommand sqlCommand )
275
289
{
276
290
const int SqlIdentifierLength = 128 ;
277
291
278
292
SqlConnection connection = sqlCommand . Connection ;
279
293
280
294
// Return null if we have no connection.
281
- if ( connection == null )
295
+ if ( connection is null )
282
296
{
283
- return null ;
297
+ return ( null , null ) ;
284
298
}
285
299
286
- StringBuilder cacheLookupKeyBuilder = new StringBuilder ( connection . DataSource , capacity : connection . DataSource . Length + SqlIdentifierLength + sqlCommand . CommandText . Length + 6 ) ;
300
+ StringBuilder cacheLookupKeyBuilder = new ( connection . DataSource , capacity : connection . DataSource . Length + SqlIdentifierLength + sqlCommand . CommandText . Length + 6 ) ;
287
301
cacheLookupKeyBuilder . Append ( ":::" ) ;
288
302
// Pad database name to 128 characters to avoid any false cache matches because of weird DB names.
289
303
cacheLookupKeyBuilder . Append ( connection . Database . PadRight ( SqlIdentifierLength ) ) ;
290
304
cacheLookupKeyBuilder . Append ( ":::" ) ;
291
305
cacheLookupKeyBuilder . Append ( sqlCommand . CommandText ) ;
292
306
293
- return cacheLookupKeyBuilder . ToString ( ) ;
307
+ string cacheLookupKey = cacheLookupKeyBuilder . ToString ( ) ;
308
+ string enclaveLookupKey = cacheLookupKeyBuilder . Append ( ":::enclaveKeys" ) . ToString ( ) ;
309
+ return ( cacheLookupKey , enclaveLookupKey ) ;
310
+ }
311
+
312
+ private ConcurrentDictionary < int , SqlTceCipherInfoEntry > CreateCopyOfEnclaveKeys ( ConcurrentDictionary < int , SqlTceCipherInfoEntry > keysToBeSentToEnclave )
313
+ {
314
+ ConcurrentDictionary < int , SqlTceCipherInfoEntry > enclaveKeys = new ( ) ;
315
+ foreach ( KeyValuePair < int , SqlTceCipherInfoEntry > kvp in keysToBeSentToEnclave )
316
+ {
317
+ int ordinal = kvp . Key ;
318
+ SqlTceCipherInfoEntry original = kvp . Value ;
319
+ SqlTceCipherInfoEntry copy = new ( ordinal ) ;
320
+ foreach ( SqlEncryptionKeyInfo cekInfo in original . ColumnEncryptionKeyValues )
321
+ {
322
+ copy . Add ( cekInfo . encryptedKey , cekInfo . databaseId , cekInfo . cekId , cekInfo . cekVersion ,
323
+ cekInfo . cekMdVersion , cekInfo . keyPath , cekInfo . keyStoreName , cekInfo . algorithmName ) ;
324
+ }
325
+ enclaveKeys . TryAdd ( ordinal , copy ) ;
326
+ }
327
+ return enclaveKeys ;
294
328
}
295
329
}
296
330
}
0 commit comments