diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj index 498849102a..7190117fa2 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index 57497fd2b0..a503a30acc 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -936,10 +936,10 @@ - + diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj index 19e1e5c7b6..0b43452f7f 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index 2dd8d69b08..71b374dbe2 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -738,6 +738,7 @@ + $(SystemTextEncodingsWebVersion) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs index 75b55847f7..adc8e4e809 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs @@ -5,13 +5,13 @@ using System; using System.Collections.Concurrent; using System.Linq; -using System.Runtime.Caching; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using Azure.Core; using Azure.Identity; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Identity.Client; using Microsoft.Identity.Client.Extensibility; @@ -27,7 +27,7 @@ public sealed class ActiveDirectoryAuthenticationProvider : SqlAuthenticationPro /// private static ConcurrentDictionary s_pcaMap = new ConcurrentDictionary(); - private static readonly MemoryCache s_accountPwCache = new(nameof(ActiveDirectoryAuthenticationProvider)); + private static readonly MemoryCache s_accountPwCache = new MemoryCache(new MemoryCacheOptions()); private static readonly int s_accountPwCacheTtlInHours = 2; private static readonly string s_nativeClientRedirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient"; private static readonly string s_defaultScopeSuffix = "/.default"; @@ -270,11 +270,11 @@ previousPw is byte[] previousPwBytes && // We cache the password hash to ensure future connection requests include a validated password // when we check for a cached MSAL account. Otherwise, a connection request with the same username // against the same tenant could succeed with an invalid password when we re-use the cached token. - if (!s_accountPwCache.Add(pwCacheKey, GetHash(parameters.Password), DateTime.UtcNow.AddHours(s_accountPwCacheTtlInHours))) + using (ICacheEntry entry = s_accountPwCache.CreateEntry(pwCacheKey)) { - s_accountPwCache.Remove(pwCacheKey); - s_accountPwCache.Add(pwCacheKey, GetHash(parameters.Password), DateTime.UtcNow.AddHours(s_accountPwCacheTtlInHours)); - } + entry.Value = GetHash(parameters.Password); + entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(s_accountPwCacheTtlInHours); + }; SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Password auth mode. Expiry Time: {0}", result?.ExpiresOn); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs index 7b1b2bf2e4..3c51716828 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs @@ -6,11 +6,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.IdentityModel.Tokens.Jwt; -using System.Runtime.Caching; using System.Security.Claims; using System.Security.Cryptography; using System.Text; using System.Threading; +using Microsoft.Extensions.Caching.Memory; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Protocols; @@ -59,7 +59,7 @@ internal class AzureAttestationEnclaveProvider : EnclaveProviderBase // such as https://sql.azure.attest.com/.well-known/openid-configuration private const string AttestationUrlSuffix = @"/.well-known/openid-configuration"; - private static readonly MemoryCache OpenIdConnectConfigurationCache = new MemoryCache("OpenIdConnectConfigurationCache"); + private static readonly MemoryCache OpenIdConnectConfigurationCache = new MemoryCache(new MemoryCacheOptions()); #endregion #region Internal methods @@ -332,7 +332,7 @@ private static string GetInnerMostExceptionMessage(Exception exception) // It also caches that information for 1 day to avoid DDOS attacks. private OpenIdConnectConfiguration GetOpenIdConfigForSigningKeys(string url, bool forceUpdate) { - OpenIdConnectConfiguration openIdConnectConfig = OpenIdConnectConfigurationCache[url] as OpenIdConnectConfiguration; + OpenIdConnectConfiguration openIdConnectConfig = OpenIdConnectConfigurationCache.Get(url); if (forceUpdate || openIdConnectConfig == null) { // Compute the meta data endpoint @@ -348,7 +348,11 @@ private OpenIdConnectConfiguration GetOpenIdConfigForSigningKeys(string url, boo throw SQL.AttestationFailed(string.Format(Strings.GetAttestationTokenSigningKeysFailed, GetInnerMostExceptionMessage(exception)), exception); } - OpenIdConnectConfigurationCache.Add(url, openIdConnectConfig, DateTime.UtcNow.AddDays(1)); + MemoryCacheEntryOptions options = new MemoryCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1) + }; + OpenIdConnectConfigurationCache.Set(url, openIdConnectConfig, options); } return openIdConnectConfig; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveProviderBase.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveProviderBase.cs index b8a52b9e4b..b666819dde 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveProviderBase.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveProviderBase.cs @@ -3,9 +3,9 @@ // See the LICENSE file in the project root for more information. using System; -using System.Runtime.Caching; using System.Security.Cryptography; using System.Threading; +using Microsoft.Extensions.Caching.Memory; // Enclave session locking model // 1. For doing the enclave attestation, driver makes either 1, 2 or 3 API calls(in order) @@ -84,7 +84,7 @@ internal abstract class EnclaveProviderBase : SqlColumnEncryptionEnclaveProvider private static readonly Object lockUpdateSessionLock = new Object(); // It is used to save the attestation url and nonce value across API calls - protected static readonly MemoryCache ThreadRetryCache = new MemoryCache("ThreadRetryCache"); + protected static readonly MemoryCache ThreadRetryCache = new MemoryCache(new MemoryCacheOptions()); #endregion #region protected methods @@ -102,7 +102,7 @@ protected void GetEnclaveSessionHelper(EnclaveSessionParameters enclaveSessionPa // In case if on some thread we are running SQL workload which don't require attestation, then in those cases we don't want same thread to wait for event to be signaled. // hence skipping it - string retryThreadID = ThreadRetryCache[Thread.CurrentThread.ManagedThreadId.ToString()] as string; + string retryThreadID = ThreadRetryCache.Get(Thread.CurrentThread.ManagedThreadId.ToString()); if (!string.IsNullOrEmpty(retryThreadID)) { sameThreadRetry = true; @@ -167,7 +167,11 @@ protected void GetEnclaveSessionHelper(EnclaveSessionParameters enclaveSessionPa retryThreadID = Thread.CurrentThread.ManagedThreadId.ToString(); } - ThreadRetryCache.Set(Thread.CurrentThread.ManagedThreadId.ToString(), retryThreadID, DateTime.UtcNow.AddMinutes(ThreadRetryCacheTimeoutInMinutes)); + MemoryCacheEntryOptions options = new MemoryCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(ThreadRetryCacheTimeoutInMinutes) + }; + ThreadRetryCache.Set(Thread.CurrentThread.ManagedThreadId.ToString(), retryThreadID, options); } } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveSessionCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveSessionCache.cs index 2cc34eeeb5..5673395e11 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveSessionCache.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveSessionCache.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. using System; -using System.Runtime.Caching; +using Microsoft.Extensions.Caching.Memory; using System.Threading; namespace Microsoft.Data.SqlClient @@ -11,7 +11,7 @@ namespace Microsoft.Data.SqlClient // Maintains a cache of SqlEnclaveSession instances internal class EnclaveSessionCache { - private readonly MemoryCache enclaveMemoryCache = new MemoryCache("EnclaveMemoryCache"); + private readonly MemoryCache enclaveMemoryCache = new MemoryCache(new MemoryCacheOptions()); private readonly object enclaveCacheLock = new object(); // Nonce for each message sent by the client to the server to prevent replay attacks by the server, @@ -25,7 +25,7 @@ internal class EnclaveSessionCache internal SqlEnclaveSession GetEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, out long counter) { string cacheKey = GenerateCacheKey(enclaveSessionParameters); - SqlEnclaveSession enclaveSession = enclaveMemoryCache[cacheKey] as SqlEnclaveSession; + SqlEnclaveSession enclaveSession = enclaveMemoryCache.Get(cacheKey); counter = Interlocked.Increment(ref _counter); return enclaveSession; } @@ -41,8 +41,12 @@ internal void InvalidateSession(EnclaveSessionParameters enclaveSessionParameter if (enclaveSession != null && enclaveSession.SessionId == enclaveSessionToInvalidate.SessionId) { - SqlEnclaveSession enclaveSessionRemoved = enclaveMemoryCache.Remove(cacheKey) as SqlEnclaveSession; - if (enclaveSessionRemoved == null) + enclaveMemoryCache.TryGetValue(cacheKey, out SqlEnclaveSession enclaveSessionToRemove); + if (enclaveSessionToRemove != null) + { + enclaveMemoryCache.Remove(cacheKey); + } + else { throw new InvalidOperationException(Strings.EnclaveSessionInvalidationFailed); } @@ -58,7 +62,11 @@ internal SqlEnclaveSession CreateSession(EnclaveSessionParameters enclaveSession lock (enclaveCacheLock) { enclaveSession = new SqlEnclaveSession(sharedSecret, sessionId); - enclaveMemoryCache.Add(cacheKey, enclaveSession, DateTime.UtcNow.AddHours(enclaveCacheTimeOutInHours)); + MemoryCacheEntryOptions options = new MemoryCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(enclaveCacheTimeOutInHours) + }; + enclaveMemoryCache.Set(cacheKey, enclaveSession, options); counter = Interlocked.Increment(ref _counter); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SignatureVerificationCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SignatureVerificationCache.cs index c181637c4b..e54e88f23e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SignatureVerificationCache.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SignatureVerificationCache.cs @@ -3,9 +3,9 @@ // See the LICENSE file in the project root for more information. using System; -using System.Runtime.Caching; using System.Text; using System.Threading; +using Microsoft.Extensions.Caching.Memory; namespace Microsoft.Data.SqlClient { @@ -35,7 +35,7 @@ internal class ColumnMasterKeyMetadataSignatureVerificationCache private ColumnMasterKeyMetadataSignatureVerificationCache() { - _cache = new MemoryCache(_className); + _cache = new MemoryCache(new MemoryCacheOptions()); _inTrim = 0; } @@ -46,17 +46,15 @@ private ColumnMasterKeyMetadataSignatureVerificationCache() /// Key Path for CMK /// boolean indicating whether the key can be sent to enclave /// Signature for the CMK metadata - /// null if the data is not found in cache otherwise returns true/false indicating signature verification success/failure - internal bool? GetSignatureVerificationResult(string keyStoreName, string masterKeyPath, bool allowEnclaveComputations, byte[] signature) + internal bool GetSignatureVerificationResult(string keyStoreName, string masterKeyPath, bool allowEnclaveComputations, byte[] signature) { - ValidateStringArgumentNotNullOrEmpty(masterKeyPath, _masterkeypathArgumentName, _getSignatureVerificationResultMethodName); ValidateStringArgumentNotNullOrEmpty(keyStoreName, _keyStoreNameArgumentName, _getSignatureVerificationResultMethodName); ValidateSignatureNotNullOrEmpty(signature, _getSignatureVerificationResultMethodName); string cacheLookupKey = GetCacheLookupKey(masterKeyPath, allowEnclaveComputations, signature, keyStoreName); - return _cache.Get(cacheLookupKey) as bool?; + return _cache.TryGetValue(cacheLookupKey, out bool value); } /// @@ -69,7 +67,6 @@ private ColumnMasterKeyMetadataSignatureVerificationCache() /// result indicating signature verification success/failure internal void AddSignatureVerificationResult(string keyStoreName, string masterKeyPath, bool allowEnclaveComputations, byte[] signature, bool result) { - ValidateStringArgumentNotNullOrEmpty(masterKeyPath, _masterkeypathArgumentName, _addSignatureVerificationResultMethodName); ValidateStringArgumentNotNullOrEmpty(keyStoreName, _keyStoreNameArgumentName, _addSignatureVerificationResultMethodName); ValidateSignatureNotNullOrEmpty(signature, _addSignatureVerificationResultMethodName); @@ -79,7 +76,11 @@ internal void AddSignatureVerificationResult(string keyStoreName, string masterK TrimCacheIfNeeded(); // By default evict after 10 days. - _cache.Set(cacheLookupKey, result, DateTimeOffset.UtcNow.AddDays(10)); + MemoryCacheEntryOptions options = new MemoryCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(10) + }; + _cache.Set(cacheLookupKey, result, options); } private void ValidateSignatureNotNullOrEmpty(byte[] signature, string methodName) @@ -115,15 +116,17 @@ private void ValidateStringArgumentNotNullOrEmpty(string stringArgValue, string private void TrimCacheIfNeeded() { // If the size of the cache exceeds the threshold, set that we are in trimming and trim the cache accordingly. - long currentCacheSize = _cache.GetCount(); + long currentCacheSize = _cache.Count; if ((currentCacheSize > _cacheSize + _cacheTrimThreshold) && (0 == Interlocked.CompareExchange(ref _inTrim, 1, 0))) { try { - _cache.Trim((int)(((double)(currentCacheSize - _cacheSize) / (double)currentCacheSize) * 100)); + // Example: 2301 - 2000 = 301; 301 / 2301 = 0.1308 * 100 = 13% compacting + _cache.Compact((((double)(currentCacheSize - _cacheSize) / (double)currentCacheSize) * 100)); } finally { + // Reset _inTrim flag Interlocked.CompareExchange(ref _inTrim, 0, 1); } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs index 5475eb5a0c..964e46aca3 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs @@ -7,9 +7,9 @@ using System.Collections.Generic; using System.Data; using System.Diagnostics; -using System.Runtime.Caching; using System.Text; using System.Threading; +using Microsoft.Extensions.Caching.Memory; namespace Microsoft.Data.SqlClient { @@ -34,7 +34,7 @@ sealed internal class SqlQueryMetadataCache private SqlQueryMetadataCache() { - _cache = new MemoryCache("SqlQueryMetadataCache"); + _cache = new MemoryCache(new MemoryCacheOptions()); } internal static SqlQueryMetadataCache GetInstance() @@ -61,7 +61,7 @@ internal bool GetQueryMetadataIfExists(SqlCommand sqlCommand) return false; } - Dictionary cipherMetadataDictionary = _cache.Get(cacheLookupKey) as Dictionary; + Dictionary cipherMetadataDictionary = _cache.Get>(cacheLookupKey); // If we had a cache miss just return false. if (cipherMetadataDictionary is null) @@ -144,7 +144,7 @@ internal bool GetQueryMetadataIfExists(SqlCommand sqlCommand) } ConcurrentDictionary enclaveKeys = - _cache.Get(enclaveLookupKey) as ConcurrentDictionary; + _cache.Get>(enclaveLookupKey); if (enclaveKeys is not null) { sqlCommand.keysToBeSentToEnclave = CreateCopyOfEnclaveKeys(enclaveKeys); @@ -215,7 +215,7 @@ internal void AddQueryMetadata(SqlCommand sqlCommand, bool ignoreQueriesWithRetu } // If the size of the cache exceeds the threshold, set that we are in trimming and trim the cache accordingly. - long currentCacheSize = _cache.GetCount(); + long currentCacheSize = _cache.Count; if ((currentCacheSize > CacheSize + CacheTrimThreshold) && (0 == Interlocked.CompareExchange(ref _inTrim, 1, 0))) { try @@ -226,7 +226,7 @@ internal void AddQueryMetadata(SqlCommand sqlCommand, bool ignoreQueriesWithRetu Thread.Sleep(TimeSpan.FromSeconds(10)); } #endif - _cache.Trim((int)(((double)(currentCacheSize - CacheSize) / (double)currentCacheSize) * 100)); + _cache.Compact((int)(((double)(currentCacheSize - CacheSize) / (double)currentCacheSize) * 100)); } finally { @@ -235,11 +235,15 @@ internal void AddQueryMetadata(SqlCommand sqlCommand, bool ignoreQueriesWithRetu } // By default evict after 10 hours. - _cache.Set(cacheLookupKey, cipherMetadataDictionary, DateTimeOffset.UtcNow.AddHours(10)); + MemoryCacheEntryOptions options = new MemoryCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(10) + }; + _cache.Set>(cacheLookupKey, cipherMetadataDictionary, options); if (sqlCommand.requiresEnclaveComputations) { ConcurrentDictionary keysToBeCached = CreateCopyOfEnclaveKeys(sqlCommand.keysToBeSentToEnclave); - _cache.Set(enclaveLookupKey, keysToBeCached, DateTimeOffset.UtcNow.AddHours(10)); + _cache.Set>(enclaveLookupKey, keysToBeCached, options); } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs index d9fea6b211..01d2d1bc61 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs @@ -374,8 +374,8 @@ internal static void VerifyColumnMasterKeySignature(string keyStoreName, string } else { - bool? signatureVerificationResult = ColumnMasterKeyMetadataSignatureVerificationCache.GetSignatureVerificationResult(keyStoreName, keyPath, isEnclaveEnabled, CMKSignature); - if (signatureVerificationResult is null) + bool signatureVerificationResult = ColumnMasterKeyMetadataSignatureVerificationCache.GetSignatureVerificationResult(keyStoreName, keyPath, isEnclaveEnabled, CMKSignature); + if (signatureVerificationResult == false) { // We will simply bubble up the exception from VerifyColumnMasterKeyMetadata function. isValidSignature = provider.VerifyColumnMasterKeyMetadata(keyPath, isEnclaveEnabled, @@ -385,7 +385,7 @@ internal static void VerifyColumnMasterKeySignature(string keyStoreName, string } else { - isValidSignature = signatureVerificationResult.Value; + isValidSignature = signatureVerificationResult; } } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSymmetricKeyCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSymmetricKeyCache.cs index 663116ed59..fb9ea2997d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSymmetricKeyCache.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSymmetricKeyCache.cs @@ -4,8 +4,8 @@ using System; using System.Diagnostics; -using System.Runtime.Caching; using System.Text; +using Microsoft.Extensions.Caching.Memory; namespace Microsoft.Data.SqlClient { @@ -20,7 +20,7 @@ sealed internal class SqlSymmetricKeyCache private SqlSymmetricKeyCache() { - _cache = new MemoryCache("ColumnEncryptionKeyCache"); + _cache = new MemoryCache(new MemoryCacheOptions()); } internal static SqlSymmetricKeyCache GetInstance() @@ -53,7 +53,8 @@ internal SqlClientSymmetricKey GetKey(SqlEncryptionKeyInfo keyInfo, SqlConnectio #endif //DEBUG // Lookup the key in cache - if (!(_cache.Get(cacheLookupKey) is SqlClientSymmetricKey encryptionKey)) + SqlClientSymmetricKey encryptionKey; + if (!(_cache.TryGetValue(cacheLookupKey, out encryptionKey))) { Debug.Assert(SqlConnection.ColumnEncryptionTrustedMasterKeyPaths is not null, @"SqlConnection.ColumnEncryptionTrustedMasterKeyPaths should not be null"); @@ -90,8 +91,11 @@ internal SqlClientSymmetricKey GetKey(SqlEncryptionKeyInfo keyInfo, SqlConnectio { // In case multiple threads reach here at the same time, the first one wins. // The allocated memory will be reclaimed by Garbage Collector. - DateTimeOffset expirationTime = DateTimeOffset.UtcNow.Add(SqlConnection.ColumnEncryptionKeyCacheTtl); - _cache.Add(cacheLookupKey, encryptionKey, expirationTime); + MemoryCacheEntryOptions options = new MemoryCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = SqlConnection.ColumnEncryptionKeyCacheTtl + }; + _cache.Set(cacheLookupKey, encryptionKey, options); } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs index cfbe531e82..ab327aa689 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs @@ -3,10 +3,10 @@ // See the LICENSE file in the project root for more information. using System; -using System.Runtime.Caching; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Threading; +using Microsoft.Extensions.Caching.Memory; namespace Microsoft.Data.SqlClient { @@ -14,7 +14,7 @@ internal abstract class VirtualizationBasedSecurityEnclaveProviderBase : Enclave { #region Members - private static readonly MemoryCache rootSigningCertificateCache = new MemoryCache("RootSigningCertificateCache"); + private static readonly MemoryCache rootSigningCertificateCache = new MemoryCache(new MemoryCacheOptions()); #endregion @@ -192,7 +192,7 @@ private void VerifyAttestationInfo(string attestationUrl, HealthReport healthRep private X509Certificate2Collection GetSigningCertificate(string attestationUrl, bool forceUpdate) { attestationUrl = GetAttestationUrl(attestationUrl); - X509Certificate2Collection signingCertificates = (X509Certificate2Collection)rootSigningCertificateCache[attestationUrl]; + X509Certificate2Collection signingCertificates = rootSigningCertificateCache.Get(attestationUrl); if (forceUpdate || signingCertificates == null || AnyCertificatesExpired(signingCertificates)) { byte[] data = MakeRequest(attestationUrl); @@ -207,10 +207,14 @@ private X509Certificate2Collection GetSigningCertificate(string attestationUrl, throw SQL.AttestationFailed(string.Format(Strings.GetAttestationSigningCertificateFailedInvalidCertificate, attestationUrl), exception); } - rootSigningCertificateCache.Add(attestationUrl, certificateCollection, DateTime.Now.AddDays(1)); + MemoryCacheEntryOptions options = new MemoryCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1) + }; + rootSigningCertificateCache.Set(attestationUrl, certificateCollection, options); } - return (X509Certificate2Collection)rootSigningCertificateCache[attestationUrl]; + return rootSigningCertificateCache.Get(attestationUrl); } // Return the endpoint for given attestation url diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj index b5ac559337..15249ba3cd 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj @@ -123,10 +123,10 @@ - - PreserveNewest - %(Filename)%(Extension) - + + PreserveNewest + %(Filename)%(Extension) + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs index d0b1bfd076..8e62ee95fc 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs @@ -1449,5 +1449,4 @@ public IEnumerator GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } - } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs index 3d3c717b31..d695c2c9dd 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs @@ -3,17 +3,18 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; -using System.Runtime.Caching; -using System.Runtime.CompilerServices; -using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using Azure; using Azure.Identity; using Azure.Security.KeyVault.Keys; +using Microsoft.Extensions.Caching.Memory; + namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { class CertificateUtility @@ -98,11 +99,7 @@ internal static void CleanSqlClientCache() { object sqlSymmetricKeyCache = SqlSymmetricKeyCacheGetInstance.Invoke(null, null); MemoryCache cache = SqlSymmetricKeyCacheFieldCache.GetValue(sqlSymmetricKeyCache) as MemoryCache; - - foreach (KeyValuePair item in cache) - { - cache.Remove(item.Key); - } + ClearCache(cache); } /// @@ -308,5 +305,28 @@ public static void ChangeServerTceSetting(bool fEnable, SqlConnectionStringBuild } } } + + private static void ClearCache(MemoryCache cache) + { + // Get the Clear method of the cache and use it if available. This is available in Microsoft.Extensions.Caching 8.0 + MethodInfo clearMethod = cache.GetType().GetMethod("Clear", BindingFlags.Instance | BindingFlags.Public); + if (clearMethod != null) + { + clearMethod.Invoke(cache, null); + } + else + { + // Otherwise, use the Remove function to remove all entries using all keys in the cache gathered using reflection. + PropertyInfo cacheEntriesCollectionDefinition = typeof(MemoryCache).GetProperty("EntriesCollection", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + ICollection cacheEntriesCollection = (ICollection)cacheEntriesCollectionDefinition.GetValue(cache); + List cacheCollectionValues = new List(); + + foreach (object cacheItem in cacheEntriesCollection) + { + ICacheEntry cacheItemValue = (ICacheEntry)cacheItem.GetType().GetProperty("Value").GetValue(cacheItem, null); + cache.Remove(cacheItemValue.Key); + } + } + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index f75aef8edb..09e10df92c 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -312,6 +312,7 @@ + @@ -340,7 +341,6 @@ - @@ -348,10 +348,10 @@ - - PreserveNewest - %(Filename)%(Extension) - + + PreserveNewest + %(Filename)%(Extension) + diff --git a/tools/specs/Microsoft.Data.SqlClient.nuspec b/tools/specs/Microsoft.Data.SqlClient.nuspec index b1666d4459..1e4f5e5a09 100644 --- a/tools/specs/Microsoft.Data.SqlClient.nuspec +++ b/tools/specs/Microsoft.Data.SqlClient.nuspec @@ -46,7 +46,7 @@ When using NuGet 3.x this package requires at least version 3.4. - + @@ -56,7 +56,7 @@ When using NuGet 3.x this package requires at least version 3.4. - +