diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionEnclaveProvider.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionEnclaveProvider.xml index 6a325a831f..c85f90fc84 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionEnclaveProvider.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionEnclaveProvider.xml @@ -21,12 +21,17 @@ the enclave attestation protocol as well as the logic for creating and caching e A Diffie-Hellman algorithm object that encapsulates a client-side key pair. The endpoint of an attestation service for attesting the enclave. The name of the SQL Server instance containing the enclave. + The set of extra data needed for attestating the enclave. + The length of the extra data needed for attestating the enclave. The requested enclave session or if the provider doesn't implement session caching. A counter that the enclave provider is expected to increment each time SqlClient retrieves the session from the cache. The purpose of this field is to prevent replay attacks. When overridden in a derived class, performs enclave attestation, generates a symmetric key for the session, creates a an enclave session and stores the session information in the cache. To be added. + The endpoint of an attestation service for attesting the enclave. + A set of extra data needed for attestating the enclave. + The length of the extra data needed for attestating the enclave. Gets the information that SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. The information SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. To be added. @@ -34,8 +39,11 @@ the enclave attestation protocol as well as the logic for creating and caching e The name of the SQL Server instance containing the enclave. The endpoint of an attestation service, SqlClient contacts to attest the enclave. + to indicate that a set of extra data needs to be generated for attestation; otherwise, . When this method returns, the requested enclave session or if the provider doesn't implement session caching. This parameter is treated as uninitialized. A counter that the enclave provider is expected to increment each time SqlClient retrieves the session from the cache. The purpose of this field is to prevent replay attacks. + A set of extra data needed for attestating the enclave. + The length of the extra data needed for attestating the enclave. When overridden in a derived class, looks up an existing enclave session information in the enclave session cache. If the enclave provider doesn't implement enclave session caching, this method is expected to return in the parameter. To be added. diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.NetCoreApp.cs index be2233a4ac..965902f3b8 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.NetCoreApp.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.NetCoreApp.cs @@ -185,11 +185,11 @@ public abstract partial class SqlColumnEncryptionEnclaveProvider /// protected SqlColumnEncryptionEnclaveProvider() { } /// - public abstract void CreateEnclaveSession(byte[] enclaveAttestationInfo, System.Security.Cryptography.ECDiffieHellmanCng clientDiffieHellmanKey, string attestationUrl, string servername, out Microsoft.Data.SqlClient.SqlEnclaveSession sqlEnclaveSession, out long counter); + public abstract void CreateEnclaveSession(byte[] enclaveAttestationInfo, System.Security.Cryptography.ECDiffieHellmanCng clientDiffieHellmanKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out Microsoft.Data.SqlClient.SqlEnclaveSession sqlEnclaveSession, out long counter); /// - public abstract Microsoft.Data.SqlClient.SqlEnclaveAttestationParameters GetAttestationParameters(); + public abstract Microsoft.Data.SqlClient.SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength); /// - public abstract void GetEnclaveSession(string serverName, string attestationUrl, out Microsoft.Data.SqlClient.SqlEnclaveSession sqlEnclaveSession, out long counter); + public abstract void GetEnclaveSession(string serverName, string attestationUrl, bool generateCustomData, out Microsoft.Data.SqlClient.SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength); /// public abstract void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, Microsoft.Data.SqlClient.SqlEnclaveSession enclaveSession); } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.NetCoreApp.cs index affbab8080..1fcb95a3a8 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.NetCoreApp.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.NetCoreApp.cs @@ -66,35 +66,35 @@ internal class AzureAttestationEnclaveProvider : EnclaveProviderBase #region Public methods // When overridden in a derived class, looks up an existing enclave session information in the enclave session cache. // If the enclave provider doesn't implement enclave session caching, this method is expected to return null in the sqlEnclaveSession parameter. - public override void GetEnclaveSession(string servername, string attestationUrl, out SqlEnclaveSession sqlEnclaveSession, out long counter) + public override void GetEnclaveSession(string servername, string attestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) { - GetEnclaveSessionHelper(servername, attestationUrl, true, out sqlEnclaveSession, out counter); + GetEnclaveSessionHelper(servername, attestationUrl, generateCustomData, out sqlEnclaveSession, out counter, out customData, out customDataLength); } // Gets the information that SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. - public override SqlEnclaveAttestationParameters GetAttestationParameters() + public override SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength) { ECDiffieHellmanCng clientDHKey = new ECDiffieHellmanCng(DiffieHellmanKeySize); clientDHKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash; clientDHKey.HashAlgorithm = CngAlgorithm.Sha256; - byte[] attestationParam = PrepareAttestationParameters(); + byte[] attestationParam = PrepareAttestationParameters(attestationUrl, customData, customDataLength); return new SqlEnclaveAttestationParameters(AzureBasedAttestationProtocolId, attestationParam, clientDHKey); } // When overridden in a derived class, performs enclave attestation, generates a symmetric key for the session, creates a an enclave session and stores the session information in the cache. - public override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, string attestationUrl, string servername, out SqlEnclaveSession sqlEnclaveSession, out long counter) + public override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter) { sqlEnclaveSession = null; counter = 0; try { - AttestationInfoCacheItem attestationInfoCacheItem = AttestationInfoCache.Remove(Thread.CurrentThread.ManagedThreadId.ToString()) as AttestationInfoCacheItem; + ThreadRetryCache.Remove(Thread.CurrentThread.ManagedThreadId.ToString()); sqlEnclaveSession = GetEnclaveSessionFromCache(servername, attestationUrl, out counter); if (sqlEnclaveSession == null) { - if (attestationInfoCacheItem != null) + if (!string.IsNullOrEmpty(attestationUrl) && customData != null && customDataLength > 0) { - byte[] nonce = attestationInfoCacheItem.AttestNonce; + byte[] nonce = customData; IdentityModelEventSource.ShowPII = true; @@ -241,19 +241,18 @@ public AzureAttestationToken(byte[] payload) // Attestation Url // Size of nonce // Nonce value - internal byte[] PrepareAttestationParameters() + internal byte[] PrepareAttestationParameters(string attestationUrl, byte[] attestNonce, int attestNonceLength) { - AttestationInfoCacheItem attestationInfoCacheItem = AttestationInfoCache[Thread.CurrentThread.ManagedThreadId.ToString()] as AttestationInfoCacheItem; - if (attestationInfoCacheItem != null) + if (!string.IsNullOrEmpty(attestationUrl) && attestNonce != null && attestNonceLength > 0) { // In c# strings are not null terminated, so adding the null termination before serializing it - string attestationUrlLocal = attestationInfoCacheItem.AttestationUrl + char.MinValue; + string attestationUrlLocal = attestationUrl + char.MinValue; byte[] serializedAttestationUrl = Encoding.Unicode.GetBytes(attestationUrlLocal); byte[] serializedAttestationUrlLength = BitConverter.GetBytes(serializedAttestationUrl.Length); // serializing nonce - byte[] serializedNonce = attestationInfoCacheItem.AttestNonce; - byte[] serializedNonceLength = BitConverter.GetBytes(attestationInfoCacheItem.AttestNonce.Length); + byte[] serializedNonce = attestNonce; + byte[] serializedNonceLength = BitConverter.GetBytes(attestNonceLength); // Computing the total length of the data int totalDataSize = serializedAttestationUrl.Length + serializedAttestationUrlLength.Length + serializedNonce.Length + serializedNonceLength.Length; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveDelegate.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveDelegate.NetCoreApp.cs index e7d69e0dc6..48473b0722 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveDelegate.NetCoreApp.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveDelegate.NetCoreApp.cs @@ -66,8 +66,10 @@ internal byte[] GetSerializedAttestationParameters(SqlEnclaveAttestationParamete /// attestation url for attestation service endpoint /// attestation info from SQL Server /// attestation parameters + /// A set of extra data needed for attestating the enclave. + /// The length of the extra data needed for attestating the enclave. internal void CreateEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, string serverName, string attestationUrl, - byte[] attestationInfo, SqlEnclaveAttestationParameters attestationParameters) + byte[] attestationInfo, SqlEnclaveAttestationParameters attestationParameters, byte[] customData, int customDataLength) { lock (_lock) @@ -75,16 +77,22 @@ internal void CreateEnclaveSession(SqlConnectionAttestationProtocol attestationP SqlColumnEncryptionEnclaveProvider sqlColumnEncryptionEnclaveProvider = GetEnclaveProvider(attestationProtocol, enclaveType); long counter; SqlEnclaveSession sqlEnclaveSession = null; - sqlColumnEncryptionEnclaveProvider.GetEnclaveSession(serverName, attestationUrl, out sqlEnclaveSession, out counter); + byte[] dummyCustomData = null; + int dummyCustomDataLength; + + sqlColumnEncryptionEnclaveProvider.GetEnclaveSession(serverName, attestationUrl, false, out sqlEnclaveSession, out counter, out dummyCustomData, out dummyCustomDataLength); if (sqlEnclaveSession != null) { return; } - sqlColumnEncryptionEnclaveProvider.CreateEnclaveSession(attestationInfo, attestationParameters.ClientDiffieHellmanKey, attestationUrl, serverName, out sqlEnclaveSession, out counter); + sqlColumnEncryptionEnclaveProvider.CreateEnclaveSession(attestationInfo, attestationParameters.ClientDiffieHellmanKey, attestationUrl, serverName, customData, customDataLength, out sqlEnclaveSession, out counter); - if (sqlEnclaveSession == null) throw SQL.NullEnclaveSessionReturnedFromProvider(enclaveType, attestationUrl); + if (sqlEnclaveSession == null) + { + throw SQL.NullEnclaveSessionReturnedFromProvider(enclaveType, attestationUrl); + } } } @@ -103,9 +111,12 @@ internal EnclavePackage GenerateEnclavePackage(SqlConnectionAttestationProtocol SqlEnclaveSession sqlEnclaveSession = null; long counter; + byte[] dummyCustomData = null; + int dummyCustomDataLength; + try { - GetEnclaveSession(attestationProtocol, enclaveType, serverName, enclaveAttestationUrl, out sqlEnclaveSession, out counter, throwIfNull: true); + GetEnclaveSession(attestationProtocol, enclaveType, serverName, enclaveAttestationUrl, false, out sqlEnclaveSession, out counter, out dummyCustomData, out dummyCustomDataLength, throwIfNull: true); } catch (Exception e) { @@ -129,10 +140,10 @@ internal void InvalidateEnclaveSession(SqlConnectionAttestationProtocol attestat } - internal SqlEnclaveAttestationParameters GetAttestationParameters(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType) + internal SqlEnclaveAttestationParameters GetAttestationParameters(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, string attestationUrl, byte[] customData, int customDataLength) { SqlColumnEncryptionEnclaveProvider sqlColumnEncryptionEnclaveProvider = GetEnclaveProvider(attestationProtocol, enclaveType); - return sqlColumnEncryptionEnclaveProvider.GetAttestationParameters(); + return sqlColumnEncryptionEnclaveProvider.GetAttestationParameters(attestationUrl, customData, customDataLength); } private SqlColumnEncryptionEnclaveProvider GetEnclaveProvider(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType) @@ -183,16 +194,16 @@ private string ConvertAttestationProtocolToString(SqlConnectionAttestationProtoc } } - internal void GetEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, string serverName, string enclaveAttestationUrl, out SqlEnclaveSession sqlEnclaveSession) + internal void GetEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, string serverName, string enclaveAttestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out byte[] customData, out int customDataLength) { long counter; - GetEnclaveSession(attestationProtocol, enclaveType, serverName, enclaveAttestationUrl, out sqlEnclaveSession, out counter, throwIfNull: false); + GetEnclaveSession(attestationProtocol, enclaveType, serverName, enclaveAttestationUrl, generateCustomData, out sqlEnclaveSession, out counter, out customData, out customDataLength, throwIfNull: false); } - private void GetEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, string serverName, string enclaveAttestationUrl, out SqlEnclaveSession sqlEnclaveSession, out long counter, bool throwIfNull) + private void GetEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, string serverName, string enclaveAttestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength, bool throwIfNull) { SqlColumnEncryptionEnclaveProvider sqlColumnEncryptionEnclaveProvider = GetEnclaveProvider(attestationProtocol, enclaveType); - sqlColumnEncryptionEnclaveProvider.GetEnclaveSession(serverName, enclaveAttestationUrl, out sqlEnclaveSession, out counter); + sqlColumnEncryptionEnclaveProvider.GetEnclaveSession(serverName, enclaveAttestationUrl, generateCustomData, out sqlEnclaveSession, out counter, out customData, out customDataLength); if (throwIfNull && sqlEnclaveSession == null) { diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveDelegate.NetStandard.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveDelegate.NetStandard.cs index 8d1ebc9fb8..63db20022a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveDelegate.NetStandard.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveDelegate.NetStandard.cs @@ -27,13 +27,15 @@ internal byte[] GetSerializedAttestationParameters( /// attestation url for attestation service endpoint /// attestation info from SQL Server /// attestation parameters + /// A set of extra data needed for attestating the enclave. + /// The length of the extra data needed for attestating the enclave. internal void CreateEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, string serverName, string attestationUrl, - byte[] attestationInfo, SqlEnclaveAttestationParameters attestationParameters) + byte[] attestationInfo, SqlEnclaveAttestationParameters attestationParameters, byte[] customData, int customDataLength) { throw new PlatformNotSupportedException(); } - internal void GetEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, string serverName, string enclaveAttestationUrl, out SqlEnclaveSession sqlEnclaveSession) + internal void GetEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, string serverName, string enclaveAttestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out byte[] customData, out int customDataLength) { throw new PlatformNotSupportedException(); } @@ -48,7 +50,7 @@ internal void InvalidateEnclaveSession(SqlConnectionAttestationProtocol attestat throw new PlatformNotSupportedException(); } - internal SqlEnclaveAttestationParameters GetAttestationParameters(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType) + internal SqlEnclaveAttestationParameters GetAttestationParameters(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, string attestationUrl, byte[] customData, int customDataLength) { throw new PlatformNotSupportedException(); } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveProviderBase.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveProviderBase.NetCoreApp.cs index 456224a068..32f9555c8c 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveProviderBase.NetCoreApp.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveProviderBase.NetCoreApp.cs @@ -20,10 +20,9 @@ // In case if the enclave session is cached and validate(not expired), GetEnclaveSession API returns the SqlEnclaveSession. // In case if the enclave session is not cached then driver end up calling GetAttestationParameters and CreateEnclaveSession. // Note: When we have non-enclave query, then in those cases we never call CreateEnclaveSession. This is one of main pivot point for designing the below locking model. -// As per current API design driver passes attestation url and the server name during GetEnclaveSession but not in GetAttestationParameters. -// In order to create the attestation parameter enclave provider needs to know the attestation url. To overcome this limitation we added a AttestationInfoCache memory cache -// which save the attestation url and nonce with current thread as the lookup key. -// Later we use the AttestationInfoCache object to retrieve the attestation url in GetAttestationParameters which we saved during CreateEnclaveSession call. +// After the change to the API design, driver passes attestation url and servername during GetEnclaveSession and GetAttestationParameters. The extra correlation ID, such as nonce, +// will be generated during GetEnclaveSession on demand and be passed back to driver as customData and customDataLength. Later, GetAttestationParameters and CreateEnclaveSession +// use the customData and customDataLength to do the attestation. // 2. In case during application start, if app spins of multiple threads at the same time (during stress test or benchmarking) where DB connection is Always encrypted enabled, // then with the existing design we end up creating multiple enclave session. Each enclave session adds an extra memory overhead to the system and also it generates multiple calls to attestation @@ -71,7 +70,7 @@ internal abstract class EnclaveProviderBase : SqlColumnEncryptionEnclaveProvider { #region Constants private const int NonceSize = 256; - private const int AttestationInfoCacheTimeoutInMinutes = 10; + private const int ThreadRetryCacheTimeoutInMinutes = 10; private const int LockTimeoutMaxInMilliseconds = 15 * 1000; // 15 seconds #endregion @@ -86,28 +85,18 @@ internal abstract class EnclaveProviderBase : SqlColumnEncryptionEnclaveProvider private static readonly Object lockUpdateSessionLock = new Object(); - internal class AttestationInfoCacheItem - { - public string AttestationUrl { get; private set; } - - public byte[] AttestNonce { get; private set; } - - public AttestationInfoCacheItem(string attestationUri, byte[] nonce) - { - AttestationUrl = attestationUri; - AttestNonce = nonce; - } - } - // It is used to save the attestation url and nonce value across API calls - protected static readonly MemoryCache AttestationInfoCache = new MemoryCache("AttestationInfoCache"); + protected static readonly MemoryCache ThreadRetryCache = new MemoryCache("ThreadRetryCache"); #endregion #region Public methods // Helper method to get the enclave session from the cache if present - protected void GetEnclaveSessionHelper(string servername, string attestationUrl, bool shouldGenerateNonce, out SqlEnclaveSession sqlEnclaveSession, out long counter) + protected void GetEnclaveSessionHelper(string servername, string attestationUrl, bool shouldGenerateNonce, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) { + customData = null; + customDataLength = 0; sqlEnclaveSession = SessionCache.GetEnclaveSession(servername, attestationUrl, out counter); + if (sqlEnclaveSession == null) { bool sessionCacheLockTaken = false; @@ -115,8 +104,8 @@ protected void GetEnclaveSessionHelper(string servername, string attestationUrl, // 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 - AttestationInfoCacheItem attestationInfoCacheItem = AttestationInfoCache[Thread.CurrentThread.ManagedThreadId.ToString()] as AttestationInfoCacheItem; - if (attestationInfoCacheItem != null) + string retryThreadID = ThreadRetryCache[Thread.CurrentThread.ManagedThreadId.ToString()] as string; + if (!string.IsNullOrEmpty(retryThreadID)) { sameThreadRetry = true; } @@ -162,23 +151,25 @@ protected void GetEnclaveSessionHelper(string servername, string attestationUrl, if (sqlEnclaveSession == null) { - if (!sameThreadRetry) + if (shouldGenerateNonce) { - // Client decides to initiate the process of attesting the enclave and to establish a secure session with the enclave. - // To ensure that server send new attestation request instead of replaying / re-sending the old token, we will create a nonce for current attestation request. - byte[] nonce = new byte[NonceSize]; - if (shouldGenerateNonce) + using (RandomNumberGenerator rng = new RNGCryptoServiceProvider()) { - using (RandomNumberGenerator rng = new RNGCryptoServiceProvider()) - { - rng.GetBytes(nonce); - } + // Client decides to initiate the process of attesting the enclave and to establish a secure session with the enclave. + // To ensure that server send new attestation request instead of replaying / re-sending the old token, we will create a nonce for current attestation request. + byte[] nonce = new byte[NonceSize]; + rng.GetBytes(nonce); + customData = nonce; + customDataLength = nonce.Length; } + } - attestationInfoCacheItem = new AttestationInfoCacheItem(attestationUrl, nonce); + if (!sameThreadRetry) + { + retryThreadID = Thread.CurrentThread.ManagedThreadId.ToString(); } - AttestationInfoCache.Set(Thread.CurrentThread.ManagedThreadId.ToString(), attestationInfoCacheItem, DateTime.UtcNow.AddMinutes(AttestationInfoCacheTimeoutInMinutes)); + ThreadRetryCache.Set(Thread.CurrentThread.ManagedThreadId.ToString(), retryThreadID, DateTime.UtcNow.AddMinutes(ThreadRetryCacheTimeoutInMinutes)); } } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.NetCoreApp.cs index 042915e158..e008b58edc 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.NetCoreApp.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.NetCoreApp.cs @@ -16,9 +16,11 @@ public abstract partial class SqlColumnEncryptionEnclaveProvider /// A Diffie-Hellman algorithm object encapsulating a client-side key pair. /// The endpoint of an attestation service for attesting the enclave. /// The name of the SQL Server instance containing the enclave. + /// The set of extra data needed for attestating the enclave. + /// The length of the extra data needed for attestating the enclave. /// The requested enclave session or null if the provider does not implement session caching. /// A counter that the enclave provider is expected to increment each time SqlClient retrieves the session from the cache. The purpose of this field is to prevent replay attacks. - public abstract void CreateEnclaveSession(byte[] enclaveAttestationInfo, ECDiffieHellmanCng clientDiffieHellmanKey, string attestationUrl, string servername, + public abstract void CreateEnclaveSession(byte[] enclaveAttestationInfo, ECDiffieHellmanCng clientDiffieHellmanKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter); } } \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs index 72c5fa73d8..0e9af83456 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs @@ -8,10 +8,10 @@ namespace Microsoft.Data.SqlClient public abstract partial class SqlColumnEncryptionEnclaveProvider { /// - public abstract void GetEnclaveSession(string serverName, string attestationUrl, out SqlEnclaveSession sqlEnclaveSession, out long counter); + public abstract void GetEnclaveSession(string serverName, string attestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength); /// - public abstract SqlEnclaveAttestationParameters GetAttestationParameters(); + public abstract SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength); /// public abstract void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSession); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs index 0283736785..98f517ea17 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -115,6 +115,8 @@ private enum EXECTYPE private bool requiresEnclaveComputations = false; internal EnclavePackage enclavePackage = null; private SqlEnclaveAttestationParameters enclaveAttestationParameters = null; + private byte[] customData = null; + private int customDataLength = 0; // Last TaskCompletionSource for reconnect task - use for cancellation only private TaskCompletionSource _reconnectionCompletionSource = null; @@ -2997,6 +2999,8 @@ private void ResetEncryptionState() enclavePackage = null; requiresEnclaveComputations = false; enclaveAttestationParameters = null; + customData = null; + customDataLength = 0; } /// @@ -3356,10 +3360,10 @@ private SqlDataReader TryFetchInputParameterEncryptionInfo(int timeout, string dataSource = this._activeConnection.DataSource; string enclaveAttestationUrl = this._activeConnection.EnclaveAttestationUrl; SqlEnclaveSession sqlEnclaveSession = null; - EnclaveDelegate.Instance.GetEnclaveSession(attestationProtocol, enclaveType, dataSource, enclaveAttestationUrl, out sqlEnclaveSession); + EnclaveDelegate.Instance.GetEnclaveSession(attestationProtocol, enclaveType, dataSource, enclaveAttestationUrl, true, out sqlEnclaveSession, out customData, out customDataLength); if (sqlEnclaveSession == null) { - enclaveAttestationParameters = EnclaveDelegate.Instance.GetAttestationParameters(attestationProtocol, enclaveType); + enclaveAttestationParameters = EnclaveDelegate.Instance.GetAttestationParameters(attestationProtocol, enclaveType, enclaveAttestationUrl, customData, customDataLength); serializedAttestatationParameters = EnclaveDelegate.Instance.GetSerializedAttestationParameters(enclaveAttestationParameters, enclaveType); } } @@ -3879,7 +3883,7 @@ private void ReadDescribeEncryptionParameterResults(SqlDataReader ds, ReadOnlyDi string dataSource = this._activeConnection.DataSource; string enclaveAttestationUrl = this._activeConnection.EnclaveAttestationUrl; - EnclaveDelegate.Instance.CreateEnclaveSession(attestationProtocol, enclaveType, dataSource, enclaveAttestationUrl, attestationInfo, enclaveAttestationParameters); + EnclaveDelegate.Instance.CreateEnclaveSession(attestationProtocol, enclaveType, dataSource, enclaveAttestationUrl, attestationInfo, enclaveAttestationParameters, customData, customDataLength); enclaveAttestationParameters = null; attestationInfoRead = true; } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.NetCoreApp.cs index 864e7d8436..b768c32bae 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.NetCoreApp.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.NetCoreApp.cs @@ -86,13 +86,13 @@ internal abstract class VirtualizationBasedSecurityEnclaveProviderBase : Enclave // When overridden in a derived class, looks up an existing enclave session information in the enclave session cache. // If the enclave provider doesn't implement enclave session caching, this method is expected to return null in the sqlEnclaveSession parameter. - public override void GetEnclaveSession(string servername, string attestationUrl, out SqlEnclaveSession sqlEnclaveSession, out long counter) + public override void GetEnclaveSession(string servername, string attestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) { - GetEnclaveSessionHelper(servername, attestationUrl, false, out sqlEnclaveSession, out counter); + GetEnclaveSessionHelper(servername, attestationUrl, false, out sqlEnclaveSession, out counter, out customData, out customDataLength); } // Gets the information that SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. - public override SqlEnclaveAttestationParameters GetAttestationParameters() + public override SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength) { ECDiffieHellmanCng clientDHKey = new ECDiffieHellmanCng(DiffieHellmanKeySize); clientDHKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash; @@ -101,17 +101,17 @@ public override SqlEnclaveAttestationParameters GetAttestationParameters() } // When overridden in a derived class, performs enclave attestation, generates a symmetric key for the session, creates a an enclave session and stores the session information in the cache. - public override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, string attestationUrl, string servername, out SqlEnclaveSession sqlEnclaveSession, out long counter) + public override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter) { sqlEnclaveSession = null; counter = 0; try { - AttestationInfoCacheItem attestationInfoCacheItem = AttestationInfoCache.Remove(Thread.CurrentThread.ManagedThreadId.ToString()) as AttestationInfoCacheItem; + ThreadRetryCache.Remove(Thread.CurrentThread.ManagedThreadId.ToString()); sqlEnclaveSession = GetEnclaveSessionFromCache(servername, attestationUrl, out counter); if (sqlEnclaveSession == null) { - if (attestationInfoCacheItem != null) + if (!string.IsNullOrEmpty(attestationUrl)) { // Deserialize the payload AttestationInfo info = new AttestationInfo(attestationInfo); diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs index 95eed9298e..359dffe1bd 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -415,11 +415,11 @@ public abstract partial class SqlColumnEncryptionEnclaveProvider /// protected SqlColumnEncryptionEnclaveProvider() { } /// - public abstract void CreateEnclaveSession(byte[] enclaveAttestationInfo, System.Security.Cryptography.ECDiffieHellmanCng clientDiffieHellmanKey, string attestationUrl, string servername, out Microsoft.Data.SqlClient.SqlEnclaveSession sqlEnclaveSession, out long counter); + public abstract void CreateEnclaveSession(byte[] enclaveAttestationInfo, System.Security.Cryptography.ECDiffieHellmanCng clientDiffieHellmanKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out Microsoft.Data.SqlClient.SqlEnclaveSession sqlEnclaveSession, out long counter); /// - public abstract Microsoft.Data.SqlClient.SqlEnclaveAttestationParameters GetAttestationParameters(); + public abstract Microsoft.Data.SqlClient.SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength); /// - public abstract void GetEnclaveSession(string serverName, string attestationUrl, out Microsoft.Data.SqlClient.SqlEnclaveSession sqlEnclaveSession, out long counter); + public abstract void GetEnclaveSession(string serverName, string attestationUrl, bool generateCustomData, out Microsoft.Data.SqlClient.SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength); /// public abstract void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, Microsoft.Data.SqlClient.SqlEnclaveSession enclaveSession); } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs index ea55b0cbd6..f530600bb3 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs @@ -66,36 +66,36 @@ internal class AzureAttestationEnclaveProvider : EnclaveProviderBase // When overridden in a derived class, looks up an existing enclave session information in the enclave session cache. // If the enclave provider doesn't implement enclave session caching, this method is expected to return null in the sqlEnclaveSession parameter. - public override void GetEnclaveSession(string servername, string attestationUrl, out SqlEnclaveSession sqlEnclaveSession, out long counter) + public override void GetEnclaveSession(string servername, string attestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) { - GetEnclaveSessionHelper(servername, attestationUrl, true, out sqlEnclaveSession, out counter); + GetEnclaveSessionHelper(servername, attestationUrl, generateCustomData, out sqlEnclaveSession, out counter, out customData, out customDataLength); } // Gets the information that SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. - public override SqlEnclaveAttestationParameters GetAttestationParameters() + public override SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength) { ECDiffieHellmanCng clientDHKey = new ECDiffieHellmanCng(DiffieHellmanKeySize); clientDHKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash; clientDHKey.HashAlgorithm = CngAlgorithm.Sha256; - byte[] attestationParam = PrepareAttestationParameters(); + byte[] attestationParam = PrepareAttestationParameters(attestationUrl, customData, customDataLength); return new SqlEnclaveAttestationParameters(AzureBasedAttestationProtocolId, attestationParam, clientDHKey); } // When overridden in a derived class, performs enclave attestation, generates a symmetric key for the session, creates a an enclave session and stores the session information in the cache. - public override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, string attestationUrl, string servername, out SqlEnclaveSession sqlEnclaveSession, out long counter) + public override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter) { sqlEnclaveSession = null; counter = 0; try { - AttestationInfoCacheItem attestationInfoCacheItem = AttestationInfoCache.Remove(Thread.CurrentThread.ManagedThreadId.ToString()) as AttestationInfoCacheItem; + ThreadRetryCache.Remove(Thread.CurrentThread.ManagedThreadId.ToString()); sqlEnclaveSession = GetEnclaveSessionFromCache(servername, attestationUrl, out counter); if (sqlEnclaveSession == null) { - if (attestationInfoCacheItem != null) + if (!string.IsNullOrEmpty(attestationUrl) && customData != null && customDataLength > 0) { - byte[] nonce = attestationInfoCacheItem.AttestNonce; + byte[] nonce = customData; IdentityModelEventSource.ShowPII = true; @@ -241,19 +241,18 @@ public AzureAttestationToken(byte[] payload) // Attestation Url // Size of nonce // Nonce value - internal byte[] PrepareAttestationParameters() + internal byte[] PrepareAttestationParameters(string attestationUrl, byte[] attestNonce, int attestNonceLength) { - AttestationInfoCacheItem attestationInfoCacheItem = AttestationInfoCache[Thread.CurrentThread.ManagedThreadId.ToString()] as AttestationInfoCacheItem; - if (attestationInfoCacheItem != null) + if (!string.IsNullOrEmpty(attestationUrl) && attestNonce != null && attestNonceLength > 0) { // In c# strings are not null terminated, so adding the null termination before serializing it - string attestationUrlLocal = attestationInfoCacheItem.AttestationUrl + char.MinValue; + string attestationUrlLocal = attestationUrl + char.MinValue; byte[] serializedAttestationUrl = Encoding.Unicode.GetBytes(attestationUrlLocal); byte[] serializedAttestationUrlLength = BitConverter.GetBytes(serializedAttestationUrl.Length); // serializing nonce - byte[] serializedNonce = attestationInfoCacheItem.AttestNonce; - byte[] serializedNonceLength = BitConverter.GetBytes(attestationInfoCacheItem.AttestNonce.Length); + byte[] serializedNonce = attestNonce; + byte[] serializedNonceLength = BitConverter.GetBytes(attestNonceLength); // Computing the total length of the data int totalDataSize = serializedAttestationUrl.Length + serializedAttestationUrlLength.Length + serializedNonce.Length + serializedNonceLength.Length; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveDelegate.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveDelegate.cs index e041c0f8a6..3ab2a78a38 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveDelegate.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveDelegate.cs @@ -50,9 +50,11 @@ internal EnclavePackage GenerateEnclavePackage(SqlConnectionAttestationProtocol SqlEnclaveSession sqlEnclaveSession = null; long counter; + byte[] dummyCustomData = null; + int dummyCustomDataLength; try { - GetEnclaveSession(attestationProtocol, enclaveType, serverName, enclaveAttestationUrl, out sqlEnclaveSession, out counter, throwIfNull: true); + GetEnclaveSession(attestationProtocol, enclaveType, serverName, enclaveAttestationUrl, false, out sqlEnclaveSession, out counter, out dummyCustomData, out dummyCustomDataLength, throwIfNull: true); } catch (Exception e) { @@ -75,16 +77,16 @@ internal void InvalidateEnclaveSession(SqlConnectionAttestationProtocol attestat sqlColumnEncryptionEnclaveProvider.InvalidateEnclaveSession(serverName, EnclaveAttestationUrl, enclaveSession); } - internal void GetEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, string serverName, string enclaveAttestationUrl, out SqlEnclaveSession sqlEnclaveSession) + internal void GetEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, string serverName, string enclaveAttestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out byte[] customData, out int customDataLength) { long counter; - GetEnclaveSession(attestationProtocol, enclaveType, serverName, enclaveAttestationUrl, out sqlEnclaveSession, out counter, throwIfNull: false); + GetEnclaveSession(attestationProtocol, enclaveType, serverName, enclaveAttestationUrl, generateCustomData, out sqlEnclaveSession, out counter, out customData, out customDataLength, throwIfNull: false); } - private void GetEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, string serverName, string enclaveAttestationUrl, out SqlEnclaveSession sqlEnclaveSession, out long counter, bool throwIfNull) + private void GetEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, string serverName, string enclaveAttestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength, bool throwIfNull) { SqlColumnEncryptionEnclaveProvider sqlColumnEncryptionEnclaveProvider = GetEnclaveProvider(attestationProtocol, enclaveType); - sqlColumnEncryptionEnclaveProvider.GetEnclaveSession(serverName, enclaveAttestationUrl, out sqlEnclaveSession, out counter); + sqlColumnEncryptionEnclaveProvider.GetEnclaveSession(serverName, enclaveAttestationUrl, generateCustomData, out sqlEnclaveSession, out counter, out customData, out customDataLength); if (throwIfNull && sqlEnclaveSession == null) { @@ -92,10 +94,10 @@ private void GetEnclaveSession(SqlConnectionAttestationProtocol attestationProto } } - internal SqlEnclaveAttestationParameters GetAttestationParameters(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType) + internal SqlEnclaveAttestationParameters GetAttestationParameters(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, string attestationUrl, byte[] customData, int customDataLength) { SqlColumnEncryptionEnclaveProvider sqlColumnEncryptionEnclaveProvider = GetEnclaveProvider(attestationProtocol, enclaveType); - return sqlColumnEncryptionEnclaveProvider.GetAttestationParameters(); + return sqlColumnEncryptionEnclaveProvider.GetAttestationParameters(attestationUrl, customData, customDataLength); } internal byte[] GetSerializedAttestationParameters(SqlEnclaveAttestationParameters sqlEnclaveAttestationParameters, string enclaveType) @@ -161,8 +163,10 @@ private byte[] GetUintBytes(string enclaveType, int intValue, string variableNam /// attestation url for attestation service endpoint /// attestation info from SQL Server /// attestation parameters + /// A set of extra data needed for attestating the enclave. + /// The length of the extra data needed for attestating the enclave. internal void CreateEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, string serverName, string attestationUrl, - byte[] attestationInfo, SqlEnclaveAttestationParameters attestationParameters) + byte[] attestationInfo, SqlEnclaveAttestationParameters attestationParameters, byte[] customData, int customDataLength) { lock (_lock) @@ -170,17 +174,21 @@ internal void CreateEnclaveSession(SqlConnectionAttestationProtocol attestationP SqlColumnEncryptionEnclaveProvider sqlColumnEncryptionEnclaveProvider = GetEnclaveProvider(attestationProtocol, enclaveType); long counter; SqlEnclaveSession sqlEnclaveSession = null; - sqlColumnEncryptionEnclaveProvider.GetEnclaveSession(serverName, attestationUrl, out sqlEnclaveSession, out counter); + byte[] dummyCustomData = null; + int dummyCustomDataLength; + sqlColumnEncryptionEnclaveProvider.GetEnclaveSession(serverName, attestationUrl, false, out sqlEnclaveSession, out counter, out dummyCustomData, out dummyCustomDataLength); if (sqlEnclaveSession != null) { return; } - sqlColumnEncryptionEnclaveProvider.CreateEnclaveSession(attestationInfo, attestationParameters.ClientDiffieHellmanKey, attestationUrl, serverName, out sqlEnclaveSession, out counter); + sqlColumnEncryptionEnclaveProvider.CreateEnclaveSession(attestationInfo, attestationParameters.ClientDiffieHellmanKey, attestationUrl, serverName, customData, customDataLength, out sqlEnclaveSession, out counter); if (sqlEnclaveSession == null) + { throw SQL.NullEnclaveSessionReturnedFromProvider(enclaveType, attestationUrl); + } } } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveProviderBase.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveProviderBase.cs index 394da6f76e..953faaa018 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveProviderBase.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveProviderBase.cs @@ -21,10 +21,9 @@ // In case if the enclave session is cached and validate(not expired), GetEnclaveSession API returns the SqlEnclaveSession. // In case if the enclave session is not cached then driver end up calling GetAttestationParameters and CreateEnclaveSession. // Note: When we have non-enclave query, then in those cases we never call CreateEnclaveSession. This is one of main pivot point for designing the below locking model. -// As per current API design driver passes attestation url and the server name during GetEnclaveSession but not in GetAttestationParameters. -// In order to create the attestation parameter enclave provider needs to know the attestation url. To overcome this limitation we added a AttestationInfoCache memory cache -// which save the attestation url and nonce with current thread as the lookup key. -// Later we use the AttestationInfoCache object to retrieve the attestation url in GetAttestationParameters which we saved during CreateEnclaveSession call. +// After the change to the API design, driver passes attestation url and servername during GetEnclaveSession and GetAttestationParameters. The extra correlation ID, such as nonce, +// will be generated during GetEnclaveSession on demand and be passed back to driver as customData and customDataLength. Later, GetAttestationParameters and CreateEnclaveSession +// use the customData and customDataLength to do the attestation. // 2. In case during application start, if app spins of multiple threads at the same time (during stress test or benchmarking) where DB connection is Always encrypted enabled, // then with the existing design we end up creating multiple enclave session. Each enclave session adds an extra memory overhead to the system and also it generates multiple calls to attestation @@ -70,7 +69,7 @@ internal abstract class EnclaveProviderBase : SqlColumnEncryptionEnclaveProvider { #region Constants private const int NonceSize = 256; - private const int AttestationInfoCacheTimeoutInMinutes = 10; + private const int ThreadRetryCacheTimeoutInMinutes = 10; private const int LockTimeoutMaxInMilliseconds = 15 * 1000; // 15 seconds #endregion @@ -85,28 +84,19 @@ internal abstract class EnclaveProviderBase : SqlColumnEncryptionEnclaveProvider private static readonly Object lockUpdateSessionLock = new Object(); - internal class AttestationInfoCacheItem - { - public string AttestationUrl { get; private set; } - - public byte[] AttestNonce { get; private set; } - public AttestationInfoCacheItem(string attestationUri, byte[] nonce) - { - AttestationUrl = attestationUri; - AttestNonce = nonce; - } - } - // It is used to save the attestation url and nonce value across API calls - protected static readonly MemoryCache AttestationInfoCache = new MemoryCache("AttestationInfoCache"); + protected static readonly MemoryCache ThreadRetryCache = new MemoryCache("ThreadRetryCache"); #endregion #region Public methods // Helper method to get the enclave session from the cache if present - protected void GetEnclaveSessionHelper(string servername, string attestationUrl, bool shouldGenerateNonce, out SqlEnclaveSession sqlEnclaveSession, out long counter) + protected void GetEnclaveSessionHelper(string servername, string attestationUrl, bool shouldGenerateNonce, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) { + customData = null; + customDataLength = 0; sqlEnclaveSession = SessionCache.GetEnclaveSession(servername, attestationUrl, out counter); + if (sqlEnclaveSession == null) { bool sessionCacheLockTaken = false; @@ -114,8 +104,8 @@ protected void GetEnclaveSessionHelper(string servername, string attestationUrl, // 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 - AttestationInfoCacheItem attestationInfoCacheItem = AttestationInfoCache[Thread.CurrentThread.ManagedThreadId.ToString()] as AttestationInfoCacheItem; - if (attestationInfoCacheItem != null) + string retryThreadID = ThreadRetryCache[Thread.CurrentThread.ManagedThreadId.ToString()] as string; + if (!string.IsNullOrEmpty(retryThreadID)) { sameThreadRetry = true; } @@ -161,23 +151,25 @@ protected void GetEnclaveSessionHelper(string servername, string attestationUrl, if (sqlEnclaveSession == null) { - if (!sameThreadRetry) + if (shouldGenerateNonce) { - // Client decides to initiate the process of attesting the enclave and to establish a secure session with the enclave. - // To ensure that server send new attestation request instead of replaying / re-sending the old token, we will create a nonce for current attestation request. - byte[] nonce = new byte[NonceSize]; - if (shouldGenerateNonce) + using (RandomNumberGenerator rng = new RNGCryptoServiceProvider()) { - using (RandomNumberGenerator rng = new RNGCryptoServiceProvider()) - { - rng.GetBytes(nonce); - } + // Client decides to initiate the process of attesting the enclave and to establish a secure session with the enclave. + // To ensure that server send new attestation request instead of replaying / re-sending the old token, we will create a nonce for current attestation request. + byte[] nonce = new byte[NonceSize]; + rng.GetBytes(nonce); + customData = nonce; + customDataLength = nonce.Length; } + } - attestationInfoCacheItem = new AttestationInfoCacheItem(attestationUrl, nonce); + if (!sameThreadRetry) + { + retryThreadID = Thread.CurrentThread.ManagedThreadId.ToString(); } - AttestationInfoCache.Set(Thread.CurrentThread.ManagedThreadId.ToString(), attestationInfoCacheItem, DateTime.UtcNow.AddMinutes(AttestationInfoCacheTimeoutInMinutes)); + ThreadRetryCache.Set(Thread.CurrentThread.ManagedThreadId.ToString(), retryThreadID, DateTime.UtcNow.AddMinutes(ThreadRetryCacheTimeoutInMinutes)); } } } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs index eaab03d7d1..b5915c578c 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs @@ -6,19 +6,18 @@ namespace Microsoft.Data.SqlClient { - /// public abstract class SqlColumnEncryptionEnclaveProvider { /// - public abstract void GetEnclaveSession(string serverName, string attestationUrl, out SqlEnclaveSession sqlEnclaveSession, out long counter); + public abstract void GetEnclaveSession(string serverName, string attestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength); /// - public abstract SqlEnclaveAttestationParameters GetAttestationParameters(); + public abstract SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength); /// - public abstract void CreateEnclaveSession(byte[] enclaveAttestationInfo, ECDiffieHellmanCng clientDiffieHellmanKey, string attestationUrl, string servername, out SqlEnclaveSession sqlEnclaveSession, out long counter); + public abstract void CreateEnclaveSession(byte[] enclaveAttestationInfo, ECDiffieHellmanCng clientDiffieHellmanKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter); /// public abstract void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSession); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs index 61dc9301ff..3993e01e2e 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -125,6 +125,8 @@ private enum EXECTYPE private bool requiresEnclaveComputations = false; internal EnclaveDelegate.EnclavePackage enclavePackage = null; private SqlEnclaveAttestationParameters enclaveAttestationParameters = null; + private byte[] customData = null; + private int customDataLength = 0; // Last TaskCompletionSource for reconnect task - use for cancellation only TaskCompletionSource _reconnectionCompletionSource = null; @@ -3874,6 +3876,8 @@ private void ResetEncryptionState() enclavePackage = null; requiresEnclaveComputations = false; enclaveAttestationParameters = null; + customData = null; + customDataLength = 0; } /// @@ -4279,10 +4283,10 @@ private SqlDataReader TryFetchInputParameterEncryptionInfo(int timeout, string dataSource = this._activeConnection.DataSource; string enclaveAttestationUrl = this._activeConnection.EnclaveAttestationUrl; SqlEnclaveSession sqlEnclaveSession = null; - EnclaveDelegate.Instance.GetEnclaveSession(attestationProtocol, enclaveType, dataSource, enclaveAttestationUrl, out sqlEnclaveSession); + EnclaveDelegate.Instance.GetEnclaveSession(attestationProtocol, enclaveType, dataSource, enclaveAttestationUrl, true, out sqlEnclaveSession, out customData, out customDataLength); if (sqlEnclaveSession == null) { - this.enclaveAttestationParameters = EnclaveDelegate.Instance.GetAttestationParameters(attestationProtocol, enclaveType); + this.enclaveAttestationParameters = EnclaveDelegate.Instance.GetAttestationParameters(attestationProtocol, enclaveType, enclaveAttestationUrl, customData, customDataLength); serializedAttestatationParameters = EnclaveDelegate.Instance.GetSerializedAttestationParameters(this.enclaveAttestationParameters, enclaveType); } } @@ -4811,7 +4815,7 @@ private void ReadDescribeEncryptionParameterResults(SqlDataReader ds, ReadOnlyDi string dataSource = this._activeConnection.DataSource; string enclaveAttestationUrl = this._activeConnection.EnclaveAttestationUrl; - EnclaveDelegate.Instance.CreateEnclaveSession(attestationProtocol, enclaveType, dataSource, enclaveAttestationUrl, attestationInfo, enclaveAttestationParameters); + EnclaveDelegate.Instance.CreateEnclaveSession(attestationProtocol, enclaveType, dataSource, enclaveAttestationUrl, attestationInfo, enclaveAttestationParameters, customData, customDataLength); enclaveAttestationParameters = null; attestationInfoRead = true; } @@ -7533,5 +7537,3 @@ internal void CompletePendingReadWithFailure(int errorCode, bool resetForcePendi #endif } } - - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs index e90978f717..f7b256c6ea 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs @@ -86,14 +86,14 @@ internal abstract class VirtualizationBasedSecurityEnclaveProviderBase : Enclave // When overridden in a derived class, looks up an existing enclave session information in the enclave session cache. // If the enclave provider doesn't implement enclave session caching, this method is expected to return null in the sqlEnclaveSession parameter. - public override void GetEnclaveSession(string servername, string attestationUrl, out SqlEnclaveSession sqlEnclaveSession, out long counter) + public override void GetEnclaveSession(string servername, string attestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) { - GetEnclaveSessionHelper(servername, attestationUrl, false, out sqlEnclaveSession, out counter); + GetEnclaveSessionHelper(servername, attestationUrl, false, out sqlEnclaveSession, out counter, out customData, out customDataLength); } // Gets the information that SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. // The information SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. - public override SqlEnclaveAttestationParameters GetAttestationParameters() + public override SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength) { ECDiffieHellmanCng clientDHKey = new ECDiffieHellmanCng(DiffieHellmanKeySize); clientDHKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash; @@ -102,17 +102,17 @@ public override SqlEnclaveAttestationParameters GetAttestationParameters() } // When overridden in a derived class, performs enclave attestation, generates a symmetric key for the session, creates a an enclave session and stores the session information in the cache. - public override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, string attestationUrl, string servername, out SqlEnclaveSession sqlEnclaveSession, out long counter) + public override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter) { sqlEnclaveSession = null; counter = 0; try { - AttestationInfoCacheItem attestationInfoCacheItem = AttestationInfoCache.Remove(Thread.CurrentThread.ManagedThreadId.ToString()) as AttestationInfoCacheItem; + ThreadRetryCache.Remove(Thread.CurrentThread.ManagedThreadId.ToString()); sqlEnclaveSession = GetEnclaveSessionFromCache(servername, attestationUrl, out counter); if (sqlEnclaveSession == null) { - if (attestationInfoCacheItem != null) + if (!string.IsNullOrEmpty(attestationUrl)) { // Deserialize the payload AttestationInfo info = new AttestationInfo(attestationInfo);