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/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/ConversionTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs index d5c2ade38b..13795fec44 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,8 +30,6 @@ 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 ColumnMasterKey columnMasterKey; private ColumnEncryptionKey columnEncryptionKey; private SqlColumnEncryptionCertificateStoreProvider certStoreProvider = new SqlColumnEncryptionCertificateStoreProvider(); private List _databaseObjects = new List(); @@ -54,18 +53,20 @@ 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(); - } - 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) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs index f164dff220..aade12b956 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs @@ -2,14 +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 Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; using Xunit; -using System.Collections.Generic; -using static Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.CertificateUtilityWin; -#if !NETFRAMEWORK +using System.Security.Cryptography; +using Microsoft.Data.SqlClient.TestUtilities.Fixtures; +using Microsoft.Win32; + +#if NET using System.Runtime.Versioning; #endif @@ -25,185 +27,97 @@ 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() + public void TestRoundTripWithCspAndCertStoreProvider() { - const string providerName = "Microsoft Enhanced RSA and AES Cryptographic Provider"; - string providerType = "24"; + using CspCertificateFixture cspCertificateFixture = new CspCertificateFixture(); - 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); + X509Certificate2 cert = cspCertificateFixture.CspCertificate; + string cspPath = cspCertificateFixture.CspKeyPath; + string certificatePath = cspCertificateFixture.CspCertificatePath; - SqlColumnEncryptionCertificateStoreProvider certProvider = new SqlColumnEncryptionCertificateStoreProvider(); - SqlColumnEncryptionCspProvider cspProvider = new SqlColumnEncryptionCspProvider(); - byte[] columnEncryptionKey = DatabaseHelper.GenerateRandomBytes(32); + SqlColumnEncryptionCertificateStoreProvider certProvider = new SqlColumnEncryptionCertificateStoreProvider(); + SqlColumnEncryptionCspProvider cspProvider = new SqlColumnEncryptionCspProvider(); + byte[] columnEncryptionKey = DatabaseHelper.GenerateRandomBytes(32); - byte[] encryptedColumnEncryptionKeyUsingCert = certProvider.EncryptColumnEncryptionKey(certificatePath, @"RSA_OAEP", columnEncryptionKey); - byte[] columnEncryptionKeyReturnedCert2CSP = cspProvider.DecryptColumnEncryptionKey(cspPath, @"RSA_OAEP", encryptedColumnEncryptionKeyUsingCert); - Assert.True(columnEncryptionKey.SequenceEqual(columnEncryptionKeyReturnedCert2CSP)); + byte[] encryptedColumnEncryptionKeyUsingCert = certProvider.EncryptColumnEncryptionKey(certificatePath, @"RSA_OAEP", columnEncryptionKey); + byte[] columnEncryptionKeyReturnedCert2CSP = cspProvider.DecryptColumnEncryptionKey(cspPath, @"RSA_OAEP", encryptedColumnEncryptionKeyUsingCert); + Assert.True(columnEncryptionKey.SequenceEqual(columnEncryptionKeyReturnedCert2CSP)); - byte[] encryptedColumnEncryptionKeyUsingCSP = cspProvider.EncryptColumnEncryptionKey(cspPath, @"RSA_OAEP", columnEncryptionKey); - byte[] columnEncryptionKeyReturnedCSP2Cert = certProvider.DecryptColumnEncryptionKey(certificatePath, @"RSA_OAEP", encryptedColumnEncryptionKeyUsingCSP); - Assert.True(columnEncryptionKey.SequenceEqual(columnEncryptionKeyReturnedCSP2Cert)); - - } - 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) + [MemberData(nameof(TestEncryptDecryptWithCsp_Data))] + public void TestEncryptDecryptWithCsp(string connectionString, string providerName, int providerType) { - string providerName = @"Microsoft Enhanced RSA and AES Cryptographic Provider"; string keyIdentifier = DataTestUtility.GetUniqueNameForSqlServer("CSP"); + 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 + 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; + + using SqlDataReader sqlDataReader = sqlCommand.ExecuteReader(); + ValidateResultSet(sqlDataReader); + } - try + public static IEnumerable TestEncryptDecryptWithCsp_Data + { + get { - CertificateUtilityWin.RSAPersistKeyInCsp(providerName, keyIdentifier); - string cspPath = String.Concat(providerName, @"/", keyIdentifier); - - SQLSetupStrategyCspExt sqlSetupStrategyCsp = new SQLSetupStrategyCspExt(cspPath); - string tableName = sqlSetupStrategyCsp.CspProviderTable.Name; - - try + const string providerRegistryKeyPath = @"SOFTWARE\Microsoft\Cryptography\Defaults\Provider"; + using RegistryKey defaultProviderRegistryKey = Registry.LocalMachine.OpenSubKey(providerRegistryKeyPath); + if (defaultProviderRegistryKey is null) { - 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"); - Console.WriteLine(@"Exception: {0}"); - customerFirstParam.Direction = System.Data.ParameterDirection.Input; - - using SqlDataReader sqlDataReader = sqlCommand.ExecuteReader(); - ValidateResultSet(sqlDataReader); + // No test cases can be generated if the registry key doesn't exist. + yield break; } - finally + + foreach (string subKeyName in defaultProviderRegistryKey.GetSubKeyNames()) { - // clean up database resources - sqlSetupStrategyCsp.Dispose(); + // 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 }; + } } } - finally - { - CertificateUtilityWin.RSADeleteKeyInCsp(providerName, keyIdentifier); - } } /// 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/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/AzureKeyVaultKeyFixture.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AzureKeyVaultKeyFixture.cs new file mode 100644 index 0000000000..4fea191d01 --- /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 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/SQLSetupStrategy.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs index 2bc38d9930..23ea1a9d79 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; } 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(true) { - if (certificate == null) - { - certificate = CertificateUtility.CreateCertificate(); - } - keyPath = string.Concat(StoreLocation.CurrentUser.ToString(), "/", StoreName.My.ToString(), "/", certificate.Thumbprint); + ColumnMasterKeyPath = $"{StoreLocation.CurrentUser}/{StoreName.My}/{ColumnMasterKeyCertificate.Thumbprint}"; } - protected SQLSetupStrategy(string customKeyPath) => keyPath = customKeyPath; + protected SQLSetupStrategy(string customKeyPath) + : base(false) + { + ColumnMasterKeyPath = customKeyPath; + } internal virtual void SetupDatabase() { @@ -88,7 +89,15 @@ internal virtual void SetupDatabase() } } - // Insert data for TrustedMasterKeyPaths tests. + } + // 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 @@ -97,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); } } } @@ -146,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); @@ -259,13 +273,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 +284,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..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,10 @@ using System; using System.Collections.Generic; +using System.Security.Cryptography; +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 +17,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 +54,42 @@ 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() + { + RSA rsaCopy = CopyKey(ColumnMasterKeyCertificate.GetRSAPrivateKey()); + JsonWebKey rsaImport = new JsonWebKey(rsaCopy, 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 +106,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..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,12 +19,10 @@ public SQLSetupStrategyCertStoreProvider() : base() SetupDatabase(); } - protected SQLSetupStrategyCertStoreProvider(string customKeyPath) => keyPath = 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 deleted file mode 100644 index 3b965d5e7f..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, keyPath); - 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..08cdfe48ec --- /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 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(); + } + + public SqlColumnEncryptionCspProvider CspProvider { get; } + + 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/CertificateUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs index 8118e60f42..7e564362a5 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_OR_GREATER - 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/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/AlwaysEncrypted/TestTrustedMasterKeyPaths.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestTrustedMasterKeyPaths.cs index c617a990b8..c88b2767ea 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; } @@ -147,18 +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 tempThumbprint = new string('F', CertificateUtility.CreateCertificate().Thumbprint.Length); - string invalidKeyPath = string.Format(@"{0}/my/{1}", StoreLocation.CurrentUser.ToString(), tempThumbprint); - 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); @@ -277,8 +273,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/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index d04467f958..c84e07a6ef 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -39,8 +39,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; @@ -73,7 +71,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"; @@ -200,8 +197,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; @@ -473,7 +468,7 @@ public static bool IsLocalHost() // 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..f9aa8b5664 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs @@ -16,10 +16,10 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests public class SqlClientCustomTokenCredential : TokenCredential { private const string DEFAULT_PREFIX = "/.default"; + private const string AKVKeyName = "TestSqlClientAzureKeyVaultProvider"; string _authority = ""; string _resource = ""; - string _akvUrl = ""; public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) => AcquireTokenAsync().GetAwaiter().GetResult(); @@ -31,11 +31,12 @@ 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 (string.IsNullOrEmpty(_authority) || string.IsNullOrEmpty(_resource)) { using (HttpClient httpClient = new HttpClient()) { - HttpResponseMessage response = await httpClient.GetAsync(DataTestUtility.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); @@ -66,8 +67,6 @@ private async Task AcquireTokenAsync() } } } - // Since this is a test, we only create single-instance temp cache - _akvUrl = DataTestUtility.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 5faa663fa3..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 @@ -30,9 +30,8 @@ - - + @@ -49,6 +48,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/Fixtures/CertificateFixtureBase.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs index 8c948702eb..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,14 @@ 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 { public List Certificates { get; } @@ -32,7 +40,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 @@ -51,10 +59,10 @@ protected static X509Certificate2 CreateCertificate(string subjectName, IEnumera rnd.NextBytes(passwordBytes); password = Convert.ToBase64String(passwordBytes); -#if NET9_0_OR_GREATER +#if NET X500DistinguishedNameBuilder subjectBuilder = new X500DistinguishedNameBuilder(); SubjectAlternativeNameBuilder sanBuilder = new SubjectAlternativeNameBuilder(); - RSA rsaKey = RSA.Create(2048); + RSA rsaKey = CreateRSA(forceCsp); bool hasSans = false; subjectBuilder.AddCommonName(subjectName); @@ -84,8 +92,22 @@ protected static X509Certificate2 CreateCertificate(string subjectName, IEnumera // This is to ensure that it's imported into the certificate stores with its private key. using (X509Certificate2 ephemeral = request.CreateSelfSigned(notBefore, notAfter)) { - return X509CertificateLoader.LoadPkcs12(ephemeral.Export(X509ContentType.Pkcs12, password), password, + #if NET9_0_OR_GREATER + return X509CertificateLoader.LoadPkcs12( + ephemeral.Export(X509ContentType.Pkcs12, password), + password, + X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable, + new Pkcs12LoaderLimits(Pkcs12LoaderLimits.Defaults) + { + PreserveStorageProvider = true, + PreserveKeyName = true + }); + #else + return new X509Certificate2( + ephemeral.Export(X509ContentType.Pkcs12, password), + password, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); + #endif } #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. @@ -100,9 +122,9 @@ 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 + -KeyExportPolicy Exportable -HashAlgorithm SHA256 -Provider ""{5}"" -KeySpec KeyExchange if ($x509 -eq $null) {{ throw ""Certificate was null!"" }} @@ -137,7 +159,7 @@ exit 1 sanString = hasSans ? "\"2.5.29.17={text}" + sanString.Substring(0, sanString.Length - 1) + "\"" : string.Empty; - string formattedCommand = string.Format(PowerShellCommandTemplate, notBefore.ToString("O"), notAfter.ToString("O"), subjectName, sanString, password); + string formattedCommand = string.Format(PowerShellCommandTemplate, notBefore.ToString("O"), notAfter.ToString("O"), subjectName, sanString, password, CspProviderName); ProcessStartInfo startInfo = new() { @@ -190,7 +212,7 @@ exit 1 var code = psProcess.ExitCode; if (code == 0) { - return new X509Certificate2(Convert.FromBase64String(commandOutput), password); + return new X509Certificate2(Convert.FromBase64String(commandOutput), password, X509KeyStorageFlags.Exportable); } Console.WriteLine( @@ -207,6 +229,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); @@ -229,7 +263,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..b6706be1c4 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/ColumnMasterKeyCertificateFixture.cs @@ -0,0 +1,29 @@ +// 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 ColumnMasterKeyCertificateFixture() + : this(true) + { + } + + public X509Certificate2 ColumnMasterKeyCertificate { get; } + + protected ColumnMasterKeyCertificateFixture(bool createCertificate) + { + if (createCertificate) + { + ColumnMasterKeyCertificate = CreateCertificate(nameof(ColumnMasterKeyCertificate), Array.Empty(), Array.Empty()); + + 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..7fabaf1b9c --- /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 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(); + } + + public X509Certificate2 CspCertificate { get; } + + public string CspCertificatePath { get; } + + public string CspKeyPath { get; } + + 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; + } + } + } +} 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 f30ea24019..bcd0bdb27b 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 + +