99
1010namespace Microsoft . Data . SqlClient
1111{
12+ /// <summary>
13+ /// Tri-state result returned by <see cref="ColumnMasterKeyMetadataSignatureVerificationCache.GetSignatureVerificationResult"/>.
14+ /// Distinguishes a cache miss from a cached negative result so callers cannot conflate the two.
15+ /// </summary>
16+ internal enum SignatureVerificationResult
17+ {
18+ /// <summary>
19+ /// No cached entry exists for the requested CMK metadata.
20+ /// The caller must verify the signature with the key store provider.
21+ /// </summary>
22+ NotFound ,
23+
24+ /// <summary>
25+ /// A cached entry exists and indicates that signature verification previously failed.
26+ /// </summary>
27+ False ,
28+
29+ /// <summary>
30+ /// A cached entry exists and indicates that signature verification previously succeeded.
31+ /// </summary>
32+ True ,
33+ }
34+
1235 /// <summary>
1336 /// Cache for storing result of signature verification of CMK Metadata
1437 /// </summary>
@@ -17,140 +40,157 @@ internal class ColumnMasterKeyMetadataSignatureVerificationCache
1740 private const int _cacheSize = 2000 ; // Cache size in number of entries.
1841 private const int _cacheTrimThreshold = 300 ; // Threshold above the cache size when we start trimming.
1942
20- private const string _className = "ColumnMasterKeyMetadataSignatureVerificationCache" ;
21- private const string _getSignatureVerificationResultMethodName = "GetSignatureVerificationResult" ;
22- private const string _addSignatureVerificationResultMethodName = "AddSignatureVerificationResult" ;
23- private const string _masterkeypathArgumentName = "masterKeyPath" ;
24- private const string _keyStoreNameArgumentName = "keyStoreName" ;
25- private const string _signatureName = "signature" ;
2643 private const string _cacheLookupKeySeparator = ":" ;
44+ private static readonly TimeSpan s_verificationCacheTimeout = TimeSpan . FromDays ( 10 ) ;
2745
28- private static readonly ColumnMasterKeyMetadataSignatureVerificationCache _signatureVerificationCache = new ColumnMasterKeyMetadataSignatureVerificationCache ( ) ;
29-
30- //singleton instance
31- internal static ColumnMasterKeyMetadataSignatureVerificationCache Instance { get { return _signatureVerificationCache ; } }
46+ /// <summary>
47+ /// Gets the process-wide singleton instance of the signature verification cache.
48+ /// </summary>
49+ internal static ColumnMasterKeyMetadataSignatureVerificationCache Instance { get ; } = new ( ) ;
3250
3351 private readonly MemoryCache _cache ;
34- private int _inTrim = 0 ;
52+ private int _inTrim ;
3553
3654 private ColumnMasterKeyMetadataSignatureVerificationCache ( )
3755 {
3856 _cache = new MemoryCache ( new MemoryCacheOptions ( ) ) ;
39- _inTrim = 0 ;
4057 }
4158
4259 /// <summary>
43- /// Get signature verification result for given CMK metadata (KeystoreName, MasterKeyPath, allowEnclaveComputations) and a given signature
60+ /// Get signature verification result for given CMK metadata
61+ /// (KeystoreName, MasterKeyPath, allowEnclaveComputations) and a given signature
4462 /// </summary>
4563 /// <param name="keyStoreName">Key Store name for CMK</param>
4664 /// <param name="masterKeyPath">Key Path for CMK</param>
4765 /// <param name="allowEnclaveComputations">boolean indicating whether the key can be sent to enclave</param>
4866 /// <param name="signature">Signature for the CMK metadata</param>
49- internal bool GetSignatureVerificationResult ( string keyStoreName , string masterKeyPath , bool allowEnclaveComputations , byte [ ] signature )
67+ /// <returns>Tri-state result indicating whether signature verification succeeded, failed, or was not found in cache</returns>
68+ /// <exception cref="System.ArgumentNullException">
69+ /// Thrown when <paramref name="masterKeyPath"/>, <paramref name="keyStoreName"/>,
70+ /// or <paramref name="signature"/> is <see langword="null"/>.
71+ /// </exception>
72+ /// <exception cref="System.ArgumentException">
73+ /// Thrown when <paramref name="masterKeyPath"/> or <paramref name="keyStoreName"/>
74+ /// is empty or whitespace, or when <paramref name="signature"/> has length zero.
75+ /// </exception>
76+ internal SignatureVerificationResult GetSignatureVerificationResult ( string keyStoreName , string masterKeyPath , bool allowEnclaveComputations , byte [ ] signature )
5077 {
51- ValidateStringArgumentNotNullOrEmpty ( masterKeyPath , _masterkeypathArgumentName , _getSignatureVerificationResultMethodName ) ;
52- ValidateStringArgumentNotNullOrEmpty ( keyStoreName , _keyStoreNameArgumentName , _getSignatureVerificationResultMethodName ) ;
53- ValidateSignatureNotNullOrEmpty ( signature , _getSignatureVerificationResultMethodName ) ;
78+ ValidateStringArgumentNotNullOrEmpty ( masterKeyPath , nameof ( masterKeyPath ) , nameof ( GetSignatureVerificationResult ) ) ;
79+ ValidateStringArgumentNotNullOrEmpty ( keyStoreName , nameof ( keyStoreName ) , nameof ( GetSignatureVerificationResult ) ) ;
80+ ValidateSignatureNotNullOrEmpty ( signature , nameof ( GetSignatureVerificationResult ) ) ;
5481
5582 string cacheLookupKey = GetCacheLookupKey ( masterKeyPath , allowEnclaveComputations , signature , keyStoreName ) ;
5683
57- return _cache . TryGetValue < bool > ( cacheLookupKey , out bool value ) ;
84+ if ( ! _cache . TryGetValue ( cacheLookupKey , out bool value ) )
85+ {
86+ return SignatureVerificationResult . NotFound ;
87+ }
88+
89+ return value ? SignatureVerificationResult . True : SignatureVerificationResult . False ;
5890 }
5991
6092 /// <summary>
61- /// Add signature verification result for given CMK metadata (KeystoreName, MasterKeyPath, allowEnclaveComputations) and a given signature in the cache
93+ /// Add signature verification result for given CMK metadata (KeystoreName,
94+ /// MasterKeyPath, allowEnclaveComputations) and a given signature in the cache
6295 /// </summary>
6396 /// <param name="keyStoreName">Key Store name for CMK</param>
6497 /// <param name="masterKeyPath">Key Path for CMK</param>
6598 /// <param name="allowEnclaveComputations">boolean indicating whether the key can be sent to enclave</param>
6699 /// <param name="signature">Signature for the CMK metadata</param>
67100 /// <param name="result">result indicating signature verification success/failure</param>
101+ /// <exception cref="System.ArgumentNullException">
102+ /// Thrown when <paramref name="masterKeyPath"/>, <paramref name="keyStoreName"/>,
103+ /// or <paramref name="signature"/> is <see langword="null"/>.
104+ /// </exception>
105+ /// <exception cref="System.ArgumentException">
106+ /// Thrown when <paramref name="masterKeyPath"/> or <paramref name="keyStoreName"/> is empty or whitespace,
107+ /// or when <paramref name="signature"/> has length zero.
108+ /// </exception>
68109 internal void AddSignatureVerificationResult ( string keyStoreName , string masterKeyPath , bool allowEnclaveComputations , byte [ ] signature , bool result )
69110 {
70- ValidateStringArgumentNotNullOrEmpty ( masterKeyPath , _masterkeypathArgumentName , _addSignatureVerificationResultMethodName ) ;
71- ValidateStringArgumentNotNullOrEmpty ( keyStoreName , _keyStoreNameArgumentName , _addSignatureVerificationResultMethodName ) ;
72- ValidateSignatureNotNullOrEmpty ( signature , _addSignatureVerificationResultMethodName ) ;
111+ ValidateStringArgumentNotNullOrEmpty ( masterKeyPath , nameof ( masterKeyPath ) , nameof ( AddSignatureVerificationResult ) ) ;
112+ ValidateStringArgumentNotNullOrEmpty ( keyStoreName , nameof ( keyStoreName ) , nameof ( AddSignatureVerificationResult ) ) ;
113+ ValidateSignatureNotNullOrEmpty ( signature , nameof ( AddSignatureVerificationResult ) ) ;
73114
74115 string cacheLookupKey = GetCacheLookupKey ( masterKeyPath , allowEnclaveComputations , signature , keyStoreName ) ;
75116
76117 TrimCacheIfNeeded ( ) ;
77118
78119 // By default evict after 10 days.
79- MemoryCacheEntryOptions options = new MemoryCacheEntryOptions
80- {
81- AbsoluteExpirationRelativeToNow = TimeSpan . FromDays ( 10 )
82- } ;
83- _cache . Set < bool > ( cacheLookupKey , result , options ) ;
120+ _cache . Set ( cacheLookupKey , result , absoluteExpirationRelativeToNow : s_verificationCacheTimeout ) ;
84121 }
85122
86- private void ValidateSignatureNotNullOrEmpty ( byte [ ] signature , string methodName )
123+ private static void ValidateSignatureNotNullOrEmpty ( byte [ ] signature , string methodName )
87124 {
88- if ( signature == null || signature . Length == 0 )
125+ if ( signature is null )
89126 {
90- if ( signature == null )
91- {
92- throw SQL . NullArgumentInternal ( _signatureName , _className , methodName ) ;
93- }
94- else
95- {
96- throw SQL . EmptyArgumentInternal ( _signatureName , _className , methodName ) ;
97- }
127+ throw SQL . NullArgumentInternal ( nameof ( signature ) , nameof ( ColumnMasterKeyMetadataSignatureVerificationCache ) , methodName ) ;
128+ }
129+ if ( signature . Length == 0 )
130+ {
131+ throw SQL . EmptyArgumentInternal ( nameof ( signature ) , nameof ( ColumnMasterKeyMetadataSignatureVerificationCache ) , methodName ) ;
98132 }
99133 }
100134
101- private void ValidateStringArgumentNotNullOrEmpty ( string stringArgValue , string stringArgName , string methodName )
135+ private static void ValidateStringArgumentNotNullOrEmpty ( string value , string argumentName , string methodName )
102136 {
103- if ( string . IsNullOrWhiteSpace ( stringArgValue ) )
137+ if ( value is null )
138+ {
139+ throw SQL . NullArgumentInternal ( argumentName , nameof ( ColumnMasterKeyMetadataSignatureVerificationCache ) , methodName ) ;
140+ }
141+ if ( string . IsNullOrWhiteSpace ( value ) )
104142 {
105- if ( stringArgValue == null )
106- {
107- throw SQL . NullArgumentInternal ( stringArgName , _className , methodName ) ;
108- }
109- else
110- {
111- throw SQL . EmptyArgumentInternal ( stringArgName , _className , methodName ) ;
112- }
143+ throw SQL . EmptyArgumentInternal ( argumentName , nameof ( ColumnMasterKeyMetadataSignatureVerificationCache ) , methodName ) ;
113144 }
114145 }
115146
116147 private void TrimCacheIfNeeded ( )
117148 {
118149 // If the size of the cache exceeds the threshold, set that we are in trimming and trim the cache accordingly.
119150 long currentCacheSize = _cache . Count ;
120- if ( ( currentCacheSize > _cacheSize + _cacheTrimThreshold ) && ( 0 == Interlocked . CompareExchange ( ref _inTrim , 1 , 0 ) ) )
151+ if ( currentCacheSize <= _cacheSize + _cacheTrimThreshold || Interlocked . CompareExchange ( ref _inTrim , 1 , 0 ) != 0 )
121152 {
122- try
123- {
124- // Example: 2301 - 2000 = 301; 301 / 2301 = 0.1308 * 100 = 13% compacting
125- _cache . Compact ( ( ( ( double ) ( currentCacheSize - _cacheSize ) / ( double ) currentCacheSize ) * 100 ) ) ;
126- }
127- finally
128- {
129- // Reset _inTrim flag
130- Interlocked . CompareExchange ( ref _inTrim , 0 , 1 ) ;
131- }
153+ return ;
154+ }
155+
156+ try
157+ {
158+ // Example: 2301 - 2000 = 301; 301 / 2301 = 0.1308 * 100 = 13% compacting
159+ _cache . Compact ( ( double ) ( currentCacheSize - _cacheSize ) / currentCacheSize * 100 ) ;
160+ }
161+ finally
162+ {
163+ Interlocked . Exchange ( ref _inTrim , 0 ) ;
132164 }
133165 }
134166
135- private string GetCacheLookupKey ( string masterKeyPath , bool allowEnclaveComputations , byte [ ] signature , string keyStoreName )
167+ /// <summary>
168+ /// Generates a cache key for the given CMK metadata and signature. The key is a
169+ /// concatenation of the key store name, master key path, allowEnclaveComputations value, and signature, separated by a delimiter.
170+ /// </summary>
171+ /// <param name="masterKeyPath">The master key path.</param>
172+ /// <param name="allowEnclaveComputations">Whether enclave computations are allowed.</param>
173+ /// <param name="signature">The signature.</param>
174+ /// <param name="keyStoreName">The key store name.</param>
175+ /// <returns>A string that can be used as a cache key.</returns>
176+ private static string GetCacheLookupKey ( string masterKeyPath , bool allowEnclaveComputations , byte [ ] signature , string keyStoreName )
136177 {
137- StringBuilder cacheLookupKeyBuilder = new StringBuilder ( keyStoreName ,
138- capacity :
139- keyStoreName . Length +
140- masterKeyPath . Length +
141- SqlSecurityUtility . GetBase64LengthFromByteLength ( signature . Length ) +
142- 3 /*separators*/ +
143- 10 /*boolean value + somebuffer*/ ) ;
144-
145- cacheLookupKeyBuilder . Append ( _cacheLookupKeySeparator ) ;
146- cacheLookupKeyBuilder . Append ( masterKeyPath ) ;
147- cacheLookupKeyBuilder . Append ( _cacheLookupKeySeparator ) ;
148- cacheLookupKeyBuilder . Append ( allowEnclaveComputations ) ;
149- cacheLookupKeyBuilder . Append ( _cacheLookupKeySeparator ) ;
150- cacheLookupKeyBuilder . Append ( Convert . ToBase64String ( signature ) ) ;
151- cacheLookupKeyBuilder . Append ( _cacheLookupKeySeparator ) ;
152- string cacheLookupKey = cacheLookupKeyBuilder . ToString ( ) ;
153- return cacheLookupKey ;
178+ int cacheCapacity =
179+ keyStoreName . Length +
180+ masterKeyPath . Length +
181+ SqlSecurityUtility . GetBase64LengthFromByteLength ( signature . Length ) +
182+ 4 * _cacheLookupKeySeparator . Length +
183+ 10 /* boolean value + buffer */ ;
184+
185+ return new StringBuilder ( keyStoreName , capacity : cacheCapacity )
186+ . Append ( _cacheLookupKeySeparator )
187+ . Append ( masterKeyPath )
188+ . Append ( _cacheLookupKeySeparator )
189+ . Append ( allowEnclaveComputations )
190+ . Append ( _cacheLookupKeySeparator )
191+ . Append ( Convert . ToBase64String ( signature ) )
192+ . Append ( _cacheLookupKeySeparator )
193+ . ToString ( ) ;
154194 }
155195 }
156196}
0 commit comments