From bbe841075ba0b60a9504d7f065c6044bff75bfa6 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Mon, 23 Dec 2024 22:33:17 +0000 Subject: [PATCH 01/16] Initial removal of CertificateUtility.CreateCertificate One test implied that DataTestUtility.AKVUrl would point to an RSA key which aligned with the certificate's private key. Switching this to dynamically generate the key in places. --- .../ManualTests/AlwaysEncrypted/AKVTests.cs | 10 +- .../AlwaysEncrypted/ConversionTests.cs | 12 +-- .../AlwaysEncrypted/CspProviderExt.cs | 1 - .../AlwaysEncrypted/ExceptionTestAKVStore.cs | 62 ++++++------ .../TestFixtures/SQLSetupStrategy.cs | 28 +++--- .../SQLSetupStrategyAzureKeyVault.cs | 42 ++++++++- .../SQLSetupStrategyCertStoreProvider.cs | 7 +- .../TestFixtures/SQLSetupStrategyCspExt.cs | 2 +- .../TestFixtures/Setup/CertificateUtility.cs | 94 ------------------- .../TestTrustedMasterKeyPaths.cs | 12 +-- .../Fixtures/CertificateFixtureBase.cs | 10 +- .../ColumnMasterKeyCertificateFixture.cs | 21 +++++ 12 files changed, 132 insertions(+), 169 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/ColumnMasterKeyCertificateFixture.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs index f39dbb3bea..78fa0a9001 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs @@ -133,15 +133,15 @@ public void ForcedColumnDecryptErrorTestShouldFail() [PlatformSpecific(TestPlatforms.Windows)] public void TestRoundTripWithAKVAndCertStoreProvider() { - using SQLSetupStrategyCertStoreProvider certStoreFixture = new(); + SqlColumnEncryptionCertificateStoreProvider certStoreProvider = new SqlColumnEncryptionCertificateStoreProvider(); byte[] plainTextColumnEncryptionKey = ColumnEncryptionKey.GenerateRandomBytes(ColumnEncryptionKey.KeySizeInBytes); - byte[] encryptedColumnEncryptionKeyUsingAKV = _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, @"RSA_OAEP", plainTextColumnEncryptionKey); - byte[] columnEncryptionKeyReturnedAKV2Cert = certStoreFixture.CertStoreProvider.DecryptColumnEncryptionKey(certStoreFixture.CspColumnMasterKey.KeyPath, @"RSA_OAEP", encryptedColumnEncryptionKeyUsingAKV); + byte[] encryptedColumnEncryptionKeyUsingAKV = _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(_fixture.AkvKeyUrl, @"RSA_OAEP", plainTextColumnEncryptionKey); + byte[] columnEncryptionKeyReturnedAKV2Cert = certStoreProvider.DecryptColumnEncryptionKey(_fixture.ColumnMasterKeyPath, @"RSA_OAEP", encryptedColumnEncryptionKeyUsingAKV); Assert.True(plainTextColumnEncryptionKey.SequenceEqual(columnEncryptionKeyReturnedAKV2Cert), @"Roundtrip failed"); // Try the opposite. - byte[] encryptedColumnEncryptionKeyUsingCert = certStoreFixture.CertStoreProvider.EncryptColumnEncryptionKey(certStoreFixture.CspColumnMasterKey.KeyPath, @"RSA_OAEP", plainTextColumnEncryptionKey); - byte[] columnEncryptionKeyReturnedCert2AKV = _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, @"RSA_OAEP", encryptedColumnEncryptionKeyUsingCert); + byte[] encryptedColumnEncryptionKeyUsingCert = certStoreProvider.EncryptColumnEncryptionKey(_fixture.ColumnMasterKeyPath, @"RSA_OAEP", plainTextColumnEncryptionKey); + byte[] columnEncryptionKeyReturnedCert2AKV = _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(_fixture.AkvKeyUrl, @"RSA_OAEP", encryptedColumnEncryptionKeyUsingCert); Assert.True(plainTextColumnEncryptionKey.SequenceEqual(columnEncryptionKeyReturnedCert2AKV), @"Roundtrip failed"); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs index 94f137dbe9..960dffc7ae 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs @@ -14,11 +14,12 @@ using System.Security.Cryptography.X509Certificates; using Xunit; using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; +using Microsoft.Data.SqlClient.TestUtilities.Fixtures; namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { [PlatformSpecific(TestPlatforms.Windows)] - public sealed class ConversionTests : IDisposable + public sealed class ConversionTests : IDisposable, IClassFixture { private const string IdentityColumnName = "IdentityColumn"; @@ -29,7 +30,7 @@ public sealed class ConversionTests : IDisposable private const decimal SmallMoneyMinValue = -214748.3648M; private const int MaxLength = 10000; private int NumberOfRows = DataTestUtility.EnclaveEnabled ? 10 : 100; - private static X509Certificate2 certificate; + private X509Certificate2 certificate; private ColumnMasterKey columnMasterKey; private ColumnEncryptionKey columnEncryptionKey; private SqlColumnEncryptionCertificateStoreProvider certStoreProvider = new SqlColumnEncryptionCertificateStoreProvider(); @@ -54,12 +55,9 @@ public ColumnMetaData(SqlDbType columnType, int columnSize, int precision, int s public bool UseMax { get; set; } } - public ConversionTests() + public ConversionTests(ColumnMasterKeyCertificateFixture fixture) { - if (certificate == null) - { - certificate = CertificateUtility.CreateCertificate(); - } + certificate = fixture.ColumnMasterKeyCertificate; columnMasterKey = new CspColumnMasterKey(DatabaseHelper.GenerateUniqueName("CMK"), certificate.Thumbprint, certStoreProvider, DataTestUtility.EnclaveEnabled); _databaseObjects.Add(columnMasterKey); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs index f164dff220..579e11b61a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs @@ -188,7 +188,6 @@ public void TestEncryptDecryptWithCSP(string connectionString) using SqlCommand sqlCommand = new(@$"SELECT CustomerId, FirstName, LastName FROM [{tableName}] WHERE FirstName = @firstName", sqlConn, null, SqlCommandColumnEncryptionSetting.Enabled); SqlParameter customerFirstParam = sqlCommand.Parameters.AddWithValue(@"firstName", @"Microsoft"); - Console.WriteLine(@"Exception: {0}"); customerFirstParam.Direction = System.Data.ParameterDirection.Input; using SqlDataReader sqlDataReader = sqlCommand.ExecuteReader(); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionTestAKVStore.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionTestAKVStore.cs index 6cb20a4351..2465633a03 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionTestAKVStore.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionTestAKVStore.cs @@ -15,16 +15,16 @@ public class ExceptionTestAKVStore : IClassFixture(() => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, BadMasterKeyEncAlgo, cek)); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(_fixture.AkvKeyUrl, BadMasterKeyEncAlgo, cek)); Assert.Matches($@"Invalid key encryption algorithm specified: 'BadMasterKeyAlgorithm'. Expected value: 'RSA_OAEP' or 'RSA-OAEP'.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?", ex1.Message); - Exception ex2 = Assert.Throws(() => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, BadMasterKeyEncAlgo, cek)); + Exception ex2 = Assert.Throws(() => _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(_fixture.AkvKeyUrl, BadMasterKeyEncAlgo, cek)); Assert.Matches($@"Invalid key encryption algorithm specified: 'BadMasterKeyAlgorithm'. Expected value: 'RSA_OAEP' or 'RSA-OAEP'.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?", ex2.Message); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] public void NullEncryptionAlgorithm() { - Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, null, cek)); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(_fixture.AkvKeyUrl, null, cek)); Assert.Matches($@"Internal error. Key encryption algorithm cannot be null.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?", ex1.Message); - Exception ex2 = Assert.Throws(() => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, null, cek)); + Exception ex2 = Assert.Throws(() => _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(_fixture.AkvKeyUrl, null, cek)); Assert.Matches($@"Internal error. Key encryption algorithm cannot be null.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?", ex2.Message); } @@ -53,28 +53,28 @@ public void NullEncryptionAlgorithm() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] public void EmptyColumnEncryptionKey() { - Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, MasterKeyEncAlgo, new byte[] { })); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(_fixture.AkvKeyUrl, MasterKeyEncAlgo, new byte[] { })); Assert.Matches($@"Internal error. Empty columnEncryptionKey specified.", ex1.Message); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] public void NullColumnEncryptionKey() { - Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, MasterKeyEncAlgo, null)); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(_fixture.AkvKeyUrl, MasterKeyEncAlgo, null)); Assert.Matches($@"Value cannot be null.\s+\(?Parameter (name: )?'?columnEncryptionKey('\))?", ex1.Message); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] public void EmptyEncryptedColumnEncryptionKey() { - Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, MasterKeyEncAlgo, new byte[] { })); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(_fixture.AkvKeyUrl, MasterKeyEncAlgo, new byte[] { })); Assert.Matches($@"Internal error. Empty encryptedColumnEncryptionKey specified", ex1.Message); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] public void NullEncryptedColumnEncryptionKey() { - Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, MasterKeyEncAlgo, null)); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(_fixture.AkvKeyUrl, MasterKeyEncAlgo, null)); Assert.Matches($@"Value cannot be null.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?", ex1.Message); } @@ -82,10 +82,10 @@ public void NullEncryptedColumnEncryptionKey() public void InvalidAlgorithmVersion() { byte[] encrypteCekLocal = ColumnEncryptionKey.GenerateInvalidEncryptedCek(encryptedCek, ColumnEncryptionKey.ECEKCorruption.ALGORITHM_VERSION); - Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, MasterKeyEncAlgo, encrypteCekLocal)); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(_fixture.AkvKeyUrl, MasterKeyEncAlgo, encrypteCekLocal)); Assert.Matches($@"Specified encrypted column encryption key contains an invalid encryption algorithm version '10'. Expected version is '01'.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?", ex1.Message); - Exception ex2 = Assert.Throws(() => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_CORRUPT", encryptedCek)); + Exception ex2 = Assert.Throws(() => _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(_fixture.AkvKeyUrl, "RSA_CORRUPT", encryptedCek)); Assert.Contains("Invalid key encryption algorithm specified: 'RSA_CORRUPT'. Expected value: 'RSA_OAEP' or 'RSA-OAEP'.", ex2.Message); } @@ -95,9 +95,9 @@ public void InvalidCertificateSignature() // Put an invalid signature byte[] encrypteCekLocal = ColumnEncryptionKey.GenerateInvalidEncryptedCek(encryptedCek, ColumnEncryptionKey.ECEKCorruption.SIGNATURE); string errorMessage = - $@"The specified encrypted column encryption key signature does not match the signature computed with the column master key \(Asymmetric key in Azure Key Vault\) in '{DataTestUtility.AKVUrl}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + $@"The specified encrypted column encryption key signature does not match the signature computed with the column master key \(Asymmetric key in Azure Key Vault\) in '{_fixture.AkvKeyUrl}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; - Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, MasterKeyEncAlgo, encrypteCekLocal)); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(_fixture.AkvKeyUrl, MasterKeyEncAlgo, encrypteCekLocal)); Assert.Matches(errorMessage, ex1.Message); } @@ -106,9 +106,9 @@ public void InvalidCipherTextLength() { // Put an invalid signature byte[] encrypteCekLocal = ColumnEncryptionKey.GenerateInvalidEncryptedCek(encryptedCek, ColumnEncryptionKey.ECEKCorruption.CEK_LENGTH); - string errorMessage = $@"The specified encrypted column encryption key's ciphertext length: 251 does not match the ciphertext length: 256 when using column master key \(Azure Key Vault key\) in '{DataTestUtility.AKVUrl}'. The encrypted column encryption key may be corrupt, or the specified Azure Key Vault key path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + string errorMessage = $@"The specified encrypted column encryption key's ciphertext length: 251 does not match the ciphertext length: 256 when using column master key \(Azure Key Vault key\) in '{_fixture.AkvKeyUrl}'. The encrypted column encryption key may be corrupt, or the specified Azure Key Vault key path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; - Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, MasterKeyEncAlgo, encrypteCekLocal)); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(_fixture.AkvKeyUrl, MasterKeyEncAlgo, encrypteCekLocal)); Assert.Matches(errorMessage, ex1.Message); } @@ -116,9 +116,9 @@ public void InvalidCipherTextLength() public void InvalidSignatureInEncryptedCek() { byte[] encryptedCekLocal = ColumnEncryptionKey.GenerateInvalidEncryptedCek(encryptedCek, ColumnEncryptionKey.ECEKCorruption.SIGNATURE_LENGTH); - string errorMessage = $@"The specified encrypted column encryption key's signature length: 249 does not match the signature length: 256 when using column master key \(Azure Key Vault key\) in '{DataTestUtility.AKVUrl}'. The encrypted column encryption key may be corrupt, or the specified Azure Key Vault key path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + string errorMessage = $@"The specified encrypted column encryption key's signature length: 249 does not match the signature length: 256 when using column master key \(Azure Key Vault key\) in '{_fixture.AkvKeyUrl}'. The encrypted column encryption key may be corrupt, or the specified Azure Key Vault key path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; - Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, MasterKeyEncAlgo, encryptedCekLocal)); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(_fixture.AkvKeyUrl, MasterKeyEncAlgo, encryptedCekLocal)); Assert.Matches(errorMessage, ex1.Message); } @@ -134,10 +134,10 @@ public void InvalidURL() string fakePath = new string(barePath); string errorMessage = $@"Invalid url specified: '{fakePath}'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(fakePath, MasterKeyEncAlgo, cek)); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(fakePath, MasterKeyEncAlgo, cek)); Assert.Matches(errorMessage, ex1.Message); - Exception ex2 = Assert.Throws(() => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(fakePath, MasterKeyEncAlgo, encryptedCek)); + Exception ex2 = Assert.Throws(() => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(fakePath, MasterKeyEncAlgo, encryptedCek)); Assert.Matches(errorMessage, ex2.Message); } @@ -145,11 +145,11 @@ public void InvalidURL() public void NullAKVKeyPath() { Exception ex1 = Assert.Throws( - () => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(null, MasterKeyEncAlgo, cek)); + () => _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(null, MasterKeyEncAlgo, cek)); Assert.Matches($@"Azure Key Vault key path cannot be null.\s+\(?Parameter (name: )?'?masterKeyPath('\))?", ex1.Message); Exception ex2 = Assert.Throws( - () => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(null, MasterKeyEncAlgo, encryptedCek)); + () => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(null, MasterKeyEncAlgo, encryptedCek)); Assert.Matches($@"Internal error. Azure Key Vault key path cannot be null.\s+\(?Parameter (name: )?'?masterKeyPath('\))?", ex2.Message); } @@ -185,19 +185,19 @@ public void InvalidCertificatePath() string invalidTrustedEndpointErrorMessage = $@"Invalid Azure Key Vault key path specified: '{dummyPathWithInvalidKey}'. Valid trusted endpoints: vault.azure.net, vault.azure.cn, vault.usgovcloudapi.net, vault.microsoftazure.de, managedhsm.azure.net, managedhsm.azure.cn, managedhsm.usgovcloudapi.net, managedhsm.microsoftazure.de.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; Exception ex = Assert.Throws( - () => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(dummyPathWithOnlyHost, MasterKeyEncAlgo, cek)); + () => _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(dummyPathWithOnlyHost, MasterKeyEncAlgo, cek)); Assert.Matches(invalidUrlErrorMessage, ex.Message); ex = Assert.Throws( - () => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(dummyPathWithInvalidKey, MasterKeyEncAlgo, cek)); + () => _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(dummyPathWithInvalidKey, MasterKeyEncAlgo, cek)); Assert.Matches(invalidTrustedEndpointErrorMessage, ex.Message); ex = Assert.Throws( - () => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(dummyPathWithOnlyHost, MasterKeyEncAlgo, encryptedCek)); + () => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(dummyPathWithOnlyHost, MasterKeyEncAlgo, encryptedCek)); Assert.Matches(invalidUrlErrorMessage, ex.Message); ex = Assert.Throws( - () => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(dummyPathWithInvalidKey, MasterKeyEncAlgo, encryptedCek)); + () => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(dummyPathWithInvalidKey, MasterKeyEncAlgo, encryptedCek)); Assert.Matches(invalidTrustedEndpointErrorMessage, ex.Message); } @@ -207,11 +207,11 @@ public void InvalidCertificatePath() public void AkvStoreProviderVerifyFunctionWithInvalidSignature(bool fEnclaveEnabled) { //sign the cmk - byte[] cmkSignature = fixture.AkvStoreProvider.SignColumnMasterKeyMetadata(DataTestUtility.AKVUrl, allowEnclaveComputations: fEnclaveEnabled); + byte[] cmkSignature = _fixture.AkvStoreProvider.SignColumnMasterKeyMetadata(_fixture.AkvKeyUrl, allowEnclaveComputations: fEnclaveEnabled); Assert.True(cmkSignature != null); // Expect failed verification for a toggle of enclaveEnabled bit - Assert.False(fixture.AkvStoreProvider.VerifyColumnMasterKeyMetadata(DataTestUtility.AKVUrl, allowEnclaveComputations: !fEnclaveEnabled, signature: cmkSignature)); + Assert.False(_fixture.AkvStoreProvider.VerifyColumnMasterKeyMetadata(_fixture.AkvKeyUrl, allowEnclaveComputations: !fEnclaveEnabled, signature: cmkSignature)); // Prepare another cipherText buffer byte[] tamperedCmkSignature = new byte[cmkSignature.Length]; @@ -223,7 +223,7 @@ public void AkvStoreProviderVerifyFunctionWithInvalidSignature(bool fEnclaveEnab byte[] randomIndexInCipherText = new byte[1]; for (int i = 0; i < 10; i++) { - Assert.True(fixture.AkvStoreProvider.VerifyColumnMasterKeyMetadata(DataTestUtility.AKVUrl, allowEnclaveComputations: fEnclaveEnabled, signature: tamperedCmkSignature), @"tamperedCmkSignature before tampering should be verified without any problems."); + Assert.True(_fixture.AkvStoreProvider.VerifyColumnMasterKeyMetadata(_fixture.AkvKeyUrl, allowEnclaveComputations: fEnclaveEnabled, signature: tamperedCmkSignature), @"tamperedCmkSignature before tampering should be verified without any problems."); int startingByteIndex = 0; rng.GetBytes(randomIndexInCipherText); @@ -231,7 +231,7 @@ public void AkvStoreProviderVerifyFunctionWithInvalidSignature(bool fEnclaveEnab tamperedCmkSignature[startingByteIndex + randomIndexInCipherText[0]] = (byte)(cmkSignature[startingByteIndex + randomIndexInCipherText[0]] + 1); // Expect failed verification for invalid signature bytes - Assert.False(fixture.AkvStoreProvider.VerifyColumnMasterKeyMetadata(DataTestUtility.AKVUrl, allowEnclaveComputations: fEnclaveEnabled, signature: tamperedCmkSignature)); + Assert.False(_fixture.AkvStoreProvider.VerifyColumnMasterKeyMetadata(_fixture.AkvKeyUrl, allowEnclaveComputations: fEnclaveEnabled, signature: tamperedCmkSignature)); // Fix up the corrupted byte tamperedCmkSignature[startingByteIndex + randomIndexInCipherText[0]] = cmkSignature[startingByteIndex + randomIndexInCipherText[0]]; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs index 2bc38d9930..0bcff3bccb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs @@ -9,15 +9,15 @@ using System.Security.Cryptography.X509Certificates; using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.TestFixtures.Setup; +using Microsoft.Data.SqlClient.TestUtilities.Fixtures; namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { - public class SQLSetupStrategy : IDisposable + public class SQLSetupStrategy : ColumnMasterKeyCertificateFixture { internal const string ColumnEncryptionAlgorithmName = @"AEAD_AES_256_CBC_HMAC_SHA256"; - protected static X509Certificate2 certificate; - public string keyPath { get; internal set; } + public string ColumnMasterKeyPath { get; protected set; } public Table ApiTestTable { get; private set; } public Table BulkCopyAEErrorMessageTestTable { get; private set; } public Table BulkCopyAETestTable { get; private set; } @@ -59,15 +59,16 @@ public class SQLSetupStrategy : IDisposable public Dictionary sqlBulkTruncationTableNames = new Dictionary(); public SQLSetupStrategy() + : base() { - if (certificate == null) - { - certificate = CertificateUtility.CreateCertificate(); - } - keyPath = string.Concat(StoreLocation.CurrentUser.ToString(), "/", StoreName.My.ToString(), "/", certificate.Thumbprint); + ColumnMasterKeyPath = string.Concat(StoreLocation.CurrentUser.ToString(), "/", StoreName.My.ToString(), "/", ColumnMasterKeyCertificate.Thumbprint); } - protected SQLSetupStrategy(string customKeyPath) => keyPath = customKeyPath; + protected SQLSetupStrategy(string customKeyPath) + : base() + { + ColumnMasterKeyPath = customKeyPath; + } internal virtual void SetupDatabase() { @@ -259,13 +260,7 @@ protected List CreateTables(IList columnEncryptionKe protected string GenerateUniqueName(string baseName) => string.Concat("AE-", baseName, "-", Guid.NewGuid().ToString()); - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) + protected override void Dispose(bool disposing) { databaseObjects.Reverse(); foreach (string value in DataTestUtility.AEConnStringsSetup) @@ -276,6 +271,7 @@ protected virtual void Dispose(bool disposing) databaseObjects.ForEach(o => o.Drop(sqlConnection)); } } + base.Dispose(disposing); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs index 0c593ab8da..32d95fd69b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs @@ -4,6 +4,9 @@ using System; using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; +using Azure; +using Azure.Security.KeyVault.Keys; using Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider; using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; @@ -13,17 +16,25 @@ public class SQLSetupStrategyAzureKeyVault : SQLSetupStrategy { internal static bool IsAKVProviderRegistered = false; + private readonly List _akvKeyNames; + private readonly KeyClient _keyClient; + public Table AKVTestTable { get; private set; } public SqlColumnEncryptionAzureKeyVaultProvider AkvStoreProvider; public DummyMasterKeyForAKVProvider DummyMasterKey; + public string AkvKeyUrl { get; private set; } public SQLSetupStrategyAzureKeyVault() : base() { + _akvKeyNames = new List(); + _keyClient = new KeyClient(DataTestUtility.AKVBaseUri, DataTestUtility.GetTokenCredential()); AkvStoreProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new SqlClientCustomTokenCredential()); + if (!IsAKVProviderRegistered) { RegisterGlobalProviders(AkvStoreProvider); } + SetupAzureKeyVault(); SetupDatabase(); } @@ -42,10 +53,20 @@ public static void RegisterGlobalProviders(SqlColumnEncryptionAzureKeyVaultProvi IsAKVProviderRegistered = true; } + private void SetupAzureKeyVault() + { + JsonWebKey rsaImport = new JsonWebKey(ColumnMasterKeyCertificate.GetRSAPrivateKey(), true); + string akvKeyName = $"AE-{ColumnMasterKeyCertificate.Thumbprint}"; + + _keyClient.ImportKey(akvKeyName, rsaImport); + _akvKeyNames.Add(akvKeyName); + AkvKeyUrl = (new Uri(DataTestUtility.AKVBaseUri, $"/keys/{akvKeyName}")).AbsoluteUri; + } + internal override void SetupDatabase() { - ColumnMasterKey akvColumnMasterKey = new AkvColumnMasterKey(GenerateUniqueName("AKVCMK"), akvUrl: DataTestUtility.AKVUrl, AkvStoreProvider, DataTestUtility.EnclaveEnabled); - DummyMasterKey = new DummyMasterKeyForAKVProvider(GenerateUniqueName("DummyCMK"), DataTestUtility.AKVUrl, AkvStoreProvider, false); + ColumnMasterKey akvColumnMasterKey = new AkvColumnMasterKey(GenerateUniqueName("AKVCMK"), akvUrl: AkvKeyUrl, AkvStoreProvider, DataTestUtility.EnclaveEnabled); + DummyMasterKey = new DummyMasterKeyForAKVProvider(GenerateUniqueName("DummyCMK"), AkvKeyUrl, AkvStoreProvider, false); databaseObjects.Add(akvColumnMasterKey); databaseObjects.Add(DummyMasterKey); @@ -62,5 +83,22 @@ internal override void SetupDatabase() base.SetupDatabase(); } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + foreach (string keyName in _akvKeyNames) + { + try + { + _keyClient.StartDeleteKey(keyName); + } + catch (Exception) + { + continue; + } + } + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCertStoreProvider.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCertStoreProvider.cs index ae71dfc446..c368a17c98 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCertStoreProvider.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCertStoreProvider.cs @@ -20,12 +20,13 @@ public SQLSetupStrategyCertStoreProvider() : base() SetupDatabase(); } - protected SQLSetupStrategyCertStoreProvider(string customKeyPath) => keyPath = customKeyPath; + protected SQLSetupStrategyCertStoreProvider(string customKeyPath) : base(customKeyPath) + { } internal override void SetupDatabase() { - CspColumnMasterKey = new CspColumnMasterKey(GenerateUniqueName("CMK"), certificate.Thumbprint, CertStoreProvider, DataTestUtility.EnclaveEnabled); - DummyMasterKey = new DummyMasterKeyForCertStoreProvider(GenerateUniqueName("DummyCMK"), certificate.Thumbprint, CertStoreProvider, false); + CspColumnMasterKey = new CspColumnMasterKey(GenerateUniqueName("CMK"), ColumnMasterKeyCertificate.Thumbprint, CertStoreProvider, DataTestUtility.EnclaveEnabled); + DummyMasterKey = new DummyMasterKeyForCertStoreProvider(GenerateUniqueName("DummyCMK"), ColumnMasterKeyCertificate.Thumbprint, CertStoreProvider, false); databaseObjects.Add(CspColumnMasterKey); databaseObjects.Add(DummyMasterKey); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspExt.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspExt.cs index 3b965d5e7f..566717762d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspExt.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspExt.cs @@ -20,7 +20,7 @@ public SQLSetupStrategyCspExt(string cspKeyPath) : base(cspKeyPath) internal override void SetupDatabase() { - ColumnMasterKey columnMasterKey = new CspProviderColumnMasterKey(GenerateUniqueName("CspExt"), SqlColumnEncryptionCspProvider.ProviderName, keyPath); + ColumnMasterKey columnMasterKey = new CspProviderColumnMasterKey(GenerateUniqueName("CspExt"), SqlColumnEncryptionCspProvider.ProviderName, ColumnMasterKeyPath); databaseObjects.Add(columnMasterKey); List columnEncryptionKeys = CreateColumnEncryptionKeys(columnMasterKey, 2, keyStoreProvider); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs index ca6ac60243..faaeec1660 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs @@ -102,100 +102,6 @@ internal static void CleanSqlClientCache() ClearCache(cache); } - /// - /// Create a self-signed certificate. - /// - internal static X509Certificate2 CreateCertificate() - { - byte[] certificateRawBytes = new byte[] { 48, 130, 10, 36, 2, 1, 3, 48, 130, 9, 224, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 160, 130, 9, 209, 4, 130, 9, 205, 48, 130, 9, 201, 48, 130, 5, 250, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 160, 130, 5, 235, 4, 130, 5, 231, 48, 130, 5, 227, 48, 130, 5, 223, 6, 11, 42, 134, 72, 134, 247, 13, 1, 12, 10, 1, 2, 160, 130, 4, 254, 48, 130, 4, 250, 48, 28, 6, 10, 42, 134, 72, 134, 247, 13, 1, 12, 1, 3, 48, 14, 4, 8, 146, 126, 191, 6, 130, 18, 111, 71, 2, 2, 7, 208, 4, 130, 4, 216, 55, 138, 10, 135, 82, 84, 240, 82, 107, 75, 21, 156, 54, 53, 188, 62, 36, 248, 59, 17, 18, 41, 206, 171, 226, 168, 175, 59, 48, 50, 36, 26, 58, 39, 118, 231, 200, 107, 86, 144, 200, 20, 135, 22, 105, 159, 229, 116, 123, 122, 194, 69, 172, 171, 128, 251, 129, 222, 113, 27, 253, 48, 164, 116, 72, 194, 123, 12, 247, 186, 162, 40, 39, 114, 22, 118, 91, 192, 73, 122, 235, 247, 40, 89, 3, 222, 64, 214, 184, 67, 204, 188, 197, 188, 107, 126, 225, 194, 161, 110, 156, 45, 70, 26, 86, 69, 63, 120, 153, 164, 136, 15, 220, 153, 104, 50, 121, 87, 10, 180, 149, 98, 220, 73, 175, 50, 146, 231, 112, 230, 204, 132, 76, 43, 142, 7, 104, 142, 146, 92, 21, 52, 38, 59, 154, 108, 159, 192, 93, 174, 39, 134, 96, 189, 150, 77, 90, 160, 43, 127, 173, 199, 189, 4, 69, 44, 104, 148, 225, 44, 149, 167, 149, 121, 220, 232, 98, 131, 212, 130, 35, 79, 10, 173, 177, 150, 161, 91, 26, 12, 221, 136, 230, 124, 73, 96, 126, 12, 241, 99, 60, 140, 126, 140, 0, 166, 47, 16, 87, 102, 138, 45, 97, 21, 31, 224, 126, 231, 102, 99, 35, 207, 75, 22, 249, 115, 51, 106, 79, 208, 21, 108, 124, 143, 108, 130, 6, 61, 215, 227, 7, 224, 174, 193, 97, 211, 241, 224, 90, 37, 101, 147, 149, 173, 239, 113, 214, 1, 41, 69, 158, 203, 3, 63, 101, 196, 134, 7, 127, 58, 113, 243, 228, 162, 99, 75, 207, 153, 19, 193, 187, 52, 124, 85, 234, 7, 249, 75, 65, 230, 107, 247, 145, 64, 94, 106, 50, 117, 83, 138, 49, 10, 22, 211, 115, 183, 20, 119, 18, 117, 166, 153, 30, 210, 248, 118, 200, 21, 180, 118, 208, 53, 90, 243, 74, 76, 109, 106, 46, 103, 112, 197, 89, 92, 178, 83, 48, 97, 162, 73, 78, 105, 145, 213, 230, 17, 211, 121, 200, 101, 179, 158, 85, 99, 211, 68, 122, 234, 176, 4, 33, 225, 120, 139, 163, 110, 35, 199, 23, 45, 126, 199, 80, 145, 14, 74, 217, 200, 172, 216, 159, 237, 241, 157, 85, 210, 141, 180, 150, 187, 82, 48, 245, 154, 125, 60, 223, 244, 21, 20, 39, 88, 8, 153, 185, 227, 76, 78, 137, 99, 98, 81, 141, 27, 197, 41, 39, 251, 80, 27, 85, 78, 65, 15, 216, 106, 106, 113, 33, 253, 210, 46, 214, 47, 49, 89, 170, 215, 207, 62, 182, 88, 25, 186, 166, 214, 172, 63, 94, 17, 123, 235, 226, 72, 73, 204, 18, 173, 134, 92, 66, 2, 213, 151, 251, 95, 175, 38, 56, 156, 138, 96, 123, 190, 107, 59, 230, 24, 210, 224, 206, 169, 159, 95, 180, 237, 34, 194, 62, 4, 213, 228, 85, 216, 138, 157, 50, 20, 101, 160, 195, 138, 207, 18, 17, 232, 6, 73, 82, 247, 173, 50, 180, 53, 58, 156, 97, 230, 112, 211, 251, 204, 120, 188, 34, 41, 67, 83, 197, 131, 251, 176, 20, 70, 169, 116, 237, 43, 117, 45, 31, 66, 74, 152, 216, 3, 108, 102, 99, 5, 127, 76, 129, 57, 180, 90, 218, 157, 108, 85, 4, 240, 101, 149, 154, 221, 208, 70, 152, 34, 128, 57, 135, 38, 17, 139, 142, 167, 109, 73, 129, 181, 105, 45, 151, 106, 171, 166, 0, 113, 147, 141, 19, 228, 196, 88, 175, 219, 18, 213, 54, 105, 179, 8, 249, 250, 164, 86, 28, 185, 19, 60, 50, 140, 73, 237, 148, 201, 33, 204, 189, 43, 83, 163, 138, 1, 10, 13, 240, 196, 211, 221, 169, 207, 100, 167, 203, 146, 115, 70, 118, 230, 4, 224, 192, 209, 242, 144, 150, 72, 170, 149, 255, 196, 7, 91, 55, 251, 57, 127, 103, 98, 113, 83, 224, 97, 118, 132, 81, 119, 8, 105, 250, 155, 107, 149, 28, 127, 66, 127, 224, 79, 96, 9, 168, 73, 84, 228, 123, 161, 222, 179, 115, 73, 184, 62, 24, 228, 44, 156, 42, 124, 209, 29, 81, 19, 169, 24, 212, 6, 238, 239, 221, 68, 220, 106, 0, 45, 201, 129, 3, 50, 150, 244, 32, 220, 237, 20, 39, 175, 249, 80, 189, 166, 68, 251, 102, 60, 137, 93, 209, 86, 194, 55, 164, 100, 76, 220, 249, 30, 233, 101, 177, 150, 71, 28, 227, 180, 44, 115, 83, 201, 129, 44, 128, 247, 68, 175, 97, 36, 170, 76, 236, 57, 119, 240, 0, 129, 185, 35, 160, 231, 183, 56, 162, 197, 237, 186, 109, 118, 232, 84, 108, 125, 93, 92, 101, 193, 180, 210, 192, 244, 47, 55, 56, 217, 178, 200, 168, 232, 80, 223, 209, 255, 234, 146, 46, 215, 170, 197, 94, 84, 213, 233, 140, 247, 69, 185, 103, 183, 91, 23, 232, 32, 246, 244, 30, 41, 156, 28, 72, 109, 90, 127, 135, 132, 19, 136, 233, 168, 29, 98, 17, 111, 5, 185, 234, 86, 234, 114, 47, 227, 81, 77, 108, 179, 184, 91, 31, 74, 23, 29, 248, 41, 207, 8, 23, 181, 33, 99, 217, 48, 145, 97, 126, 139, 133, 11, 100, 69, 151, 146, 38, 79, 231, 155, 92, 134, 139, 189, 237, 132, 196, 95, 45, 141, 15, 26, 37, 58, 219, 10, 0, 36, 221, 240, 82, 117, 163, 121, 141, 206, 21, 180, 195, 58, 109, 56, 123, 152, 206, 116, 161, 221, 125, 248, 23, 31, 240, 227, 186, 52, 171, 147, 51, 39, 203, 92, 205, 182, 146, 149, 111, 27, 59, 219, 234, 216, 52, 89, 22, 224, 76, 62, 94, 76, 131, 48, 162, 134, 161, 177, 44, 205, 101, 253, 13, 237, 40, 29, 72, 224, 121, 74, 189, 57, 81, 58, 169, 178, 173, 157, 182, 143, 205, 64, 225, 137, 188, 235, 43, 195, 3, 187, 105, 113, 72, 82, 153, 58, 97, 38, 251, 212, 149, 191, 11, 153, 157, 106, 16, 236, 237, 209, 210, 208, 19, 68, 92, 176, 65, 24, 115, 181, 94, 24, 126, 2, 216, 63, 200, 136, 178, 92, 248, 11, 128, 68, 122, 14, 46, 234, 48, 142, 219, 92, 29, 136, 70, 200, 52, 78, 70, 160, 215, 113, 102, 190, 66, 16, 69, 120, 25, 201, 23, 209, 41, 79, 25, 151, 38, 38, 82, 244, 143, 121, 216, 111, 91, 167, 232, 32, 234, 243, 195, 168, 240, 135, 188, 1, 92, 145, 77, 240, 107, 20, 82, 147, 168, 132, 78, 115, 206, 95, 47, 8, 80, 91, 255, 28, 38, 161, 52, 168, 211, 236, 143, 238, 146, 172, 104, 2, 254, 240, 229, 210, 225, 47, 41, 76, 134, 5, 20, 203, 188, 48, 195, 120, 103, 234, 94, 217, 142, 238, 254, 131, 146, 214, 106, 212, 229, 201, 79, 151, 198, 100, 132, 99, 228, 82, 182, 94, 216, 226, 163, 42, 113, 110, 201, 70, 221, 127, 242, 7, 176, 60, 121, 158, 37, 56, 6, 156, 191, 75, 94, 222, 10, 155, 39, 64, 172, 216, 106, 210, 202, 246, 66, 83, 107, 250, 17, 134, 222, 212, 71, 200, 215, 103, 35, 82, 225, 106, 17, 106, 74, 18, 130, 236, 175, 45, 145, 155, 169, 88, 72, 244, 3, 38, 245, 208, 49, 129, 205, 48, 19, 6, 9, 42, 134, 72, 134, 247, 13, 1, 9, 21, 49, 6, 4, 4, 1, 0, 0, 0, 48, 87, 6, 9, 42, 134, 72, 134, 247, 13, 1, 9, 20, 49, 74, 30, 72, 0, 100, 0, 99, 0, 99, 0, 52, 0, 51, 0, 48, 0, 56, 0, 56, 0, 45, 0, 50, 0, 57, 0, 54, 0, 53, 0, 45, 0, 52, 0, 57, 0, 97, 0, 48, 0, 45, 0, 56, 0, 51, 0, 54, 0, 53, 0, 45, 0, 50, 0, 52, 0, 101, 0, 52, 0, 97, 0, 52, 0, 49, 0, 100, 0, 55, 0, 50, 0, 52, 0, 48, 48, 93, 6, 9, 43, 6, 1, 4, 1, 130, 55, 17, 1, 49, 80, 30, 78, 0, 77, 0, 105, 0, 99, 0, 114, 0, 111, 0, 115, 0, 111, 0, 102, 0, 116, 0, 32, 0, 83, 0, 116, 0, 114, 0, 111, 0, 110, 0, 103, 0, 32, 0, 67, 0, 114, 0, 121, 0, 112, 0, 116, 0, 111, 0, 103, 0, 114, 0, 97, 0, 112, 0, 104, 0, 105, 0, 99, 0, 32, 0, 80, 0, 114, 0, 111, 0, 118, 0, 105, 0, 100, 0, 101, 0, 114, 48, 130, 3, 199, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 6, 160, 130, 3, 184, 48, 130, 3, 180, 2, 1, 0, 48, 130, 3, 173, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 48, 28, 6, 10, 42, 134, 72, 134, 247, 13, 1, 12, 1, 3, 48, 14, 4, 8, 206, 244, 28, 93, 203, 68, 165, 233, 2, 2, 7, 208, 128, 130, 3, 128, 74, 136, 80, 43, 195, 182, 181, 122, 132, 229, 10, 181, 229, 1, 78, 122, 145, 95, 16, 236, 242, 107, 9, 141, 186, 205, 32, 139, 154, 132, 184, 180, 80, 26, 3, 85, 196, 10, 33, 216, 101, 105, 172, 196, 77, 222, 232, 229, 37, 199, 6, 189, 152, 8, 203, 15, 231, 164, 140, 163, 120, 23, 137, 34, 16, 241, 186, 64, 11, 241, 210, 160, 186, 90, 55, 39, 21, 210, 145, 74, 151, 40, 122, 221, 240, 191, 185, 115, 85, 208, 125, 136, 51, 210, 137, 124, 155, 65, 135, 50, 35, 233, 223, 157, 131, 108, 11, 142, 152, 217, 162, 163, 218, 47, 89, 255, 229, 21, 224, 139, 187, 4, 175, 251, 248, 8, 18, 16, 112, 134, 75, 17, 90, 246, 62, 150, 31, 207, 95, 172, 5, 220, 135, 201, 179, 247, 193, 177, 23, 5, 170, 207, 66, 219, 145, 117, 99, 167, 238, 100, 158, 169, 44, 22, 199, 132, 38, 67, 203, 66, 187, 53, 216, 98, 113, 76, 142, 153, 36, 238, 110, 152, 251, 68, 6, 154, 255, 51, 65, 75, 91, 9, 121, 86, 116, 35, 224, 47, 220, 194, 17, 136, 175, 76, 165, 210, 153, 89, 104, 197, 133, 200, 49, 173, 1, 167, 5, 88, 183, 58, 193, 146, 30, 60, 129, 195, 3, 16, 78, 87, 167, 135, 182, 182, 150, 68, 116, 161, 116, 125, 180, 155, 103, 63, 0, 98, 27, 179, 142, 64, 73, 31, 35, 63, 138, 137, 30, 169, 149, 221, 104, 21, 182, 23, 67, 246, 2, 162, 217, 165, 238, 124, 229, 149, 84, 5, 203, 174, 149, 79, 153, 25, 153, 233, 213, 86, 250, 10, 42, 6, 226, 113, 123, 90, 76, 153, 39, 203, 237, 124, 36, 191, 232, 132, 127, 82, 163, 109, 100, 121, 54, 254, 116, 155, 26, 255, 50, 150, 140, 172, 240, 208, 245, 65, 72, 49, 183, 149, 220, 244, 120, 193, 37, 222, 144, 137, 82, 168, 233, 13, 179, 2, 217, 29, 177, 4, 136, 69, 192, 133, 249, 180, 9, 62, 162, 216, 251, 164, 188, 173, 143, 149, 32, 204, 255, 246, 249, 33, 216, 75, 23, 127, 215, 134, 69, 79, 112, 213, 198, 89, 44, 51, 19, 226, 16, 210, 125, 212, 232, 18, 252, 178, 93, 245, 33, 62, 81, 207, 78, 167, 144, 238, 251, 27, 194, 21, 53, 44, 63, 58, 26, 176, 75, 79, 164, 67, 59, 80, 17, 54, 209, 58, 184, 2, 36, 202, 135, 91, 35, 78, 55, 203, 134, 238, 79, 178, 84, 242, 46, 223, 131, 227, 87, 255, 182, 244, 117, 162, 60, 134, 161, 49, 59, 95, 64, 190, 30, 195, 100, 106, 7, 120, 181, 202, 122, 174, 234, 30, 11, 88, 65, 238, 53, 64, 243, 233, 185, 168, 34, 8, 58, 233, 171, 210, 104, 105, 93, 49, 206, 11, 40, 172, 248, 204, 80, 128, 53, 143, 54, 95, 92, 70, 152, 209, 193, 116, 252, 138, 19, 50, 249, 43, 14, 225, 167, 8, 205, 112, 103, 79, 223, 14, 141, 147, 70, 197, 91, 11, 117, 202, 19, 180, 240, 21, 118, 108, 25, 63, 54, 94, 156, 112, 109, 16, 216, 113, 192, 246, 207, 156, 203, 65, 75, 143, 157, 125, 158, 151, 167, 207, 96, 6, 162, 97, 66, 114, 95, 227, 52, 44, 98, 121, 139, 181, 240, 89, 27, 59, 156, 189, 93, 28, 48, 165, 11, 245, 102, 198, 29, 5, 6, 180, 147, 58, 130, 65, 201, 10, 164, 193, 93, 168, 96, 156, 89, 225, 139, 70, 245, 74, 128, 3, 141, 133, 137, 21, 163, 77, 3, 19, 226, 35, 248, 156, 56, 56, 37, 221, 69, 67, 214, 3, 152, 149, 224, 92, 72, 173, 39, 196, 229, 153, 67, 151, 190, 115, 20, 70, 126, 210, 140, 109, 186, 46, 82, 88, 185, 96, 1, 254, 161, 217, 130, 226, 133, 18, 103, 175, 132, 249, 102, 51, 229, 192, 94, 44, 10, 25, 197, 237, 77, 196, 1, 253, 153, 78, 237, 151, 136, 89, 203, 113, 244, 217, 235, 252, 31, 116, 139, 233, 40, 197, 22, 176, 157, 130, 109, 149, 215, 11, 20, 3, 156, 239, 29, 250, 95, 188, 241, 184, 117, 108, 216, 74, 91, 169, 186, 122, 175, 214, 36, 62, 240, 142, 107, 172, 7, 250, 31, 101, 75, 83, 255, 56, 8, 231, 200, 194, 154, 105, 202, 170, 207, 252, 128, 10, 249, 53, 41, 168, 94, 225, 163, 10, 251, 149, 64, 10, 144, 252, 44, 136, 149, 119, 183, 7, 230, 87, 160, 46, 62, 185, 82, 218, 213, 125, 62, 70, 43, 27, 5, 181, 50, 193, 11, 30, 0, 8, 81, 94, 169, 171, 143, 113, 235, 171, 38, 129, 116, 11, 191, 75, 235, 185, 184, 178, 36, 193, 174, 177, 51, 87, 163, 142, 52, 62, 161, 237, 139, 50, 51, 227, 188, 164, 106, 233, 209, 8, 237, 241, 92, 145, 51, 6, 36, 197, 24, 255, 143, 5, 144, 43, 87, 242, 208, 251, 79, 171, 90, 103, 219, 73, 242, 95, 36, 48, 95, 127, 40, 128, 201, 80, 79, 74, 226, 25, 43, 50, 56, 180, 59, 84, 148, 110, 151, 9, 45, 4, 212, 172, 31, 189, 44, 115, 59, 169, 48, 59, 48, 31, 48, 7, 6, 5, 43, 14, 3, 2, 26, 4, 20, 238, 91, 24, 104, 64, 45, 237, 63, 114, 36, 111, 106, 82, 43, 251, 110, 60, 159, 42, 178, 4, 20, 20, 49, 70, 55, 115, 247, 221, 156, 47, 189, 197, 19, 116, 77, 161, 163, 216, 77, 166, 144, 2, 2, 7, 208 }; - X509Certificate2 certificate = -#if NET9_0 - X509CertificateLoader.LoadPkcs12(certificateRawBytes, "", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.Exportable); -#else - new(certificateRawBytes, "", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.Exportable); -#endif - X509Store certStore = null; - try - { - certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); - certStore.Open(OpenFlags.ReadWrite); - if (!certStore.Certificates.Contains(certificate)) - { - certStore.Add(certificate); - } - - } - finally - { - if (certStore != null) - { - certStore.Close(); - } - } - - if (DataTestUtility.IsAKVSetupAvailable()) - { - SetupAKVKeysAsync().Wait(); - } - - return certificate; - } - - private static async Task SetupAKVKeysAsync() - { - KeyClient keyClient = new KeyClient(DataTestUtility.AKVBaseUri, DataTestUtility.GetTokenCredential()); - AsyncPageable keys = keyClient.GetPropertiesOfKeysAsync(); - IAsyncEnumerator enumerator = keys.GetAsyncEnumerator(); - - bool testAKVKeyExists = false; - try - { - while (await enumerator.MoveNextAsync()) - { - KeyProperties keyProperties = enumerator.Current; - if (keyProperties.Name.Equals(DataTestUtility.AKVKeyName)) - { - testAKVKeyExists = true; - } - } - } - finally - { - await enumerator.DisposeAsync(); - } - - if (!testAKVKeyExists) - { - var rsaKeyOptions = new CreateRsaKeyOptions(DataTestUtility.AKVKeyName, hardwareProtected: false) - { - KeySize = 2048, - ExpiresOn = DateTimeOffset.Now.AddYears(1) - }; - keyClient.CreateRsaKey(rsaKeyOptions); - } - } - - /// - /// Removes a certificate from the local certificate store (useful for test cleanup). - /// - internal static void RemoveCertificate(X509Certificate2 certificate) - { - X509Store certStore = null; - try - { - certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); - certStore.Open(OpenFlags.ReadWrite); - certStore.Remove(certificate); - } - finally - { - if (certStore != null) - { - certStore.Close(); - } - } - } - internal static byte[] DecryptRsaDirectly(byte[] rsaPfx, byte[] ciphertextCek, string masterKeyPath) { Debug.Assert(rsaPfx != null && rsaPfx.Length > 0); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestTrustedMasterKeyPaths.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestTrustedMasterKeyPaths.cs index c617a990b8..4f9f57ff6a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestTrustedMasterKeyPaths.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestTrustedMasterKeyPaths.cs @@ -7,14 +7,14 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { public class TestTrustedMasterKeyPaths : IClassFixture { - private SQLSetupStrategyCertStoreProvider fixture; + private readonly string dummyThumbprint; private readonly string tableName; private readonly string columnMasterKeyPath; public TestTrustedMasterKeyPaths(SQLSetupStrategyCertStoreProvider fixture) { - columnMasterKeyPath = string.Format(@"{0}/{1}/{2}", StoreLocation.CurrentUser.ToString(), @"my", CertificateUtility.CreateCertificate().Thumbprint); - this.fixture = fixture; + dummyThumbprint = new string('F', fixture.ColumnMasterKeyCertificate.Thumbprint.Length); + columnMasterKeyPath = fixture.ColumnMasterKeyPath; tableName = fixture.TrustedMasterKeyPathsTestTable.Name; } @@ -152,8 +152,7 @@ public void TestTrustedColumnEncryptionMasterKeyPathsWithMultipleServers(string // Add some random key paths foreach (char c in new char[] { 'A', 'B' }) { - string tempThumbprint = new string('F', CertificateUtility.CreateCertificate().Thumbprint.Length); - string invalidKeyPath = string.Format(@"{0}/my/{1}", StoreLocation.CurrentUser.ToString(), tempThumbprint); + string invalidKeyPath = string.Format(@"{0}/my/{1}", StoreLocation.CurrentUser.ToString(), dummyThumbprint); server1TrustedKeyPaths.Add(invalidKeyPath); } @@ -277,8 +276,7 @@ FROM [{tableName}] // Prepare dictionary with invalid key path List invalidKeyPathList = new List(); - string tempThumbprint = new string('F', CertificateUtility.CreateCertificate().Thumbprint.Length); - string invalidKeyPath = string.Format(@"{0}/my/{1}", StoreLocation.CurrentUser.ToString(), tempThumbprint); + string invalidKeyPath = string.Format(@"{0}/my/{1}", StoreLocation.CurrentUser.ToString(), dummyThumbprint); invalidKeyPathList.Add(invalidKeyPath); SqlConnection.ColumnEncryptionTrustedMasterKeyPaths.Add(connBuilder.DataSource, invalidKeyPathList); diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs index d512ae113a..81e301cccf 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs @@ -99,7 +99,7 @@ protected static X509Certificate2 CreateCertificate(string subjectName, IEnumera try {{ - $x509 = New-SelfSignedCertificate -Subject $subject -TextExtension $sAN -KeyLength 2048 -KeyAlgorithm RSA ` + $x509 = PKI\New-SelfSignedCertificate -Subject $subject -TextExtension $sAN -KeyLength 2048 -KeyAlgorithm RSA ` -CertStoreLocation ""Cert:\CurrentUser\My"" -NotBefore $notBefore -NotAfter $notAfter ` -KeyExportPolicy Exportable -HashAlgorithm SHA256 @@ -198,7 +198,13 @@ protected void AddToStore(X509Certificate2 cert, StoreLocation storeLocation, St storeContext.Certificates.Add(cert); } - public virtual void Dispose() + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) { foreach (CertificateStoreContext storeContext in _certificateStoreModifications) { diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/ColumnMasterKeyCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/ColumnMasterKeyCertificateFixture.cs new file mode 100644 index 0000000000..3409c489fe --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/ColumnMasterKeyCertificateFixture.cs @@ -0,0 +1,21 @@ +// 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.X509Certificates; + +namespace Microsoft.Data.SqlClient.TestUtilities.Fixtures +{ + public class ColumnMasterKeyCertificateFixture : CertificateFixtureBase + { + public X509Certificate2 ColumnMasterKeyCertificate { get; } + + public ColumnMasterKeyCertificateFixture() + { + ColumnMasterKeyCertificate = CreateCertificate(nameof(ColumnMasterKeyCertificate), Array.Empty(), Array.Empty()); + + AddToStore(ColumnMasterKeyCertificate, StoreLocation.CurrentUser, StoreName.My); + } + } +} From 3d3230d1edbffd0c90e1efc55cfc0cb8bba948c7 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 28 Dec 2024 18:49:54 +0000 Subject: [PATCH 02/16] Hotfix for Azure Key Vault tests --- .../tests/ManualTests/AlwaysEncrypted/AKVTests.cs | 6 +++--- .../Fixtures/CertificateFixtureBase.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs index 78fa0a9001..7c9a5094d5 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs @@ -29,7 +29,7 @@ public AKVTest(SQLSetupStrategyAzureKeyVault fixture) [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE), nameof(DataTestUtility.IsAKVSetupAvailable))] public void TestEncryptDecryptWithAKV() { - SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionStringHGSVBS) + SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString) { ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Enabled, AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified, @@ -70,7 +70,7 @@ It aims to confirm that three consecutive connections will consistently fail wit [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE), nameof(DataTestUtility.IsAKVSetupAvailable))] public void ForcedColumnDecryptErrorTestShouldFail() { - SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionStringHGSVBS) + SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString) { ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Enabled, AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified, @@ -148,7 +148,7 @@ public void TestRoundTripWithAKVAndCertStoreProvider() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE), nameof(DataTestUtility.IsAKVSetupAvailable))] public void TestLocalCekCacheIsScopedToProvider() { - SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionStringHGSVBS) + SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString) { ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Enabled, AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified, diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs index 81e301cccf..dc78dd4f99 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs @@ -101,7 +101,7 @@ protected static X509Certificate2 CreateCertificate(string subjectName, IEnumera {{ $x509 = PKI\New-SelfSignedCertificate -Subject $subject -TextExtension $sAN -KeyLength 2048 -KeyAlgorithm RSA ` -CertStoreLocation ""Cert:\CurrentUser\My"" -NotBefore $notBefore -NotAfter $notAfter ` - -KeyExportPolicy Exportable -HashAlgorithm SHA256 + -KeyExportPolicy Exportable -HashAlgorithm SHA256 -Provider ""Microsoft Enhanced RSA and AES Cryptographic Provider"" -KeySpec KeyExchange if ($x509 -eq $null) {{ throw ""Certificate was null!"" }} @@ -166,7 +166,7 @@ exit 1 // Process completed successfully if it had an exit code of zero, the command output will be the base64-encoded certificate if (psProcess.ExitCode == 0) { - return new X509Certificate2(Convert.FromBase64String(commandOutput), password); + return new X509Certificate2(Convert.FromBase64String(commandOutput), password, X509KeyStorageFlags.Exportable); } else { From 41f5002641f4ad286290e799a610fd5136655544 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 28 Dec 2024 20:49:42 +0000 Subject: [PATCH 03/16] Removed hardcoded references to Azure Key Vault key --- .../AlwaysEncrypted/AKVUnitTests.cs | 77 ++++++++++--------- .../EnclaveAzureDatabaseTests.cs | 6 +- .../TestFixtures/AzureKeyVaultKeyFixture.cs | 19 +++++ .../ManualTests/DataCommon/DataTestUtility.cs | 7 +- .../SqlClientCustomTokenCredential.cs | 9 ++- ....Data.SqlClient.ManualTesting.Tests.csproj | 1 + .../Fixtures/AzureKeyVaultKeyFixtureBase.cs | 63 +++++++++++++++ ...rosoft.Data.SqlClient.TestUtilities.csproj | 2 + 8 files changed, 137 insertions(+), 47 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AzureKeyVaultKeyFixture.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/AzureKeyVaultKeyFixtureBase.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVUnitTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVUnitTests.cs index 9374b3783c..2111c7a1af 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVUnitTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVUnitTests.cs @@ -14,13 +14,20 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { - public static class AKVUnitTests + public class AKVUnitTests : IClassFixture { const string EncryptionAlgorithm = "RSA_OAEP"; public static readonly byte[] s_columnEncryptionKey = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 }; private const string cekCacheName = "_columnEncryptionKeyCache"; private const string signatureVerificationResultCacheName = "_columnMasterKeyMetadataSignatureVerificationCache"; + private readonly AzureKeyVaultKeyFixture _fixture; + + public AKVUnitTests(AzureKeyVaultKeyFixture fixture) + { + _fixture = fixture; + } + private static void ValidateAKVTraces(List eventData, Guid threadActivityId) { Assert.NotNull(eventData); @@ -64,36 +71,36 @@ private static void ValidateAKVTraces(List eventData, Gui } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] - public static void LegacyAuthenticationCallbackTest() + public void LegacyAuthenticationCallbackTest() { Guid activityId = Trace.CorrelationManager.ActivityId = Guid.NewGuid(); using DataTestUtility.AKVEventListener AKVListener = new(); // SqlClientCustomTokenCredential implements legacy authentication callback to request access token at client-side. SqlColumnEncryptionAzureKeyVaultProvider akvProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new SqlClientCustomTokenCredential()); - byte[] encryptedCek = akvProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, s_columnEncryptionKey); - byte[] decryptedCek = akvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, encryptedCek); + byte[] encryptedCek = akvProvider.EncryptColumnEncryptionKey(_fixture.GeneratedKeyUri, EncryptionAlgorithm, s_columnEncryptionKey); + byte[] decryptedCek = akvProvider.DecryptColumnEncryptionKey(_fixture.GeneratedKeyUri, EncryptionAlgorithm, encryptedCek); Assert.Equal(s_columnEncryptionKey, decryptedCek); ValidateAKVTraces(AKVListener.EventData, activityId); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] - public static void TokenCredentialTest() + public void TokenCredentialTest() { Guid activityId = Trace.CorrelationManager.ActivityId = Guid.NewGuid(); using DataTestUtility.AKVEventListener AKVListener = new(); SqlColumnEncryptionAzureKeyVaultProvider akvProvider = new SqlColumnEncryptionAzureKeyVaultProvider(DataTestUtility.GetTokenCredential()); - byte[] encryptedCek = akvProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, s_columnEncryptionKey); - byte[] decryptedCek = akvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, encryptedCek); + byte[] encryptedCek = akvProvider.EncryptColumnEncryptionKey(_fixture.GeneratedKeyUri, EncryptionAlgorithm, s_columnEncryptionKey); + byte[] decryptedCek = akvProvider.DecryptColumnEncryptionKey(_fixture.GeneratedKeyUri, EncryptionAlgorithm, encryptedCek); Assert.Equal(s_columnEncryptionKey, decryptedCek); ValidateAKVTraces(AKVListener.EventData, activityId); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] - public static void TokenCredentialRotationTest() + public void TokenCredentialRotationTest() { Guid activityId = Trace.CorrelationManager.ActivityId = Guid.NewGuid(); using DataTestUtility.AKVEventListener AKVListener = new(); @@ -103,19 +110,19 @@ public static void TokenCredentialRotationTest() SqlColumnEncryptionAzureKeyVaultProvider newAkvProvider = new SqlColumnEncryptionAzureKeyVaultProvider(DataTestUtility.GetTokenCredential()); - byte[] encryptedCekWithNewProvider = newAkvProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, s_columnEncryptionKey); - byte[] decryptedCekWithOldProvider = oldAkvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, encryptedCekWithNewProvider); + byte[] encryptedCekWithNewProvider = newAkvProvider.EncryptColumnEncryptionKey(_fixture.GeneratedKeyUri, EncryptionAlgorithm, s_columnEncryptionKey); + byte[] decryptedCekWithOldProvider = oldAkvProvider.DecryptColumnEncryptionKey(_fixture.GeneratedKeyUri, EncryptionAlgorithm, encryptedCekWithNewProvider); Assert.Equal(s_columnEncryptionKey, decryptedCekWithOldProvider); - byte[] encryptedCekWithOldProvider = oldAkvProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, s_columnEncryptionKey); - byte[] decryptedCekWithNewProvider = newAkvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, encryptedCekWithOldProvider); + byte[] encryptedCekWithOldProvider = oldAkvProvider.EncryptColumnEncryptionKey(_fixture.GeneratedKeyUri, EncryptionAlgorithm, s_columnEncryptionKey); + byte[] decryptedCekWithNewProvider = newAkvProvider.DecryptColumnEncryptionKey(_fixture.GeneratedKeyUri, EncryptionAlgorithm, encryptedCekWithOldProvider); Assert.Equal(s_columnEncryptionKey, decryptedCekWithNewProvider); ValidateAKVTraces(AKVListener.EventData, activityId); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] - public static void ReturnSpecifiedVersionOfKeyWhenItIsNotTheMostRecentVersion() + public void ReturnSpecifiedVersionOfKeyWhenItIsNotTheMostRecentVersion() { Uri keyPathUri = new Uri(DataTestUtility.AKVOriginalUrl); Uri vaultUri = new Uri(keyPathUri.GetLeftPart(UriPartial.Authority)); @@ -161,7 +168,7 @@ public static void ThrowWhenUrlHasLessThanThreeSegments() } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] - public static void DecryptedCekIsCachedDuringDecryption() + public void DecryptedCekIsCachedDuringDecryption() { Guid activityId = Trace.CorrelationManager.ActivityId = Guid.NewGuid(); using DataTestUtility.AKVEventListener AKVListener = new(); @@ -170,23 +177,23 @@ public static void DecryptedCekIsCachedDuringDecryption() byte[] plaintextKey1 = { 1, 2, 3 }; byte[] plaintextKey2 = { 1, 2, 3 }; byte[] plaintextKey3 = { 0, 1, 2, 3 }; - byte[] encryptedKey1 = akvProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", plaintextKey1); - byte[] encryptedKey2 = akvProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", plaintextKey2); - byte[] encryptedKey3 = akvProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", plaintextKey3); + byte[] encryptedKey1 = akvProvider.EncryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", plaintextKey1); + byte[] encryptedKey2 = akvProvider.EncryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", plaintextKey2); + byte[] encryptedKey3 = akvProvider.EncryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", plaintextKey3); - byte[] decryptedKey1 = akvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", encryptedKey1); + byte[] decryptedKey1 = akvProvider.DecryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", encryptedKey1); Assert.Equal(1, GetCacheCount(cekCacheName, akvProvider)); Assert.Equal(plaintextKey1, decryptedKey1); - decryptedKey1 = akvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", encryptedKey1); + decryptedKey1 = akvProvider.DecryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", encryptedKey1); Assert.Equal(1, GetCacheCount(cekCacheName, akvProvider)); Assert.Equal(plaintextKey1, decryptedKey1); - byte[] decryptedKey2 = akvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", encryptedKey2); + byte[] decryptedKey2 = akvProvider.DecryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", encryptedKey2); Assert.Equal(2, GetCacheCount(cekCacheName, akvProvider)); Assert.Equal(plaintextKey2, decryptedKey2); - byte[] decryptedKey3 = akvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", encryptedKey3); + byte[] decryptedKey3 = akvProvider.DecryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", encryptedKey3); Assert.Equal(3, GetCacheCount(cekCacheName, akvProvider)); Assert.Equal(plaintextKey3, decryptedKey3); @@ -194,33 +201,33 @@ public static void DecryptedCekIsCachedDuringDecryption() } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] - public static void SignatureVerificationResultIsCachedDuringVerification() + public void SignatureVerificationResultIsCachedDuringVerification() { Guid activityId = Trace.CorrelationManager.ActivityId = Guid.NewGuid(); using DataTestUtility.AKVEventListener AKVListener = new(); SqlColumnEncryptionAzureKeyVaultProvider akvProvider = new(new SqlClientCustomTokenCredential()); - byte[] signature = akvProvider.SignColumnMasterKeyMetadata(DataTestUtility.AKVUrl, true); - byte[] signature2 = akvProvider.SignColumnMasterKeyMetadata(DataTestUtility.AKVUrl, true); - byte[] signatureWithoutEnclave = akvProvider.SignColumnMasterKeyMetadata(DataTestUtility.AKVUrl, false); + byte[] signature = akvProvider.SignColumnMasterKeyMetadata(_fixture.GeneratedKeyUri, true); + byte[] signature2 = akvProvider.SignColumnMasterKeyMetadata(_fixture.GeneratedKeyUri, true); + byte[] signatureWithoutEnclave = akvProvider.SignColumnMasterKeyMetadata(_fixture.GeneratedKeyUri, false); - Assert.True(akvProvider.VerifyColumnMasterKeyMetadata(DataTestUtility.AKVUrl, true, signature)); + Assert.True(akvProvider.VerifyColumnMasterKeyMetadata(_fixture.GeneratedKeyUri, true, signature)); Assert.Equal(1, GetCacheCount(signatureVerificationResultCacheName, akvProvider)); - Assert.True(akvProvider.VerifyColumnMasterKeyMetadata(DataTestUtility.AKVUrl, true, signature)); + Assert.True(akvProvider.VerifyColumnMasterKeyMetadata(_fixture.GeneratedKeyUri, true, signature)); Assert.Equal(1, GetCacheCount(signatureVerificationResultCacheName, akvProvider)); - Assert.True(akvProvider.VerifyColumnMasterKeyMetadata(DataTestUtility.AKVUrl, true, signature2)); + Assert.True(akvProvider.VerifyColumnMasterKeyMetadata(_fixture.GeneratedKeyUri, true, signature2)); Assert.Equal(1, GetCacheCount(signatureVerificationResultCacheName, akvProvider)); - Assert.True(akvProvider.VerifyColumnMasterKeyMetadata(DataTestUtility.AKVUrl, false, signatureWithoutEnclave)); + Assert.True(akvProvider.VerifyColumnMasterKeyMetadata(_fixture.GeneratedKeyUri, false, signatureWithoutEnclave)); Assert.Equal(2, GetCacheCount(signatureVerificationResultCacheName, akvProvider)); ValidateAKVTraces(AKVListener.EventData, activityId); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] - public static void CekCacheEntryIsEvictedAfterTtlExpires() + public void CekCacheEntryIsEvictedAfterTtlExpires() { Guid activityId = Trace.CorrelationManager.ActivityId = Guid.NewGuid(); using DataTestUtility.AKVEventListener AKVListener = new(); @@ -228,9 +235,9 @@ public static void CekCacheEntryIsEvictedAfterTtlExpires() SqlColumnEncryptionAzureKeyVaultProvider akvProvider = new(new SqlClientCustomTokenCredential()); akvProvider.ColumnEncryptionKeyCacheTtl = TimeSpan.FromSeconds(5); byte[] plaintextKey = { 1, 2, 3 }; - byte[] encryptedKey = akvProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", plaintextKey); + byte[] encryptedKey = akvProvider.EncryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", plaintextKey); - akvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", encryptedKey); + akvProvider.DecryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", encryptedKey); Assert.True(CekCacheContainsKey(encryptedKey, akvProvider)); Assert.Equal(1, GetCacheCount(cekCacheName, akvProvider)); @@ -242,7 +249,7 @@ public static void CekCacheEntryIsEvictedAfterTtlExpires() } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] - public static void CekCacheShouldBeDisabledWhenCustomProviderIsRegisteredGlobally() + public void CekCacheShouldBeDisabledWhenCustomProviderIsRegisteredGlobally() { if (SQLSetupStrategyAzureKeyVault.IsAKVProviderRegistered) { @@ -255,9 +262,9 @@ public static void CekCacheShouldBeDisabledWhenCustomProviderIsRegisteredGloball SqlColumnEncryptionAzureKeyVaultProvider akvProviderInGlobalCache = globalProviders["AZURE_KEY_VAULT"] as SqlColumnEncryptionAzureKeyVaultProvider; byte[] plaintextKey = { 1, 2, 3 }; - byte[] encryptedKey = akvProviderInGlobalCache.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", plaintextKey); + byte[] encryptedKey = akvProviderInGlobalCache.EncryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", plaintextKey); - akvProviderInGlobalCache.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", encryptedKey); + akvProviderInGlobalCache.DecryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", encryptedKey); Assert.Equal(0, GetCacheCount(cekCacheName, akvProviderInGlobalCache)); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/EnclaveAzureDatabaseTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/EnclaveAzureDatabaseTests.cs index c372e39bca..536be83a3e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/EnclaveAzureDatabaseTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/EnclaveAzureDatabaseTests.cs @@ -14,7 +14,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { // This test class is for internal use only - public sealed class EnclaveAzureDatabaseTests : IDisposable + public sealed class EnclaveAzureDatabaseTests : IDisposable, IClassFixture { private ColumnMasterKey akvColumnMasterKey; private ColumnEncryptionKey akvColumnEncryptionKey; @@ -22,7 +22,7 @@ public sealed class EnclaveAzureDatabaseTests : IDisposable private List databaseObjects = new List(); private List connStrings = new List(); - public EnclaveAzureDatabaseTests() + public EnclaveAzureDatabaseTests(AzureKeyVaultKeyFixture keyVaultKeyFixture) { if (DataTestUtility.IsEnclaveAzureDatabaseSetup()) { @@ -32,7 +32,7 @@ public EnclaveAzureDatabaseTests() SQLSetupStrategyAzureKeyVault.RegisterGlobalProviders(sqlColumnEncryptionAzureKeyVaultProvider); } - akvColumnMasterKey = new AkvColumnMasterKey(DatabaseHelper.GenerateUniqueName("AKVCMK"), akvUrl: DataTestUtility.AKVUrl, sqlColumnEncryptionAzureKeyVaultProvider, DataTestUtility.EnclaveEnabled); + akvColumnMasterKey = new AkvColumnMasterKey(DatabaseHelper.GenerateUniqueName("AKVCMK"), akvUrl: keyVaultKeyFixture.GeneratedKeyUri, sqlColumnEncryptionAzureKeyVaultProvider, DataTestUtility.EnclaveEnabled); databaseObjects.Add(akvColumnMasterKey); akvColumnEncryptionKey = new ColumnEncryptionKey(DatabaseHelper.GenerateUniqueName("AKVCEK"), diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AzureKeyVaultKeyFixture.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AzureKeyVaultKeyFixture.cs new file mode 100644 index 0000000000..14a10c5272 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AzureKeyVaultKeyFixture.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.using System; + +using Microsoft.Data.SqlClient.TestUtilities.Fixtures; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted +{ + public sealed class AzureKeyVaultKeyFixture : AzureKeyVaultKeyFixtureBase + { + public string GeneratedKeyUri { get; } + + public AzureKeyVaultKeyFixture() + : base(DataTestUtility.AKVBaseUri, DataTestUtility.GetTokenCredential()) + { + GeneratedKeyUri = CreateKey(nameof(GeneratedKeyUri), 2048).ToString(); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 54058a9218..a9f9ee3f68 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -38,8 +38,6 @@ public static class DataTestUtility public static readonly string AADPasswordConnectionString = null; public static readonly string AADServicePrincipalId = null; public static readonly string AADServicePrincipalSecret = null; - public static readonly string AKVBaseUrl = null; - public static readonly string AKVUrl = null; public static readonly string AKVOriginalUrl = null; public static readonly string AKVTenantId = null; public static readonly string LocalDbAppName = null; @@ -72,7 +70,6 @@ public static class DataTestUtility public static string AADUserIdentityAccessToken = null; public const string ApplicationClientId = "2fd908ad-0664-4344-b9be-cd3e8b574c38"; public const string UdtTestDbName = "UdtTestDb"; - public const string AKVKeyName = "TestSqlClientAzureKeyVaultProvider"; public const string EventSourcePrefix = "Microsoft.Data.SqlClient"; public const string MDSEventSourceName = "Microsoft.Data.SqlClient.EventSource"; public const string AKVEventSourceName = "Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.EventSource"; @@ -199,8 +196,6 @@ static DataTestUtility() if (!string.IsNullOrEmpty(AKVOriginalUrl) && Uri.TryCreate(AKVOriginalUrl, UriKind.Absolute, out AKVBaseUri)) { AKVBaseUri = new Uri(AKVBaseUri, "/"); - AKVBaseUrl = AKVBaseUri.AbsoluteUri; - AKVUrl = (new Uri(AKVBaseUri, $"/keys/{AKVKeyName}")).AbsoluteUri; } AKVTenantId = c.AzureKeyVaultTenantId; @@ -461,7 +456,7 @@ public static bool IsNotAzureServer() // Ref: https://feedback.azure.com/forums/307516-azure-synapse-analytics/suggestions/17858869-support-always-encrypted-in-sql-data-warehouse public static bool IsAKVSetupAvailable() { - return !string.IsNullOrEmpty(AKVUrl) && !string.IsNullOrEmpty(UserManagedIdentityClientId) && !string.IsNullOrEmpty(AKVTenantId) && IsNotAzureSynapse(); + return AKVBaseUri != null && !string.IsNullOrEmpty(UserManagedIdentityClientId) && !string.IsNullOrEmpty(AKVTenantId) && IsNotAzureSynapse(); } private static readonly DefaultAzureCredential s_defaultCredential = new(new DefaultAzureCredentialOptions { ManagedIdentityClientId = UserManagedIdentityClientId }); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs index fa57a93697..4bcae36326 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs @@ -16,6 +16,9 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests public class SqlClientCustomTokenCredential : TokenCredential { private const string DEFAULT_PREFIX = "/.default"; + private const string AKVKeyName = "TestSqlClientAzureKeyVaultProvider"; + + private static string AKVUrl = (new Uri(DataTestUtility.AKVBaseUri, $"/keys/{AKVKeyName}")).AbsoluteUri; string _authority = ""; string _resource = ""; @@ -31,11 +34,11 @@ private async Task AcquireTokenAsync() { // Added to reduce HttpClient calls. // For multi-user support, a better design can be implemented as needed. - if (_akvUrl != DataTestUtility.AKVUrl) + if (_akvUrl != AKVUrl) { using (HttpClient httpClient = new HttpClient()) { - HttpResponseMessage response = await httpClient.GetAsync(DataTestUtility.AKVUrl); + HttpResponseMessage response = await httpClient.GetAsync(AKVUrl); string challenge = response?.Headers.WwwAuthenticate.FirstOrDefault()?.ToString(); string trimmedChallenge = ValidateChallenge(challenge); string[] pairs = trimmedChallenge.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries); @@ -67,7 +70,7 @@ private async Task AcquireTokenAsync() } } // Since this is a test, we only create single-instance temp cache - _akvUrl = DataTestUtility.AKVUrl; + _akvUrl = AKVUrl; } AccessToken accessToken = await AzureActiveDirectoryAuthenticationCallback(_authority, _resource); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index a5b217f1cd..d39758a824 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -50,6 +50,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/AzureKeyVaultKeyFixtureBase.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/AzureKeyVaultKeyFixtureBase.cs new file mode 100644 index 0000000000..695e3ef2ea --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/AzureKeyVaultKeyFixtureBase.cs @@ -0,0 +1,63 @@ +// 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; +using System.Collections.Generic; +using Azure.Core; +using Azure.Security.KeyVault.Keys; + +namespace Microsoft.Data.SqlClient.TestUtilities.Fixtures +{ + public abstract class AzureKeyVaultKeyFixtureBase : IDisposable + { + private readonly KeyClient _keyClient; + private readonly Random _randomGenerator; + + private readonly List _createdKeys = new List(); + + protected AzureKeyVaultKeyFixtureBase(Uri keyVaultUri, TokenCredential keyVaultToken) + { + _keyClient = new KeyClient(keyVaultUri, keyVaultToken); + _randomGenerator = new Random(); + } + + protected Uri CreateKey(string name, int keySize) + { + CreateRsaKeyOptions createOptions = new CreateRsaKeyOptions(GenerateUniqueName(name)) { KeySize = keySize }; + KeyVaultKey created = _keyClient.CreateRsaKey(createOptions); + + _createdKeys.Add(created); + return created.Id; + } + + private string GenerateUniqueName(string name) + { + byte[] rndBytes = new byte[16]; + + _randomGenerator.NextBytes(rndBytes); + return name + "-" + BitConverter.ToString(rndBytes); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + foreach (KeyVaultKey key in _createdKeys) + { + try + { + _keyClient.StartDeleteKey(key.Name).WaitForCompletion(); + } + catch(Exception) + { + continue; + } + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Microsoft.Data.SqlClient.TestUtilities.csproj b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Microsoft.Data.SqlClient.TestUtilities.csproj index c719a37006..706fb02d31 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Microsoft.Data.SqlClient.TestUtilities.csproj +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Microsoft.Data.SqlClient.TestUtilities.csproj @@ -15,6 +15,8 @@ PreserveNewest + + From d2ca8e5ea2d5a434e4cac725c296955c6ecbc531 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 29 Dec 2024 23:45:50 +0000 Subject: [PATCH 04/16] Removed hardcoded references to CertificateUtilityWin These were mostly related to generating CSP keys. --- .../AlwaysEncrypted/CspProviderExt.cs | 193 ++----------- .../TestFixtures/DatabaseHelper.cs | 35 +++ .../TestFixtures/SQLSetupStrategy.cs | 25 +- .../SQLSetupStrategyCertStoreProvider.cs | 4 - .../TestFixtures/SQLSetupStrategyCspExt.cs | 50 ---- .../SQLSetupStrategyCspProvider.cs | 77 +++++ .../Setup/CertificateUtilityWin.cs | 264 ------------------ ....Data.SqlClient.ManualTesting.Tests.csproj | 3 +- .../Fixtures/CertificateFixtureBase.cs | 24 +- .../ColumnMasterKeyCertificateFixture.cs | 12 +- .../Fixtures/CspCertificateFixture.cs | 47 ++++ 11 files changed, 236 insertions(+), 498 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspExt.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspProvider.cs delete mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtilityWin.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CspCertificateFixture.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs index 579e11b61a..24dd9572bc 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs @@ -5,10 +5,9 @@ using System; using System.Linq; using System.Security.Cryptography.X509Certificates; -using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; using Xunit; -using System.Collections.Generic; -using static Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.CertificateUtilityWin; +using System.Security.Cryptography; +using Microsoft.Data.SqlClient.TestUtilities.Fixtures; #if !NETFRAMEWORK using System.Runtime.Versioning; #endif @@ -25,184 +24,48 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted [PlatformSpecific(TestPlatforms.Windows)] public class CspProviderExt { - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] - [ClassData(typeof(AEConnectionStringProvider))] - public void TestKeysFromCertificatesCreatedWithMultipleCryptoProviders(string connectionString) - { - const string providersRegistryKeyPath = @"SOFTWARE\Microsoft\Cryptography\Defaults\Provider"; - Microsoft.Win32.RegistryKey defaultCryptoProvidersRegistryKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(providersRegistryKeyPath); - - foreach (string subKeyName in defaultCryptoProvidersRegistryKey.GetSubKeyNames()) - { - // NOTE: RSACryptoServiceProvider.SignData() fails for other providers when testing locally - if (!subKeyName.Contains(@"RSA and AES")) - { - Console.WriteLine(@"INFO: Skipping Certificate creation for {0}.", subKeyName); - continue; - } - string providerName; - string providerType; - string certificateName; - using (Microsoft.Win32.RegistryKey providerKey = defaultCryptoProvidersRegistryKey.OpenSubKey(subKeyName)) - { - // Get Provider Name and its type - providerName = providerKey.Name.Substring(providerKey.Name.LastIndexOf(@"\", StringComparison.Ordinal) + 1); - providerType = providerKey.GetValue(@"Type").ToString(); - - // Create a certificate from that provider - certificateName = string.Format(@"AETest - {0}", providerName); - } - - var extensions = new List>(); - CertificateOption certOption = new() - { - Subject = certificateName, - KeyExportPolicy = "Exportable", - CertificateStoreLocation = $"{StoreLocation.CurrentUser}\\My", - Provider = providerName, - Type = providerType, - }; - CreateCertificate(certOption); - SQLSetupStrategyCspExt sqlSetupStrategyCsp = null; - try - { - if (false == CertificateUtilityWin.CertificateExists(certificateName, StoreLocation.CurrentUser)) - { - Console.WriteLine(@"INFO: Certificate creation for provider {0} failed so skipping it.", providerName); - continue; - } - - X509Certificate2 cert = CertificateUtilityWin.GetCertificate(certificateName, StoreLocation.CurrentUser); - string cspPath = CertificateUtilityWin.GetCspPathFromCertificate(cert); - - if (string.IsNullOrEmpty(cspPath)) - { - Console.WriteLine(@"INFO: Certificate provider {0} is not a csp provider so skipping it.", providerName); - continue; - } - - Console.WriteLine("CSP path is {0}", cspPath); - - sqlSetupStrategyCsp = new SQLSetupStrategyCspExt(cspPath); - string tableName = sqlSetupStrategyCsp.CspProviderTable.Name; - - using SqlConnection sqlConn = new(connectionString); - sqlConn.Open(); - - Table.DeleteData(tableName, sqlConn); - - // insert 1 row data - Customer customer = new Customer(45, "Microsoft", "Corporation"); - - DatabaseHelper.InsertCustomerData(sqlConn, null, tableName, customer); - - // Test INPUT parameter on an encrypted parameter - using SqlCommand sqlCommand = new(@$"SELECT CustomerId, FirstName, LastName FROM [{tableName}] WHERE FirstName = @firstName", - sqlConn, null, SqlCommandColumnEncryptionSetting.Enabled); - SqlParameter customerFirstParam = sqlCommand.Parameters.AddWithValue(@"firstName", @"Microsoft"); - customerFirstParam.Direction = System.Data.ParameterDirection.Input; - - using SqlDataReader sqlDataReader = sqlCommand.ExecuteReader(); - ValidateResultSet(sqlDataReader); - Console.WriteLine(@"INFO: Successfully validated using a certificate using provider:{0}", providerName); - } - finally - { - CertificateUtilityWin.RemoveCertificate(certificateName, StoreLocation.CurrentUser); - // clean up database resources - sqlSetupStrategyCsp?.Dispose(); - } - - } - } - // [Fact(Skip="Run this in non-parallel mode")] or [ConditionalFact()] [Fact(Skip = "Failing in TCE")] public void TestRoundTripWithCSPAndCertStoreProvider() { - const string providerName = "Microsoft Enhanced RSA and AES Cryptographic Provider"; - string providerType = "24"; - - string certificateName = string.Format(@"AETest - {0}", providerName); - CertificateOption options = new() - { - Subject = certificateName, - CertificateStoreLocation = StoreLocation.CurrentUser.ToString(), - Provider = providerName, - Type = providerType - }; - CertificateUtilityWin.CreateCertificate(options); - try - { - X509Certificate2 cert = CertificateUtilityWin.GetCertificate(certificateName, StoreLocation.CurrentUser); - string cspPath = CertificateUtilityWin.GetCspPathFromCertificate(cert); - string certificatePath = String.Concat(@"CurrentUser/my/", cert.Thumbprint); + using CspCertificateFixture cspCertificateFixture = new CspCertificateFixture(); - SqlColumnEncryptionCertificateStoreProvider certProvider = new SqlColumnEncryptionCertificateStoreProvider(); - SqlColumnEncryptionCspProvider cspProvider = new SqlColumnEncryptionCspProvider(); - byte[] columnEncryptionKey = DatabaseHelper.GenerateRandomBytes(32); + X509Certificate2 cert = cspCertificateFixture.CspCertificate; + string cspPath = cspCertificateFixture.CspKeyPath; + string certificatePath = cspCertificateFixture.CspCertificatePath; - byte[] encryptedColumnEncryptionKeyUsingCert = certProvider.EncryptColumnEncryptionKey(certificatePath, @"RSA_OAEP", columnEncryptionKey); - byte[] columnEncryptionKeyReturnedCert2CSP = cspProvider.DecryptColumnEncryptionKey(cspPath, @"RSA_OAEP", encryptedColumnEncryptionKeyUsingCert); - Assert.True(columnEncryptionKey.SequenceEqual(columnEncryptionKeyReturnedCert2CSP)); + SqlColumnEncryptionCertificateStoreProvider certProvider = new SqlColumnEncryptionCertificateStoreProvider(); + SqlColumnEncryptionCspProvider cspProvider = new SqlColumnEncryptionCspProvider(); + byte[] columnEncryptionKey = DatabaseHelper.GenerateRandomBytes(32); - byte[] encryptedColumnEncryptionKeyUsingCSP = cspProvider.EncryptColumnEncryptionKey(cspPath, @"RSA_OAEP", columnEncryptionKey); - byte[] columnEncryptionKeyReturnedCSP2Cert = certProvider.DecryptColumnEncryptionKey(certificatePath, @"RSA_OAEP", encryptedColumnEncryptionKeyUsingCSP); - Assert.True(columnEncryptionKey.SequenceEqual(columnEncryptionKeyReturnedCSP2Cert)); + byte[] encryptedColumnEncryptionKeyUsingCert = certProvider.EncryptColumnEncryptionKey(certificatePath, @"RSA_OAEP", columnEncryptionKey); + byte[] columnEncryptionKeyReturnedCert2CSP = cspProvider.DecryptColumnEncryptionKey(cspPath, @"RSA_OAEP", encryptedColumnEncryptionKeyUsingCert); + Assert.True(columnEncryptionKey.SequenceEqual(columnEncryptionKeyReturnedCert2CSP)); - } - finally - { - CertificateUtilityWin.RemoveCertificate(certificateName, StoreLocation.CurrentUser); - } + byte[] encryptedColumnEncryptionKeyUsingCSP = cspProvider.EncryptColumnEncryptionKey(cspPath, @"RSA_OAEP", columnEncryptionKey); + byte[] columnEncryptionKeyReturnedCSP2Cert = certProvider.DecryptColumnEncryptionKey(certificatePath, @"RSA_OAEP", encryptedColumnEncryptionKeyUsingCSP); + Assert.True(columnEncryptionKey.SequenceEqual(columnEncryptionKeyReturnedCSP2Cert)); } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] - [ClassData(typeof(AEConnectionStringProvider))] - public void TestEncryptDecryptWithCSP(string connectionString) + [ClassData(typeof(AEConnectionStringProviderWithCspParameters))] + public void TestEncryptDecryptWithCSP(string connectionString, CspParameters cspParameters) { - string providerName = @"Microsoft Enhanced RSA and AES Cryptographic Provider"; string keyIdentifier = DataTestUtility.GetUniqueNameForSqlServer("CSP"); + CspParameters namedCspParameters = new CspParameters(cspParameters.ProviderType, cspParameters.ProviderName, keyIdentifier); + using SQLSetupStrategyCspProvider sqlSetupStrategyCsp = new SQLSetupStrategyCspProvider(namedCspParameters); - try - { - CertificateUtilityWin.RSAPersistKeyInCsp(providerName, keyIdentifier); - string cspPath = String.Concat(providerName, @"/", keyIdentifier); - - SQLSetupStrategyCspExt sqlSetupStrategyCsp = new SQLSetupStrategyCspExt(cspPath); - string tableName = sqlSetupStrategyCsp.CspProviderTable.Name; - - try - { - using SqlConnection sqlConn = new(connectionString); - sqlConn.Open(); - - Table.DeleteData(tableName, sqlConn); - - // insert 1 row data - Customer customer = new Customer(45, "Microsoft", "Corporation"); - - DatabaseHelper.InsertCustomerData(sqlConn, null, tableName, customer); + using SqlConnection sqlConn = new(connectionString); + sqlConn.Open(); - // Test INPUT parameter on an encrypted parameter - using SqlCommand sqlCommand = new(@$"SELECT CustomerId, FirstName, LastName FROM [{tableName}] WHERE FirstName = @firstName", - sqlConn, null, SqlCommandColumnEncryptionSetting.Enabled); - SqlParameter customerFirstParam = sqlCommand.Parameters.AddWithValue(@"firstName", @"Microsoft"); - customerFirstParam.Direction = System.Data.ParameterDirection.Input; + // Test INPUT parameter on an encrypted parameter + using SqlCommand sqlCommand = new(@$"SELECT CustomerId, FirstName, LastName FROM [{sqlSetupStrategyCsp.ApiTestTable.Name}] WHERE FirstName = @firstName", + sqlConn, null, SqlCommandColumnEncryptionSetting.Enabled); + SqlParameter customerFirstParam = sqlCommand.Parameters.AddWithValue(@"firstName", @"Microsoft"); + customerFirstParam.Direction = System.Data.ParameterDirection.Input; - using SqlDataReader sqlDataReader = sqlCommand.ExecuteReader(); - ValidateResultSet(sqlDataReader); - } - finally - { - // clean up database resources - sqlSetupStrategyCsp.Dispose(); - } - } - finally - { - CertificateUtilityWin.RSADeleteKeyInCsp(providerName, keyIdentifier); - } + using SqlDataReader sqlDataReader = sqlCommand.ExecuteReader(); + ValidateResultSet(sqlDataReader); } /// diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/DatabaseHelper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/DatabaseHelper.cs index 63cfdab974..5b296c9b65 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/DatabaseHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/DatabaseHelper.cs @@ -342,4 +342,39 @@ public IEnumerator GetEnumerator() } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } + + public class AEConnectionStringProviderWithCspParameters : IEnumerable + { + public IEnumerator GetEnumerator() + { + const string ProvidersRegistryKeyPath = @"SOFTWARE\Microsoft\Cryptography\Defaults\Provider"; + using Microsoft.Win32.RegistryKey defaultCryptoProvidersRegistryKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(ProvidersRegistryKeyPath); + + foreach (string subKeyName in defaultCryptoProvidersRegistryKey.GetSubKeyNames()) + { + CspParameters providerCspParameters; + + // NOTE: RSACryptoServiceProvider.SignData() fails for other providers when testing locally + if (!subKeyName.Contains(@"RSA and AES")) + { + continue; + } + + using (Microsoft.Win32.RegistryKey providerKey = defaultCryptoProvidersRegistryKey.OpenSubKey(subKeyName)) + { + // Get Provider Name and its type + string providerName = providerKey.Name.Substring(providerKey.Name.LastIndexOf(@"\", StringComparison.Ordinal) + 1); + int providerType = (int)providerKey.GetValue(@"Type"); + + providerCspParameters = new CspParameters(providerType, providerName); + } + + foreach (string connStrAE in DataTestUtility.AEConnStrings) + { + yield return new object[] { connStrAE, providerCspParameters }; + } + } + } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs index 0bcff3bccb..3a1f4aef4f 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs @@ -59,13 +59,13 @@ public class SQLSetupStrategy : ColumnMasterKeyCertificateFixture public Dictionary sqlBulkTruncationTableNames = new Dictionary(); public SQLSetupStrategy() - : base() + : base(true) { ColumnMasterKeyPath = string.Concat(StoreLocation.CurrentUser.ToString(), "/", StoreName.My.ToString(), "/", ColumnMasterKeyCertificate.Thumbprint); } protected SQLSetupStrategy(string customKeyPath) - : base() + : base(false) { ColumnMasterKeyPath = customKeyPath; } @@ -90,6 +90,14 @@ internal virtual void SetupDatabase() } // Insert data for TrustedMasterKeyPaths tests. + InsertSampleData(TrustedMasterKeyPathsTestTable.Name); + } + } + + protected void InsertSampleData(string tableName) + { + foreach(string value in DataTestUtility.AEConnStringsSetup) + { SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(value) { ConnectTimeout = 10000 @@ -98,7 +106,9 @@ internal virtual void SetupDatabase() using (SqlConnection sqlConn = new SqlConnection(builder.ToString())) { sqlConn.Open(); - DatabaseHelper.InsertCustomerData(sqlConn, null, TrustedMasterKeyPathsTestTable.Name, customer); + + Table.DeleteData(tableName, sqlConn); + DatabaseHelper.InsertCustomerData(sqlConn, null, tableName, customer); } } } @@ -147,9 +157,12 @@ protected List
CreateTables(IList columnEncryptionKe SqlNullValuesTable = new SqlNullValuesTable(GenerateUniqueName("SqlNullValuesTable"), columnEncryptionKeys[0]); tables.Add(SqlNullValuesTable); - // columnEncryptionKeys[2] is encrypted with DummyCMK. use this encrypted column to test custom key store providers - CustomKeyStoreProviderTestTable = new ApiTestTable(GenerateUniqueName("CustomKeyStoreProviderTestTable"), columnEncryptionKeys[2], columnEncryptionKeys[0], useDeterministicEncryption: true); - tables.Add(CustomKeyStoreProviderTestTable); + if (columnEncryptionKeys.Count > 2) + { + // columnEncryptionKeys[2] is encrypted with DummyCMK. use this encrypted column to test custom key store providers + CustomKeyStoreProviderTestTable = new ApiTestTable(GenerateUniqueName("CustomKeyStoreProviderTestTable"), columnEncryptionKeys[2], columnEncryptionKeys[0], useDeterministicEncryption: true); + tables.Add(CustomKeyStoreProviderTestTable); + } TabNVarCharMaxSource = new BulkCopyTruncationTables(GenerateUniqueName("TabNVarCharMaxSource"), columnEncryptionKeys[0]); tables.Add(TabNVarCharMaxSource); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCertStoreProvider.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCertStoreProvider.cs index c368a17c98..db935a5fed 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCertStoreProvider.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCertStoreProvider.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information.using System; using System.Collections.Generic; -using System.Security.Cryptography.X509Certificates; using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted @@ -20,9 +19,6 @@ public SQLSetupStrategyCertStoreProvider() : base() SetupDatabase(); } - protected SQLSetupStrategyCertStoreProvider(string customKeyPath) : base(customKeyPath) - { } - internal override void SetupDatabase() { CspColumnMasterKey = new CspColumnMasterKey(GenerateUniqueName("CMK"), ColumnMasterKeyCertificate.Thumbprint, CertStoreProvider, DataTestUtility.EnclaveEnabled); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspExt.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspExt.cs deleted file mode 100644 index 566717762d..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspExt.cs +++ /dev/null @@ -1,50 +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 Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; - -namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted -{ - public class SQLSetupStrategyCspExt : SQLSetupStrategyCertStoreProvider - { - public Table CspProviderTable { get; private set; } - public SqlColumnEncryptionCspProvider keyStoreProvider { get; } - - public SQLSetupStrategyCspExt(string cspKeyPath) : base(cspKeyPath) - { - keyStoreProvider = new SqlColumnEncryptionCspProvider(); - this.SetupDatabase(); - } - - internal override void SetupDatabase() - { - ColumnMasterKey columnMasterKey = new CspProviderColumnMasterKey(GenerateUniqueName("CspExt"), SqlColumnEncryptionCspProvider.ProviderName, ColumnMasterKeyPath); - databaseObjects.Add(columnMasterKey); - - List columnEncryptionKeys = CreateColumnEncryptionKeys(columnMasterKey, 2, keyStoreProvider); - databaseObjects.AddRange(columnEncryptionKeys); - - Table table = CreateTable(columnEncryptionKeys); - databaseObjects.Add(table); - - foreach(string value in DataTestUtility.AEConnStringsSetup) - { - using (SqlConnection sqlConnection = new SqlConnection(value)) - { - sqlConnection.Open(); - databaseObjects.ForEach(o => o.Create(sqlConnection)); - } - } - - } - - private Table CreateTable(IList columnEncryptionKeys) - { - CspProviderTable = new ApiTestTable(GenerateUniqueName("CspProviderTable"), columnEncryptionKeys[0], columnEncryptionKeys[1]); - - return CspProviderTable; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspProvider.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspProvider.cs new file mode 100644 index 0000000000..1ee0b4f616 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspProvider.cs @@ -0,0 +1,77 @@ +// 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; +using System.Collections.Generic; +using System.Security.Cryptography; +using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted +{ + public class SQLSetupStrategyCspProvider : SQLSetupStrategy + { + private const int KeySize = 2048; + + private readonly List _cspKeyParameters = new List(); + + public SqlColumnEncryptionCspProvider CspProvider { get; } + + public SQLSetupStrategyCspProvider(CspParameters cspParameters) + : base(cspParameters.ProviderName + "/" + cspParameters.KeyContainerName) + { + // Create a new instance of RSACryptoServiceProvider to generate + // a new key pair. Pass the CspParameters class to persist the + // key in the container. + using RSACryptoServiceProvider rsaAlg = new RSACryptoServiceProvider(KeySize, cspParameters); + rsaAlg.PersistKeyInCsp = true; + + _cspKeyParameters.Add(cspParameters); + + CspProvider = new SqlColumnEncryptionCspProvider(); + SetupDatabase(); + } + + internal override void SetupDatabase() + { + ColumnMasterKey columnMasterKey = new CspProviderColumnMasterKey(GenerateUniqueName("CspExt"), SqlColumnEncryptionCspProvider.ProviderName, ColumnMasterKeyPath); + databaseObjects.Add(columnMasterKey); + + List columnEncryptionKeys = CreateColumnEncryptionKeys(columnMasterKey, 2, CspProvider); + databaseObjects.AddRange(columnEncryptionKeys); + + List
tables = CreateTables(columnEncryptionKeys); + databaseObjects.AddRange(tables); + + base.SetupDatabase(); + + InsertSampleData(ApiTestTable.Name); + } + + protected override void Dispose(bool disposing) + { + foreach (CspParameters cspParameters in _cspKeyParameters) + { + try + { + // Create a new instance of RSACryptoServiceProvider. + // Pass the CspParameters class to use the + // key in the container. + using RSACryptoServiceProvider rsaAlg = new RSACryptoServiceProvider(cspParameters); + + // Delete the key entry in the container. + rsaAlg.PersistKeyInCsp = false; + + // Call Clear to release resources and delete the key from the container. + rsaAlg.Clear(); + } + catch (Exception) + { + continue; + } + } + + base.Dispose(disposing); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtilityWin.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtilityWin.cs deleted file mode 100644 index f1c0f5f9c1..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtilityWin.cs +++ /dev/null @@ -1,264 +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; -using System.Diagnostics; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -using Xunit; -using System.Collections.Generic; -using static System.Net.Mime.MediaTypeNames; -#if !NETFRAMEWORK -using System.Runtime.Versioning; -#endif - -namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted -{ -#if !NETFRAMEWORK - [SupportedOSPlatform("windows")] -#endif - [PlatformSpecific(TestPlatforms.Windows)] - class CertificateUtilityWin - { - - public class CertificateOption - { - public string Subject { get; set; } - public string KeyExportPolicy { get; set; } = "Exportable"; - public string CertificateStoreLocation { get; set; } - public List> TextExtensions { get; set; } = new List> { new Tuple("2.5.29.3", "1.3.6.1.5.5.8.2.2", "1.3.6.1.4.1.311.10.3.11") }; - public string KeySpec { get; set; } = "KeyExchange"; - public string Provider { get; set; } = "Microsoft Enhanced RSA and AES Cryptographic Provider"; - public string Type { get; set; } = "24"; - public string KeyAlgorithm { get; set; } = "rsa"; - public string KeyLength { get; set; } = "2048"; - public string HashAlgorithm { get; set; } = "sha256"; - public string KeyUsage { get; set; } = "None"; - public string TestRoot { get; set; } = "-TestRoot"; - } - - private CertificateUtilityWin() - { - } - - /// - /// Create a self-signed certificate through PowerShell. - /// - internal static void CreateCertificate(CertificateOption option) - { - Assert.False(string.IsNullOrWhiteSpace(option.Subject), "FAILED: certificateName should not be null or empty."); - Assert.False(string.IsNullOrWhiteSpace(option.CertificateStoreLocation), "FAILED: certificateLocation should not be null or empty."); - - string powerShellPath = string.IsNullOrEmpty(DataTestUtility.PowerShellPath) ? "powershell.exe" : DataTestUtility.PowerShellPath; - ProcessStartInfo processStartInfo = new ProcessStartInfo(powerShellPath) - { - Arguments = $"New-SelfSignedCertificate -Subject " + - $"'CN={option.Subject}' " + - $"-KeyExportPolicy {option.KeyExportPolicy} " + - $"-CertStoreLocation '{option.CertificateStoreLocation}\\My' " + - $"-TextExtension @('{option.TextExtensions[0].Item1}={{text}}{option.TextExtensions[0].Item2},{option.TextExtensions[0].Item3}') " + - $"-KeySpec {option.KeySpec} " + - $"-Provider '{option.Provider}' " + - $"-Type {option.Type} " + - $"-KeyAlgorithm {option.KeyAlgorithm} " + - $"-KeyLength {option.KeyLength} " + - $"-HashAlgorithm {option.HashAlgorithm} " + - $"-KeyUsage {option.KeyUsage} " + - $"{option.TestRoot}", - Verb="runas" - }; - Process process = new() - { - StartInfo = processStartInfo - }; - process.StartInfo.UseShellExecute = true; - process.StartInfo.CreateNoWindow = true; - process.Start(); - process.WaitForExit(); - } - - /// - /// Creates an RSA 2048 key inside the specified CSP. - /// - /// CSP name - /// Container name - /// - internal static bool RSAPersistKeyInCsp(string providerName, string containerName) - { - try - { - const int KEYSIZE = 2048; - int providerType = GetProviderKey(providerName); - - // Create a new instance of CspParameters. - CspParameters cspParams = new CspParameters(providerType, providerName, containerName); - - //Create a new instance of RSACryptoServiceProvider to generate - //a new key pair. Pass the CspParameters class to persist the - //key in the container. - RSACryptoServiceProvider rsaAlg = new RSACryptoServiceProvider(KEYSIZE, cspParams); - rsaAlg.PersistKeyInCsp = true; - } - catch (CryptographicException e) - { - Console.WriteLine("\tFAILURE: The RSA key was not persisted in the container, \"{0}\".", containerName); - Console.WriteLine(@" {0}", e.Message); - return false; - } - - return true; - } - - /// - /// Deletes the specified RSA key - /// - /// CSP name - /// Container name to be deleted - /// - internal static bool RSADeleteKeyInCsp(string providerName, string containerName) - { - try - { - int providerType = GetProviderKey(providerName); - - // Create a new instance of CspParameters. - CspParameters cspParams = new CspParameters(providerType, providerName, containerName); - - //Create a new instance of RSACryptoServiceProvider. - //Pass the CspParameters class to use the - //key in the container. - RSACryptoServiceProvider rsaAlg = new RSACryptoServiceProvider(cspParams); - - //Delete the key entry in the container. - rsaAlg.PersistKeyInCsp = false; - - //Call Clear to release resources and delete the key from the container. - rsaAlg.Clear(); - } - catch (CryptographicException e) - { - Console.WriteLine("\tFAILURE: The RSA key was not deleted from the container, \"{0}\".", containerName); - Console.WriteLine("\t{0}", e.Message); - return false; - } - - return true; - } - - internal static int GetProviderKey(string providerName) - { - Microsoft.Win32.RegistryKey key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Cryptography\Defaults\Provider"); - Microsoft.Win32.RegistryKey providerKey = key.OpenSubKey(providerName); - return (int)providerKey.GetValue(@"Type"); - } - - /// - /// Checks if a cert exists or not - /// - /// - /// - /// - internal static bool CertificateExists(string certificateName, StoreLocation certificateStoreLocation) - { - Assert.False(string.IsNullOrWhiteSpace(certificateName), "FAILED: certificateName should not be null or empty."); - - X509Store certStore = null; - try - { - certStore = new X509Store(StoreName.My, certificateStoreLocation); - certStore.Open(OpenFlags.ReadOnly); - X509Certificate2Collection certCollection = certStore.Certificates.Find(X509FindType.FindBySubjectName, certificateName, validOnly: false); - - if (certCollection != null && certCollection.Count > 0) - { - return true; - } - else - { - return false; - } - } - finally - { - if (certStore != null) - { - certStore.Close(); - } - } - } - - /// - /// Gets the certificate. - /// - /// - /// - /// - internal static X509Certificate2 GetCertificate(string certificateName, StoreLocation certificateStoreLocation) - { - Assert.False(string.IsNullOrWhiteSpace(certificateName), "FAILED: certificateName should not be null or empty."); - X509Store certStore = null; - try - { - certStore = new X509Store(StoreName.My, certificateStoreLocation); - certStore.Open(OpenFlags.ReadOnly); - X509Certificate2Collection certCollection = certStore.Certificates.Find(X509FindType.FindBySubjectName, certificateName, validOnly: false); - Debug.Assert(certCollection != null && certCollection.Count > 0); - return certCollection[0]; - } - finally - { - if (certStore != null) - { - certStore.Close(); - } - } - } - - /// - /// Gets Csp path from a given certificate. - /// - internal static string GetCspPathFromCertificate(X509Certificate2 certificate) - { - if (certificate.GetRSAPrivateKey() is RSACryptoServiceProvider csp) - { - return string.Concat(csp.CspKeyContainerInfo.ProviderName, @"/", csp.CspKeyContainerInfo.KeyContainerName); - } - else - { - return null; - } - } - - /// - /// Removes a certificate from the store. Cleanup purposes. - /// - /// - /// - /// - internal static void RemoveCertificate(string certificateName, StoreLocation certificateStoreLocation) - { - Assert.False(string.IsNullOrWhiteSpace(certificateName), "FAILED: certificateName should not be null or empty."); - - X509Store certStore = null; - try - { - certStore = new X509Store(StoreName.My, certificateStoreLocation); - certStore.Open(OpenFlags.ReadWrite); - X509Certificate2Collection certificateCollection = certStore.Certificates.Find(X509FindType.FindBySubjectName, certificateName, validOnly: false); - - if (certificateCollection != null && certificateCollection.Count > 0) - { - certStore.RemoveRange(certificateCollection); - } - } - finally - { - if (certStore != null) - { - certStore.Close(); - } - } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index d39758a824..1afc8c90aa 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -30,9 +30,8 @@ - - + diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs index dc78dd4f99..44026a06c2 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs @@ -13,6 +13,8 @@ namespace Microsoft.Data.SqlClient.TestUtilities.Fixtures { public abstract class CertificateFixtureBase : IDisposable { + private const string CspProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider"; + private sealed class CertificateStoreContext { public List Certificates { get; } @@ -31,7 +33,7 @@ public CertificateStoreContext(StoreLocation location, StoreName name) private readonly List _certificateStoreModifications = new List(); - protected static X509Certificate2 CreateCertificate(string subjectName, IEnumerable dnsNames, IEnumerable ipAddresses) + protected X509Certificate2 CreateCertificate(string subjectName, IEnumerable dnsNames, IEnumerable ipAddresses, bool forceCsp = false) { // This will always generate a certificate with: // * Start date: 24hrs ago @@ -53,7 +55,7 @@ protected static X509Certificate2 CreateCertificate(string subjectName, IEnumera #if NET9_0 X500DistinguishedNameBuilder subjectBuilder = new X500DistinguishedNameBuilder(); SubjectAlternativeNameBuilder sanBuilder = new SubjectAlternativeNameBuilder(); - RSA rsaKey = RSA.Create(2048); + RSA rsaKey = CreateRSA(forceCsp); bool hasSans = false; subjectBuilder.AddCommonName(subjectName); @@ -84,7 +86,7 @@ protected static X509Certificate2 CreateCertificate(string subjectName, IEnumera using (X509Certificate2 ephemeral = request.CreateSelfSigned(notBefore, notAfter)) { return X509CertificateLoader.LoadPkcs12(ephemeral.Export(X509ContentType.Pkcs12, password), password, - X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); + X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable, new Pkcs12LoaderLimits(Pkcs12LoaderLimits.Defaults) { PreserveStorageProvider = true, PreserveKeyName = true }); } #else // The CertificateRequest API is available in .NET Core, but was only added to .NET Framework 4.7.2; it thus can't be used in the test projects. @@ -101,7 +103,7 @@ protected static X509Certificate2 CreateCertificate(string subjectName, IEnumera {{ $x509 = PKI\New-SelfSignedCertificate -Subject $subject -TextExtension $sAN -KeyLength 2048 -KeyAlgorithm RSA ` -CertStoreLocation ""Cert:\CurrentUser\My"" -NotBefore $notBefore -NotAfter $notAfter ` - -KeyExportPolicy Exportable -HashAlgorithm SHA256 -Provider ""Microsoft Enhanced RSA and AES Cryptographic Provider"" -KeySpec KeyExchange + -KeyExportPolicy Exportable -HashAlgorithm SHA256 -Provider ""{5}"" -KeySpec KeyExchange if ($x509 -eq $null) {{ throw ""Certificate was null!"" }} @@ -138,7 +140,7 @@ exit 1 sanString = hasSans ? "\"2.5.29.17={text}" + sanString.Substring(0, sanString.Length - 1) + "\"" : string.Empty; - formattedCommand = string.Format(PowerShellCommandTemplate, notBefore.ToString("O"), notAfter.ToString("O"), subjectName, sanString, password); + formattedCommand = string.Format(PowerShellCommandTemplate, notBefore.ToString("O"), notAfter.ToString("O"), subjectName, sanString, password, CspProviderName); using (Process psProcess = new Process() { @@ -176,6 +178,18 @@ exit 1 #endif } +#if NET + private static RSA CreateRSA(bool forceCsp) + { + const int KeySize = 2048; + const int CspProviderType = 24; + + return forceCsp && OperatingSystem.IsWindows() + ? new RSACryptoServiceProvider(KeySize, new CspParameters(CspProviderType, CspProviderName, Guid.NewGuid().ToString())) + : RSA.Create(KeySize); + } +#endif + protected void AddToStore(X509Certificate2 cert, StoreLocation storeLocation, StoreName storeName) { CertificateStoreContext storeContext = _certificateStoreModifications.Find(csc => csc.Location == storeLocation && csc.Name == storeName); diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/ColumnMasterKeyCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/ColumnMasterKeyCertificateFixture.cs index 3409c489fe..162f8d81db 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/ColumnMasterKeyCertificateFixture.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/ColumnMasterKeyCertificateFixture.cs @@ -12,10 +12,18 @@ public class ColumnMasterKeyCertificateFixture : CertificateFixtureBase public X509Certificate2 ColumnMasterKeyCertificate { get; } public ColumnMasterKeyCertificateFixture() + : this(true) { - ColumnMasterKeyCertificate = CreateCertificate(nameof(ColumnMasterKeyCertificate), Array.Empty(), Array.Empty()); + } + + protected ColumnMasterKeyCertificateFixture(bool createCertificate) + { + if (createCertificate) + { + ColumnMasterKeyCertificate = CreateCertificate(nameof(ColumnMasterKeyCertificate), Array.Empty(), Array.Empty()); - AddToStore(ColumnMasterKeyCertificate, StoreLocation.CurrentUser, StoreName.My); + AddToStore(ColumnMasterKeyCertificate, StoreLocation.CurrentUser, StoreName.My); + } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CspCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CspCertificateFixture.cs new file mode 100644 index 0000000000..b6473f4035 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CspCertificateFixture.cs @@ -0,0 +1,47 @@ +// 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.TestUtilities.Fixtures +{ + public class CspCertificateFixture : CertificateFixtureBase + { + public X509Certificate2 CspCertificate { get; } + + public string CspCertificatePath { get; } + + public string CspKeyPath { get; } + + public CspCertificateFixture() + { + CspCertificate = CreateCertificate(nameof(CspCertificate), Array.Empty(), Array.Empty(), true); + + AddToStore(CspCertificate, StoreLocation.CurrentUser, StoreName.My); + + CspCertificatePath = string.Format("{0}/{1}/{2}", StoreLocation.CurrentUser, StoreName.My, CspCertificate.Thumbprint); + CspKeyPath = GetCspPathFromCertificate(); + } + + private string GetCspPathFromCertificate() + { + RSA privateKey = CspCertificate.GetRSAPrivateKey(); + + if (privateKey is RSACryptoServiceProvider csp) + { + return string.Concat(csp.CspKeyContainerInfo.ProviderName, @"/", csp.CspKeyContainerInfo.KeyContainerName); + } + else if (privateKey is RSACng cng) + { + return string.Concat(cng.Key.Provider.Provider, @"/", cng.Key.KeyName); + } + else + { + return null; + } + } + } +} From 7eef910c8b8f48d4f9ac1c3eff7d5cf0425df30d Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Mon, 24 Feb 2025 07:34:05 +0000 Subject: [PATCH 05/16] Code review changes * Reorder properties and constructors * Move AEConnectionStringProviderWithCspParameters to its own file * Tweak to the AKV token acquisition --- ...nnectionStringProviderWithCspParameters.cs | 46 +++++++++++++++++++ .../TestFixtures/AzureKeyVaultKeyFixture.cs | 4 +- .../TestFixtures/DatabaseHelper.cs | 35 -------------- .../TestFixtures/SQLSetupStrategy.cs | 2 +- .../SQLSetupStrategyCspProvider.cs | 4 +- .../TestTrustedMasterKeyPaths.cs | 17 +++---- .../SqlClientCustomTokenCredential.cs | 8 +--- ....Data.SqlClient.ManualTesting.Tests.csproj | 1 + .../ColumnMasterKeyCertificateFixture.cs | 4 +- .../Fixtures/CspCertificateFixture.cs | 12 ++--- 10 files changed, 69 insertions(+), 64 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AEConnectionStringProviderWithCspParameters.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AEConnectionStringProviderWithCspParameters.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AEConnectionStringProviderWithCspParameters.cs new file mode 100644 index 0000000000..5c0a81e9b4 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AEConnectionStringProviderWithCspParameters.cs @@ -0,0 +1,46 @@ +// 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; +using System.Collections.Generic; +using System.Security.Cryptography; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted +{ + public class AEConnectionStringProviderWithCspParameters : IEnumerable + { + public IEnumerator GetEnumerator() + { + const string ProvidersRegistryKeyPath = @"SOFTWARE\Microsoft\Cryptography\Defaults\Provider"; + using Microsoft.Win32.RegistryKey defaultCryptoProvidersRegistryKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(ProvidersRegistryKeyPath); + + foreach (string subKeyName in defaultCryptoProvidersRegistryKey.GetSubKeyNames()) + { + CspParameters providerCspParameters; + + // NOTE: RSACryptoServiceProvider.SignData() fails for other providers when testing locally + if (!subKeyName.Contains(@"RSA and AES")) + { + continue; + } + + using (Microsoft.Win32.RegistryKey providerKey = defaultCryptoProvidersRegistryKey.OpenSubKey(subKeyName)) + { + // Get Provider Name and its type + string providerName = providerKey.Name.Substring(providerKey.Name.LastIndexOf(@"\", StringComparison.Ordinal) + 1); + int providerType = (int)providerKey.GetValue(@"Type"); + + providerCspParameters = new CspParameters(providerType, providerName); + } + + foreach (string connStrAE in DataTestUtility.AEConnStrings) + { + yield return new object[] { connStrAE, providerCspParameters }; + } + } + } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AzureKeyVaultKeyFixture.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AzureKeyVaultKeyFixture.cs index 14a10c5272..4fea191d01 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AzureKeyVaultKeyFixture.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AzureKeyVaultKeyFixture.cs @@ -8,12 +8,12 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { public sealed class AzureKeyVaultKeyFixture : AzureKeyVaultKeyFixtureBase { - public string GeneratedKeyUri { get; } - public AzureKeyVaultKeyFixture() : base(DataTestUtility.AKVBaseUri, DataTestUtility.GetTokenCredential()) { GeneratedKeyUri = CreateKey(nameof(GeneratedKeyUri), 2048).ToString(); } + + public string GeneratedKeyUri { get; } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/DatabaseHelper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/DatabaseHelper.cs index 5b296c9b65..63cfdab974 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/DatabaseHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/DatabaseHelper.cs @@ -342,39 +342,4 @@ public IEnumerator GetEnumerator() } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } - - public class AEConnectionStringProviderWithCspParameters : IEnumerable - { - public IEnumerator GetEnumerator() - { - const string ProvidersRegistryKeyPath = @"SOFTWARE\Microsoft\Cryptography\Defaults\Provider"; - using Microsoft.Win32.RegistryKey defaultCryptoProvidersRegistryKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(ProvidersRegistryKeyPath); - - foreach (string subKeyName in defaultCryptoProvidersRegistryKey.GetSubKeyNames()) - { - CspParameters providerCspParameters; - - // NOTE: RSACryptoServiceProvider.SignData() fails for other providers when testing locally - if (!subKeyName.Contains(@"RSA and AES")) - { - continue; - } - - using (Microsoft.Win32.RegistryKey providerKey = defaultCryptoProvidersRegistryKey.OpenSubKey(subKeyName)) - { - // Get Provider Name and its type - string providerName = providerKey.Name.Substring(providerKey.Name.LastIndexOf(@"\", StringComparison.Ordinal) + 1); - int providerType = (int)providerKey.GetValue(@"Type"); - - providerCspParameters = new CspParameters(providerType, providerName); - } - - foreach (string connStrAE in DataTestUtility.AEConnStrings) - { - yield return new object[] { connStrAE, providerCspParameters }; - } - } - } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs index 3a1f4aef4f..81793ad1e3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs @@ -61,7 +61,7 @@ public class SQLSetupStrategy : ColumnMasterKeyCertificateFixture public SQLSetupStrategy() : base(true) { - ColumnMasterKeyPath = string.Concat(StoreLocation.CurrentUser.ToString(), "/", StoreName.My.ToString(), "/", ColumnMasterKeyCertificate.Thumbprint); + ColumnMasterKeyPath = string.Format("{0}/{1}/{2}", StoreLocation.CurrentUser, StoreName.My, ColumnMasterKeyCertificate.Thumbprint); } protected SQLSetupStrategy(string customKeyPath) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspProvider.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspProvider.cs index 1ee0b4f616..08cdfe48ec 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspProvider.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspProvider.cs @@ -15,8 +15,6 @@ public class SQLSetupStrategyCspProvider : SQLSetupStrategy private readonly List _cspKeyParameters = new List(); - public SqlColumnEncryptionCspProvider CspProvider { get; } - public SQLSetupStrategyCspProvider(CspParameters cspParameters) : base(cspParameters.ProviderName + "/" + cspParameters.KeyContainerName) { @@ -32,6 +30,8 @@ public SQLSetupStrategyCspProvider(CspParameters cspParameters) SetupDatabase(); } + public SqlColumnEncryptionCspProvider CspProvider { get; } + internal override void SetupDatabase() { ColumnMasterKey columnMasterKey = new CspProviderColumnMasterKey(GenerateUniqueName("CspExt"), SqlColumnEncryptionCspProvider.ProviderName, ColumnMasterKeyPath); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestTrustedMasterKeyPaths.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestTrustedMasterKeyPaths.cs index 4f9f57ff6a..c88b2767ea 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestTrustedMasterKeyPaths.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestTrustedMasterKeyPaths.cs @@ -147,17 +147,14 @@ public void TestTrustedColumnEncryptionMasterKeyPathsWithMultipleServers(string } // Add entries for one server - List server1TrustedKeyPaths = new List(); - - // Add some random key paths - foreach (char c in new char[] { 'A', 'B' }) + List server1TrustedKeyPaths = new List() { - string invalidKeyPath = string.Format(@"{0}/my/{1}", StoreLocation.CurrentUser.ToString(), dummyThumbprint); - server1TrustedKeyPaths.Add(invalidKeyPath); - } - - // Add the key path used by the test - server1TrustedKeyPaths.Add(columnMasterKeyPath); + // Add some random key paths + string.Format(@"{0}/my/{1}", StoreLocation.CurrentUser.ToString(), dummyThumbprint), + string.Format(@"{0}/my/{1}", StoreLocation.CurrentUser.ToString(), dummyThumbprint), + // Add the key path used by the test + columnMasterKeyPath + }; // Add it to the dictionary SqlConnection.ColumnEncryptionTrustedMasterKeyPaths.Add(connBuilder.DataSource, server1TrustedKeyPaths); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs index 4bcae36326..471450762c 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs @@ -18,11 +18,8 @@ public class SqlClientCustomTokenCredential : TokenCredential private const string DEFAULT_PREFIX = "/.default"; private const string AKVKeyName = "TestSqlClientAzureKeyVaultProvider"; - private static string AKVUrl = (new Uri(DataTestUtility.AKVBaseUri, $"/keys/{AKVKeyName}")).AbsoluteUri; - string _authority = ""; string _resource = ""; - string _akvUrl = ""; public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) => AcquireTokenAsync().GetAwaiter().GetResult(); @@ -34,10 +31,11 @@ private async Task AcquireTokenAsync() { // Added to reduce HttpClient calls. // For multi-user support, a better design can be implemented as needed. - if (_akvUrl != AKVUrl) + if (string.IsNullOrEmpty(_authority) || string.IsNullOrEmpty(_resource)) { using (HttpClient httpClient = new HttpClient()) { + string AKVUrl = (new Uri(DataTestUtility.AKVBaseUri, $"/keys/{AKVKeyName}")).AbsoluteUri; HttpResponseMessage response = await httpClient.GetAsync(AKVUrl); string challenge = response?.Headers.WwwAuthenticate.FirstOrDefault()?.ToString(); string trimmedChallenge = ValidateChallenge(challenge); @@ -69,8 +67,6 @@ private async Task AcquireTokenAsync() } } } - // Since this is a test, we only create single-instance temp cache - _akvUrl = AKVUrl; } AccessToken accessToken = await AzureActiveDirectoryAuthenticationCallback(_authority, _resource); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index d56ca1f2bc..fe00b85ffc 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -49,6 +49,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/ColumnMasterKeyCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/ColumnMasterKeyCertificateFixture.cs index 162f8d81db..b6706be1c4 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/ColumnMasterKeyCertificateFixture.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/ColumnMasterKeyCertificateFixture.cs @@ -9,13 +9,13 @@ namespace Microsoft.Data.SqlClient.TestUtilities.Fixtures { public class ColumnMasterKeyCertificateFixture : CertificateFixtureBase { - public X509Certificate2 ColumnMasterKeyCertificate { get; } - public ColumnMasterKeyCertificateFixture() : this(true) { } + public X509Certificate2 ColumnMasterKeyCertificate { get; } + protected ColumnMasterKeyCertificateFixture(bool createCertificate) { if (createCertificate) diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CspCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CspCertificateFixture.cs index b6473f4035..7fabaf1b9c 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CspCertificateFixture.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CspCertificateFixture.cs @@ -10,12 +10,6 @@ namespace Microsoft.Data.SqlClient.TestUtilities.Fixtures { public class CspCertificateFixture : CertificateFixtureBase { - public X509Certificate2 CspCertificate { get; } - - public string CspCertificatePath { get; } - - public string CspKeyPath { get; } - public CspCertificateFixture() { CspCertificate = CreateCertificate(nameof(CspCertificate), Array.Empty(), Array.Empty(), true); @@ -26,6 +20,12 @@ public CspCertificateFixture() CspKeyPath = GetCspPathFromCertificate(); } + public X509Certificate2 CspCertificate { get; } + + public string CspCertificatePath { get; } + + public string CspKeyPath { get; } + private string GetCspPathFromCertificate() { RSA privateKey = CspCertificate.GetRSAPrivateKey(); From 23214ce83b92d1a7aa16d0ef9a8223ead70aebc2 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Mon, 24 Feb 2025 19:04:49 +0000 Subject: [PATCH 06/16] Code review Redundant bracket, alphabetised the ManualTesting csproj --- .../ManualTests/DataCommon/SqlClientCustomTokenCredential.cs | 4 ++-- .../Microsoft.Data.SqlClient.ManualTesting.Tests.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs index 471450762c..f9aa8b5664 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs @@ -35,8 +35,8 @@ private async Task AcquireTokenAsync() { using (HttpClient httpClient = new HttpClient()) { - string AKVUrl = (new Uri(DataTestUtility.AKVBaseUri, $"/keys/{AKVKeyName}")).AbsoluteUri; - HttpResponseMessage response = await httpClient.GetAsync(AKVUrl); + string akvUrl = new Uri(DataTestUtility.AKVBaseUri, $"/keys/{AKVKeyName}").AbsoluteUri; + HttpResponseMessage response = await httpClient.GetAsync(akvUrl); string challenge = response?.Headers.WwwAuthenticate.FirstOrDefault()?.ToString(); string trimmedChallenge = ValidateChallenge(challenge); string[] pairs = trimmedChallenge.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index fe00b85ffc..c41de8e98a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -48,9 +48,9 @@ - + From 1f369c6143d7121622d301d4f236338ca9ee382c Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Thu, 27 Mar 2025 12:19:35 -0500 Subject: [PATCH 07/16] Update src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs Let's try @edwardneal's idea Co-authored-by: Edward Neal <55035479+edwardneal@users.noreply.github.com> --- .../AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs index 81793ad1e3..f5910502f0 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs @@ -89,9 +89,9 @@ internal virtual void SetupDatabase() } } - // Insert data for TrustedMasterKeyPaths tests. - InsertSampleData(TrustedMasterKeyPathsTestTable.Name); } + // Insert data for TrustedMasterKeyPaths tests. + InsertSampleData(TrustedMasterKeyPathsTestTable.Name); } protected void InsertSampleData(string tableName) From 8d7c3937f2b47ac04a647ca40a04946746c01655 Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Thu, 27 Mar 2025 12:22:00 -0500 Subject: [PATCH 08/16] Update src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs Co-authored-by: Edward Neal <55035479+edwardneal@users.noreply.github.com> From c51fb6706c839a4849d592b1bfc222f4beac393a Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Tue, 29 Apr 2025 16:02:51 -0500 Subject: [PATCH 09/16] Fixes as per @edwardneal's suggestions --- .../tests/ManualTests/AlwaysEncrypted/AKVTests.cs | 6 +++--- .../Fixtures/CertificateFixtureBase.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs index 7c9a5094d5..78fa0a9001 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs @@ -29,7 +29,7 @@ public AKVTest(SQLSetupStrategyAzureKeyVault fixture) [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE), nameof(DataTestUtility.IsAKVSetupAvailable))] public void TestEncryptDecryptWithAKV() { - SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString) + SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionStringHGSVBS) { ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Enabled, AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified, @@ -70,7 +70,7 @@ It aims to confirm that three consecutive connections will consistently fail wit [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE), nameof(DataTestUtility.IsAKVSetupAvailable))] public void ForcedColumnDecryptErrorTestShouldFail() { - SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString) + SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionStringHGSVBS) { ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Enabled, AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified, @@ -148,7 +148,7 @@ public void TestRoundTripWithAKVAndCertStoreProvider() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE), nameof(DataTestUtility.IsAKVSetupAvailable))] public void TestLocalCekCacheIsScopedToProvider() { - SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString) + SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionStringHGSVBS) { ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Enabled, AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified, diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs index 4ed9312a18..2b95927651 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs @@ -53,7 +53,7 @@ protected X509Certificate2 CreateCertificate(string subjectName, IEnumerable Date: Mon, 5 May 2025 12:45:47 -0500 Subject: [PATCH 10/16] Fix as per @edwardneal's suggestion --- .../Fixtures/CertificateFixtureBase.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs index 2b95927651..8b7c702a64 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs @@ -86,8 +86,22 @@ protected X509Certificate2 CreateCertificate(string subjectName, IEnumerable Date: Wed, 7 May 2025 11:37:05 -0500 Subject: [PATCH 11/16] Fix missing `new` Co-authored-by: Edward Neal <55035479+edwardneal@users.noreply.github.com> --- .../Fixtures/CertificateFixtureBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs index 8b7c702a64..ece7fac018 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs @@ -97,7 +97,7 @@ protected X509Certificate2 CreateCertificate(string subjectName, IEnumerable Date: Thu, 15 May 2025 17:21:31 -0500 Subject: [PATCH 12/16] Update src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs Co-authored-by: Edward Neal <55035479+edwardneal@users.noreply.github.com> --- .../SQLSetupStrategyAzureKeyVault.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs index 32d95fd69b..e64973bcf0 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs @@ -53,9 +53,31 @@ public static void RegisterGlobalProviders(SqlColumnEncryptionAzureKeyVaultProvi IsAKVProviderRegistered = true; } + private static RSA CopyKey(RSA rsa) + { +#if NET8_0 + // In .NET Framework, the key is exportable in plaintext. In .NET 9.0+, we use X509CertificateLoader2 to maintain this functionality. + // We need to manually handle this in .NET 8.0 with an non-plaintext export. + RSA replacementKey = RSA.Create(rsa.KeySize); + Span passwordBytes = stackalloc byte[32]; + PbeParameters pbeParameters = new PbeParameters(PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA256, 10000); + + Random.Shared.NextBytes(passwordBytes); + + replacementKey.ImportEncryptedPkcs8PrivateKey( + passwordBytes, + rsa.ExportEncryptedPkcs8PrivateKey(passwordBytes, pbeParameters), + out _); + return replacementKey; +#else + return rsa; +#endif + } + private void SetupAzureKeyVault() { - JsonWebKey rsaImport = new JsonWebKey(ColumnMasterKeyCertificate.GetRSAPrivateKey(), true); + RSA rsaCopy = CopyKey(ColumnMasterKeyCertificate.GetRSAPrivateKey()); + JsonWebKey rsaImport = new JsonWebKey(rsaCopy, true); string akvKeyName = $"AE-{ColumnMasterKeyCertificate.Thumbprint}"; _keyClient.ImportKey(akvKeyName, rsaImport); From b5220b0b7905316aa44e14276ac30082120f4047 Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Mon, 19 May 2025 16:27:18 -0500 Subject: [PATCH 13/16] Update src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs Co-authored-by: Edward Neal <55035479+edwardneal@users.noreply.github.com> --- .../TestFixtures/SQLSetupStrategyAzureKeyVault.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs index e64973bcf0..f1ebc3a93e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Azure; using Azure.Security.KeyVault.Keys; From 9d0617386c9a7a785f1fe8f078c1bf80f2ab28ac Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Tue, 3 Jun 2025 18:01:52 -0500 Subject: [PATCH 14/16] Address comment that we don't need a CspParameters object as part of the test arguments * Move test arguments into property (the class was only used in a single location) * Cleanup test code * Tweak default provider discovery code to handle edge cases a bit better --- .../AlwaysEncrypted/CspProviderExt.cs | 68 ++++++++++++++++--- ...nnectionStringProviderWithCspParameters.cs | 46 ------------- ....Data.SqlClient.ManualTesting.Tests.csproj | 1 - 3 files changed, 60 insertions(+), 55 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AEConnectionStringProviderWithCspParameters.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs index 24dd9572bc..aade12b956 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs @@ -2,13 +2,16 @@ // 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.IO; using System.Linq; using System.Security.Cryptography.X509Certificates; using Xunit; using System.Security.Cryptography; using Microsoft.Data.SqlClient.TestUtilities.Fixtures; -#if !NETFRAMEWORK +using Microsoft.Win32; + +#if NET using System.Runtime.Versioning; #endif @@ -26,7 +29,7 @@ public class CspProviderExt { // [Fact(Skip="Run this in non-parallel mode")] or [ConditionalFact()] [Fact(Skip = "Failing in TCE")] - public void TestRoundTripWithCSPAndCertStoreProvider() + public void TestRoundTripWithCspAndCertStoreProvider() { using CspCertificateFixture cspCertificateFixture = new CspCertificateFixture(); @@ -48,19 +51,22 @@ public void TestRoundTripWithCSPAndCertStoreProvider() } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] - [ClassData(typeof(AEConnectionStringProviderWithCspParameters))] - public void TestEncryptDecryptWithCSP(string connectionString, CspParameters cspParameters) + [MemberData(nameof(TestEncryptDecryptWithCsp_Data))] + public void TestEncryptDecryptWithCsp(string connectionString, string providerName, int providerType) { string keyIdentifier = DataTestUtility.GetUniqueNameForSqlServer("CSP"); - CspParameters namedCspParameters = new CspParameters(cspParameters.ProviderType, cspParameters.ProviderName, keyIdentifier); + CspParameters namedCspParameters = new CspParameters(providerType, providerName, keyIdentifier); using SQLSetupStrategyCspProvider sqlSetupStrategyCsp = new SQLSetupStrategyCspProvider(namedCspParameters); using SqlConnection sqlConn = new(connectionString); sqlConn.Open(); // Test INPUT parameter on an encrypted parameter - using SqlCommand sqlCommand = new(@$"SELECT CustomerId, FirstName, LastName FROM [{sqlSetupStrategyCsp.ApiTestTable.Name}] WHERE FirstName = @firstName", - sqlConn, null, SqlCommandColumnEncryptionSetting.Enabled); + string commandText = @$"SELECT CustomerId, FirstName, LastName " + + @$"FROM [{sqlSetupStrategyCsp.ApiTestTable.Name}] " + + @$"WHERE FirstName = @firstName"; + using SqlCommand sqlCommand = new(commandText, sqlConn, null, SqlCommandColumnEncryptionSetting.Enabled); + SqlParameter customerFirstParam = sqlCommand.Parameters.AddWithValue(@"firstName", @"Microsoft"); customerFirstParam.Direction = System.Data.ParameterDirection.Input; @@ -68,6 +74,52 @@ public void TestEncryptDecryptWithCSP(string connectionString, CspParameters csp ValidateResultSet(sqlDataReader); } + public static IEnumerable TestEncryptDecryptWithCsp_Data + { + get + { + const string providerRegistryKeyPath = @"SOFTWARE\Microsoft\Cryptography\Defaults\Provider"; + using RegistryKey defaultProviderRegistryKey = Registry.LocalMachine.OpenSubKey(providerRegistryKeyPath); + if (defaultProviderRegistryKey is null) + { + // No test cases can be generated if the registry key doesn't exist. + yield break; + } + + foreach (string subKeyName in defaultProviderRegistryKey.GetSubKeyNames()) + { + // Skip inappropriate providers + if (!subKeyName.Contains(@"RSA and AES")) + { + continue; + } + + // Open the provider + using RegistryKey providerKey = defaultProviderRegistryKey.OpenSubKey(subKeyName); + if (providerKey is null) + { + continue; + } + + // Read provider name + string providerName = Path.GetFileName(providerKey.Name); + + // Read provider type + object providerTypeValue = providerKey.GetValue(@"Type"); + if (providerTypeValue is not int providerType) + { + continue; + } + + // Combine with AE connection strings + foreach (string aeConnectionString in DataTestUtility.AEConnStrings) + { + yield return new object[] { aeConnectionString, providerName, providerType }; + } + } + } + } + /// /// Validates that the results are the ones expected. /// diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AEConnectionStringProviderWithCspParameters.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AEConnectionStringProviderWithCspParameters.cs deleted file mode 100644 index 5c0a81e9b4..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AEConnectionStringProviderWithCspParameters.cs +++ /dev/null @@ -1,46 +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; -using System.Collections.Generic; -using System.Security.Cryptography; - -namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted -{ - public class AEConnectionStringProviderWithCspParameters : IEnumerable - { - public IEnumerator GetEnumerator() - { - const string ProvidersRegistryKeyPath = @"SOFTWARE\Microsoft\Cryptography\Defaults\Provider"; - using Microsoft.Win32.RegistryKey defaultCryptoProvidersRegistryKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(ProvidersRegistryKeyPath); - - foreach (string subKeyName in defaultCryptoProvidersRegistryKey.GetSubKeyNames()) - { - CspParameters providerCspParameters; - - // NOTE: RSACryptoServiceProvider.SignData() fails for other providers when testing locally - if (!subKeyName.Contains(@"RSA and AES")) - { - continue; - } - - using (Microsoft.Win32.RegistryKey providerKey = defaultCryptoProvidersRegistryKey.OpenSubKey(subKeyName)) - { - // Get Provider Name and its type - string providerName = providerKey.Name.Substring(providerKey.Name.LastIndexOf(@"\", StringComparison.Ordinal) + 1); - int providerType = (int)providerKey.GetValue(@"Type"); - - providerCspParameters = new CspParameters(providerType, providerName); - } - - foreach (string connStrAE in DataTestUtility.AEConnStrings) - { - yield return new object[] { connStrAE, providerCspParameters }; - } - } - } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } -} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index 11130390e8..e85ff3ddf6 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -48,7 +48,6 @@ - From f394b97cb480063a7ba07180aeab6210b5096469 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Tue, 3 Jun 2025 18:03:11 -0500 Subject: [PATCH 15/16] Address comment regarding readonly member variables Apply long line chomping --- .../AlwaysEncrypted/ConversionTests.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs index 34ef7f2db4..13795fec44 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs @@ -30,8 +30,6 @@ public sealed class ConversionTests : IDisposable, IClassFixture _databaseObjects = new List(); @@ -57,13 +55,18 @@ public ColumnMetaData(SqlDbType columnType, int columnSize, int precision, int s public ConversionTests(ColumnMasterKeyCertificateFixture fixture) { - certificate = fixture.ColumnMasterKeyCertificate; - columnMasterKey = new CspColumnMasterKey(DatabaseHelper.GenerateUniqueName("CMK"), certificate.Thumbprint, certStoreProvider, DataTestUtility.EnclaveEnabled); - _databaseObjects.Add(columnMasterKey); - - columnEncryptionKey = new ColumnEncryptionKey(DatabaseHelper.GenerateUniqueName("CEK"), - columnMasterKey, - certStoreProvider); + X509Certificate2 certificate = fixture.ColumnMasterKeyCertificate; + ColumnMasterKey columnMasterKey1 = new CspColumnMasterKey( + DatabaseHelper.GenerateUniqueName("CMK"), + certificate.Thumbprint, + certStoreProvider, + DataTestUtility.EnclaveEnabled); + _databaseObjects.Add(columnMasterKey1); + + columnEncryptionKey = new ColumnEncryptionKey( + DatabaseHelper.GenerateUniqueName("CEK"), + columnMasterKey1, + certStoreProvider); _databaseObjects.Add(columnEncryptionKey); foreach (string connectionStr in DataTestUtility.AEConnStringsSetup) From 02470f2d7dcbdb012d99dd2e4253ccb5cbbe2e56 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Tue, 3 Jun 2025 18:12:07 -0500 Subject: [PATCH 16/16] Addressing the last of the comments. --- .../AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs | 4 ++-- .../Fixtures/CertificateFixtureBase.cs | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs index f5910502f0..23ea1a9d79 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs @@ -17,7 +17,7 @@ public class SQLSetupStrategy : ColumnMasterKeyCertificateFixture { internal const string ColumnEncryptionAlgorithmName = @"AEAD_AES_256_CBC_HMAC_SHA256"; - public string ColumnMasterKeyPath { get; protected set; } + public string ColumnMasterKeyPath { get; } public Table ApiTestTable { get; private set; } public Table BulkCopyAEErrorMessageTestTable { get; private set; } public Table BulkCopyAETestTable { get; private set; } @@ -61,7 +61,7 @@ public class SQLSetupStrategy : ColumnMasterKeyCertificateFixture public SQLSetupStrategy() : base(true) { - ColumnMasterKeyPath = string.Format("{0}/{1}/{2}", StoreLocation.CurrentUser, StoreName.My, ColumnMasterKeyCertificate.Thumbprint); + ColumnMasterKeyPath = $"{StoreLocation.CurrentUser}/{StoreName.My}/{ColumnMasterKeyCertificate.Thumbprint}"; } protected SQLSetupStrategy(string customKeyPath) diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs index ece7fac018..bcc0d93c07 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs @@ -14,6 +14,12 @@ namespace Microsoft.Data.SqlClient.TestUtilities.Fixtures { public abstract class CertificateFixtureBase : IDisposable { + /// + /// Certificates must be created using this provider. Certificates created by PowerShell + /// using another provider aren't accessible from RSACryptoServiceProvider, which means + /// that we could not roundtrip between SqlColumnEncryptionCertificateStoreProvider and + /// SqlColumnEncryptionCspProvider. + /// private const string CspProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider"; private sealed class CertificateStoreContext