From 66edd5801f0a94dc172788b294e02fd1223f2dce Mon Sep 17 00:00:00 2001 From: Karina Zhou Date: Thu, 13 Feb 2020 16:16:00 -0800 Subject: [PATCH 1/2] Internal | Add Enclave Simulator for Internal Testing (#427) - Added support for enclave simulator bypassing the actual attestation. - Added BuildSimulator property to enable the simulator support. How to use the enclave simulator: Given Attestation Protocol = SIM and Enclave Attestation Url= SomeDummyURL in the connection string and run the AE enclave-enabled tests. NOTE: This change is for internal testing only. The simulator support should be disabled in the official nuget package releases. --- .../SqlConnectionAttestationProtocol.xml | 4 + src/Directory.Build.props | 4 + .../Microsoft.Data.SqlClient.NetCoreApp.cs | 5 + .../src/Microsoft.Data.SqlClient.csproj | 3 + .../Data/Common/DbConnectionStringCommon.cs | 23 +++- .../SqlClient/EnclaveDelegate.NetCoreApp.cs | 13 ++ .../SimulatorEnclaveProvider.NetCoreApp.cs | 116 ++++++++++++++++++ .../src/Microsoft/Data/SqlClient/TdsEnums.cs | 9 +- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 23 +++- .../netfx/ref/Microsoft.Data.SqlClient.cs | 5 + .../netfx/src/Microsoft.Data.SqlClient.csproj | 3 + .../Data/Common/DbConnectionStringCommon.cs | 23 +++- .../Data/SqlClient/EnclaveDelegate.cs | 13 ++ .../SqlClient/SimulatorEnclaveProvider.cs | 116 ++++++++++++++++++ .../src/Microsoft/Data/SqlClient/TdsEnums.cs | 9 +- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 23 +++- 16 files changed, 386 insertions(+), 6 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SimulatorEnclaveProvider.NetCoreApp.cs create mode 100644 src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SimulatorEnclaveProvider.cs diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionAttestationProtocol.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionAttestationProtocol.xml index 3f64ef38b8..d5f32ef2d3 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionAttestationProtocol.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionAttestationProtocol.xml @@ -13,6 +13,10 @@ Attestation portocol for Azure Attestation Service 1 + + Attestation protocol for Simulator + 2 + Attestation protocol for Host Guardian Service 3 diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 99dc4bc27a..955ea8cc4a 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -39,6 +39,10 @@ $(DotNetRoot)dotnet $(DotNetCmd).exe true + false + + + $(DefineConstants);ENCLAVE_SIMULATOR 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 965902f3b8..837533659c 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 @@ -113,6 +113,11 @@ public enum SqlConnectionAttestationProtocol /// AAS = 1, +#if ENCLAVE_SIMULATOR + /// + SIM = 2, +#endif + /// HGS = 3 } 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 7b20a62142..1b8b1bb014 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -53,6 +53,9 @@ + + + diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionStringCommon.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionStringCommon.cs index c45b12af7f..8cf67f1aca 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionStringCommon.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionStringCommon.cs @@ -221,6 +221,9 @@ internal static string ColumnEncryptionSettingToString(SqlConnectionColumnEncryp /// const string AttestationProtocolHGS = "HGS"; const string AttestationProtocolAAS = "AAS"; +#if ENCLAVE_SIMULATOR + const string AttestationProtocolSIM = "SIM"; +#endif /// /// Convert a string value to the corresponding SqlConnectionAttestationProtocol @@ -240,6 +243,13 @@ internal static bool TryConvertToAttestationProtocol(string value, out SqlConnec result = SqlConnectionAttestationProtocol.AAS; return true; } +#if ENCLAVE_SIMULATOR + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, AttestationProtocolSIM)) + { + result = SqlConnectionAttestationProtocol.SIM; + return true; + } +#endif else { result = DbConnectionStringDefaults.AttestationProtocol; @@ -249,11 +259,18 @@ internal static bool TryConvertToAttestationProtocol(string value, out SqlConnec internal static bool IsValidAttestationProtocol(SqlConnectionAttestationProtocol value) { +#if ENCLAVE_SIMULATOR + Debug.Assert(Enum.GetNames(typeof(SqlConnectionAttestationProtocol)).Length == 4, "SqlConnectionAttestationProtocol enum has changed, update needed"); + return value == SqlConnectionAttestationProtocol.NotSpecified + || value == SqlConnectionAttestationProtocol.HGS + || value == SqlConnectionAttestationProtocol.AAS + || value == SqlConnectionAttestationProtocol.SIM; +#else Debug.Assert(Enum.GetNames(typeof(SqlConnectionAttestationProtocol)).Length == 3, "SqlConnectionAttestationProtocol enum has changed, update needed"); return value == SqlConnectionAttestationProtocol.NotSpecified || value == SqlConnectionAttestationProtocol.HGS || value == SqlConnectionAttestationProtocol.AAS; - +#endif } internal static string AttestationProtocolToString(SqlConnectionAttestationProtocol value) @@ -266,6 +283,10 @@ internal static string AttestationProtocolToString(SqlConnectionAttestationProto return AttestationProtocolHGS; case SqlConnectionAttestationProtocol.AAS: return AttestationProtocolAAS; +#if ENCLAVE_SIMULATOR + case SqlConnectionAttestationProtocol.SIM: + return AttestationProtocolSIM; +#endif default: return null; } 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 48473b0722..a9ff36f11d 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 @@ -166,6 +166,14 @@ private SqlColumnEncryptionEnclaveProvider GetEnclaveProvider(SqlConnectionAttes sqlColumnEncryptionEnclaveProvider = EnclaveProviders[attestationProtocol]; break; +#if ENCLAVE_SIMULATOR + case SqlConnectionAttestationProtocol.SIM: + SimulatorEnclaveProvider simulatorEnclaveProvider = new SimulatorEnclaveProvider(); + EnclaveProviders[attestationProtocol] = (SqlColumnEncryptionEnclaveProvider)simulatorEnclaveProvider; + sqlColumnEncryptionEnclaveProvider = EnclaveProviders[attestationProtocol]; + break; +#endif + default: break; } @@ -189,6 +197,11 @@ private string ConvertAttestationProtocolToString(SqlConnectionAttestationProtoc case SqlConnectionAttestationProtocol.HGS: return "HGS"; +#if ENCLAVE_SIMULATOR + case SqlConnectionAttestationProtocol.SIM: + return "SIM"; +#endif + default: return "NotSpecified"; } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SimulatorEnclaveProvider.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SimulatorEnclaveProvider.NetCoreApp.cs new file mode 100644 index 0000000000..542af35688 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SimulatorEnclaveProvider.NetCoreApp.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Diagnostics; +using System.Linq; +using System.Runtime.Caching; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Concurrent; + +namespace Microsoft.Data.SqlClient +{ + internal class SimulatorEnclaveProvider : EnclaveProviderBase + { + private static readonly int EnclaveSessionHandleSize = 8; + + // 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, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) + { + 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(string attestationUrl, byte[] customData, int customDataLength) + { + ECDiffieHellmanCng clientDHKey = new ECDiffieHellmanCng(384); + clientDHKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash; + clientDHKey.HashAlgorithm = CngAlgorithm.Sha256; + + return new SqlEnclaveAttestationParameters(2, new byte[] { }, 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, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter) + { + ////for simulator: enclave does not send public key, and sends an empty attestation info + //// The only non-trivial content it sends is the session setup info (DH pubkey of enclave) + + sqlEnclaveSession = null; + counter = 0; + try + { + ThreadRetryCache.Remove(Thread.CurrentThread.ManagedThreadId.ToString()); + sqlEnclaveSession = GetEnclaveSessionFromCache(servername, attestationUrl, out counter); + + if (sqlEnclaveSession == null) + { + if (!string.IsNullOrEmpty(attestationUrl)) + { + ////Read AttestationInfo + int attestationInfoOffset = 0; + uint sizeOfTrustedModuleAttestationInfoBuffer = BitConverter.ToUInt32(attestationInfo, attestationInfoOffset); + attestationInfoOffset += sizeof(UInt32); + int sizeOfTrustedModuleAttestationInfoBufferInt = checked((int)sizeOfTrustedModuleAttestationInfoBuffer); + Debug.Assert(sizeOfTrustedModuleAttestationInfoBuffer == 0); + + ////read secure session info + uint sizeOfSecureSessionInfoResponse = BitConverter.ToUInt32(attestationInfo, attestationInfoOffset); + attestationInfoOffset += sizeof(UInt32); + + byte[] enclaveSessionHandle = new byte[EnclaveSessionHandleSize]; + Buffer.BlockCopy(attestationInfo, attestationInfoOffset, enclaveSessionHandle, 0, EnclaveSessionHandleSize); + attestationInfoOffset += EnclaveSessionHandleSize; + + uint sizeOfTrustedModuleDHPublicKeyBuffer = BitConverter.ToUInt32(attestationInfo, attestationInfoOffset); + attestationInfoOffset += sizeof(UInt32); + uint sizeOfTrustedModuleDHPublicKeySignatureBuffer = BitConverter.ToUInt32(attestationInfo, attestationInfoOffset); + attestationInfoOffset += sizeof(UInt32); + int sizeOfTrustedModuleDHPublicKeyBufferInt = checked((int)sizeOfTrustedModuleDHPublicKeyBuffer); + + byte[] trustedModuleDHPublicKey = new byte[sizeOfTrustedModuleDHPublicKeyBuffer]; + Buffer.BlockCopy(attestationInfo, attestationInfoOffset, trustedModuleDHPublicKey, 0, + sizeOfTrustedModuleDHPublicKeyBufferInt); + attestationInfoOffset += sizeOfTrustedModuleDHPublicKeyBufferInt; + + byte[] trustedModuleDHPublicKeySignature = new byte[sizeOfTrustedModuleDHPublicKeySignatureBuffer]; + Buffer.BlockCopy(attestationInfo, attestationInfoOffset, trustedModuleDHPublicKeySignature, 0, + checked((int)sizeOfTrustedModuleDHPublicKeySignatureBuffer)); + + CngKey k = CngKey.Import(trustedModuleDHPublicKey, CngKeyBlobFormat.EccPublicBlob); + byte[] sharedSecret = clientDHKey.DeriveKeyMaterial(k); + long sessionId = BitConverter.ToInt64(enclaveSessionHandle, 0); + sqlEnclaveSession = AddEnclaveSessionToCache(attestationUrl, servername, sharedSecret, sessionId, out counter); + } + else + { + throw new AlwaysEncryptedAttestationException(SR.FailToCreateEnclaveSession); + } + } + } + finally + { + UpdateEnclaveSessionLockStatus(sqlEnclaveSession); + } + } + + /// + /// When overridden in a derived class, looks up and evicts an enclave session from the enclave session cache, if the provider implements session caching. + /// + /// The name of the SQL Server instance containing the enclave. + /// The endpoint of an attestation service, SqlClient contacts to attest the enclave. + /// The session to be invalidated. + public override void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSessionToInvalidate) + { + InvalidateEnclaveSessionHelper(serverName, enclaveAttestationUrl, enclaveSessionToInvalidate); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsEnums.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsEnums.cs index 48aadf85e5..8828409ddf 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -983,6 +983,9 @@ internal static string GetSniContextEnumName(SniContext sniContext) internal const int AEAD_AES_256_CBC_HMAC_SHA256 = 2; internal const string ENCLAVE_TYPE_VBS = "VBS"; internal const string ENCLAVE_TYPE_SGX = "SGX"; +#if ENCLAVE_SIMULATOR + internal const string ENCLAVE_TYPE_SIMULATOR = "SIMULATOR"; +#endif // TCE Param names for exec handling internal const string TCE_PARAM_CIPHERTEXT = "cipherText"; @@ -1056,6 +1059,11 @@ public enum SqlConnectionAttestationProtocol /// AAS = 1, +#if ENCLAVE_SIMULATOR + /// + SIM = 2, +#endif + /// HGS = 3 } @@ -1159,4 +1167,3 @@ internal enum DescribeParameterEncryptionResultSet3 AttestationInfo = 0, } } - diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index 68ce6b544b..3c827f323e 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -3031,10 +3031,13 @@ private bool TryProcessFeatureExtAck(TdsParserStateObject stateObj) private bool IsValidAttestationProtocol(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType) { - switch (enclaveType) + switch (enclaveType.ToUpper()) { case TdsEnums.ENCLAVE_TYPE_VBS: if (attestationProtocol != SqlConnectionAttestationProtocol.AAS +#if ENCLAVE_SIMULATOR + && attestationProtocol != SqlConnectionAttestationProtocol.SIM +#endif && attestationProtocol != SqlConnectionAttestationProtocol.HGS) { return false; @@ -3042,12 +3045,25 @@ private bool IsValidAttestationProtocol(SqlConnectionAttestationProtocol attesta break; case TdsEnums.ENCLAVE_TYPE_SGX: +#if ENCLAVE_SIMULATOR + if (attestationProtocol != SqlConnectionAttestationProtocol.AAS + && attestationProtocol != SqlConnectionAttestationProtocol.SIM) +#else if (attestationProtocol != SqlConnectionAttestationProtocol.AAS) +#endif { return false; } break; +#if ENCLAVE_SIMULATOR + case TdsEnums.ENCLAVE_TYPE_SIMULATOR: + if (attestationProtocol != SqlConnectionAttestationProtocol.SIM) + { + return false; + } + break; +#endif default: // if we reach here, the enclave type is not supported throw SQL.EnclaveTypeNotSupported(enclaveType); @@ -3066,6 +3082,11 @@ private string ConvertAttestationProtocolToString(SqlConnectionAttestationProtoc case SqlConnectionAttestationProtocol.HGS: return "HGS"; +#if ENCLAVE_SIMULATOR + case SqlConnectionAttestationProtocol.SIM: + return "SIM"; +#endif + default: return "NotSpecified"; } 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 8a9fa8429d..6371d4fbca 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -801,6 +801,11 @@ public enum SqlConnectionAttestationProtocol /// AAS = 1, +#if ENCLAVE_SIMULATOR + /// + SIM = 2, +#endif + /// HGS = 3 } 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 bdeb3cc6c0..ed6135157b 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -308,6 +308,9 @@ + + + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionStringCommon.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionStringCommon.cs index 772f422b1a..08857968cb 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionStringCommon.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionStringCommon.cs @@ -822,6 +822,9 @@ internal static SqlConnectionColumnEncryptionSetting ConvertToColumnEncryptionSe /// const string AttestationProtocolHGS = "HGS"; const string AttestationProtocolAAS = "AAS"; +#if ENCLAVE_SIMULATOR + const string AttestationProtocolSIM = "SIM"; +#endif /// /// Convert a string value to the corresponding SqlConnectionAttestationProtocol @@ -841,6 +844,13 @@ internal static bool TryConvertToAttestationProtocol(string value, out SqlConnec result = SqlConnectionAttestationProtocol.AAS; return true; } +#if ENCLAVE_SIMULATOR + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, AttestationProtocolSIM)) + { + result = SqlConnectionAttestationProtocol.SIM; + return true; + } +#endif else { result = DbConnectionStringDefaults.AttestationProtocol; @@ -850,11 +860,18 @@ internal static bool TryConvertToAttestationProtocol(string value, out SqlConnec internal static bool IsValidAttestationProtocol(SqlConnectionAttestationProtocol value) { +#if ENCLAVE_SIMULATOR + Debug.Assert(Enum.GetNames(typeof(SqlConnectionAttestationProtocol)).Length == 4, "SqlConnectionAttestationProtocol enum has changed, update needed"); + return value == SqlConnectionAttestationProtocol.NotSpecified + || value == SqlConnectionAttestationProtocol.HGS + || value == SqlConnectionAttestationProtocol.AAS + || value == SqlConnectionAttestationProtocol.SIM; +#else Debug.Assert(Enum.GetNames(typeof(SqlConnectionAttestationProtocol)).Length == 3, "SqlConnectionAttestationProtocol enum has changed, update needed"); return value == SqlConnectionAttestationProtocol.NotSpecified || value == SqlConnectionAttestationProtocol.HGS || value == SqlConnectionAttestationProtocol.AAS; - +#endif } internal static string AttestationProtocolToString(SqlConnectionAttestationProtocol value) @@ -867,6 +884,10 @@ internal static string AttestationProtocolToString(SqlConnectionAttestationProto return AttestationProtocolHGS; case SqlConnectionAttestationProtocol.AAS: return AttestationProtocolAAS; +#if ENCLAVE_SIMULATOR + case SqlConnectionAttestationProtocol.SIM: + return AttestationProtocolSIM; +#endif default: return null; } 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 3ab2a78a38..d57bc47067 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 @@ -212,6 +212,14 @@ private SqlColumnEncryptionEnclaveProvider GetEnclaveProvider(SqlConnectionAttes sqlColumnEncryptionEnclaveProvider = EnclaveProviders[attestationProtocol]; break; +#if ENCLAVE_SIMULATOR + case SqlConnectionAttestationProtocol.SIM: + SimulatorEnclaveProvider simulatorEnclaveProvider = new SimulatorEnclaveProvider(); + EnclaveProviders[attestationProtocol] = (SqlColumnEncryptionEnclaveProvider)simulatorEnclaveProvider; + sqlColumnEncryptionEnclaveProvider = EnclaveProviders[attestationProtocol]; + break; +#endif + default: break; } @@ -235,6 +243,11 @@ private string ConvertAttestationProtocolToString(SqlConnectionAttestationProtoc case SqlConnectionAttestationProtocol.HGS: return "HGS"; +#if ENCLAVE_SIMULATOR + case SqlConnectionAttestationProtocol.SIM: + return "SIM"; +#endif + default: return "NotSpecified"; } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SimulatorEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SimulatorEnclaveProvider.cs new file mode 100644 index 0000000000..811816f1e6 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SimulatorEnclaveProvider.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Diagnostics; +using System.Linq; +using System.Runtime.Caching; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Concurrent; + +namespace Microsoft.Data.SqlClient +{ + internal class SimulatorEnclaveProvider : EnclaveProviderBase + { + private static readonly int EnclaveSessionHandleSize = 8; + + // 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, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) + { + 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(string attestationUrl, byte[] customData, int customDataLength) + { + ECDiffieHellmanCng clientDHKey = new ECDiffieHellmanCng(384); + clientDHKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash; + clientDHKey.HashAlgorithm = CngAlgorithm.Sha256; + + return new SqlEnclaveAttestationParameters(2, new byte[] { }, 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, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter) + { + ////for simulator: enclave does not send public key, and sends an empty attestation info + //// The only non-trivial content it sends is the session setup info (DH pubkey of enclave) + + sqlEnclaveSession = null; + counter = 0; + try + { + ThreadRetryCache.Remove(Thread.CurrentThread.ManagedThreadId.ToString()); + sqlEnclaveSession = GetEnclaveSessionFromCache(servername, attestationUrl, out counter); + + if (sqlEnclaveSession == null) + { + if (!string.IsNullOrEmpty(attestationUrl)) + { + ////Read AttestationInfo + int attestationInfoOffset = 0; + uint sizeOfTrustedModuleAttestationInfoBuffer = BitConverter.ToUInt32(attestationInfo, attestationInfoOffset); + attestationInfoOffset += sizeof(UInt32); + int sizeOfTrustedModuleAttestationInfoBufferInt = checked((int)sizeOfTrustedModuleAttestationInfoBuffer); + Debug.Assert(sizeOfTrustedModuleAttestationInfoBuffer == 0); + + ////read secure session info + uint sizeOfSecureSessionInfoResponse = BitConverter.ToUInt32(attestationInfo, attestationInfoOffset); + attestationInfoOffset += sizeof(UInt32); + + byte[] enclaveSessionHandle = new byte[EnclaveSessionHandleSize]; + Buffer.BlockCopy(attestationInfo, attestationInfoOffset, enclaveSessionHandle, 0, EnclaveSessionHandleSize); + attestationInfoOffset += EnclaveSessionHandleSize; + + uint sizeOfTrustedModuleDHPublicKeyBuffer = BitConverter.ToUInt32(attestationInfo, attestationInfoOffset); + attestationInfoOffset += sizeof(UInt32); + uint sizeOfTrustedModuleDHPublicKeySignatureBuffer = BitConverter.ToUInt32(attestationInfo, attestationInfoOffset); + attestationInfoOffset += sizeof(UInt32); + int sizeOfTrustedModuleDHPublicKeyBufferInt = checked((int)sizeOfTrustedModuleDHPublicKeyBuffer); + + byte[] trustedModuleDHPublicKey = new byte[sizeOfTrustedModuleDHPublicKeyBuffer]; + Buffer.BlockCopy(attestationInfo, attestationInfoOffset, trustedModuleDHPublicKey, 0, + sizeOfTrustedModuleDHPublicKeyBufferInt); + attestationInfoOffset += sizeOfTrustedModuleDHPublicKeyBufferInt; + + byte[] trustedModuleDHPublicKeySignature = new byte[sizeOfTrustedModuleDHPublicKeySignatureBuffer]; + Buffer.BlockCopy(attestationInfo, attestationInfoOffset, trustedModuleDHPublicKeySignature, 0, + checked((int)sizeOfTrustedModuleDHPublicKeySignatureBuffer)); + + CngKey k = CngKey.Import(trustedModuleDHPublicKey, CngKeyBlobFormat.EccPublicBlob); + byte[] sharedSecret = clientDHKey.DeriveKeyMaterial(k); + long sessionId = BitConverter.ToInt64(enclaveSessionHandle, 0); + sqlEnclaveSession = AddEnclaveSessionToCache(attestationUrl, servername, sharedSecret, sessionId, out counter); + } + else + { + throw new AlwaysEncryptedAttestationException(Strings.FailToCreateEnclaveSession); + } + } + } + finally + { + UpdateEnclaveSessionLockStatus(sqlEnclaveSession); + } + } + + /// + /// When overridden in a derived class, looks up and evicts an enclave session from the enclave session cache, if the provider implements session caching. + /// + /// The name of the SQL Server instance containing the enclave. + /// The endpoint of an attestation service, SqlClient contacts to attest the enclave. + /// The session to be invalidated. + public override void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSessionToInvalidate) + { + InvalidateEnclaveSessionHelper(serverName, enclaveAttestationUrl, enclaveSessionToInvalidate); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsEnums.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsEnums.cs index 8661bc70e7..f09836b163 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -947,7 +947,9 @@ internal enum FedAuthInfoId : byte internal const int AEAD_AES_256_CBC_HMAC_SHA256 = 2; internal const string ENCLAVE_TYPE_VBS = "VBS"; internal const string ENCLAVE_TYPE_SGX = "SGX"; - +#if ENCLAVE_SIMULATOR + internal const string ENCLAVE_TYPE_SIMULATOR = "SIMULATOR"; +#endif // TCE Param names for exec handling internal const string TCE_PARAM_CIPHERTEXT = "cipherText"; internal const string TCE_PARAM_CIPHER_ALGORITHM_ID = "cipherAlgorithmId"; @@ -1046,6 +1048,11 @@ public enum SqlConnectionAttestationProtocol /// AAS = 1, +#if ENCLAVE_SIMULATOR + /// + SIM = 2, +#endif + /// HGS = 3 } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index 5f5ddc62e0..cb4fc848db 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -3509,10 +3509,13 @@ private bool TryProcessFeatureExtAck(TdsParserStateObject stateObj) private bool IsValidAttestationProtocol(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType) { - switch (enclaveType) + switch (enclaveType.ToUpper()) { case TdsEnums.ENCLAVE_TYPE_VBS: if (attestationProtocol != SqlConnectionAttestationProtocol.AAS +#if ENCLAVE_SIMULATOR + && attestationProtocol != SqlConnectionAttestationProtocol.SIM +#endif && attestationProtocol != SqlConnectionAttestationProtocol.HGS) { return false; @@ -3520,12 +3523,25 @@ private bool IsValidAttestationProtocol(SqlConnectionAttestationProtocol attesta break; case TdsEnums.ENCLAVE_TYPE_SGX: +#if ENCLAVE_SIMULATOR + if (attestationProtocol != SqlConnectionAttestationProtocol.AAS + && attestationProtocol != SqlConnectionAttestationProtocol.SIM) +#else if (attestationProtocol != SqlConnectionAttestationProtocol.AAS) +#endif { return false; } break; +#if ENCLAVE_SIMULATOR + case TdsEnums.ENCLAVE_TYPE_SIMULATOR: + if (attestationProtocol != SqlConnectionAttestationProtocol.SIM) + { + return false; + } + break; +#endif default: // if we reach here, the enclave type is not supported throw SQL.EnclaveTypeNotSupported(enclaveType); @@ -3544,6 +3560,11 @@ private string ConvertAttestationProtocolToString(SqlConnectionAttestationProtoc case SqlConnectionAttestationProtocol.HGS: return "HGS"; +#if ENCLAVE_SIMULATOR + case SqlConnectionAttestationProtocol.SIM: + return "SIM"; +#endif + default: return "NotSpecified"; } From 1b4b2727bdcd0abc0767ce5d4fb9fe83e7ba9981 Mon Sep 17 00:00:00 2001 From: dengel Date: Wed, 11 Mar 2020 12:07:30 -0700 Subject: [PATCH 2/2] Cache the authentication context in the connection pool on netcore The authentication context cache of the connection pool was not being updated in the netcore code. This was resulting in poor connection speed for subsequent connections which used AAD auth since they could not take advantage of the cached token. This code looks to have been missed when AAD authentication was ported from netfx to netcore. --- .../SqlClient/SqlInternalConnectionTds.cs | 13 ++++++++ .../ConnectivityTests/AADConnectionTest.cs | 32 ++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 1dade03a64..a5f9da7a05 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -2380,6 +2380,19 @@ internal void OnFeatureExtAck(int featureId, byte[] data) throw SQL.ParsingErrorLibraryType(ParsingErrorState.FedAuthFeatureAckUnknownLibraryType, (int)_fedAuthFeatureExtensionData.Value.libraryType); } _federatedAuthenticationAcknowledged = true; + + // If a new authentication context was used as part of this login attempt, try to update the new context in the cache, i.e.dbConnectionPool.AuthenticationContexts. + // ChooseAuthenticationContextToUpdate will take care that only the context which has more validity will remain in the cache, based on the Update logic. + if (_newDbConnectionPoolAuthenticationContext != null) + { + Debug.Assert(_dbConnectionPool != null, "_dbConnectionPool should not be null when _newDbConnectionPoolAuthenticationContext != null."); + + DbConnectionPoolAuthenticationContext newAuthenticationContextInCacheAfterAddOrUpdate = _dbConnectionPool.AuthenticationContexts.AddOrUpdate(_dbConnectionPoolAuthenticationContextKey, _newDbConnectionPoolAuthenticationContext, + (key, oldValue) => DbConnectionPoolAuthenticationContext.ChooseAuthenticationContextToUpdate(oldValue, _newDbConnectionPoolAuthenticationContext)); + + Debug.Assert(newAuthenticationContextInCacheAfterAddOrUpdate != null, "newAuthenticationContextInCacheAfterAddOrUpdate should not be null."); + } + break; } case TdsEnums.FEATUREEXT_TCE: diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs index 0459fbc5df..b2f29b6abe 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs @@ -2,8 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Xunit; using System; +using System.Diagnostics; +using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests { @@ -300,5 +301,34 @@ public static void NoCredentialsActiveDirectoryPassword() string expectedMessage = "Either Credential or both 'User ID' and 'Password' (or 'UID' and 'PWD') connection string keywords must be specified, if 'Authentication=Active Directory Password'."; Assert.Contains(expectedMessage, e.Message); } + + [ConditionalFact(nameof(IsAADConnStringsSetup))] + public static void ConnectionSpeed() + { + //Ensure server endpoints are warm + using (var connectionDrill = new SqlConnection(DataTestUtility.AADPasswordConnectionString)) + { + connectionDrill.Open(); + } + SqlConnection.ClearAllPools(); + + using (var connectionDrill = new SqlConnection(DataTestUtility.AADPasswordConnectionString)) + { + Stopwatch firstConnectionTime = new Stopwatch(); + firstConnectionTime.Start(); + connectionDrill.Open(); + firstConnectionTime.Stop(); + using (var connectionDrill2 = new SqlConnection(DataTestUtility.AADPasswordConnectionString)) + { + Stopwatch secondConnectionTime = new Stopwatch(); + secondConnectionTime.Start(); + connectionDrill2.Open(); + secondConnectionTime.Stop(); + // Subsequent AAD connections within a short timeframe should use an auth token cached from the connection pool + // Second connection speed in tests was typically 10-15% of the first connection time. Using 30% since speeds may vary. + Assert.True(secondConnectionTime.ElapsedMilliseconds / firstConnectionTime.ElapsedMilliseconds < .30, $"Second AAD connection too slow ({secondConnectionTime.ElapsedMilliseconds}ms)! (More than 30% of the first ({firstConnectionTime.ElapsedMilliseconds}ms).)"); + } + } + } } }