From 37f7fbdf66a651a38178cded703b0fe80bc3aaaf Mon Sep 17 00:00:00 2001 From: Wraith Date: Wed, 28 Apr 2021 19:26:50 +0100 Subject: [PATCH] Sync Crypto api usage (#1022) --- .../src/Microsoft.Data.SqlClient.csproj | 28 +- ...aysEncryptedKeyConverter.CrossPlatform.cs} | 127 ++-- ...EnclaveAttestationParameters.NetCoreApp.cs | 27 - .../SqlEnclaveAttestationParameters.cs | 44 -- .../netfx/src/Microsoft.Data.SqlClient.csproj | 23 +- .../AlwaysEncryptedKeyConverter.Cng.cs | 71 +++ .../AzureAttestationBasedEnclaveProvider.cs | 545 ------------------ .../SqlClient/EnclaveDelegate.CngCryto.cs | 231 -------- .../SqlColumnEncryptionEnclaveProvider.cs | 2 +- .../Microsoft/Data/SqlClient/SqlCommand.cs | 3 +- .../Data/SqlClient/SqlCommandBuilder.cs | 1 + .../Microsoft/Data/SqlClient/SqlConnection.cs | 5 +- .../Data/SqlClient/SqlDataAdapter.cs | 3 +- .../SqlEnclaveAttestationParameters.cs | 69 --- .../VirtualSecureModeEnclaveProviderBase.cs | 367 ------------ .../AzureAttestationBasedEnclaveProvider.cs | 14 +- .../Data/SqlClient/EnclaveDelegate.Crypto.cs} | 2 +- .../EnclaveDelegate.NotSupported.cs} | 0 .../SqlEnclaveAttestationParameters.Crypto.cs | 52 ++ ...claveAttestationParameters.NotSupported.cs | 19 + .../VirtualSecureModeEnclaveProviderBase.cs | 23 +- 21 files changed, 275 insertions(+), 1381 deletions(-) rename src/Microsoft.Data.SqlClient/{src/Microsoft/Data/SqlClient/AlwaysEncryptedKeyConverter.cs => netcore/src/Microsoft/Data/SqlClient/AlwaysEncryptedKeyConverter.CrossPlatform.cs} (72%) delete mode 100644 src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.NetCoreApp.cs delete mode 100644 src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs create mode 100644 src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/AlwaysEncryptedKeyConverter.Cng.cs delete mode 100644 src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs delete mode 100644 src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveDelegate.CngCryto.cs delete mode 100644 src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs delete mode 100644 src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs rename src/Microsoft.Data.SqlClient/{netcore => }/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs (97%) rename src/Microsoft.Data.SqlClient/{netcore/src/Microsoft/Data/SqlClient/EnclaveDelegate.CrossPlatformCrypto.cs => src/Microsoft/Data/SqlClient/EnclaveDelegate.Crypto.cs} (98%) rename src/Microsoft.Data.SqlClient/{netcore/src/Microsoft/Data/SqlClient/EnclaveDelegate.NetStandard.cs => src/Microsoft/Data/SqlClient/EnclaveDelegate.NotSupported.cs} (100%) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.Crypto.cs create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.NotSupported.cs rename src/Microsoft.Data.SqlClient/{netcore => }/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs (93%) 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 4546d7d1ab..1b74587f24 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -318,7 +318,12 @@ - + + Microsoft\Data\SqlClient\EnclaveDelegate.NotSupported.cs + + + Microsoft\Data\SqlClient\SqlEnclaveAttestationParameters.NotSupported.cs + @@ -335,9 +340,7 @@ Microsoft\Data\SqlClient\AlwaysEncryptedEnclaveProviderUtils.cs - - Microsoft\Data\SqlClient\AlwaysEncryptedKeyConverter.cs - + Microsoft\Data\SqlClient\EnclaveProviderBase.cs @@ -345,13 +348,21 @@ Microsoft\Data\SqlClient\EnclaveSessionCache.cs - - - + + Microsoft\Data\SqlClient\SqlEnclaveAttestationParameters.Crypto.cs + + + Microsoft\Data\SqlClient\EnclaveDelegate.Crypto.cs + + + Microsoft\Data\SqlClient\AzureAttestationBasedEnclaveProvider.cs + Microsoft\Data\SqlClient\VirtualSecureModeEnclaveProvider.cs - + + Microsoft\Data\SqlClient\VirtualSecureModeEnclaveProviderBase.cs + @@ -523,7 +534,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncryptedKeyConverter.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/AlwaysEncryptedKeyConverter.CrossPlatform.cs similarity index 72% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncryptedKeyConverter.cs rename to src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/AlwaysEncryptedKeyConverter.CrossPlatform.cs index 6ccbd67fb6..8b3def875c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncryptedKeyConverter.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/AlwaysEncryptedKeyConverter.CrossPlatform.cs @@ -5,12 +5,56 @@ using System; using System.Diagnostics; using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; namespace Microsoft.Data.SqlClient { - // Contains methods to convert cryptography keys between different formats. - internal sealed class KeyConverter - { + internal sealed partial class KeyConverter + { + // Magic numbers identifying blob types + // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/cba27df5-4880-4f95-a879-783f8657e53b + private readonly struct KeyBlobMagicNumber + { + internal static readonly byte[] ECDHPublicP384 = new byte[] { 0x45, 0x43, 0x4b, 0x33 }; + } + + // The ECC public key blob is structured as follows: + // BCRYPT_ECCKEY_BLOB header + // byte[KeySize] X + // byte[KeySize] Y + private readonly struct ECCPublicKeyBlob + { + // Size of an ECC public key blob + internal const int Size = 104; + // Size of the BCRYPT_ECCKEY_BLOB header + internal const int HeaderSize = 8; + // Size of each coordinate + internal const int KeySize = (Size - HeaderSize) / 2; + } + + // Serializes an ECDiffieHellmanPublicKey to an ECC public key blob + // "ECDiffieHellmanPublicKey.ToByteArray() doesn't have a (standards-)defined export + // format. The version used by ECDiffieHellmanPublicKeyCng is Windows-specific" + // from https://github.com/dotnet/runtime/issues/27276 + // => ECDiffieHellmanPublicKey.ToByteArray() is not supported in Unix + internal static byte[] GetECDiffieHellmanPublicKeyBlob(ECDiffieHellman ecDiffieHellman) + { + byte[] keyBlob = new byte[ECCPublicKeyBlob.Size]; + + // Set magic number + Buffer.BlockCopy(KeyBlobMagicNumber.ECDHPublicP384, 0, keyBlob, 0, 4); + // Set key size + keyBlob[4] = (byte)ECCPublicKeyBlob.KeySize; + + ECPoint ecPoint = ecDiffieHellman.PublicKey.ExportParameters().Q; + Debug.Assert(ecPoint.X.Length == ECCPublicKeyBlob.KeySize && ecPoint.Y.Length == ECCPublicKeyBlob.KeySize, + $"ECDH public key was not the expected length. Actual (X): {ecPoint.X.Length}. Actual (Y): {ecPoint.Y.Length} Expected: {ECCPublicKeyBlob.Size}"); + // Copy x and y coordinates to key blob + Buffer.BlockCopy(ecPoint.X, 0, keyBlob, ECCPublicKeyBlob.HeaderSize, ECCPublicKeyBlob.KeySize); + Buffer.BlockCopy(ecPoint.Y, 0, keyBlob, ECCPublicKeyBlob.HeaderSize + ECCPublicKeyBlob.KeySize, ECCPublicKeyBlob.KeySize); + return keyBlob; + } + // The RSA public key blob is structured as follows: // BCRYPT_RSAKEY_BLOB header // byte[ExponentSize] publicExponent @@ -29,59 +73,32 @@ private readonly struct RSAPublicKeyBlob internal const int ModulusOffset = HeaderSize; } - // Extracts the public key's modulus and exponent from an RSA public key blob - // and returns an RSAParameters object - internal static RSAParameters RSAPublicKeyBlobToParams(byte[] keyBlob) + internal static RSA CreateRSAFromPublicKeyBlob(byte[] keyBlob) { - Debug.Assert(keyBlob.Length == RSAPublicKeyBlob.Size, - $"RSA public key blob was not the expected length. Actual: {keyBlob.Length}. Expected: {RSAPublicKeyBlob.Size}"); + Debug.Assert(keyBlob.Length == RSAPublicKeyBlob.Size, $"RSA public key blob was not the expected length. Actual: {keyBlob.Length}. Expected: {RSAPublicKeyBlob.Size}"); byte[] exponent = new byte[RSAPublicKeyBlob.ExponentSize]; byte[] modulus = new byte[RSAPublicKeyBlob.ModulusSize]; Buffer.BlockCopy(keyBlob, RSAPublicKeyBlob.ExponentOffset, exponent, 0, RSAPublicKeyBlob.ExponentSize); Buffer.BlockCopy(keyBlob, RSAPublicKeyBlob.ModulusOffset, modulus, 0, RSAPublicKeyBlob.ModulusSize); - - return new RSAParameters() + var rsaParameters = new RSAParameters() { Exponent = exponent, Modulus = modulus }; + return RSA.Create(rsaParameters); } - // The ECC public key blob is structured as follows: - // BCRYPT_ECCKEY_BLOB header - // byte[KeySize] X - // byte[KeySize] Y - private readonly struct ECCPublicKeyBlob - { - // Size of an ECC public key blob - internal const int Size = 104; - // Size of the BCRYPT_ECCKEY_BLOB header - internal const int HeaderSize = 8; - // Size of each coordinate - internal const int KeySize = (Size - HeaderSize) / 2; - } - - // Magic numbers identifying blob types - // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/cba27df5-4880-4f95-a879-783f8657e53b - private readonly struct KeyBlobMagicNumber - { - internal static readonly byte[] ECDHPublicP384 = new byte[] { 0x45, 0x43, 0x4b, 0x33 }; - } - - // Extracts the public key's X and Y coordinates from an ECC public key blob - // and returns an ECParameters object - internal static ECParameters ECCPublicKeyBlobToParams(byte[] keyBlob) + internal static ECDiffieHellman CreateECDiffieHellmanFromPublicKeyBlob(byte[] keyBlob) { - Debug.Assert(keyBlob.Length == ECCPublicKeyBlob.Size, - $"ECC public key blob was not the expected length. Actual: {keyBlob.Length}. Expected: {ECCPublicKeyBlob.Size}"); + Debug.Assert(keyBlob.Length == ECCPublicKeyBlob.Size, $"ECC public key blob was not the expected length. Actual: {keyBlob.Length}. Expected: {ECCPublicKeyBlob.Size}"); byte[] x = new byte[ECCPublicKeyBlob.KeySize]; byte[] y = new byte[ECCPublicKeyBlob.KeySize]; Buffer.BlockCopy(keyBlob, ECCPublicKeyBlob.HeaderSize, x, 0, ECCPublicKeyBlob.KeySize); Buffer.BlockCopy(keyBlob, ECCPublicKeyBlob.HeaderSize + ECCPublicKeyBlob.KeySize, y, 0, ECCPublicKeyBlob.KeySize); - return new ECParameters + var parameters = new ECParameters { Curve = ECCurve.NamedCurves.nistP384, Q = new ECPoint @@ -90,29 +107,29 @@ internal static ECParameters ECCPublicKeyBlobToParams(byte[] keyBlob) Y = y }, }; + + return ECDiffieHellman.Create(parameters); } - // Serializes an ECDiffieHellmanPublicKey to an ECC public key blob - // "ECDiffieHellmanPublicKey.ToByteArray() doesn't have a (standards-)defined export - // format. The version used by ECDiffieHellmanPublicKeyCng is Windows-specific" - // from https://github.com/dotnet/runtime/issues/27276 - // => ECDiffieHellmanPublicKey.ToByteArray() is not supported in Unix - internal static byte[] ECDHPublicKeyToECCKeyBlob(ECDiffieHellmanPublicKey publicKey) + internal static ECDiffieHellman CreateECDiffieHellman(int keySize) { - byte[] keyBlob = new byte[ECCPublicKeyBlob.Size]; + // platform agnostic creates a key of the correct size but does not + // set the key derivation type or algorithm, these must be set by calling + // DeriveKeyFromHash later in DeriveKey + ECDiffieHellman clientDHKey = ECDiffieHellman.Create(); + clientDHKey.KeySize = keySize; + return clientDHKey; + } - // Set magic number - Buffer.BlockCopy(KeyBlobMagicNumber.ECDHPublicP384, 0, keyBlob, 0, 4); - // Set key size - keyBlob[4] = (byte)ECCPublicKeyBlob.KeySize; + internal static byte[] DeriveKey(ECDiffieHellman ecd, ECDiffieHellmanPublicKey publicKey) + { + // see notes in CreateECDDiffieHellman + return ecd.DeriveKeyFromHash(publicKey, HashAlgorithmName.SHA256); + } - ECPoint ecPoint = publicKey.ExportParameters().Q; - Debug.Assert(ecPoint.X.Length == ECCPublicKeyBlob.KeySize && ecPoint.Y.Length == ECCPublicKeyBlob.KeySize, - $"ECDH public key was not the expected length. Actual (X): {ecPoint.X.Length}. Actual (Y): {ecPoint.Y.Length} Expected: {ECCPublicKeyBlob.Size}"); - // Copy x and y coordinates to key blob - Buffer.BlockCopy(ecPoint.X, 0, keyBlob, ECCPublicKeyBlob.HeaderSize, ECCPublicKeyBlob.KeySize); - Buffer.BlockCopy(ecPoint.Y, 0, keyBlob, ECCPublicKeyBlob.HeaderSize + ECCPublicKeyBlob.KeySize, ECCPublicKeyBlob.KeySize); - return keyBlob; + internal static RSA GetRSAFromCertificate(X509Certificate2 certificate) + { + return certificate.GetRSAPublicKey(); } } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.NetCoreApp.cs deleted file mode 100644 index 739187a7e9..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.NetCoreApp.cs +++ /dev/null @@ -1,27 +0,0 @@ -// 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.Security.Cryptography; - -namespace Microsoft.Data.SqlClient -{ - /// - internal partial class SqlEnclaveAttestationParameters - { - private static readonly string _clientDiffieHellmanKeyName = "ClientDiffieHellmanKey"; - private static readonly string _inputName = "input"; - private static readonly string _className = "EnclaveAttestationParameters"; - - /// - internal ECDiffieHellman ClientDiffieHellmanKey { get; } - - /// - internal SqlEnclaveAttestationParameters(int protocol, byte[] input, ECDiffieHellman clientDiffieHellmanKey) - { - _input = input ?? throw SQL.NullArgumentInConstructorInternal(_inputName, _className); - Protocol = protocol; - ClientDiffieHellmanKey = clientDiffieHellmanKey ?? throw SQL.NullArgumentInConstructorInternal(_clientDiffieHellmanKeyName, _className); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs deleted file mode 100644 index 25f2737c70..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs +++ /dev/null @@ -1,44 +0,0 @@ -// 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. - -namespace Microsoft.Data.SqlClient -{ - /// - internal partial class SqlEnclaveAttestationParameters - { - private readonly byte[] _input = null; - - /// - internal int Protocol { get; } - - /// - internal byte[] GetInput() - { - return Clone(_input); - } - - /// - /// Deep copy the array into a new array - /// - /// - /// - private byte[] Clone(byte[] arrayToClone) - { - - if (null == arrayToClone) - { - return null; - } - - byte[] returnValue = new byte[arrayToClone.Length]; - - for (int i = 0; i < arrayToClone.Length; i++) - { - returnValue[i] = arrayToClone[i]; - } - - return returnValue; - } - } -} 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 d80ca43a2d..1ad37256a2 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -151,9 +151,16 @@ Microsoft\Data\SqlClient\AlwaysEncryptedEnclaveProviderUtils.cs + + + Microsoft\Data\SqlClient\AzureAttestationBasedEnclaveProvider.cs + Microsoft\Data\SqlClient\EnclaveDelegate.cs + + Microsoft\Data\SqlClient\EnclaveDelegate.Crypto.cs + Microsoft\Data\SqlClient\EnclavePackage.cs @@ -283,6 +290,12 @@ Microsoft\Data\SqlClient\SqlConnectionPoolProviderInfo.cs + + Microsoft\Data\SqlClient\SqlEnclaveAttestationParameters.Crypto.cs + + + Microsoft\Data\SqlClient\SqlEnclaveSession.cs + Microsoft\Data\SqlClient\SqlInfoMessageEventHandler.cs @@ -319,6 +332,9 @@ Microsoft\Data\SqlClient\VirtualSecureModeEnclaveProvider.cs + + Microsoft\Data\SqlClient\VirtualSecureModeEnclaveProviderBase.cs + Microsoft\Data\SqlTypes\SqlTypeWorkarounds.cs @@ -403,15 +419,12 @@ - - - @@ -449,10 +462,6 @@ - - - Microsoft\Data\SqlClient\SqlEnclaveSession.cs - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/AlwaysEncryptedKeyConverter.Cng.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/AlwaysEncryptedKeyConverter.Cng.cs new file mode 100644 index 0000000000..f0d9943ca8 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/AlwaysEncryptedKeyConverter.Cng.cs @@ -0,0 +1,71 @@ +// 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.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +namespace Microsoft.Data.SqlClient +{ + internal sealed partial class KeyConverter + { + internal static RSA CreateRSAFromPublicKeyBlob(byte[] keyBlob) + { + CngKey key = CngKey.Import(keyBlob, CngKeyBlobFormat.GenericPublicBlob); + return new RSACng(key); + } + + internal static ECDiffieHellman CreateECDiffieHellmanFromPublicKeyBlob(byte[] keyBlob) + { + CngKey key = CngKey.Import(keyBlob, CngKeyBlobFormat.GenericPublicBlob); + return new ECDiffieHellmanCng(key); + } + + internal static ECDiffieHellman CreateECDiffieHellman(int keySize) + { + // Cng sets the key size and hash algorithm at creation time and these + // parameters are then used later when DeriveKeyMaterial is called + ECDiffieHellmanCng clientDHKey = new ECDiffieHellmanCng(keySize); + clientDHKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash; + clientDHKey.HashAlgorithm = CngAlgorithm.Sha256; + return clientDHKey; + } + + public static byte[] GetECDiffieHellmanPublicKeyBlob(ECDiffieHellman ecDiffieHellman) + { + if (ecDiffieHellman is ECDiffieHellmanCng cng) + { + return cng.Key.Export(CngKeyBlobFormat.EccPublicBlob); + } + else + { + throw new InvalidOperationException(); + } + } + + internal static byte[] DeriveKey(ECDiffieHellman ecDiffieHellman, ECDiffieHellmanPublicKey publicKey) + { + if (ecDiffieHellman is ECDiffieHellmanCng cng) + { + return cng.DeriveKeyMaterial(publicKey); + } + else + { + throw new InvalidOperationException(); + } + } + + internal static RSA GetRSAFromCertificate(X509Certificate2 certificate) + { + RSAParameters parameters; + using (RSA rsaCsp = certificate.GetRSAPublicKey()) + { + parameters = rsaCsp.ExportParameters(includePrivateParameters: false); + } + RSACng rsaCng = new RSACng(); + rsaCng.ImportParameters(parameters); + return rsaCng; + } + } +} 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 deleted file mode 100644 index 84a92c7ccc..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs +++ /dev/null @@ -1,545 +0,0 @@ -// 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.IdentityModel.Tokens.Jwt; -using System.Linq; -using System.Runtime.Caching; -using System.Security.Claims; -using System.Security.Cryptography; -using System.Text; -using System.Threading; -using Microsoft.IdentityModel.JsonWebTokens; -using Microsoft.IdentityModel.Logging; -using Microsoft.IdentityModel.Protocols; -using Microsoft.IdentityModel.Protocols.OpenIdConnect; -using Microsoft.IdentityModel.Tokens; - -// Azure Attestation Protocol Flow -// To start the attestation process, Sql Client sends the Protocol Id (i.e. 1), Nonce, Attestation Url and ECDH Public Key -// Sql Server uses attestation Url to attest the enclave and send the JWT to Sql client. -// Along with JWT, Sql server also sends enclave RSA public key, enclave Type, Enclave ECDH Public key. - -// To verify the chain of trust here is how it works -// JWT is signed by well-known signing keys which Sql client can download over https (via OpenIdConnect protocol). -// JWT contains the Enclave public key to safeguard against spoofing enclave RSA public key. -// Enclave ECDH public key signed by enclave RSA key - -// JWT validation -// To get the signing key for the JWT, we use OpenIdConnect API's. It download the signing keys from the well-known endpoint. -// We validate that JWT is signed, valid (i.e. not expired) and check the Issuer. - -// Claim validation: -// Validate the RSA public key send by Sql server matches the value specified in JWT. - -// Enclave Specific checks -// VSM -// Validate the nonce send by Sql client during start of attestation is same as that of specified in the JWT - -// SGX -// JWT for SGX enclave does not contain nonce claim. To workaround this limitation Sql Server sends the RSA public key XOR with the Nonce. -// In Sql server tempered with the nonce value then both Sql Server and client will not able to compute the same shared secret. - -namespace Microsoft.Data.SqlClient -{ - // Implementation of an Enclave provider (both for Sgx and Vsm) with Azure Attestation - internal class AzureAttestationEnclaveProvider : EnclaveProviderBase - { - #region Constants - private const int DiffieHellmanKeySize = 384; - private const int AzureBasedAttestationProtocolId = 1; - private const int SigningKeyRetryInSec = 3; - #endregion - - #region Members - // this is meta data endpoint for AAS provided by Windows team - // i.e. https:///.well-known/openid-configuration - // such as https://sql.azure.attest.com/.well-known/openid-configuration - private const string AttestationUrlSuffix = @"/.well-known/openid-configuration"; - - private static readonly MemoryCache OpenIdConnectConfigurationCache = new MemoryCache("OpenIdConnectConfigurationCache"); - #endregion - - #region Internal 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. - internal override void GetEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) - { - GetEnclaveSessionHelper(enclaveSessionParameters, 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. - internal 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(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. - internal override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, EnclaveSessionParameters enclaveSessionParameters, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter) - { - sqlEnclaveSession = null; - counter = 0; - try - { - ThreadRetryCache.Remove(Thread.CurrentThread.ManagedThreadId.ToString()); - sqlEnclaveSession = GetEnclaveSessionFromCache(enclaveSessionParameters, out counter); - if (sqlEnclaveSession == null) - { - if (!string.IsNullOrEmpty(enclaveSessionParameters.AttestationUrl) && customData != null && customDataLength > 0) - { - byte[] nonce = customData; - - IdentityModelEventSource.ShowPII = true; - - // Deserialize the payload - AzureAttestationInfo attestInfo = new AzureAttestationInfo(attestationInfo); - - // Validate the attestation info - VerifyAzureAttestationInfo(enclaveSessionParameters.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(enclaveSessionParameters, sharedSecret, attestInfo.SessionId, out counter); - } - else - { - throw new AlwaysEncryptedAttestationException(Strings.FailToCreateEnclaveSession); - } - } - } - finally - { - // As per current design, we want to minimize the number of create session calls. To achieve 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 enclave 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); - } - } - - // When overridden in a derived class, looks up and evicts an enclave session from the enclave session cache, if the provider implements session caching. - internal override void InvalidateEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, SqlEnclaveSession enclaveSessionToInvalidate) - { - InvalidateEnclaveSessionHelper(enclaveSessionParameters, enclaveSessionToInvalidate); - } - #endregion - - #region Internal Class - - // A model class representing the deserialization of the byte payload the client - // receives from SQL Server while setting up a session. - // Protocol format: - // 1. Total Size of the attestation blob as UINT - // 2. Size of Enclave RSA public key as UINT - // 3. Size of Attestation token as UINT - // 4. Enclave Type as UINT - // 5. Enclave RSA public key (raw key, of length #2) - // 6. Attestation token (of length #3) - // 7. Size of Session Id was UINT - // 8. Session id value - // 9. Size of enclave ECDH public key - // 10. Enclave ECDH public key (of length #9) - internal class AzureAttestationInfo - { - public uint TotalSize { get; set; } - - // The enclave's RSA Public Key. - // Needed to establish trust of the enclave. - // Used to verify the enclave's DiffieHellman info. - public EnclavePublicKey Identity { get; set; } - - // The enclave report from the SQL Server host's enclave. - public AzureAttestationToken AttestationToken { get; set; } - - // The id of the current session. - // Needed to set up a secure session between the client and enclave. - public long SessionId { get; set; } - - public EnclaveType EnclaveType { get; set; } - - // The DiffieHellman public key and signature of SQL Server host's enclave. - // Needed to set up a secure session between the client and enclave. - public EnclaveDiffieHellmanInfo EnclaveDHInfo { get; set; } - - public AzureAttestationInfo(byte[] attestationInfo) - { - try - { - int offset = 0; - - // Total size of the attestation info buffer - TotalSize = BitConverter.ToUInt32(attestationInfo, offset); - offset += sizeof(uint); - - // Size of the Enclave public key - int identitySize = BitConverter.ToInt32(attestationInfo, offset); - offset += sizeof(uint); - - // Size of the Azure attestation token - int attestationTokenSize = BitConverter.ToInt32(attestationInfo, offset); - offset += sizeof(uint); - - // Enclave type - int enclaveType = BitConverter.ToInt32(attestationInfo, offset); - EnclaveType = (EnclaveType)enclaveType; - offset += sizeof(uint); - - // Get the enclave public key - byte[] identityBuffer = attestationInfo.Skip(offset).Take(identitySize).ToArray(); - Identity = new EnclavePublicKey(identityBuffer); - offset += identitySize; - - // Get Azure attestation token - byte[] attestationTokenBuffer = attestationInfo.Skip(offset).Take(attestationTokenSize).ToArray(); - AttestationToken = new AzureAttestationToken(attestationTokenBuffer); - offset += attestationTokenSize; - - uint secureSessionInfoResponseSize = BitConverter.ToUInt32(attestationInfo, offset); - offset += sizeof(uint); - - SessionId = BitConverter.ToInt64(attestationInfo, offset); - offset += sizeof(long); - - int secureSessionBufferSize = Convert.ToInt32(secureSessionInfoResponseSize) - sizeof(uint); - byte[] secureSessionBuffer = attestationInfo.Skip(offset).Take(secureSessionBufferSize).ToArray(); - EnclaveDHInfo = new EnclaveDiffieHellmanInfo(secureSessionBuffer); - offset += Convert.ToInt32(EnclaveDHInfo.Size); - } - catch (Exception exception) - { - throw new AlwaysEncryptedAttestationException(String.Format(Strings.FailToParseAttestationInfo, exception.Message)); - } - } - } - - // A managed model representing the output of EnclaveGetAttestationReport - // https://msdn.microsoft.com/en-us/library/windows/desktop/mt844233(v=vs.85).aspx - internal class AzureAttestationToken - { - public string AttestationToken { get; set; } - - public AzureAttestationToken(byte[] payload) - { - string jwt = System.Text.Encoding.Default.GetString(payload); - AttestationToken = jwt.Trim().Trim('"'); - } - } - #endregion Internal Class - - #region Private helpers - // Prepare the attestation data in following format - // Attestation Url length - // Attestation Url - // Size of nonce - // Nonce value - internal byte[] PrepareAttestationParameters(string attestationUrl, byte[] attestNonce, int attestNonceLength) - { - 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 = attestationUrl + char.MinValue; - byte[] serializedAttestationUrl = Encoding.Unicode.GetBytes(attestationUrlLocal); - byte[] serializedAttestationUrlLength = BitConverter.GetBytes(serializedAttestationUrl.Length); - - // serializing nonce - 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; - - 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(Strings.FailToCreateEnclaveSession); - } - } - - // Performs Attestation per the protocol used by Azure Attestation Service - private void VerifyAzureAttestationInfo(string attestationUrl, EnclaveType enclaveType, string attestationToken, EnclavePublicKey enclavePublicKey, byte[] nonce) - { - bool shouldForceUpdateSigningKeys = false; - string attestationInstanceUrl = GetAttestationInstanceUrl(attestationUrl); - - bool shouldRetryValidation; - bool isSignatureValid; - string exceptionMessage = string.Empty; - do - { - shouldRetryValidation = false; - - // Get the OpenId config object for the signing keys - OpenIdConnectConfiguration openIdConfig = GetOpenIdConfigForSigningKeys(attestationInstanceUrl, shouldForceUpdateSigningKeys); - - // Verify the token signature against the signing keys downloaded from meta data end point - bool isKeySigningExpired; - isSignatureValid = VerifyTokenSignature(attestationToken, attestationInstanceUrl, openIdConfig.SigningKeys, out isKeySigningExpired, out exceptionMessage); - - // In cases if we fail to validate the token, since we are using the old signing keys - // let's re-download the signing keys again and re-validate the token signature - if (!isSignatureValid && isKeySigningExpired && !shouldForceUpdateSigningKeys) - { - shouldForceUpdateSigningKeys = true; - shouldRetryValidation = true; - } - } - while (shouldRetryValidation); - - if (!isSignatureValid) - { - throw new AlwaysEncryptedAttestationException(String.Format(Strings.AttestationTokenSignatureValidationFailed, exceptionMessage)); - } - - // Validate claims in the token - ValidateAttestationClaims(enclaveType, attestationToken, enclavePublicKey, nonce); - } - - // Returns the innermost exception value - private static string GetInnerMostExceptionMessage(Exception exception) - { - Exception exLocal = exception; - while (exLocal.InnerException != null) - { - exLocal = exLocal.InnerException; - } - - return exLocal.Message; - } - - // For the given attestation url it downloads the token signing keys from the well-known openid configuration end point. - // It also caches that information for 1 day to avoid DDOS attacks. - private OpenIdConnectConfiguration GetOpenIdConfigForSigningKeys(string url, bool forceUpdate) - { - OpenIdConnectConfiguration openIdConnectConfig = OpenIdConnectConfigurationCache[url] as OpenIdConnectConfiguration; - if (forceUpdate || openIdConnectConfig == null) - { - // Compute the meta data endpoint - string openIdMetadataEndpoint = url + AttestationUrlSuffix; - - try - { - IConfigurationManager configurationManager = new ConfigurationManager(openIdMetadataEndpoint, new OpenIdConnectConfigurationRetriever()); - openIdConnectConfig = configurationManager.GetConfigurationAsync(CancellationToken.None).Result; - } - catch (Exception exception) - { - throw new AlwaysEncryptedAttestationException(String.Format(Strings.GetAttestationTokenSigningKeysFailed, GetInnerMostExceptionMessage(exception)), exception); - } - - OpenIdConnectConfigurationCache.Add(url, openIdConnectConfig, DateTime.UtcNow.AddDays(1)); - } - - return openIdConnectConfig; - } - - // Return the attestation instance url for given attestation url - // such as for https://sql.azure.attest.com/attest/SgxEnclave?api-version=2017-11-01 - // It will return https://sql.azure.attest.com - private string GetAttestationInstanceUrl(string attestationUrl) - { - Uri attestationUri = new Uri(attestationUrl); - return attestationUri.GetLeftPart(UriPartial.Authority); - } - - // Generate the list of valid issuer Url's (in case if tokenIssuerUrl is using default port) - private static ICollection GenerateListOfIssuers(string tokenIssuerUrl) - { - List issuerUrls = new List(); - - Uri tokenIssuerUri = new Uri(tokenIssuerUrl); - int port = tokenIssuerUri.Port; - bool isDefaultPort = tokenIssuerUri.IsDefaultPort; - - string issuerUrl = tokenIssuerUri.GetLeftPart(UriPartial.Authority); - issuerUrls.Add(issuerUrl); - - if (isDefaultPort) - { - issuerUrls.Add(String.Concat(issuerUrl, ":", port.ToString())); - } - - return issuerUrls; - } - - // Verifies the attestation token is signed by correct signing keys. - private bool VerifyTokenSignature(string attestationToken, string tokenIssuerUrl, ICollection issuerSigningKeys, out bool isKeySigningExpired, out string exceptionMessage) - { - exceptionMessage = string.Empty; - bool isSignatureValid = false; - isKeySigningExpired = false; - - // Configure the TokenValidationParameters - TokenValidationParameters validationParameters = - new TokenValidationParameters - { - RequireExpirationTime = true, - ValidateLifetime = true, - ValidateIssuer = true, - ValidateAudience = false, - RequireSignedTokens = true, - ValidIssuers = GenerateListOfIssuers(tokenIssuerUrl), - IssuerSigningKeys = issuerSigningKeys - }; - - try - { - SecurityToken validatedToken; - JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler(); - var token = handler.ValidateToken(attestationToken, validationParameters, out validatedToken); - isSignatureValid = true; - } - catch (SecurityTokenExpiredException securityException) - { - throw new AlwaysEncryptedAttestationException(Strings.ExpiredAttestationToken, securityException); - } - catch (SecurityTokenValidationException securityTokenException) - { - isKeySigningExpired = true; - - // Sleep for SigningKeyRetryInSec sec before retrying to download the signing keys again. - Thread.Sleep(SigningKeyRetryInSec * 1000); - exceptionMessage = GetInnerMostExceptionMessage(securityTokenException); - } - catch (Exception exception) - { - throw new AlwaysEncryptedAttestationException(String.Format(Strings.InvalidAttestationToken, GetInnerMostExceptionMessage(exception))); - } - - return isSignatureValid; - } - - // Computes the SHA256 hash of the byte array - private byte[] ComputeSHA256(byte[] data) - { - byte[] result = null; - try - { - using (SHA256 sha256 = SHA256.Create()) - { - result = sha256.ComputeHash(data); - } - } - catch (Exception argumentException) - { - throw new AlwaysEncryptedAttestationException(Strings.InvalidArgumentToSHA256, argumentException); - } - return result; - } - - // Validate the claims in the attestation token - private void ValidateAttestationClaims(EnclaveType enclaveType, string attestationToken, EnclavePublicKey enclavePublicKey, byte[] nonce) - { - // Read the json token - JsonWebToken token = null; - try - { - JsonWebTokenHandler tokenHandler = new JsonWebTokenHandler(); - token = tokenHandler.ReadJsonWebToken(attestationToken); - } - catch (ArgumentException argumentException) - { - throw new AlwaysEncryptedAttestationException(String.Format(Strings.FailToParseAttestationToken, argumentException.Message)); - } - - // Get all the claims from the token - Dictionary claims = new Dictionary(); - foreach (Claim claim in token.Claims.ToList()) - { - claims.Add(claim.Type, claim.Value); - } - - // Get Enclave held data claim and validate it with the Base64UrlEncode(enclave public key) - ValidateClaim(claims, "aas-ehd", enclavePublicKey.PublicKey); - - if (enclaveType == EnclaveType.Vbs) - { - // Get rp_data claim and validate it with the Base64UrlEncode(nonce) - ValidateClaim(claims, "rp_data", nonce); - } - } - - // Validate the claim value against the actual data - private void ValidateClaim(Dictionary claims, string claimName, byte[] actualData) - { - // Get required claim data - string claimData; - bool hasClaim = claims.TryGetValue(claimName, out claimData); - if (!hasClaim) - { - throw new AlwaysEncryptedAttestationException(String.Format(Strings.MissingClaimInAttestationToken, claimName)); - } - - // Get the Base64Url of the actual data and compare it with claim - string encodedActualData = string.Empty; - try - { - encodedActualData = Base64UrlEncoder.Encode(actualData); - } - catch (Exception) - { - throw new AlwaysEncryptedAttestationException(Strings.InvalidArgumentToBase64UrlDecoder); - } - - bool hasValidClaim = String.Equals(encodedActualData, claimData, StringComparison.Ordinal); - if (!hasValidClaim) - { - throw new AlwaysEncryptedAttestationException(String.Format(Strings.InvalidClaimInAttestationToken, claimName, claimData)); - } - } - - private byte[] GetSharedSecret(EnclavePublicKey enclavePublicKey, byte[] nonce, EnclaveType enclaveType, EnclaveDiffieHellmanInfo enclaveDHInfo, ECDiffieHellmanCng clientDHKey) - { - byte[] enclaveRsaPublicKey = enclavePublicKey.PublicKey; - - // For SGX enclave we Sql server sends the enclave public key XOR'ed with Nonce. - // In case if Sql server replayed old JWT then shared secret will not match and hence client will not able to determine the updated enclave keys. - if (enclaveType == EnclaveType.Sgx) - { - for (int iterator = 0; iterator < enclaveRsaPublicKey.Length; iterator++) - { - enclaveRsaPublicKey[iterator] = (byte)(enclaveRsaPublicKey[iterator] ^ nonce[iterator % nonce.Length]); - } - } - - // Perform signature verification. The enclave's DiffieHellman public key was signed by the enclave's RSA public key. - CngKey cngkey = CngKey.Import(enclaveRsaPublicKey, CngKeyBlobFormat.GenericPublicBlob); - using (RSACng rsacng = new RSACng(cngkey)) - { - if (!rsacng.VerifyData(enclaveDHInfo.PublicKey, enclaveDHInfo.PublicKeySignature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)) - { - throw new ArgumentException(Strings.GetSharedSecretFailed); - } - } - - CngKey key = CngKey.Import(enclaveDHInfo.PublicKey, CngKeyBlobFormat.GenericPublicBlob); - return clientDHKey.DeriveKeyMaterial(key); - } - #endregion - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveDelegate.CngCryto.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveDelegate.CngCryto.cs deleted file mode 100644 index aceb598436..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveDelegate.CngCryto.cs +++ /dev/null @@ -1,231 +0,0 @@ -// 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.Security.Cryptography; - -namespace Microsoft.Data.SqlClient -{ - internal sealed partial class EnclaveDelegate - { - private static readonly Dictionary s_enclaveProviders = new Dictionary(); - - /// - /// Create a new enclave session - /// - /// attestation protocol - /// enclave type - /// The set of parameters required for enclave session. - /// 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, EnclaveSessionParameters enclaveSessionParameters, - byte[] attestationInfo, SqlEnclaveAttestationParameters attestationParameters, byte[] customData, int customDataLength) - { - lock (_lock) - { - SqlColumnEncryptionEnclaveProvider sqlColumnEncryptionEnclaveProvider = GetEnclaveProvider(attestationProtocol, enclaveType); - - sqlColumnEncryptionEnclaveProvider.GetEnclaveSession( - enclaveSessionParameters, - generateCustomData: false, - sqlEnclaveSession: out SqlEnclaveSession sqlEnclaveSession, - counter: out _, - customData: out _, - customDataLength: out _ - ); - - if (sqlEnclaveSession != null) - { - return; - } - - sqlColumnEncryptionEnclaveProvider.CreateEnclaveSession( - attestationInfo, - attestationParameters.ClientDiffieHellmanKey, - enclaveSessionParameters, - customData, - customDataLength, - out sqlEnclaveSession, - counter: out _ - ); - - if (sqlEnclaveSession == null) - { - throw SQL.NullEnclaveSessionReturnedFromProvider(enclaveType, enclaveSessionParameters.AttestationUrl); - } - } - } - - internal void GetEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out byte[] customData, out int customDataLength) - { - GetEnclaveSession(attestationProtocol, enclaveType, enclaveSessionParameters, generateCustomData, out sqlEnclaveSession, out _, out customData, out customDataLength, throwIfNull: false); - } - - private void GetEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength, bool throwIfNull) - { - SqlColumnEncryptionEnclaveProvider sqlColumnEncryptionEnclaveProvider = GetEnclaveProvider(attestationProtocol, enclaveType); - sqlColumnEncryptionEnclaveProvider.GetEnclaveSession(enclaveSessionParameters, generateCustomData, out sqlEnclaveSession, out counter, out customData, out customDataLength); - - if (throwIfNull && sqlEnclaveSession == null) - { - throw SQL.NullEnclaveSessionDuringQueryExecution(enclaveType, enclaveSessionParameters.AttestationUrl); - } - } - - internal void InvalidateEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, EnclaveSessionParameters enclaveSessionParameters, SqlEnclaveSession enclaveSession) - { - SqlColumnEncryptionEnclaveProvider sqlColumnEncryptionEnclaveProvider = GetEnclaveProvider(attestationProtocol, enclaveType); - sqlColumnEncryptionEnclaveProvider.InvalidateEnclaveSession(enclaveSessionParameters, enclaveSession); - } - - - private SqlColumnEncryptionEnclaveProvider GetEnclaveProvider(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType) - { - if (!s_enclaveProviders.TryGetValue(attestationProtocol, out SqlColumnEncryptionEnclaveProvider sqlColumnEncryptionEnclaveProvider)) - { - switch (attestationProtocol) - { - case SqlConnectionAttestationProtocol.AAS: - AzureAttestationEnclaveProvider azureAttestationEnclaveProvider = new AzureAttestationEnclaveProvider(); - s_enclaveProviders[attestationProtocol] = azureAttestationEnclaveProvider; - sqlColumnEncryptionEnclaveProvider = s_enclaveProviders[attestationProtocol]; - break; - - case SqlConnectionAttestationProtocol.HGS: - HostGuardianServiceEnclaveProvider hostGuardianServiceEnclaveProvider = new HostGuardianServiceEnclaveProvider(); - s_enclaveProviders[attestationProtocol] = hostGuardianServiceEnclaveProvider; - sqlColumnEncryptionEnclaveProvider = s_enclaveProviders[attestationProtocol]; - break; - -#if ENCLAVE_SIMULATOR - case SqlConnectionAttestationProtocol.SIM: - SimulatorEnclaveProvider simulatorEnclaveProvider = new SimulatorEnclaveProvider(); - s_enclaveProviders[attestationProtocol] = (SqlColumnEncryptionEnclaveProvider)simulatorEnclaveProvider; - sqlColumnEncryptionEnclaveProvider = s_enclaveProviders[attestationProtocol]; - break; -#endif - - default: - break; - } - } - - if (sqlColumnEncryptionEnclaveProvider == null) - { - throw SQL.EnclaveProviderNotFound(enclaveType, ConvertAttestationProtocolToString(attestationProtocol)); - } - - return sqlColumnEncryptionEnclaveProvider; - } - - /// - /// Generate the byte package that needs to be sent to the enclave - /// - /// attestation protocol - /// Keys to be sent to enclave - /// - /// enclave type - /// The set of parameters required for enclave session. - /// connection executing the query - /// - internal EnclavePackage GenerateEnclavePackage(SqlConnectionAttestationProtocol attestationProtocol, Dictionary keysToBeSentToEnclave, string queryText, string enclaveType, EnclaveSessionParameters enclaveSessionParameters, SqlConnection connection) - { - SqlEnclaveSession sqlEnclaveSession; - long counter; - - try - { - GetEnclaveSession( - attestationProtocol, - enclaveType, - enclaveSessionParameters, - generateCustomData: false, - sqlEnclaveSession: out sqlEnclaveSession, - counter: out counter, - customData: out _, - customDataLength: out _, - throwIfNull: true - ); - } - catch (Exception e) - { - throw new RetryableEnclaveQueryExecutionException(e.Message, e); - } - - List decryptedKeysToBeSentToEnclave = GetDecryptedKeysToBeSentToEnclave(keysToBeSentToEnclave, enclaveSessionParameters.ServerName, connection); - byte[] queryStringHashBytes = ComputeQueryStringHash(queryText); - byte[] keyBytePackage = GenerateBytePackageForKeys(counter, queryStringHashBytes, decryptedKeysToBeSentToEnclave); - byte[] sessionKey = sqlEnclaveSession.GetSessionKey(); - byte[] encryptedBytePackage = EncryptBytePackage(keyBytePackage, sessionKey, enclaveSessionParameters.ServerName); - byte[] enclaveSessionHandle = BitConverter.GetBytes(sqlEnclaveSession.SessionId); - byte[] byteArrayToBeSentToEnclave = CombineByteArrays(enclaveSessionHandle, encryptedBytePackage); - return new EnclavePackage(byteArrayToBeSentToEnclave, sqlEnclaveSession); - } - - - internal SqlEnclaveAttestationParameters GetAttestationParameters(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, string attestationUrl, byte[] customData, int customDataLength) - { - SqlColumnEncryptionEnclaveProvider sqlColumnEncryptionEnclaveProvider = GetEnclaveProvider(attestationProtocol, enclaveType); - return sqlColumnEncryptionEnclaveProvider.GetAttestationParameters(attestationUrl, customData, customDataLength); - } - - internal byte[] GetSerializedAttestationParameters(SqlEnclaveAttestationParameters sqlEnclaveAttestationParameters, string enclaveType) - { - byte[] attestationProtocolBytes = null; - byte[] attestationProtocolInputLengthBytes = null; - byte[] clientDHPublicKeyLengthBytes = null; - int attestationProtocolInt = sqlEnclaveAttestationParameters.Protocol; - - attestationProtocolBytes = GetUintBytes(enclaveType, attestationProtocolInt, "attestationProtocol"); - - if (attestationProtocolBytes == null) - { - throw SQL.NullArgumentInternal(nameof(attestationProtocolBytes), nameof(EnclaveDelegate), nameof(GetSerializedAttestationParameters)); - } - - byte[] attestationProtocolInputBytes = sqlEnclaveAttestationParameters.GetInput(); - - attestationProtocolInputLengthBytes = GetUintBytes(enclaveType, attestationProtocolInputBytes.Length, "attestationProtocolInputLength"); - - if (attestationProtocolInputLengthBytes == null) - { - throw SQL.NullArgumentInternal(nameof(attestationProtocolInputLengthBytes), nameof(EnclaveDelegate), nameof(GetSerializedAttestationParameters)); - } - - byte[] clientDHPublicKey = sqlEnclaveAttestationParameters.ClientDiffieHellmanKey.Key.Export(CngKeyBlobFormat.EccPublicBlob); - - clientDHPublicKeyLengthBytes = GetUintBytes(enclaveType, clientDHPublicKey.Length, "clientDHPublicKeyLength"); - - if (clientDHPublicKeyLengthBytes == null) - { - throw SQL.NullArgumentInternal(nameof(clientDHPublicKeyLengthBytes), nameof(EnclaveDelegate), nameof(GetSerializedAttestationParameters)); - } - - return CombineByteArrays(attestationProtocolBytes, attestationProtocolInputLengthBytes, attestationProtocolInputBytes, clientDHPublicKeyLengthBytes, clientDHPublicKey); - } - - private string ConvertAttestationProtocolToString(SqlConnectionAttestationProtocol attestationProtocol) - { - switch (attestationProtocol) - { - case SqlConnectionAttestationProtocol.AAS: - return "AAS"; - - 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/SqlColumnEncryptionEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs index 00f05363bc..9017b84717 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 @@ -17,7 +17,7 @@ internal abstract class SqlColumnEncryptionEnclaveProvider internal abstract SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength); /// - internal abstract void CreateEnclaveSession(byte[] enclaveAttestationInfo, ECDiffieHellmanCng clientDiffieHellmanKey, EnclaveSessionParameters enclaveSessionParameters, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter); + internal abstract void CreateEnclaveSession(byte[] enclaveAttestationInfo, ECDiffieHellman clientDiffieHellmanKey, EnclaveSessionParameters enclaveSessionParameters, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter); /// internal abstract void InvalidateEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, 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 aa054ae5a1..e962a5695c 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 @@ -31,7 +31,8 @@ namespace Microsoft.Data.SqlClient [ DefaultEvent("RecordsAffected"), ToolboxItem(true), - Designer("Microsoft.VSDesigner.Data.VS.SqlCommandDesigner, " + AssemblyRef.MicrosoftVSDesigner) + Designer("Microsoft.VSDesigner.Data.VS.SqlCommandDesigner, " + AssemblyRef.MicrosoftVSDesigner), + DesignerCategory("") ] public sealed class SqlCommand : DbCommand, ICloneable { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommandBuilder.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommandBuilder.cs index 0ee215f1fd..11839689f5 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommandBuilder.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommandBuilder.cs @@ -14,6 +14,7 @@ namespace Microsoft.Data.SqlClient { /// + [DesignerCategory("")] public sealed class SqlCommandBuilder : DbCommandBuilder { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs index a7b0c5cfd4..70252f0471 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -35,7 +35,10 @@ namespace Microsoft.Data.SqlClient using Microsoft.Data.Common; /// - [DefaultEvent("InfoMessage")] + [ + DefaultEvent("InfoMessage"), + DesignerCategory("") + ] public sealed partial class SqlConnection : DbConnection, ICloneable { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataAdapter.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataAdapter.cs index 68da2c6f4e..0e223df8ba 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataAdapter.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataAdapter.cs @@ -15,7 +15,8 @@ namespace Microsoft.Data.SqlClient [ DefaultEvent("RowUpdated"), ToolboxItem("Microsoft.VSDesigner.Data.VS.SqlDataAdapterToolboxItem, " + AssemblyRef.MicrosoftVSDesigner), - Designer("Microsoft.VSDesigner.Data.VS.SqlDataAdapterDesigner, " + AssemblyRef.MicrosoftVSDesigner) + Designer("Microsoft.VSDesigner.Data.VS.SqlDataAdapterDesigner, " + AssemblyRef.MicrosoftVSDesigner), + DesignerCategory("") ] public sealed class SqlDataAdapter : DbDataAdapter, IDbDataAdapter, ICloneable { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs deleted file mode 100644 index 3422180d8e..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs +++ /dev/null @@ -1,69 +0,0 @@ -// 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.Security.Cryptography; - -namespace Microsoft.Data.SqlClient -{ - - /// - internal class SqlEnclaveAttestationParameters - { - - private static readonly string _clientDiffieHellmanKeyName = "ClientDiffieHellmanKey"; - private static readonly string _inputName = "input"; - private static readonly string _className = "EnclaveAttestationParameters"; - - private readonly byte[] _input; - - /// - internal int Protocol { get; } - - - /// - internal ECDiffieHellmanCng ClientDiffieHellmanKey { get; } - - /// - internal byte[] GetInput() - { - return Clone(_input); - } - - /// - /// Deep copy the array into a new array - /// - /// - /// - private byte[] Clone(byte[] arrayToClone) - { - - if (null == arrayToClone) - { - return null; - } - - byte[] returnValue = new byte[arrayToClone.Length]; - - for (int i = 0; i < arrayToClone.Length; i++) - { - returnValue[i] = arrayToClone[i]; - } - - return returnValue; - } - - /// - internal SqlEnclaveAttestationParameters(int protocol, byte[] input, ECDiffieHellmanCng clientDiffieHellmanKey) - { - if (null == clientDiffieHellmanKey) - { throw SQL.NullArgumentInConstructorInternal(_clientDiffieHellmanKeyName, _className); } - if (null == input) - { throw SQL.NullArgumentInConstructorInternal(_inputName, _className); } - - _input = input; - Protocol = protocol; - ClientDiffieHellmanKey = clientDiffieHellmanKey; - } - } -} 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 deleted file mode 100644 index 6f7c62ce66..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs +++ /dev/null @@ -1,367 +0,0 @@ -// 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.Linq; -using System.Runtime.Caching; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -using System.Threading; - -namespace Microsoft.Data.SqlClient -{ - internal abstract class VirtualizationBasedSecurityEnclaveProviderBase : EnclaveProviderBase - { - #region Members - - private static readonly MemoryCache rootSigningCertificateCache = new MemoryCache("RootSigningCertificateCache"); - - #endregion - - #region Constants - - private const int DiffieHellmanKeySize = 384; - private const int VsmHGSProtocolId = 3; - - // ENCLAVE_IDENTITY related constants - private static readonly EnclaveIdentity ExpectedPolicy = new EnclaveIdentity() - { - OwnerId = new byte[] - { - 0x10, 0x20, 0x30, 0x40, 0x41, 0x31, 0x21, 0x11, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }, - - UniqueId = new byte[] { }, - - // This field is calculated as follows: - // "Fixed Microsoft GUID" = {845319A6-706C-47BC-A7E8-5137B0BC750D} - // CN of the certificate that signed the file - // Opus Info - Authenticated Attribute that contains a Name and a URL - // - // In our case the Opus Info is: - // Description: Microsoft SQL Server Always Encrypted VBS Enclave Library, - // Description URL: https://go.microsoft.com/fwlink/?linkid=2018716 - AuthorId = new byte[] - { - 0x04, 0x37, 0xCA, 0xE2, 0x53, 0x7D, 0x8B, 0x9B, - 0x07, 0x76, 0xB6, 0x1B, 0x11, 0xE6, 0xCE, 0xD3, - 0xD2, 0x32, 0xE9, 0x30, 0x8F, 0x60, 0xE2, 0x1A, - 0xDA, 0xB2, 0xFD, 0x91, 0xE3, 0xDA, 0x95, 0x98 - }, - - FamilyId = new byte[] - { - 0xFE, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }, - - ImageId = new byte[] - { - // This value should be same as defined in sqlserver code Sql/Ntdbms/aetm/enclave/dllmain.cpp - 0x19, 0x17, 0x12, 0x00, 0x01, 0x05, 0x20, 0x13, - 0x00, 0x05, 0x14, 0x03, 0x12, 0x01, 0x22, 0x05 - }, - - EnclaveSvn = 0, - - SecureKernelSvn = 0, - - PlatformSvn = 1, - - // 0: ENCLAVE_VBS_FLAG_NO_DEBUG in ds_main; Flag does not permit debug enclaves - Flags = 0, - - SigningLevel = 0, - - Reserved = 0 - }; - - #endregion - - #region Internal 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. - internal override void GetEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) - { - GetEnclaveSessionHelper(enclaveSessionParameters, 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. - internal override SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength) - { - ECDiffieHellmanCng clientDHKey = new ECDiffieHellmanCng(DiffieHellmanKeySize); - clientDHKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash; - clientDHKey.HashAlgorithm = CngAlgorithm.Sha256; - return new SqlEnclaveAttestationParameters(VsmHGSProtocolId, 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. - internal override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, EnclaveSessionParameters enclaveSessionParameters, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter) - { - sqlEnclaveSession = null; - counter = 0; - try - { - ThreadRetryCache.Remove(Thread.CurrentThread.ManagedThreadId.ToString()); - sqlEnclaveSession = GetEnclaveSessionFromCache(enclaveSessionParameters, out counter); - if (sqlEnclaveSession == null) - { - if (!string.IsNullOrEmpty(enclaveSessionParameters.AttestationUrl)) - { - // Deserialize the payload - AttestationInfo info = new AttestationInfo(attestationInfo); - - // Verify enclave policy matches expected policy - VerifyEnclavePolicy(info.EnclaveReportPackage); - - // Perform Attestation per VSM protocol - VerifyAttestationInfo(enclaveSessionParameters.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(enclaveSessionParameters, sharedSecret, info.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. - internal override void InvalidateEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, SqlEnclaveSession enclaveSessionToInvalidate) - { - InvalidateEnclaveSessionHelper(enclaveSessionParameters, enclaveSessionToInvalidate); - } - - #endregion - - #region Private helpers - - // Performs Attestation per the protocol used by Virtual Secure Modules. - private void VerifyAttestationInfo(string attestationUrl, HealthReport healthReport, EnclaveReportPackage enclaveReportPackage) - { - bool shouldRetryValidation; - bool shouldForceUpdateSigningKeys = false; - do - { - shouldRetryValidation = false; - - // Get HGS Root signing certs from HGS - X509Certificate2Collection signingCerts = GetSigningCertificate(attestationUrl, shouldForceUpdateSigningKeys); - - // Verify SQL Health report root chain of trust is the HGS root signing cert - X509ChainStatusFlags chainStatus = VerifyHealthReportAgainstRootCertificate(signingCerts, healthReport.Certificate); - if (chainStatus != X509ChainStatusFlags.NoError) - { - // In cases if we fail to validate the health report, it might be possible that we are using old signing keys - // let's re-download the signing keys again and re-validate the health report - if (!shouldForceUpdateSigningKeys) - { - shouldForceUpdateSigningKeys = true; - shouldRetryValidation = true; - } - else - { - throw new AlwaysEncryptedAttestationException(String.Format(Strings.VerifyHealthCertificateChainFormat, attestationUrl, chainStatus)); - } - } - } while (shouldRetryValidation); - - // Verify enclave report is signed by IDK_S from health report - VerifyEnclaveReportSignature(enclaveReportPackage, healthReport.Certificate); - } - - // Makes a web request to the provided url and returns the response as a byte[] - protected abstract byte[] MakeRequest(string url); - - // Gets the root signing certificate for the provided attestation service. - // If the certificate does not exist in the cache, this will make a call to the - // attestation service's "/signingCertificates" endpoint. This endpoint can - // return multiple certificates if the attestation service consists - // of multiple nodes. - private X509Certificate2Collection GetSigningCertificate(string attestationUrl, bool forceUpdate) - { - attestationUrl = GetAttestationUrl(attestationUrl); - X509Certificate2Collection signingCertificates = (X509Certificate2Collection)rootSigningCertificateCache[attestationUrl]; - if (forceUpdate || signingCertificates == null || AnyCertificatesExpired(signingCertificates)) - { - byte[] data = MakeRequest(attestationUrl); - var certificateCollection = new X509Certificate2Collection(); - - try - { - certificateCollection.Import(data); - } - catch (CryptographicException exception) - { - throw new AlwaysEncryptedAttestationException(String.Format(Strings.GetAttestationSigningCertificateFailedInvalidCertificate, attestationUrl), exception); - } - - rootSigningCertificateCache.Add(attestationUrl, certificateCollection, DateTime.Now.AddDays(1)); - } - - return (X509Certificate2Collection)rootSigningCertificateCache[attestationUrl]; - } - - // Return the endpoint for given attestation url - protected abstract string GetAttestationUrl(string attestationUrl); - - // Checks if any certificates in the collection are expired - private bool AnyCertificatesExpired(X509Certificate2Collection certificates) - { - return certificates.OfType().Any(c => c.NotAfter < DateTime.Now); - } - - // Verifies that a chain of trust can be built from the health report provided - // by SQL Server and the attestation service's root signing certificate(s). - private X509ChainStatusFlags VerifyHealthReportAgainstRootCertificate(X509Certificate2Collection signingCerts, X509Certificate2 healthReportCert) - { - var chain = new X509Chain(); - - foreach (var cert in signingCerts) - { - chain.ChainPolicy.ExtraStore.Add(cert); - } - - chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; - - if (!chain.Build(healthReportCert)) - { - bool untrustedRoot = false; - - // iterate over the chain status to check why the build failed - foreach (X509ChainStatus status in chain.ChainStatus) - { - if (status.Status == X509ChainStatusFlags.UntrustedRoot) - { - untrustedRoot = true; - } - else - { - return status.Status; - } - } - - // if the chain failed with untrusted root, this could be because the client doesn't have the root cert - // installed. If the chain's untrusted root cert has the same thumbprint as the signing cert, then we - // do trust it. - if (untrustedRoot) - { - // iterate through the certificate chain, starting at the root since it's likely the - // signing certificate is the root - for (int i = 0; i < chain.ChainElements.Count; i++) - { - X509ChainElement element = chain.ChainElements[chain.ChainElements.Count - 1 - i]; - - foreach (X509Certificate2 cert in signingCerts) - { - if (element.Certificate.Thumbprint == cert.Thumbprint) - { - return X509ChainStatusFlags.NoError; - } - } - } - - // in the case where we didn't find matching thumbprint - return X509ChainStatusFlags.UntrustedRoot; - } - } - - return X509ChainStatusFlags.NoError; - } - - // Verifies the enclave report signature using the health report. - private void VerifyEnclaveReportSignature(EnclaveReportPackage enclaveReportPackage, X509Certificate2 healthReportCert) - { - // Check if report is formatted correctly - UInt32 calculatedSize = Convert.ToUInt32(enclaveReportPackage.PackageHeader.GetSizeInPayload()) + enclaveReportPackage.PackageHeader.SignedStatementSize + enclaveReportPackage.PackageHeader.SignatureSize; - - if (calculatedSize != enclaveReportPackage.PackageHeader.PackageSize) - { - throw new ArgumentException(Strings.VerifyEnclaveReportFormatFailed); - } - - // IDK_S is contained in healthReport cert public key - RSA rsacsp = healthReportCert.GetRSAPublicKey(); - RSAParameters rsaparams = rsacsp.ExportParameters(includePrivateParameters: false); - RSACng rsacng = new RSACng(); - rsacng.ImportParameters(rsaparams); - - if (!rsacng.VerifyData(enclaveReportPackage.ReportAsBytes, enclaveReportPackage.SignatureBlob, HashAlgorithmName.SHA256, RSASignaturePadding.Pss)) - { - throw new ArgumentException(Strings.VerifyEnclaveReportFailed); - } - } - - // Verifies the enclave policy matches expected policy. - private void VerifyEnclavePolicy(EnclaveReportPackage enclaveReportPackage) - { - EnclaveIdentity identity = enclaveReportPackage.Report.Identity; - - VerifyEnclavePolicyProperty("OwnerId", identity.OwnerId, ExpectedPolicy.OwnerId); - VerifyEnclavePolicyProperty("AuthorId", identity.AuthorId, ExpectedPolicy.AuthorId); - VerifyEnclavePolicyProperty("FamilyId", identity.FamilyId, ExpectedPolicy.FamilyId); - VerifyEnclavePolicyProperty("ImageId", identity.ImageId, ExpectedPolicy.ImageId); - VerifyEnclavePolicyProperty("EnclaveSvn", identity.EnclaveSvn, ExpectedPolicy.EnclaveSvn); - VerifyEnclavePolicyProperty("SecureKernelSvn", identity.SecureKernelSvn, ExpectedPolicy.SecureKernelSvn); - VerifyEnclavePolicyProperty("PlatformSvn", identity.PlatformSvn, ExpectedPolicy.PlatformSvn); - - // This is a check that the enclave is running without debug support or not. - // - if (identity.Flags != ExpectedPolicy.Flags) - { - throw new InvalidOperationException(Strings.VerifyEnclaveDebuggable); - } - } - - // Verifies a byte[] enclave policy property - private void VerifyEnclavePolicyProperty(string property, byte[] actual, byte[] expected) - { - if (!actual.SequenceEqual(expected)) - { - string exceptionMessage = String.Format(Strings.VerifyEnclavePolicyFailedFormat, property, BitConverter.ToString(actual), BitConverter.ToString(expected)); - throw new ArgumentException(exceptionMessage); - } - } - - // Verifies a uint enclave policy property - private void VerifyEnclavePolicyProperty(string property, uint actual, uint expected) - { - if (actual < expected) - { - string exceptionMessage = String.Format(Strings.VerifyEnclavePolicyFailedFormat, property, actual, expected); - throw new ArgumentException(exceptionMessage); - } - } - - // Derives the shared secret between the client and enclave. - private byte[] GetSharedSecret(EnclavePublicKey enclavePublicKey, EnclaveDiffieHellmanInfo enclaveDHInfo, ECDiffieHellmanCng clientDHKey) - { - // Perform signature verification. The enclave's DiffieHellman public key was signed by the enclave's RSA public key. - CngKey cngkey = CngKey.Import(enclavePublicKey.PublicKey, CngKeyBlobFormat.GenericPublicBlob); - RSACng rsacng = new RSACng(cngkey); - if (!rsacng.VerifyData(enclaveDHInfo.PublicKey, enclaveDHInfo.PublicKeySignature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)) - { - throw new ArgumentException(Strings.GetSharedSecretFailed); - } - - CngKey key = CngKey.Import(enclaveDHInfo.PublicKey, CngKeyBlobFormat.GenericPublicBlob); - return clientDHKey.DeriveKeyMaterial(key); - } - - #endregion - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs similarity index 97% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs index 0ffa1ad5ce..d08db25036 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs @@ -73,9 +73,7 @@ internal override void GetEnclaveSession(EnclaveSessionParameters enclaveSession // Gets the information that SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. internal override SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength) { - // The key derivation function and hash algorithm name are specified when key derivation is performed - ECDiffieHellman clientDHKey = ECDiffieHellman.Create(); - clientDHKey.KeySize = DiffieHellmanKeySize; + ECDiffieHellman clientDHKey = KeyConverter.CreateECDiffieHellman(DiffieHellmanKeySize); byte[] attestationParam = PrepareAttestationParameters(attestationUrl, customData, customDataLength); return new SqlEnclaveAttestationParameters(AzureBasedAttestationProtocolId, attestationParam, clientDHKey); } @@ -528,8 +526,7 @@ private byte[] GetSharedSecret(EnclavePublicKey enclavePublicKey, byte[] nonce, } // Perform signature verification. The enclave's DiffieHellman public key was signed by the enclave's RSA public key. - RSAParameters rsaParams = KeyConverter.RSAPublicKeyBlobToParams(enclaveRsaPublicKey); - using (RSA rsa = RSA.Create(rsaParams)) + using (RSA rsa = KeyConverter.CreateRSAFromPublicKeyBlob(enclaveRsaPublicKey)) { if (!rsa.VerifyData(enclaveDHInfo.PublicKey, enclaveDHInfo.PublicKeySignature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)) { @@ -537,9 +534,10 @@ private byte[] GetSharedSecret(EnclavePublicKey enclavePublicKey, byte[] nonce, } } - ECParameters ecParams = KeyConverter.ECCPublicKeyBlobToParams(enclaveDHInfo.PublicKey); - ECDiffieHellman enclaveDHKey = ECDiffieHellman.Create(ecParams); - return clientDHKey.DeriveKeyFromHash(enclaveDHKey.PublicKey, HashAlgorithmName.SHA256); + using (ECDiffieHellman enclaveDHKey = KeyConverter.CreateECDiffieHellmanFromPublicKeyBlob(enclaveDHInfo.PublicKey)) + { + return KeyConverter.DeriveKey(clientDHKey, enclaveDHKey.PublicKey); + } } #endregion } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveDelegate.CrossPlatformCrypto.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.Crypto.cs similarity index 98% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveDelegate.CrossPlatformCrypto.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.Crypto.cs index 98ba7dd83d..21c91725a5 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveDelegate.CrossPlatformCrypto.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.Crypto.cs @@ -195,7 +195,7 @@ internal byte[] GetSerializedAttestationParameters(SqlEnclaveAttestationParamete throw SQL.NullArgumentInternal(nameof(attestationProtocolInputLengthBytes), nameof(EnclaveDelegate), nameof(GetSerializedAttestationParameters)); } - byte[] clientDHPublicKey = KeyConverter.ECDHPublicKeyToECCKeyBlob(sqlEnclaveAttestationParameters.ClientDiffieHellmanKey.PublicKey); + byte[] clientDHPublicKey = KeyConverter.GetECDiffieHellmanPublicKeyBlob(sqlEnclaveAttestationParameters.ClientDiffieHellmanKey); clientDHPublicKeyLengthBytes = GetUintBytes(enclaveType, clientDHPublicKey.Length, "clientDHPublicKeyLength"); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveDelegate.NetStandard.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.NotSupported.cs similarity index 100% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveDelegate.NetStandard.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.NotSupported.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.Crypto.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.Crypto.cs new file mode 100644 index 0000000000..06262b364f --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.Crypto.cs @@ -0,0 +1,52 @@ +// 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.Security.Cryptography; + +namespace Microsoft.Data.SqlClient +{ + /// + internal class SqlEnclaveAttestationParameters + { + private readonly byte[] _input; + + /// + internal SqlEnclaveAttestationParameters(int protocol, byte[] input, ECDiffieHellman clientDiffieHellmanKey) + { + if (input == null) + { + throw SQL.NullArgumentInConstructorInternal(nameof(input), nameof(SqlEnclaveAttestationParameters)); + } + if (clientDiffieHellmanKey == null) + { + throw SQL.NullArgumentInConstructorInternal(nameof(clientDiffieHellmanKey), nameof(SqlEnclaveAttestationParameters)); + } + + _input = input; + Protocol = protocol; + ClientDiffieHellmanKey = clientDiffieHellmanKey; + } + + /// + internal int Protocol { get; private set; } + + /// + internal ECDiffieHellman ClientDiffieHellmanKey { get; private set; } + + /// + internal byte[] GetInput() + { + // return a new array for safety so the caller cannot mutate the original + if (_input == null) + { + return null; + } + + byte[] output = new byte[_input.Length]; + Buffer.BlockCopy(_input, 0, output, 0, _input.Length); + return output; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.NotSupported.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.NotSupported.cs new file mode 100644 index 0000000000..0ae67d4a94 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.NotSupported.cs @@ -0,0 +1,19 @@ +// 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. + +namespace Microsoft.Data.SqlClient +{ + /// + internal partial class SqlEnclaveAttestationParameters + { + /// + internal int Protocol { get; } + + /// + internal byte[] GetInput() + { + return null; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs similarity index 93% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs index 663b4a4e19..f71047d965 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs @@ -94,10 +94,7 @@ internal override void GetEnclaveSession(EnclaveSessionParameters enclaveSession // Gets the information that SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. internal override SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength) { - // The key derivation function and hash algorithm name are specified when key derivation is performed - ECDiffieHellman clientDHKey = ECDiffieHellman.Create(); - clientDHKey.KeySize = DiffieHellmanKeySize; - + ECDiffieHellman clientDHKey = KeyConverter.CreateECDiffieHellman(DiffieHellmanKeySize); return new SqlEnclaveAttestationParameters(VsmHGSProtocolId, new byte[] { }, clientDHKey); } @@ -288,21 +285,19 @@ private X509ChainStatusFlags VerifyHealthReportAgainstRootCertificate(X509Certif private void VerifyEnclaveReportSignature(EnclaveReportPackage enclaveReportPackage, X509Certificate2 healthReportCert) { // Check if report is formatted correctly - UInt32 calculatedSize = Convert.ToUInt32(enclaveReportPackage.PackageHeader.GetSizeInPayload()) + enclaveReportPackage.PackageHeader.SignedStatementSize + enclaveReportPackage.PackageHeader.SignatureSize; + uint calculatedSize = Convert.ToUInt32(enclaveReportPackage.PackageHeader.GetSizeInPayload()) + enclaveReportPackage.PackageHeader.SignedStatementSize + enclaveReportPackage.PackageHeader.SignatureSize; if (calculatedSize != enclaveReportPackage.PackageHeader.PackageSize) { throw new ArgumentException(Strings.VerifyEnclaveReportFormatFailed); } - // IDK_S is contained in healthReport cert public key - using (RSA rsa = healthReportCert.GetRSAPublicKey()) + using (RSA rsa = KeyConverter.GetRSAFromCertificate(healthReportCert)) { if (!rsa.VerifyData(enclaveReportPackage.ReportAsBytes, enclaveReportPackage.SignatureBlob, HashAlgorithmName.SHA256, RSASignaturePadding.Pss)) { throw new ArgumentException(Strings.VerifyEnclaveReportFailed); } - } } @@ -342,7 +337,7 @@ private void VerifyEnclavePolicyProperty(string property, uint actual, uint expe { if (actual < expected) { - string exceptionMessage = String.Format(Strings.VerifyEnclavePolicyFailedFormat, property, actual, expected); + string exceptionMessage = string.Format(Strings.VerifyEnclavePolicyFailedFormat, property, actual, expected); throw new ArgumentException(exceptionMessage); } } @@ -351,8 +346,7 @@ private void VerifyEnclavePolicyProperty(string property, uint actual, uint expe private byte[] GetSharedSecret(EnclavePublicKey enclavePublicKey, EnclaveDiffieHellmanInfo enclaveDHInfo, ECDiffieHellman clientDHKey) { // Perform signature verification. The enclave's DiffieHellman public key was signed by the enclave's RSA public key. - RSAParameters rsaParams = KeyConverter.RSAPublicKeyBlobToParams(enclavePublicKey.PublicKey); - using (RSA rsa = RSA.Create(rsaParams)) + using (RSA rsa = KeyConverter.CreateRSAFromPublicKeyBlob(enclavePublicKey.PublicKey)) { if (!rsa.VerifyData(enclaveDHInfo.PublicKey, enclaveDHInfo.PublicKeySignature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)) { @@ -360,9 +354,10 @@ private byte[] GetSharedSecret(EnclavePublicKey enclavePublicKey, EnclaveDiffieH } } - ECParameters ecParams = KeyConverter.ECCPublicKeyBlobToParams(enclaveDHInfo.PublicKey); - ECDiffieHellman enclaveDHKey = ECDiffieHellman.Create(ecParams); - return clientDHKey.DeriveKeyFromHash(enclaveDHKey.PublicKey, HashAlgorithmName.SHA256); + using (ECDiffieHellman ecdh = KeyConverter.CreateECDiffieHellmanFromPublicKeyBlob(enclaveDHInfo.PublicKey)) + { + return KeyConverter.DeriveKey(clientDHKey, ecdh.PublicKey); + } } #endregion }