// 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) { sqlEnclaveSession = null; counter = 0; try { AttestationInfoCacheItem attestationInfoCacheItem = AttestationInfoCache.Remove(Thread.CurrentThread.ManagedThreadId.ToString()) as AttestationInfoCacheItem; sqlEnclaveSession = GetEnclaveSessionFromCache(servername, attestationUrl, out counter); if (sqlEnclaveSession == null) { if (attestationInfoCacheItem != null) { byte[] nonce = attestationInfoCacheItem.AttestNonce; IdentityModelEventSource.ShowPII = true; // Deserialize the payload AzureAttestationInfo attestInfo = new AzureAttestationInfo(attestationInfo); // Validate the attestation info VerifyAzureAttestationInfo(attestationUrl, attestInfo.EnclaveType, attestInfo.AttestationToken.AttestationToken, attestInfo.Identity, nonce); // Set up shared secret and validate signature byte[] sharedSecret = GetSharedSecret(attestInfo.Identity, nonce, attestInfo.EnclaveType, attestInfo.EnclaveDHInfo, clientDHKey); // add session to cache sqlEnclaveSession = AddEnclaveSessionToCache(attestationUrl, servername, sharedSecret, attestInfo.SessionId, out counter); } else { throw new AlwaysEncryptedAttestationException(SR.FailToCreateEnclaveSession); } } } finally { // As per current design, we want to minimize the number of create session calls. To acheive this we block all the GetEnclaveSession calls until the first call to // GetEnclaveSession -> GetAttestationParameters -> CreateEnclaveSession completes or the event timeout happen. // Case 1: When the first request successfully creates the session, then all outstanding GetEnclaveSession will use the current session. // Case 2: When the first request unable to create the encalve session (may be due to some error or the first request doesn't require enclave computation) then in those case we set the event timeout to 0. UpdateEnclaveSessionLockStatus(sqlEnclaveSession); } }
// Prepare the attestation data in following format // Attestation Url length // Attestation Url // Size of nonce // Nonce value internal byte[] PrepareAttestationParameters() { AttestationInfoCacheItem attestationInfoCacheItem = AttestationInfoCache[Thread.CurrentThread.ManagedThreadId.ToString()] as AttestationInfoCacheItem; if (attestationInfoCacheItem != null) { // In c# strings are not null terminated, so adding the null termination before serializing it string attestationUrlLocal = attestationInfoCacheItem.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); // Computing the total length of the data int totalDataSize = serializedAttestationUrl.Length + serializedAttestationUrlLength.Length + serializedNonce.Length + serializedNonceLength.Length; int dataCopied = 0; byte[] attestationParam = new byte[totalDataSize]; // copy the attestation url and url length Buffer.BlockCopy(serializedAttestationUrlLength, 0, attestationParam, dataCopied, serializedAttestationUrlLength.Length); dataCopied += serializedAttestationUrlLength.Length; Buffer.BlockCopy(serializedAttestationUrl, 0, attestationParam, dataCopied, serializedAttestationUrl.Length); dataCopied += serializedAttestationUrl.Length; // copy the nonce and nonce length Buffer.BlockCopy(serializedNonceLength, 0, attestationParam, dataCopied, serializedNonceLength.Length); dataCopied += serializedNonceLength.Length; Buffer.BlockCopy(serializedNonce, 0, attestationParam, dataCopied, serializedNonce.Length); dataCopied += serializedNonce.Length; return(attestationParam); } else { throw new AlwaysEncryptedAttestationException(SR.FailToCreateEnclaveSession); } }
// 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) { sqlEnclaveSession = null; counter = 0; try { AttestationInfoCacheItem attestationInfoCacheItem = AttestationInfoCache.Remove(Thread.CurrentThread.ManagedThreadId.ToString()) as AttestationInfoCacheItem; sqlEnclaveSession = GetEnclaveSessionFromCache(servername, attestationUrl, out counter); if (sqlEnclaveSession == null) { if (attestationInfoCacheItem != null) { // Deserialize the payload AttestationInfo info = new AttestationInfo(attestationInfo); // Verify enclave policy matches expected policy VerifyEnclavePolicy(info.EnclaveReportPackage); // Perform Attestation per VSM protocol VerifyAttestationInfo(attestationUrl, info.HealthReport, info.EnclaveReportPackage); // Set up shared secret and validate signature byte[] sharedSecret = GetSharedSecret(info.Identity, info.EnclaveDHInfo, clientDHKey); // add session to cache sqlEnclaveSession = AddEnclaveSessionToCache(attestationUrl, servername, sharedSecret, info.SessionId, out counter); } else { throw new AlwaysEncryptedAttestationException(SR.FailToCreateEnclaveSession); } } } finally { UpdateEnclaveSessionLockStatus(sqlEnclaveSession); } }
// 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) { sqlEnclaveSession = SessionCache.GetEnclaveSession(servername, attestationUrl, out counter); if (sqlEnclaveSession == null) { bool sessionCacheLockTaken = false; bool sameThreadRetry = false; // 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) { sameThreadRetry = true; } else { // We are explicitly not signalling the event here, as we want to hold the event till driver calls CreateEnclaveSession // If we signal the event now, then multiple thread end up calling GetAttestationParameters which triggers the attestation workflow. sessionCacheLockTaken = sessionLockEvent.WaitOne(lockTimeoutInMilliseconds); if (sessionCacheLockTaken) { lock (lockUpdateSessionLock) { isSessionLockAcquired = true; } } } // In case of multi-threaded application, first thread will set the event and all the subsequent threads will wait here either until the enclave // session is created or timeout happens. if (sessionCacheLockTaken || sameThreadRetry) { // While the current thread is waiting for event to be signaled and in the meanwhile we already completed the attestation on different thread // then we need to signal the event here sqlEnclaveSession = SessionCache.GetEnclaveSession(servername, attestationUrl, out counter); if (sqlEnclaveSession != null && !sameThreadRetry) { lock (lockUpdateSessionLock) { isSessionLockAcquired = false; sessionLockEvent.Set(); } } } else { // In case if we are unable to signal the event, then it represents either // 1. On other thread we have an ongoing attestation request which is taking more time may due to slow network or // 2. Current workload doesn't require enclave computation due to which driver is not invoking the CreateEnclaveSession, hence sqlEnclaveSession is never set. // In both cases we need to reduce the timeout to 0 so that subsequent request should not wait. Interlocked.Exchange(ref lockTimeoutInMilliseconds, 0); } if (sqlEnclaveSession == null) { if (!sameThreadRetry) { // 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()) { rng.GetBytes(nonce); } } attestationInfoCacheItem = new AttestationInfoCacheItem(attestationUrl, nonce); } AttestationInfoCache.Set(Thread.CurrentThread.ManagedThreadId.ToString(), attestationInfoCacheItem, DateTime.UtcNow.AddMinutes(AttestationInfoCacheTimeoutInMinutes)); } } }