From ee6e1465ef46dec5b5123ed687d58ea57403f505 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 3 Sep 2025 07:58:26 -0300 Subject: [PATCH 01/11] User Story 38467: Backport mac server name fix - Backported part of #3494 and #3591: - Added configurable test jobs timeout, defaulting to 90 minutes. - Reduced generated database names to 96 chars to try to fix macOS test failures. --- .../jobs/run-tests-package-reference-job.yml | 10 ++++++++++ .../dotnet-sqlclient-signing-pipeline.yml | 7 +++++++ .../ManualTests/DataCommon/DataTestUtility.cs | 17 ++++++++++++++--- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml b/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml index a9fcd12720..9973e362e7 100644 --- a/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml +++ b/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml @@ -17,11 +17,21 @@ parameters: type: string default: empty + # The timeout, in minutes, for this job. + - name: timeout + type: string + default: 90 + jobs: - job: run_tests_package_reference displayName: 'Run tests with package reference' ${{ if ne(parameters.dependsOn, 'empty')}}: dependsOn: '${{parameters.dependsOn }}' + + # Some of our tests take longer than the default 60 minutes to run on some + # OSes and configurations. + timeoutInMinutes: ${{ parameters.timeout }} + pool: type: windows # read more about custom job pool types at https://aka.ms/obpipelines/yaml/jobs isCustom: true diff --git a/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml b/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml index 299a4085fa..b422fa7836 100644 --- a/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml @@ -65,6 +65,12 @@ parameters: # parameters are shown up in ADO UI in a build queue time - NonOfficial - Official +# The timeout, in minutes, for each test job. +- name: testsTimeout + displayName: 'Tests timeout (in minutes)' + type: string + default: 90 + variables: - template: /eng/pipelines/libraries/variables.yml@self - name: packageFolderName @@ -172,6 +178,7 @@ extends: - template: eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml@self parameters: packageFolderName: $(packageFolderName) + timeout: ${{ parameters.testsTimeout }} downloadPackageStep: download: current artifact: $(packageFolderName) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 1375cbea2f..7b080e5128 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -442,7 +442,7 @@ public static string GetUniqueName(string prefix, bool withBracket = true) /// /// Add the prefix to the generate string. /// Database name must be pass with brackets by default. - /// Unique name by considering the Sql Server naming rules. + /// Unique name by considering the Sql Server naming rules, never longer than 96 characters. public static string GetUniqueNameForSqlServer(string prefix, bool withBracket = true) { string extendedPrefix = string.Format( @@ -452,10 +452,21 @@ public static string GetUniqueNameForSqlServer(string prefix, bool withBracket = Environment.MachineName, DateTime.Now.ToString("yyyy_MM_dd", CultureInfo.InvariantCulture)); string name = GetUniqueName(extendedPrefix, withBracket); - if (name.Length > 128) + + // Truncate to no more than 96 characters. + const int maxLen = 96; + if (name.Length > maxLen) { - throw new ArgumentOutOfRangeException("the name is too long - SQL Server names are limited to 128"); + if (withBracket) + { + name = name.Substring(0, maxLen - 1) + ']'; + } + else + { + name = name.Substring(0, maxLen); + } } + return name; } From 0213b163ee04bc27be8156c7e6cea1e3d80af518 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 5 Sep 2025 09:30:46 -0300 Subject: [PATCH 02/11] User Story 38481: Fix unique db object name issues - Fixed the unique name generators to: - Keep max lengths to 30 and 96 characters respectively. - Ensure uniqueness at the start of the names. - Added link to database identifier syntax. --- .../ManualTests/AlwaysEncrypted/ApiShould.cs | 10 +- .../AlwaysEncrypted/CspProviderExt.cs | 49 ---- .../ManualTests/DataCommon/DataTestUtility.cs | 209 ++++++++++++++---- .../ProviderAgnostic/ReaderTest/ReaderTest.cs | 6 +- .../SQL/AdapterTest/AdapterTest.cs | 10 +- .../SQL/ConnectivityTests/ConnectivityTest.cs | 14 +- .../DataClassificationTest.cs | 6 +- .../SQL/DataReaderTest/DataReaderTest.cs | 8 +- .../SQL/DataStreamTest/DataStreamTest.cs | 10 +- .../SQL/ParameterTest/DateTimeVariantTest.cs | 52 ++--- .../SQL/ParameterTest/ParametersTest.cs | 141 +++++++++++- .../ParameterTest/SqlAdapterUpdateBatch.cs | 2 +- .../SQL/ParameterTest/SqlVariantParam.cs | 4 +- .../RetryLogic/SqlCommandReliabilityTest.cs | 4 +- .../SqlConnectionReliabilityTest.cs | 2 +- .../AdjustPrecScaleForBulkCopy.cs | 2 +- .../AzureDistributedTransaction.cs | 2 +- .../CopyWidenNullInexactNumerics.cs | 4 +- .../DataConversionErrorMessageTest.cs | 2 +- .../SQL/SqlCommand/SqlCommandCompletedTest.cs | 2 +- .../SQL/SqlCommand/SqlCommandSetTest.cs | 4 +- .../SqlFileStreamTest/SqlFileStreamTest.cs | 4 +- .../SQL/UdtTest/SqlServerTypesTest.cs | 2 +- .../SQL/UdtTest/UdtBulkCopyTest.cs | 6 +- .../tests/ManualTests/SQL/UdtTest/UdtTest2.cs | 16 +- .../SQL/Utf8SupportTest/Utf8SupportTest.cs | 2 +- 26 files changed, 376 insertions(+), 197 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs index 8e7458f02e..b987bdbc99 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs @@ -148,8 +148,8 @@ public void SqlParameterProperties(string connection) const string firstColumnName = @"firstColumn"; const string secondColumnName = @"secondColumn"; const string thirdColumnName = @"thirdColumn"; - string inputProcedureName = DataTestUtility.GetUniqueName("InputProc").ToString(); - string outputProcedureName = DataTestUtility.GetUniqueName("OutputProc").ToString(); + string inputProcedureName = DataTestUtility.GetShortName("InputProc").ToString(); + string outputProcedureName = DataTestUtility.GetShortName("OutputProc").ToString(); const int charColumnSize = 100; const int decimalColumnPrecision = 10; const int decimalColumnScale = 4; @@ -694,7 +694,7 @@ public void TestExecuteReader(string connection) [ClassData(typeof(AEConnectionStringProvider))] public async void TestExecuteReaderAsyncWithLargeQuery(string connectionString) { - string randomName = DataTestUtility.GetUniqueName(Guid.NewGuid().ToString().Replace("-", ""), false); + string randomName = DataTestUtility.GetShortName(Guid.NewGuid().ToString().Replace("-", ""), false); if (randomName.Length > 50) { randomName = randomName.Substring(0, 50); @@ -878,8 +878,8 @@ public void TestEnclaveStoredProceduresWithAndWithoutParameters(string connectio using SqlCommand sqlCommand = new("", sqlConnection, transaction: null, columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Enabled); - string procWithoutParams = DataTestUtility.GetUniqueName("EnclaveWithoutParams", withBracket: false); - string procWithParam = DataTestUtility.GetUniqueName("EnclaveWithParams", withBracket: false); + string procWithoutParams = DataTestUtility.GetShortName("EnclaveWithoutParams", withBracket: false); + string procWithParam = DataTestUtility.GetShortName("EnclaveWithParams", withBracket: false); try { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs index 49071def2e..e26c67382e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs @@ -157,55 +157,6 @@ public void TestRoundTripWithCSPAndCertStoreProvider() } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] - [ClassData(typeof(AEConnectionStringProvider))] - public void TestEncryptDecryptWithCSP(string connectionString) - { - string providerName = @"Microsoft Enhanced RSA and AES Cryptographic Provider"; - string keyIdentifier = DataTestUtility.GetUniqueNameForSqlServer("CSP"); - - try - { - CertificateUtilityWin.RSAPersistKeyInCsp(providerName, keyIdentifier); - string cspPath = String.Concat(providerName, @"/", keyIdentifier); - - SQLSetupStrategyCspExt sqlSetupStrategyCsp = new SQLSetupStrategyCspExt(cspPath); - string tableName = sqlSetupStrategyCsp.CspProviderTable.Name; - - try - { - using SqlConnection sqlConn = new(connectionString); - sqlConn.Open(); - - Table.DeleteData(tableName, sqlConn); - - // insert 1 row data - Customer customer = new Customer(45, "Microsoft", "Corporation"); - - DatabaseHelper.InsertCustomerData(sqlConn, null, tableName, customer); - - // 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); - } - finally - { - // clean up database resources - sqlSetupStrategyCsp.Dispose(); - } - } - finally - { - CertificateUtilityWin.RSADeleteKeyInCsp(providerName, keyIdentifier); - } - } - /// /// Validates that the results are the ones expected. /// diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 7b080e5128..2723853f27 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -88,7 +88,7 @@ public static bool TcpConnectionStringDoesNotUseAadAuth { get { - SqlConnectionStringBuilder builder = new (TCPConnectionString); + SqlConnectionStringBuilder builder = new(TCPConnectionString); return builder.Authentication == SqlAuthenticationMethod.SqlPassword || builder.Authentication == SqlAuthenticationMethod.NotSpecified; } } @@ -415,59 +415,176 @@ public static bool DoesHostAddressContainBothIPv4AndIPv6() } } + // Generate a new GUID and return the characters from its 1st and 4th + // parts, as shown here: + // + // 7ff01cb8-88c7-11f0-b433-00155d7e531e + // ^^^^^^^^ ^^^^ + // + // These 12 characters are concatenated together without any + // separators. These 2 parts typically comprise a timestamp and clock + // sequence, most likely to be unique for tests that generate names in + // quick succession. + private static string GetGuidParts() + { + var guid = Guid.NewGuid().ToString(); + // GOTCHA: The slice operator is inclusive of the start index and + // exclusive of the end index! + return guid.Substring(0, 8) + guid.Substring(19, 4); + } + /// - /// Generate a unique name to use in Sql Server; - /// some providers does not support names (Oracle supports up to 30). + /// Generate a short unique database object name, whose maximum length + /// is 30 characters, with the format: + /// + /// _ + /// + /// The Prefix will be truncated to satisfy the overall maximum length. + /// + /// The GUID parts will be the characters from the 1st and 4th blocks + /// from a traditional string representation, as shown here: + /// + /// 7ff01cb8-88c7-11f0-b433-00155d7e531e + /// ^^^^^^^^ ^^^^ + /// + /// These 2 parts typically comprise a timestamp and clock sequence, + /// most likely to be unique for tests that generate names in quick + /// succession. The 12 characters are concatenated together without any + /// separators. /// - /// The name length will be no more then (16 + prefix.Length + escapeLeft.Length + escapeRight.Length). - /// Name without brackets. - /// Unique name by considering the Sql Server naming rules. - public static string GetUniqueName(string prefix, bool withBracket = true) - { - string escapeLeft = withBracket ? "[" : string.Empty; - string escapeRight = withBracket ? "]" : string.Empty; - string uniqueName = string.Format("{0}{1}_{2}_{3}{4}", - escapeLeft, - prefix, - DateTime.Now.Ticks.ToString("X", CultureInfo.InvariantCulture), // up to 8 characters - Guid.NewGuid().ToString().Substring(0, 6), // take the first 6 characters only - escapeRight); - return uniqueName; + /// + /// + /// The prefix to use when generating the unique name, truncated to at + /// most 18 characters when withBracket is false, and 16 characters when + /// withBracket is true. + /// + /// This should not contain any characters that cannot be used in + /// database object names. See: + /// + /// https://learn.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?view=sql-server-ver17#rules-for-regular-identifiers + /// + /// + /// + /// When true, the entire generated name will be enclosed in square + /// brackets, for example: + /// + /// [MyPrefix_7ff01cb811f0] + /// + /// + /// + /// A unique database object name, no more than 30 characters long. + /// + public static string GetShortName(string prefix, bool withBracket = true) + { + StringBuilder name = new(30); + + if (withBracket) + { + name.Append('['); + } + + int maxPrefixLength = withBracket ? 16 : 18; + if (prefix.Length > maxPrefixLength) + { + prefix = prefix.Substring(0, maxPrefixLength); + } + + name.Append(prefix); + name.Append('_'); + name.Append(GetGuidParts()); + + if (withBracket) + { + name.Append(']'); + } + + return name.ToString(); } /// - /// Uses environment values `UserName` and `MachineName` in addition to the specified `prefix` and current date - /// to generate a unique name to use in Sql Server; - /// SQL Server supports long names (up to 128 characters), add extra info for troubleshooting. + /// Generate a long unique database object name, whose maximum length is + /// 96 characters, with the format: + /// + /// ___ + /// + /// The Prefix will be truncated to satisfy the overall maximum length. + /// + /// The GUID Parts will be the characters from the 1st and 4th blocks + /// from a traditional string representation, as shown here: + /// + /// 7ff01cb8-88c7-11f0-b433-00155d7e531e + /// ^^^^^^^^ ^^^^ + /// + /// These 2 parts typically comprise a timestamp and clock sequence, + /// most likely to be unique for tests that generate names in quick + /// succession. The 12 characters are concatenated together without any + /// separators. + /// + /// The UserName and MachineName are obtained from the Environment, + /// and will be truncated to satisfy the maximum overall length. /// - /// Add the prefix to the generate string. - /// Database name must be pass with brackets by default. - /// Unique name by considering the Sql Server naming rules, never longer than 96 characters. - public static string GetUniqueNameForSqlServer(string prefix, bool withBracket = true) - { - string extendedPrefix = string.Format( - "{0}_{1}_{2}@{3}", - prefix, - Environment.UserName, - Environment.MachineName, - DateTime.Now.ToString("yyyy_MM_dd", CultureInfo.InvariantCulture)); - string name = GetUniqueName(extendedPrefix, withBracket); - - // Truncate to no more than 96 characters. - const int maxLen = 96; - if (name.Length > maxLen) - { - if (withBracket) - { - name = name.Substring(0, maxLen - 1) + ']'; - } - else - { - name = name.Substring(0, maxLen); - } + /// + /// + /// The prefix to use when generating the unique name, truncated to at + /// most 32 characters. + /// + /// This should not contain any characters that cannot be used in + /// database object names. See: + /// + /// https://learn.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?view=sql-server-ver17#rules-for-regular-identifiers + /// + /// + /// + /// When true, the entire generated name will be enclosed in square + /// brackets, for example: + /// + /// [MyPrefix_7ff01cb811f0_test_user_ci_agent_machine_name] + /// + /// + /// + /// A unique database object name, no more than 96 characters long. + /// + public static string GetLongName(string prefix, bool withBracket = true) + { + StringBuilder name = new(96); + + if (withBracket) + { + name.Append('['); + } + + if (prefix.Length > 32) + { + prefix = prefix.Substring(0, 32); + } + + name.Append(prefix); + name.Append('_'); + name.Append(GetGuidParts()); + name.Append('_'); + + var suffix = + Environment.UserName + '_' + + Environment.MachineName; + + int maxSuffixLength = 96 - name.Length; + if (withBracket) + { + --maxSuffixLength; + } + if (suffix.Length > maxSuffixLength) + { + suffix = suffix.Substring(0, maxSuffixLength); + } + + name.Append(suffix); + + if (withBracket) + { + name.Append(']'); } - return name; + return name.ToString(); } public static bool IsSupportingDistributedTransactions() diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/ReaderTest/ReaderTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/ReaderTest/ReaderTest.cs index 5d09be77f4..5728a8b1ac 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/ReaderTest/ReaderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/ReaderTest/ReaderTest.cs @@ -19,7 +19,7 @@ public static void TestMain() { string connectionString = DataTestUtility.TCPConnectionString; - string tempTable = DataTestUtility.GetUniqueNameForSqlServer("table"); + string tempTable = DataTestUtility.GetLongName("table"); DbProviderFactory provider = SqlClientFactory.Instance; try @@ -275,7 +275,7 @@ public static void TestMain() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public static void SqlDataReader_SqlBuffer_GetFieldValue() { - string tableName = DataTestUtility.GetUniqueNameForSqlServer("SqlBuffer_GetFieldValue"); + string tableName = DataTestUtility.GetLongName("SqlBuffer_GetFieldValue"); DateTimeOffset dtoffset = DateTimeOffset.Now; DateTime dt = DateTime.Now; //Exclude the millisecond because of rounding at some points by SQL Server. @@ -374,7 +374,7 @@ public static void SqlDataReader_SqlBuffer_GetFieldValue() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public static async Task SqlDataReader_SqlBuffer_GetFieldValue_Async() { - string tableName = DataTestUtility.GetUniqueNameForSqlServer("SqlBuffer_GetFieldValue_Async"); + string tableName = DataTestUtility.GetLongName("SqlBuffer_GetFieldValue_Async"); DateTimeOffset dtoffset = DateTimeOffset.Now; DateTime dt = DateTime.Now; //Exclude the millisecond because of rounding at some points by SQL Server. diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs index 9d43567cf1..371550e105 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs @@ -54,7 +54,7 @@ public class AdapterTest public AdapterTest() { // create random name for temp tables - _tempTable = DataTestUtility.GetUniqueName("AdapterTest"); + _tempTable = DataTestUtility.GetShortName("AdapterTest"); _tempTable = _tempTable.Replace('-', '_'); _randomGuid = Guid.NewGuid().ToString(); @@ -487,7 +487,7 @@ public void ParameterTest_AllTypes() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public void ParameterTest_InOut() { - string procName = DataTestUtility.GetUniqueName("P"); + string procName = DataTestUtility.GetShortName("P"); // input, output string spCreateInOut = "CREATE PROCEDURE " + procName + " @in int, @inout int OUTPUT, @out nvarchar(8) OUTPUT " + @@ -768,13 +768,13 @@ public void BulkUpdateTest() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public void UpdateRefreshTest() { - string identTableName = DataTestUtility.GetUniqueName("ID_"); + string identTableName = DataTestUtility.GetShortName("ID_"); string createIdentTable = $"CREATE TABLE {identTableName} (id int IDENTITY," + "LastName nvarchar(50) NULL," + "Firstname nvarchar(50) NULL)"; - string spName = DataTestUtility.GetUniqueName("sp_insert", withBracket: false); + string spName = DataTestUtility.GetShortName("sp_insert", withBracket: false); string spCreateInsert = $"CREATE PROCEDURE {spName}" + "(@FirstName nvarchar(50), @LastName nvarchar(50), @id int OUTPUT) " + @@ -1087,7 +1087,7 @@ public void AutoGenUpdateTest() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public void AutoGenErrorTest() { - string identTableName = DataTestUtility.GetUniqueName("ID_"); + string identTableName = DataTestUtility.GetShortName("ID_"); string createIdentTable = $"CREATE TABLE {identTableName} (id int IDENTITY," + "LastName nvarchar(50) NULL," + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs index 7675643382..dfdbf5229f 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs @@ -368,9 +368,10 @@ public static void ConnectionOpenDisableRetry() { SqlConnectionStringBuilder connectionStringBuilder = new(DataTestUtility.TCPConnectionString) { - InitialCatalog = "DoesNotExist0982532435423", + InitialCatalog = DataTestUtility.GetLongName("DoesNotExist", false), Pooling = false, - ConnectTimeout=15 + ConnectTimeout = 15, + ConnectRetryCount = 3 }; using SqlConnection sqlConnection = new(connectionStringBuilder.ConnectionString); Stopwatch timer = new(); @@ -397,15 +398,6 @@ private static bool CanCreateAliases() return false; } - using (WindowsIdentity identity = WindowsIdentity.GetCurrent()) - { - WindowsPrincipal principal = new(identity); - if (!principal.IsInRole(WindowsBuiltInRole.Administrator)) - { - return false; - } - } - using RegistryKey key = Registry.LocalMachine.OpenSubKey(ConnectToPath, true); if (key == null) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataClassificationTest/DataClassificationTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataClassificationTest/DataClassificationTest.cs index 8119bd2586..24786999e7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataClassificationTest/DataClassificationTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataClassificationTest/DataClassificationTest.cs @@ -18,7 +18,7 @@ public static class DataClassificationTest [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsSupportedDataClassification))] public static void TestDataClassificationResultSetRank() { - s_tableName = DataTestUtility.GetUniqueNameForSqlServer("DC"); + s_tableName = DataTestUtility.GetLongName("DC"); using (SqlConnection sqlConnection = new SqlConnection(DataTestUtility.TCPConnectionString)) using (SqlCommand sqlCommand = sqlConnection.CreateCommand()) { @@ -41,7 +41,7 @@ public static void TestDataClassificationResultSetRank() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsSupportedDataClassification))] public static void TestDataClassificationResultSet() { - s_tableName = DataTestUtility.GetUniqueNameForSqlServer("DC"); + s_tableName = DataTestUtility.GetLongName("DC"); using (SqlConnection sqlConnection = new SqlConnection(DataTestUtility.TCPConnectionString)) using (SqlCommand sqlCommand = sqlConnection.CreateCommand()) { @@ -232,7 +232,7 @@ public static void TestDataClassificationBulkCopy() data.Rows.Add(Guid.NewGuid(), "Company 2", "sample2@contoso.com", 1); data.Rows.Add(Guid.NewGuid(), "Company 3", "sample3@contoso.com", 1); - var tableName = DataTestUtility.GetUniqueNameForSqlServer("DC"); + var tableName = DataTestUtility.GetLongName("DC"); using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString)) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs index b8731ea672..0aaaaf890d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs @@ -123,9 +123,7 @@ public static void CheckSparseColumnBit() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer), nameof(DataTestUtility.IsNotAzureSynapse))] public static void CollatedDataReaderTest() { - var databaseName = DataTestUtility.GetUniqueName("DB"); - // Remove square brackets - var dbName = databaseName.Substring(1, databaseName.Length - 2); + string dbName = DataTestUtility.GetShortName("CollationTest", false); SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString) { @@ -140,7 +138,7 @@ public static void CollatedDataReaderTest() con.Open(); // Create collated database - cmd.CommandText = $"CREATE DATABASE {databaseName} COLLATE KAZAKH_90_CI_AI"; + cmd.CommandText = $"CREATE DATABASE {dbName} COLLATE KAZAKH_90_CI_AI"; cmd.ExecuteNonQuery(); //Create connection without pooling in order to delete database later. @@ -165,7 +163,7 @@ public static void CollatedDataReaderTest() } finally { - cmd.CommandText = $"DROP DATABASE {databaseName}"; + cmd.CommandText = $"DROP DATABASE {dbName}"; cmd.ExecuteNonQuery(); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs index 30a1505442..e622121cbb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs @@ -50,7 +50,7 @@ public static async Task AsyncMultiPacketStreamRead() byte[] inputData = null; byte[] outputData = null; - string tableName = DataTestUtility.GetUniqueNameForSqlServer("data"); + string tableName = DataTestUtility.GetLongName("data"); using (SqlConnection connection = new(connectionString)) { @@ -546,7 +546,7 @@ private static void RowBuffer(string connectionString) private static void TimestampRead(string connectionString) { - string tempTable = DataTestUtility.GetUniqueNameForSqlServer("##Temp"); + string tempTable = DataTestUtility.GetLongName("##Temp"); tempTable = tempTable.Replace('-', '_'); using (SqlConnection conn = new SqlConnection(connectionString)) @@ -1041,7 +1041,7 @@ private static void SequentialAccess(string connectionString) private static void NumericRead(string connectionString) { - string tempTable = DataTestUtility.GetUniqueNameForSqlServer("##Temp"); + string tempTable = DataTestUtility.GetLongName("##Temp"); tempTable = tempTable.Replace('-', '_'); using (SqlConnection conn = new SqlConnection(connectionString)) @@ -1872,8 +1872,8 @@ private static void StreamingBlobDataTypes(string connectionString) private static void VariantCollationsTest(string connectionString) { - string dbName = DataTestUtility.GetUniqueName("JPN"); - string tableName = DataTestUtility.GetUniqueName("T"); + string dbName = DataTestUtility.GetShortName("JPN"); + string tableName = DataTestUtility.GetShortName("T"); using (SqlConnection connection = new SqlConnection(connectionString)) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTest.cs index 31c232e3d0..20768e9329 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTest.cs @@ -75,7 +75,7 @@ private static void TestSimpleParameter_Type(object paramValue, string expectedT { string tag = "TestSimpleParameter_Type"; DisplayHeader(tag, paramValue, expectedBaseTypeName); - string procName = DataTestUtility.GetUniqueNameForSqlServer("paramProc1"); + string procName = DataTestUtility.GetLongName("paramProc1"); try { using SqlConnection conn = new(s_connStr); @@ -115,7 +115,7 @@ private static void TestSimpleParameter_Variant(object paramValue, string expect { string tag = "TestSimpleParameter_Variant"; DisplayHeader(tag, paramValue, expectedBaseTypeName); - string procName = DataTestUtility.GetUniqueNameForSqlServer("paramProc2"); + string procName = DataTestUtility.GetLongName("paramProc2"); try { using SqlConnection conn = new(s_connStr); @@ -153,7 +153,7 @@ private static void TestSqlDataRecordParameterToTVP_Type(object paramValue, stri { string tag = "TestSqlDataRecordParameterToTVP_Type"; DisplayHeader(tag, paramValue, expectedBaseTypeName); - string tvpTypeName = DataTestUtility.GetUniqueNameForSqlServer("tvpType"); + string tvpTypeName = DataTestUtility.GetLongName("tvpType"); try { using SqlConnection conn = new(s_connStr); @@ -200,7 +200,7 @@ private static void TestSqlDataRecordParameterToTVP_Variant(object paramValue, s { string tag = "TestSqlDataRecordParameterToTVP_Variant"; DisplayHeader(tag, paramValue, expectedBaseTypeName); - string tvpTypeName = DataTestUtility.GetUniqueNameForSqlServer("tvpVariant"); + string tvpTypeName = DataTestUtility.GetLongName("tvpVariant"); try { using SqlConnection conn = new(s_connStr); @@ -245,7 +245,7 @@ private static void TestSqlDataReaderParameterToTVP_Type(object paramValue, stri { string tag = "TestSqlDataReaderParameterToTVP_Type"; DisplayHeader(tag, paramValue, expectedBaseTypeName); - string tvpTypeName = DataTestUtility.GetUniqueNameForSqlServer("tvpType"); + string tvpTypeName = DataTestUtility.GetLongName("tvpType"); try { using SqlConnection conn = new(s_connStr); @@ -295,7 +295,7 @@ private static void TestSqlDataReaderParameterToTVP_Variant(object paramValue, s { string tag = "TestSqlDataReaderParameterToTVP_Variant"; DisplayHeader(tag, paramValue, expectedBaseTypeName); - string tvpTypeName = DataTestUtility.GetUniqueNameForSqlServer("tvpVariant"); + string tvpTypeName = DataTestUtility.GetLongName("tvpVariant"); try { using SqlConnection conn = new(s_connStr); @@ -347,10 +347,10 @@ private static void TestSqlDataReader_TVP_Type(object paramValue, string expecte { string tag = "TestSqlDataReader_TVP_Type"; DisplayHeader(tag, paramValue, expectedBaseTypeName); - string tvpTypeName = DataTestUtility.GetUniqueNameForSqlServer("tvpType"); - string InputTableName = DataTestUtility.GetUniqueNameForSqlServer("InputTable"); - string OutputTableName = DataTestUtility.GetUniqueNameForSqlServer("OutputTable"); - string ProcName = DataTestUtility.GetUniqueNameForSqlServer("spTVPProc"); + string tvpTypeName = DataTestUtility.GetLongName("tvpType"); + string InputTableName = DataTestUtility.GetLongName("InputTable"); + string OutputTableName = DataTestUtility.GetLongName("OutputTable"); + string ProcName = DataTestUtility.GetLongName("spTVPProc"); try { using SqlConnection conn = new(s_connStr); @@ -428,10 +428,10 @@ private static void TestSqlDataReader_TVP_Variant(object paramValue, string expe { string tag = "TestSqlDataReader_TVP_Variant"; DisplayHeader(tag, paramValue, expectedBaseTypeName); - string tvpTypeName = DataTestUtility.GetUniqueNameForSqlServer("tvpVariant_DRdrTVPVar"); - string InputTableName = DataTestUtility.GetUniqueNameForSqlServer("InputTable"); - string OutputTableName = DataTestUtility.GetUniqueNameForSqlServer("OutputTable"); - string ProcName = DataTestUtility.GetUniqueNameForSqlServer("spTVPProc_DRdrTVPVar"); + string tvpTypeName = DataTestUtility.GetLongName("tvpVariant_DRdrTVPVar"); + string InputTableName = DataTestUtility.GetLongName("InputTable"); + string OutputTableName = DataTestUtility.GetLongName("OutputTable"); + string ProcName = DataTestUtility.GetLongName("spTVPProc_DRdrTVPVar"); try { using SqlConnection conn = new(s_connStr); @@ -512,8 +512,8 @@ private static void TestSimpleDataReader_Type(object paramValue, string expected { string tag = "TestSimpleDataReader_Type"; DisplayHeader(tag, paramValue, expectedBaseTypeName); - string inputTable = DataTestUtility.GetUniqueNameForSqlServer("inputTable"); - string procName = DataTestUtility.GetUniqueNameForSqlServer("paramProc3"); + string inputTable = DataTestUtility.GetLongName("inputTable"); + string procName = DataTestUtility.GetLongName("paramProc3"); try { using SqlConnection conn = new(s_connStr); @@ -568,8 +568,8 @@ private static void TestSimpleDataReader_Variant(object paramValue, string expec { string tag = "TestSimpleDataReader_Variant"; DisplayHeader(tag, paramValue, expectedBaseTypeName); - string inputTable = DataTestUtility.GetUniqueNameForSqlServer("inputTable"); - string procName = DataTestUtility.GetUniqueNameForSqlServer("paramProc4"); + string inputTable = DataTestUtility.GetLongName("inputTable"); + string procName = DataTestUtility.GetLongName("paramProc4"); try { using SqlConnection conn = new(s_connStr); @@ -624,8 +624,8 @@ private static void SqlBulkCopySqlDataReader_Type(object paramValue, string expe { string tag = "SqlBulkCopySqlDataReader_Type"; DisplayHeader(tag, paramValue, expectedBaseTypeName); - string bulkCopySrcTableName = DataTestUtility.GetUniqueNameForSqlServer("bulkSrcTable"); - string bulkCopyTableName = DataTestUtility.GetUniqueNameForSqlServer("bulkDestTable"); + string bulkCopySrcTableName = DataTestUtility.GetLongName("bulkSrcTable"); + string bulkCopyTableName = DataTestUtility.GetLongName("bulkDestTable"); try { using SqlConnection conn = new(s_connStr); @@ -698,8 +698,8 @@ private static void SqlBulkCopySqlDataReader_Variant(object paramValue, string e { string tag = "SqlBulkCopySqlDataReader_Variant"; DisplayHeader(tag, paramValue, expectedBaseTypeName); - string bulkCopySrcTableName = DataTestUtility.GetUniqueNameForSqlServer("bulkSrcTable"); - string bulkCopyTableName = DataTestUtility.GetUniqueNameForSqlServer("bulkDestTable"); + string bulkCopySrcTableName = DataTestUtility.GetLongName("bulkSrcTable"); + string bulkCopyTableName = DataTestUtility.GetLongName("bulkDestTable"); try { using SqlConnection conn = new(s_connStr); @@ -776,7 +776,7 @@ private static void SqlBulkCopyDataTable_Type(object paramValue, string expected { string tag = "SqlBulkCopyDataTable_Type"; DisplayHeader(tag, paramValue, expectedBaseTypeName); - string bulkCopyTableName = DataTestUtility.GetUniqueNameForSqlServer("bulkDestType"); + string bulkCopyTableName = DataTestUtility.GetLongName("bulkDestType"); try { using SqlConnection conn = new(s_connStr); @@ -836,7 +836,7 @@ private static void SqlBulkCopyDataTable_Variant(object paramValue, string expec { string tag = "SqlBulkCopyDataTable_Variant"; DisplayHeader(tag, paramValue, expectedBaseTypeName); - string bulkCopyTableName = DataTestUtility.GetUniqueNameForSqlServer("bulkDestVariant"); + string bulkCopyTableName = DataTestUtility.GetLongName("bulkDestVariant"); try { using SqlConnection conn = new(s_connStr); @@ -886,7 +886,7 @@ private static void SqlBulkCopyDataRow_Type(object paramValue, string expectedTy { string tag = "SqlBulkCopyDataRow_Type"; DisplayHeader(tag, paramValue, expectedBaseTypeName); - string bulkCopyTableName = DataTestUtility.GetUniqueNameForSqlServer("bulkDestType"); + string bulkCopyTableName = DataTestUtility.GetLongName("bulkDestType"); try { using SqlConnection conn = new(s_connStr); @@ -941,7 +941,7 @@ private static void SqlBulkCopyDataRow_Variant(object paramValue, string expecte { string tag = "SqlBulkCopyDataRow_Variant"; DisplayHeader(tag, paramValue, expectedBaseTypeName); - string bulkCopyTableName = DataTestUtility.GetUniqueNameForSqlServer("bulkDestVariant"); + string bulkCopyTableName = DataTestUtility.GetLongName("bulkDestVariant"); try { using SqlConnection conn = new(s_connStr); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs index 93f31bf9f1..7182ce768c 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs @@ -8,6 +8,7 @@ using System.Data; using System.Data.SqlTypes; using System.Threading; +using Microsoft.Data.SqlClient.Server; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -111,7 +112,7 @@ public static void CodeCoverageSqlClient() public static void Test_Copy_SqlParameter() { using var conn = new SqlConnection(s_connString); - string cTableName = DataTestUtility.GetUniqueNameForSqlServer("#tmp"); + string cTableName = DataTestUtility.GetLongName("#tmp"); try { // Create tmp table @@ -253,9 +254,9 @@ public static void TestParametersWithDatatablesTVPInsert() }; using SqlConnection connection = new(builder.ConnectionString); - string tableName = DataTestUtility.GetUniqueNameForSqlServer("Table"); - string procName = DataTestUtility.GetUniqueNameForSqlServer("Proc"); - string typeName = DataTestUtility.GetUniqueName("Type"); + string tableName = DataTestUtility.GetLongName("Table"); + string procName = DataTestUtility.GetLongName("Proc"); + string typeName = DataTestUtility.GetShortName("Type"); try { connection.Open(); @@ -306,6 +307,126 @@ public static void TestParametersWithDatatablesTVPInsert() } } +#if !NETFRAMEWORK + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + public static void TestDateOnlyTVPDataTable_CommandSP() + { + string tableTypeName = "[dbo]." + DataTestUtility.GetLongName("UDTTTestDateOnlyTVP"); + string spName = DataTestUtility.GetLongName("spTestDateOnlyTVP"); + SqlConnection connection = new(s_connString); + try + { + connection.Open(); + using (SqlCommand cmd = connection.CreateCommand()) + { + cmd.CommandType = CommandType.Text; + cmd.CommandText = $"CREATE TYPE {tableTypeName} AS TABLE ([DateColumn] date NULL, [TimeColumn] time NULL)"; + cmd.ExecuteNonQuery(); + cmd.CommandText = $"CREATE PROCEDURE {spName} (@dates {tableTypeName} READONLY) AS SELECT COUNT(*) FROM @dates"; + cmd.ExecuteNonQuery(); + } + using (SqlCommand cmd = connection.CreateCommand()) + { + cmd.CommandText = spName; + cmd.CommandType = CommandType.StoredProcedure; + + DataTable dtTest = new(); + dtTest.Columns.Add(new DataColumn("DateColumn", typeof(DateOnly))); + dtTest.Columns.Add(new DataColumn("TimeColumn", typeof(TimeOnly))); + var dataRow = dtTest.NewRow(); + dataRow["DateColumn"] = new DateOnly(2023, 11, 15); + dataRow["TimeColumn"] = new TimeOnly(12, 30, 45); + dtTest.Rows.Add(dataRow); + + cmd.Parameters.Add(new SqlParameter + { + ParameterName = "@dates", + SqlDbType = SqlDbType.Structured, + TypeName = tableTypeName, + Value = dtTest, + }); + + cmd.ExecuteNonQuery(); + } + } + finally + { + DataTestUtility.DropStoredProcedure(connection, spName); + DataTestUtility.DropUserDefinedType(connection, tableTypeName); + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + public static void TestDateOnlyTVPSqlDataRecord_CommandSP() + { + string tableTypeName = "[dbo]." + DataTestUtility.GetLongName("UDTTTestDateOnlySqlDataRecordTVP"); + string spName = DataTestUtility.GetLongName("spTestDateOnlySqlDataRecordTVP"); + SqlConnection connection = new(s_connString); + try + { + connection.Open(); + using (SqlCommand cmd = connection.CreateCommand()) + { + cmd.CommandType = CommandType.Text; + cmd.CommandText = $"CREATE TYPE {tableTypeName} AS TABLE ([DateColumn] date NULL, [TimeColumn] time NULL)"; + cmd.ExecuteNonQuery(); + cmd.CommandText = $"CREATE PROCEDURE {spName} (@dates {tableTypeName} READONLY) AS SELECT COUNT(*) FROM @dates"; + cmd.ExecuteNonQuery(); + } + using (SqlCommand cmd = connection.CreateCommand()) + { + cmd.CommandText = spName; + cmd.CommandType = CommandType.StoredProcedure; + + SqlMetaData[] metadata = new SqlMetaData[] + { + new SqlMetaData("DateColumn", SqlDbType.Date), + new SqlMetaData("TimeColumn", SqlDbType.Time) + }; + + SqlDataRecord record1 = new SqlDataRecord(metadata); + record1.SetValues(new DateOnly(2023, 11, 15), new TimeOnly(12, 30, 45)); + + SqlDataRecord record2 = new SqlDataRecord(metadata); + record2.SetValues(new DateOnly(2025, 11, 15), new TimeOnly(13, 31, 46)); + + IList featureInserts = new List + { + record1, + record2, + }; + + cmd.Parameters.Add(new SqlParameter + { + ParameterName = "@dates", + SqlDbType = SqlDbType.Structured, + TypeName = tableTypeName, + Value = featureInserts, + }); + + using var reader = cmd.ExecuteReader(); + + Assert.True(reader.HasRows); + + int count = 0; + while (reader.Read()) + { + Assert.NotNull(reader[0]); + count++; + } + + Assert.Equal(1, count); + } + } + finally + { + DataTestUtility.DropStoredProcedure(connection, spName); + DataTestUtility.DropUserDefinedType(connection, tableTypeName); + } + } +#endif + #region Scaled Decimal Parameter & TVP Test [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] [InlineData("CAST(1.0 as decimal(38, 37))", "1.0000000000000000000000000000")] @@ -360,7 +481,7 @@ public static void SqlDecimalConvertToDecimal_TestOutOfRange(string sqlDecimalVa [ClassData(typeof(ConnectionStringsProvider))] public static void TestScaledDecimalParameter_CommandInsert(string connectionString, bool truncateScaledDecimal) { - string tableName = DataTestUtility.GetUniqueNameForSqlServer("TestDecimalParameterCMD"); + string tableName = DataTestUtility.GetLongName("TestDecimalParameterCMD"); using SqlConnection connection = InitialDatabaseTable(connectionString, tableName); try { @@ -392,7 +513,7 @@ public static void TestScaledDecimalParameter_CommandInsert(string connectionStr [ClassData(typeof(ConnectionStringsProvider))] public static void TestScaledDecimalParameter_BulkCopy(string connectionString, bool truncateScaledDecimal) { - string tableName = DataTestUtility.GetUniqueNameForSqlServer("TestDecimalParameterBC"); + string tableName = DataTestUtility.GetLongName("TestDecimalParameterBC"); using SqlConnection connection = InitialDatabaseTable(connectionString, tableName); try { @@ -426,9 +547,9 @@ public static void TestScaledDecimalParameter_BulkCopy(string connectionString, [ClassData(typeof(ConnectionStringsProvider))] public static void TestScaledDecimalTVP_CommandSP(string connectionString, bool truncateScaledDecimal) { - string tableName = DataTestUtility.GetUniqueNameForSqlServer("TestDecimalParameterBC"); - string tableTypeName = DataTestUtility.GetUniqueNameForSqlServer("UDTTTestDecimalParameterBC"); - string spName = DataTestUtility.GetUniqueNameForSqlServer("spTestDecimalParameterBC"); + string tableName = DataTestUtility.GetLongName("TestDecimalParameterBC"); + string tableTypeName = DataTestUtility.GetLongName("UDTTTestDecimalParameterBC"); + string spName = DataTestUtility.GetLongName("spTestDecimalParameterBC"); using SqlConnection connection = InitialDatabaseUDTT(connectionString, tableName, tableTypeName, spName); try { @@ -713,7 +834,7 @@ private static void EnableOptimizedParameterBinding_ReturnSucceeds() { int firstInput = 12; - string sprocName = DataTestUtility.GetUniqueName("P"); + string sprocName = DataTestUtility.GetShortName("P"); // input, output string createSprocQuery = "CREATE PROCEDURE " + sprocName + " @in int " + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlAdapterUpdateBatch.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlAdapterUpdateBatch.cs index 7f383e8201..aa59bc319c 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlAdapterUpdateBatch.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlAdapterUpdateBatch.cs @@ -15,7 +15,7 @@ public class SqlAdapterUpdateBatch [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public void SqlAdapterTest() { - string tableName = DataTestUtility.GetUniqueNameForSqlServer("Adapter"); + string tableName = DataTestUtility.GetLongName("Adapter"); string tableNameNoBrackets = tableName.Substring(1, tableName.Length - 2); try { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParam.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParam.cs index 2d11274191..e1592825b1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParam.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParam.cs @@ -108,7 +108,7 @@ private static void SendVariantParam(object paramValue, string expectedTypeName, /// private static void SendVariantBulkCopy(object paramValue, string expectedTypeName, string expectedBaseTypeName) { - string bulkCopyTableName = DataTestUtility.GetUniqueNameForSqlServer("bulkDest"); + string bulkCopyTableName = DataTestUtility.GetLongName("bulkDest"); // Fetch reader using type. using SqlDataReader dr = GetReaderForVariant(paramValue, false); @@ -194,7 +194,7 @@ private static void SendVariantBulkCopy(object paramValue, string expectedTypeNa /// private static void SendVariantTvp(object paramValue, string expectedTypeName, string expectedBaseTypeName) { - string tvpTypeName = DataTestUtility.GetUniqueNameForSqlServer("tvpVariant"); + string tvpTypeName = DataTestUtility.GetLongName("tvpVariant"); using SqlConnection connTvp = new(s_connStr); connTvp.Open(); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlCommandReliabilityTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlCommandReliabilityTest.cs index 9e3ed81af4..dc467e3775 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlCommandReliabilityTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlCommandReliabilityTest.cs @@ -240,7 +240,7 @@ public void RetryExecuteUnauthorizedSqlStatementDML(string cnnString, SqlRetryLo public void DropDatabaseWithActiveConnection(string cnnString, SqlRetryLogicBaseProvider provider) { int currentRetries = 0; - string database = DataTestUtility.GetUniqueNameForSqlServer($"RetryLogic_{provider.RetryLogic.RetryIntervalEnumerator.GetType().Name}", false); + string database = DataTestUtility.GetLongName($"RetryLogic_{provider.RetryLogic.RetryIntervalEnumerator.GetType().Name}", false); var builder = new SqlConnectionStringBuilder(cnnString) { InitialCatalog = database, @@ -302,7 +302,7 @@ public void DropDatabaseWithActiveConnection(string cnnString, SqlRetryLogicBase public void UpdateALockedTable(string cnnString, SqlRetryLogicBaseProvider provider) { int currentRetries = 0; - string tableName = DataTestUtility.GetUniqueNameForSqlServer("Region"); + string tableName = DataTestUtility.GetLongName("Region"); string fieldName = "RegionDescription"; using (var cnn1 = new SqlConnection(cnnString)) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs index c78c060677..d6f1e39f77 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs @@ -58,7 +58,7 @@ public void ConnectionCancelRetryOpenInvalidCatalog(string cnnString, SqlRetryLo public void CreateDatabaseWhileTryingToConnect(string cnnString, SqlRetryLogicBaseProvider provider) { int currentRetries = 0; - string database = DataTestUtility.GetUniqueNameForSqlServer($"RetryLogic_{provider.RetryLogic.RetryIntervalEnumerator.GetType().Name}", false); + string database = DataTestUtility.GetLongName($"RetryLogic_{provider.RetryLogic.RetryIntervalEnumerator.GetType().Name}", false); var builder = new SqlConnectionStringBuilder(cnnString) { InitialCatalog = database, diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs index 72bab47869..a845710d50 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs @@ -41,7 +41,7 @@ public static void RunTest() private static SqlDecimal BulkCopySqlDecimalToTable(SqlDecimal decimalValue, int sourcePrecision, int sourceScale, int targetPrecision, int targetScale) { - string tableName = DataTestUtility.GetUniqueNameForSqlServer("Table"); + string tableName = DataTestUtility.GetLongName("Table"); string connectionString = DataTestUtility.TCPConnectionString; SqlDecimal resultValue; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AzureDistributedTransaction.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AzureDistributedTransaction.cs index 823bc50a9d..2a853d7ed4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AzureDistributedTransaction.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AzureDistributedTransaction.cs @@ -11,7 +11,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests public class AzureDistributedTransaction { private static readonly string s_connectionString = DataTestUtility.TCPConnectionString; - private static readonly string s_tableName = DataTestUtility.GetUniqueNameForSqlServer("Azure"); + private static readonly string s_tableName = DataTestUtility.GetLongName("Azure"); private static readonly string s_createTableCmd = $"CREATE TABLE {s_tableName} (NAME NVARCHAR(40), AGE INT)"; private static readonly string s_sqlBulkCopyCmd = "SELECT * FROM(VALUES ('Fuller', 33), ('Davon', 49)) AS q (FirstName, Age)"; private static readonly int s_commandTimeout = 30; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWidenNullInexactNumerics.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWidenNullInexactNumerics.cs index 5ccda71fb9..f961521233 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWidenNullInexactNumerics.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWidenNullInexactNumerics.cs @@ -12,8 +12,8 @@ public class CopyWidenNullInexactNumerics { public static void Test(string sourceDatabaseConnectionString, string destinationDatabaseConnectionString) { - string sourceTableName = DataTestUtility.GetUniqueNameForSqlServer("BCP_SRC"); - string destTableName = DataTestUtility.GetUniqueNameForSqlServer("BCP_DST"); + string sourceTableName = DataTestUtility.GetLongName("BCP_SRC"); + string destTableName = DataTestUtility.GetLongName("BCP_DST"); // this test copies float and real inexact numeric types into decimal targets using bulk copy to check that the widening of the type succeeds. using (var sourceConnection = new SqlConnection(sourceDatabaseConnectionString)) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/DataConversionErrorMessageTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/DataConversionErrorMessageTest.cs index 4c3d594ad1..4a722dd409 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/DataConversionErrorMessageTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/DataConversionErrorMessageTest.cs @@ -28,7 +28,7 @@ public InitialDatabase() srcConstr = DataTestUtility.TCPConnectionString; Connection = new SqlConnection(srcConstr); - TableName = DataTestUtility.GetUniqueNameForSqlServer("SqlBulkCopyTest_CopyStringToIntTest_"); + TableName = DataTestUtility.GetLongName("SqlBulkCopyTest_CopyStringToIntTest_"); InitialTable(Connection, TableName); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCompletedTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCompletedTest.cs index 8e38bee7c0..21ff771ac0 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCompletedTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCompletedTest.cs @@ -11,7 +11,7 @@ public static class SqlCommandCompletedTest [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] public static void VerifyStatmentCompletedCalled() { - string tableName = DataTestUtility.GetUniqueNameForSqlServer("stmt"); + string tableName = DataTestUtility.GetLongName("stmt"); using (var conn = new SqlConnection(s_connStr)) using (var cmd = conn.CreateCommand()) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandSetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandSetTest.cs index 26b11055c2..7f28a4a09a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandSetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandSetTest.cs @@ -15,8 +15,8 @@ public class SqlCommandSetTest [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public void TestByteArrayParameters() { - string tableName = DataTestUtility.GetUniqueNameForSqlServer("CMD"); - string procName = DataTestUtility.GetUniqueNameForSqlServer("CMD"); + string tableName = DataTestUtility.GetLongName("CMD"); + string procName = DataTestUtility.GetLongName("CMD"); byte[] bArray = new byte[] { 1, 2, 3 }; using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString)) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlFileStreamTest/SqlFileStreamTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlFileStreamTest/SqlFileStreamTest.cs index 09c369e11e..3bea30c6b8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlFileStreamTest/SqlFileStreamTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlFileStreamTest/SqlFileStreamTest.cs @@ -221,7 +221,7 @@ private static string SetupFileStreamDB() fileStreamDir += "\\"; } - string dbName = DataTestUtility.GetUniqueName("FS", false); + string dbName = DataTestUtility.GetShortName("FS", false); string createDBQuery = @$"CREATE DATABASE [{dbName}] ON PRIMARY (NAME = PhotoLibrary_data, @@ -266,7 +266,7 @@ private static void DropFileStreamDb(string connString) private static string SetupTable(string connString) { // Generate random table name - string tempTable = DataTestUtility.GetUniqueNameForSqlServer("fs"); + string tempTable = DataTestUtility.GetLongName("fs"); // Create table string createTable = $"CREATE TABLE {tempTable} (EmployeeId INT NOT NULL PRIMARY KEY, Photo VARBINARY(MAX) FILESTREAM NULL, RowGuid UNIQUEIDENTIFIER NOT NULL ROWGUIDCOL UNIQUE DEFAULT NEWID() ) "; ExecuteNonQueryCommand(createTable, connString); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/SqlServerTypesTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/SqlServerTypesTest.cs index 0e64b091b6..8f697af02e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/SqlServerTypesTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/SqlServerTypesTest.cs @@ -434,7 +434,7 @@ private static string GetUdtName(Type udtClrType) [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public static void TestSqlServerTypesInsertAndRead() { - string tableName = DataTestUtility.GetUniqueNameForSqlServer("Type"); + string tableName = DataTestUtility.GetLongName("Type"); string allTypesSQL = @$" if not exists (select * from sysobjects where name='{tableName}' and xtype='U') Begin diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtBulkCopyTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtBulkCopyTest.cs index 90ceed4952..5f17036b4f 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtBulkCopyTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtBulkCopyTest.cs @@ -18,9 +18,9 @@ public void RunCopyTest() _connStr = (new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { InitialCatalog = DataTestUtility.UdtTestDbName }).ConnectionString; SqlConnection conn = new SqlConnection(_connStr); - string cities = DataTestUtility.GetUniqueNameForSqlServer("UdtBulkCopy_cities"); - string customers = DataTestUtility.GetUniqueNameForSqlServer("UdtBulkCopy_customers"); - string circles = DataTestUtility.GetUniqueNameForSqlServer("UdtBulkCopy_circles"); + string cities = DataTestUtility.GetLongName("UdtBulkCopy_cities"); + string customers = DataTestUtility.GetLongName("UdtBulkCopy_customers"); + string circles = DataTestUtility.GetLongName("UdtBulkCopy_circles"); conn.Open(); try diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs index 1f07242ee3..ef38e43dda 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs @@ -84,8 +84,8 @@ public void UDTParams_Binary() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsUdtTestDatabasePresent), nameof(DataTestUtility.AreConnStringsSetup))] public void UDTParams_Invalid2() { - string spInsertCustomer = DataTestUtility.GetUniqueNameForSqlServer("spUdtTest2_InsertCustomer"); - string tableName = DataTestUtility.GetUniqueNameForSqlServer("UdtTest2"); + string spInsertCustomer = DataTestUtility.GetLongName("spUdtTest2_InsertCustomer"); + string tableName = DataTestUtility.GetLongName("UdtTest2"); using (SqlConnection conn = new SqlConnection(_connStr)) using (SqlCommand cmd = conn.CreateCommand()) @@ -143,8 +143,8 @@ public void UDTParams_Invalid() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsUdtTestDatabasePresent), nameof(DataTestUtility.AreConnStringsSetup))] public void UDTParams_TypedNull() { - string spInsertCustomer = DataTestUtility.GetUniqueNameForSqlServer("spUdtTest2_InsertCustomer"); - string tableName = DataTestUtility.GetUniqueNameForSqlServer("UdtTest2_Customer"); + string spInsertCustomer = DataTestUtility.GetLongName("spUdtTest2_InsertCustomer"); + string tableName = DataTestUtility.GetLongName("UdtTest2_Customer"); using (SqlConnection conn = new SqlConnection(_connStr)) using (SqlCommand cmd = conn.CreateCommand()) @@ -188,8 +188,8 @@ public void UDTParams_TypedNull() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsUdtTestDatabasePresent), nameof(DataTestUtility.AreConnStringsSetup))] public void UDTParams_NullInput() { - string spInsertCustomer = DataTestUtility.GetUniqueNameForSqlServer("spUdtTest2_InsertCustomer"); - string tableName = DataTestUtility.GetUniqueNameForSqlServer("UdtTest2_Customer"); + string spInsertCustomer = DataTestUtility.GetLongName("spUdtTest2_InsertCustomer"); + string tableName = DataTestUtility.GetLongName("UdtTest2_Customer"); using (SqlConnection conn = new SqlConnection(_connStr)) using (SqlCommand cmd = conn.CreateCommand()) @@ -232,8 +232,8 @@ public void UDTParams_NullInput() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsUdtTestDatabasePresent), nameof(DataTestUtility.AreConnStringsSetup))] public void UDTParams_InputOutput() { - string spInsertCity = DataTestUtility.GetUniqueNameForSqlServer("spUdtTest2_InsertCity"); - string tableName = DataTestUtility.GetUniqueNameForSqlServer("UdtTest2"); + string spInsertCity = DataTestUtility.GetLongName("spUdtTest2_InsertCity"); + string tableName = DataTestUtility.GetLongName("UdtTest2"); using (SqlConnection conn = new SqlConnection(_connStr)) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Utf8SupportTest/Utf8SupportTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Utf8SupportTest/Utf8SupportTest.cs index effecb35b3..41f81b12e3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Utf8SupportTest/Utf8SupportTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Utf8SupportTest/Utf8SupportTest.cs @@ -37,7 +37,7 @@ public static void CheckSupportUtf8ConnectionProperty() public static void UTF8databaseTest() { const string letters = @"!\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u007f€\u0081‚ƒ„…†‡ˆ‰Š‹Œ\u008dŽ\u008f\u0090‘’“”•–—˜™š›œ\u009džŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"; - string dbName = DataTestUtility.GetUniqueNameForSqlServer("UTF8databaseTest", false); + string dbName = DataTestUtility.GetLongName("UTF8databaseTest", false); string tblName = "Table1"; SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); From a236dac188dbc0db7ac3fa93254a3d305733fde2 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Mon, 8 Sep 2025 12:09:23 -0300 Subject: [PATCH 03/11] User Story 38481: Fix unique db object name issues - Removed DateOnly tests that aren't supported on 5.1. --- .../SQL/ParameterTest/ParametersTest.cs | 120 ------------------ 1 file changed, 120 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs index 7182ce768c..cd2c52083c 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs @@ -307,126 +307,6 @@ public static void TestParametersWithDatatablesTVPInsert() } } -#if !NETFRAMEWORK - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - public static void TestDateOnlyTVPDataTable_CommandSP() - { - string tableTypeName = "[dbo]." + DataTestUtility.GetLongName("UDTTTestDateOnlyTVP"); - string spName = DataTestUtility.GetLongName("spTestDateOnlyTVP"); - SqlConnection connection = new(s_connString); - try - { - connection.Open(); - using (SqlCommand cmd = connection.CreateCommand()) - { - cmd.CommandType = CommandType.Text; - cmd.CommandText = $"CREATE TYPE {tableTypeName} AS TABLE ([DateColumn] date NULL, [TimeColumn] time NULL)"; - cmd.ExecuteNonQuery(); - cmd.CommandText = $"CREATE PROCEDURE {spName} (@dates {tableTypeName} READONLY) AS SELECT COUNT(*) FROM @dates"; - cmd.ExecuteNonQuery(); - } - using (SqlCommand cmd = connection.CreateCommand()) - { - cmd.CommandText = spName; - cmd.CommandType = CommandType.StoredProcedure; - - DataTable dtTest = new(); - dtTest.Columns.Add(new DataColumn("DateColumn", typeof(DateOnly))); - dtTest.Columns.Add(new DataColumn("TimeColumn", typeof(TimeOnly))); - var dataRow = dtTest.NewRow(); - dataRow["DateColumn"] = new DateOnly(2023, 11, 15); - dataRow["TimeColumn"] = new TimeOnly(12, 30, 45); - dtTest.Rows.Add(dataRow); - - cmd.Parameters.Add(new SqlParameter - { - ParameterName = "@dates", - SqlDbType = SqlDbType.Structured, - TypeName = tableTypeName, - Value = dtTest, - }); - - cmd.ExecuteNonQuery(); - } - } - finally - { - DataTestUtility.DropStoredProcedure(connection, spName); - DataTestUtility.DropUserDefinedType(connection, tableTypeName); - } - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - public static void TestDateOnlyTVPSqlDataRecord_CommandSP() - { - string tableTypeName = "[dbo]." + DataTestUtility.GetLongName("UDTTTestDateOnlySqlDataRecordTVP"); - string spName = DataTestUtility.GetLongName("spTestDateOnlySqlDataRecordTVP"); - SqlConnection connection = new(s_connString); - try - { - connection.Open(); - using (SqlCommand cmd = connection.CreateCommand()) - { - cmd.CommandType = CommandType.Text; - cmd.CommandText = $"CREATE TYPE {tableTypeName} AS TABLE ([DateColumn] date NULL, [TimeColumn] time NULL)"; - cmd.ExecuteNonQuery(); - cmd.CommandText = $"CREATE PROCEDURE {spName} (@dates {tableTypeName} READONLY) AS SELECT COUNT(*) FROM @dates"; - cmd.ExecuteNonQuery(); - } - using (SqlCommand cmd = connection.CreateCommand()) - { - cmd.CommandText = spName; - cmd.CommandType = CommandType.StoredProcedure; - - SqlMetaData[] metadata = new SqlMetaData[] - { - new SqlMetaData("DateColumn", SqlDbType.Date), - new SqlMetaData("TimeColumn", SqlDbType.Time) - }; - - SqlDataRecord record1 = new SqlDataRecord(metadata); - record1.SetValues(new DateOnly(2023, 11, 15), new TimeOnly(12, 30, 45)); - - SqlDataRecord record2 = new SqlDataRecord(metadata); - record2.SetValues(new DateOnly(2025, 11, 15), new TimeOnly(13, 31, 46)); - - IList featureInserts = new List - { - record1, - record2, - }; - - cmd.Parameters.Add(new SqlParameter - { - ParameterName = "@dates", - SqlDbType = SqlDbType.Structured, - TypeName = tableTypeName, - Value = featureInserts, - }); - - using var reader = cmd.ExecuteReader(); - - Assert.True(reader.HasRows); - - int count = 0; - while (reader.Read()) - { - Assert.NotNull(reader[0]); - count++; - } - - Assert.Equal(1, count); - } - } - finally - { - DataTestUtility.DropStoredProcedure(connection, spName); - DataTestUtility.DropUserDefinedType(connection, tableTypeName); - } - } -#endif - #region Scaled Decimal Parameter & TVP Test [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] [InlineData("CAST(1.0 as decimal(38, 37))", "1.0000000000000000000000000000")] From cca23e5e834bcdc1a582e31b862c771e53759daf Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 7 Aug 2025 15:18:15 -0300 Subject: [PATCH 04/11] Add CodeQL suppression for DefaultAzureCredential use in Production (#3542) - Adjusted CodeQL suppression to meet the strict requirements of where it may appear relative to the flagged code. - Adding catch for macOS socket error to log and ignore. --- .../Data/SqlClient/SNI/SNITcpHandle.cs | 27 ++++++++++++++++--- .../ActiveDirectoryAuthenticationProvider.cs | 23 +++++++++++++++- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs index b8e38978b9..9537eaf1c6 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs @@ -851,9 +851,30 @@ public override uint Receive(out SNIPacket packet, int timeoutInMilliseconds) } finally { - // Reset the socket timeout to Timeout.Infinite after the receive operation is done - // to avoid blocking the thread in case of a timeout error. - _socket.ReceiveTimeout = Timeout.Infinite; + const int resetTimeout = Timeout.Infinite; + + try + { + // Reset the socket timeout to Timeout.Infinite after + // the receive operation is done to avoid blocking the + // thread in case of a timeout error. + _socket.ReceiveTimeout = resetTimeout; + + } + catch (SocketException ex) + { + // We sometimes see setting the ReceiveTimeout fail + // on macOS. There's isn't much we can do about it + // though, so just log and move on. + SqlClientEventSource.Log.TrySNITraceEvent( + nameof(SNITCPHandle), + EventType.ERR, + "Connection Id {0}, Failed to reset socket " + + "receive timeout to {1}: {2}", + _connectionId, + resetTimeout, + ex.Message); + } } } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs index 73fce50774..d69cbe06ab 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs @@ -587,7 +587,28 @@ private static TokenCredentialData CreateTokenCredentialInstance(TokenCredential defaultAzureCredentialOptions.WorkloadIdentityClientId = tokenCredentialKey._clientId; } - return new TokenCredentialData(new DefaultAzureCredential(defaultAzureCredentialOptions), GetHash(secret)); + // SqlClient is a library and provides support to acquire access + // token using 'DefaultAzureCredential' on user demand when they + // specify 'Authentication = Active Directory Default' in + // connection string. + // + // Default Azure Credential is instantiated by the calling + // application when using "Active Directory Default" + // authentication code to connect to Azure SQL instance. + // SqlClient is a library, doesn't instantiate the credential + // without running application instructions. + // + // Note that CodeQL suppression support can only detect + // suppression comments that appear immediately above the + // flagged statement, or appended to the end of the statement. + // Multi-line justifications are not supported. + // + // https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-docs/codeql/codeql-semmle#guidance-on-suppressions + // + // CodeQL [SM05137] See above for justification. + DefaultAzureCredential cred = new(defaultAzureCredentialOptions); + + return new TokenCredentialData(cred, GetHash(secret)); } TokenCredentialOptions tokenCredentialOptions = new() { AuthorityHost = new Uri(tokenCredentialKey._authority) }; From 7dfa969c382a5161384a877df45b39f4d8d6553a Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Tue, 9 Sep 2025 10:43:31 -0300 Subject: [PATCH 05/11] - Added back some blocks that were removed by cherry-picks. - Added console diagnostics to see when Enclave tables are dropped. --- .../AlwaysEncrypted/CspProviderExt.cs | 49 +++++++++++++++++++ .../TestFixtures/Setup/Table.cs | 3 ++ .../SQL/ConnectivityTests/ConnectivityTest.cs | 9 ++++ .../SQL/ParameterTest/ParametersTest.cs | 1 - 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs index e26c67382e..49071def2e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs @@ -157,6 +157,55 @@ public void TestRoundTripWithCSPAndCertStoreProvider() } } + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ClassData(typeof(AEConnectionStringProvider))] + public void TestEncryptDecryptWithCSP(string connectionString) + { + string providerName = @"Microsoft Enhanced RSA and AES Cryptographic Provider"; + string keyIdentifier = DataTestUtility.GetUniqueNameForSqlServer("CSP"); + + try + { + CertificateUtilityWin.RSAPersistKeyInCsp(providerName, keyIdentifier); + string cspPath = String.Concat(providerName, @"/", keyIdentifier); + + SQLSetupStrategyCspExt sqlSetupStrategyCsp = new SQLSetupStrategyCspExt(cspPath); + string tableName = sqlSetupStrategyCsp.CspProviderTable.Name; + + try + { + using SqlConnection sqlConn = new(connectionString); + sqlConn.Open(); + + Table.DeleteData(tableName, sqlConn); + + // insert 1 row data + Customer customer = new Customer(45, "Microsoft", "Corporation"); + + DatabaseHelper.InsertCustomerData(sqlConn, null, tableName, customer); + + // 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); + } + finally + { + // clean up database resources + sqlSetupStrategyCsp.Dispose(); + } + } + finally + { + CertificateUtilityWin.RSADeleteKeyInCsp(providerName, keyIdentifier); + } + } + /// /// Validates that the results are the ones expected. /// diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/Table.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/Table.cs index b18e5a0ae8..e19ff59f80 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/Table.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/Table.cs @@ -19,6 +19,9 @@ public override void Drop(SqlConnection sqlConnection) command.CommandText = sql; command.ExecuteNonQuery(); } + + // TODO: Remove. + System.Console.WriteLine($"Dropped table {Name} from {sqlConnection.ConnectionString}"); } public static void DeleteData(string tableName, SqlConnection sqlConnection) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs index dfdbf5229f..57d6725ce5 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs @@ -398,6 +398,15 @@ private static bool CanCreateAliases() return false; } + using (WindowsIdentity identity = WindowsIdentity.GetCurrent()) + { + WindowsPrincipal principal = new(identity); + if (!principal.IsInRole(WindowsBuiltInRole.Administrator)) + { + return false; + } + } + using RegistryKey key = Registry.LocalMachine.OpenSubKey(ConnectToPath, true); if (key == null) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs index cd2c52083c..6136f0bfc1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs @@ -8,7 +8,6 @@ using System.Data; using System.Data.SqlTypes; using System.Threading; -using Microsoft.Data.SqlClient.Server; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests From 58eca557f2e7a72438d0dfaebbec71db68b14953 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Tue, 9 Sep 2025 11:35:46 -0300 Subject: [PATCH 06/11] - Fixed typo in code that only gets compiled when building on Windows (eek!) --- .../tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs index 49071def2e..93ff7e52ec 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs @@ -162,7 +162,7 @@ public void TestRoundTripWithCSPAndCertStoreProvider() public void TestEncryptDecryptWithCSP(string connectionString) { string providerName = @"Microsoft Enhanced RSA and AES Cryptographic Provider"; - string keyIdentifier = DataTestUtility.GetUniqueNameForSqlServer("CSP"); + string keyIdentifier = DataTestUtility.GetLongName("CSP"); try { From 8f49502f5588b1d3bbf5b9b23329e3f5cd0fdca6 Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Thu, 5 Jun 2025 17:02:41 -0500 Subject: [PATCH 07/11] Tests | Remove hardcoded credentials from ManualTests (#3204) * Initial removal of CertificateUtility.CreateCertificate One test implied that DataTestUtility.AKVUrl would point to an RSA key which aligned with the certificate's private key. Switching this to dynamically generate the key in places. * Hotfix for Azure Key Vault tests * Removed hardcoded references to Azure Key Vault key * Removed hardcoded references to CertificateUtilityWin These were mostly related to generating CSP keys. * Code review changes * Reorder properties and constructors * Move AEConnectionStringProviderWithCspParameters to its own file * Tweak to the AKV token acquisition * Code review Redundant bracket, alphabetised the ManualTesting csproj * Update src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs Let's try @edwardneal's idea Co-authored-by: Edward Neal <55035479+edwardneal@users.noreply.github.com> * Update src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs Co-authored-by: Edward Neal <55035479+edwardneal@users.noreply.github.com> * Fixes as per @edwardneal's suggestions * Fix as per @edwardneal's suggestion * Fix missing `new` Co-authored-by: Edward Neal <55035479+edwardneal@users.noreply.github.com> * Update src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs Co-authored-by: Edward Neal <55035479+edwardneal@users.noreply.github.com> * Update src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs Co-authored-by: Edward Neal <55035479+edwardneal@users.noreply.github.com> * Address comment that we don't need a CspParameters object as part of the test arguments * Move test arguments into property (the class was only used in a single location) * Cleanup test code * Tweak default provider discovery code to handle edge cases a bit better * Address comment regarding readonly member variables Apply long line chomping * Addressing the last of the comments. --------- Co-authored-by: Edward Neal <55035479+edwardneal@users.noreply.github.com> --- .../ManualTests/AlwaysEncrypted/AKVTests.cs | 10 +- .../AlwaysEncrypted/AKVUnitTests.cs | 77 +++-- .../AlwaysEncrypted/ConversionTests.cs | 29 +- .../AlwaysEncrypted/CspProviderExt.cs | 246 +++++--------- .../EnclaveAzureDatabaseTests.cs | 6 +- .../AlwaysEncrypted/ExceptionTestAKVStore.cs | 62 ++-- .../TestFixtures/AzureKeyVaultKeyFixture.cs | 19 ++ .../TestFixtures/SQLSetupStrategy.cs | 51 +-- .../SQLSetupStrategyAzureKeyVault.cs | 65 +++- .../SQLSetupStrategyCertStoreProvider.cs | 7 +- .../TestFixtures/SQLSetupStrategyCspExt.cs | 50 --- .../SQLSetupStrategyCspProvider.cs | 77 +++++ .../TestFixtures/Setup/CertificateUtility.cs | 89 ----- .../Setup/CertificateUtilityWin.cs | 264 --------------- .../TestTrustedMasterKeyPaths.cs | 27 +- .../ManualTests/DataCommon/DataTestUtility.cs | 7 +- .../SqlClientCustomTokenCredential.cs | 9 +- ....Data.SqlClient.ManualTesting.Tests.csproj | 4 +- .../Fixtures/AzureKeyVaultKeyFixtureBase.cs | 63 ++++ .../Fixtures/CertificateFixtureBase.cs | 308 ++++++++++++++++++ .../ColumnMasterKeyCertificateFixture.cs | 29 ++ .../Fixtures/CspCertificateFixture.cs | 47 +++ ...rosoft.Data.SqlClient.TestUtilities.csproj | 2 + 23 files changed, 834 insertions(+), 714 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AzureKeyVaultKeyFixture.cs delete mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspExt.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspProvider.cs delete mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtilityWin.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/AzureKeyVaultKeyFixtureBase.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/ColumnMasterKeyCertificateFixture.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CspCertificateFixture.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs index 3f2b7a83fa..ab4f38f882 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs @@ -62,15 +62,15 @@ public void TestEncryptDecryptWithAKV() [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 893c50799b..ed7061cf65 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 04c661902d..a7be11add4 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 93ff7e52ec..f3e9bcd7fa 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 NET6_0_OR_GREATER +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(@"\") + 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.GetLongName("CSP"); + 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 4d3c635684..a3ef1e12b0 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; } @@ -58,15 +58,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() { @@ -87,7 +88,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 @@ -96,7 +105,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); } } } @@ -142,9 +153,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); @@ -255,13 +269,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) @@ -272,6 +280,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 3d3c717b31..dfe94050f7 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 @@ -105,95 +105,6 @@ internal static void CleanSqlClientCache() } } - /// - /// 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 = new X509Certificate2(certificateRawBytes, "", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.Exportable); - 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 893362e027..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 NET6_0_OR_GREATER -using System.Runtime.Versioning; -#endif - -namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted -{ -#if NET6_0_OR_GREATER - [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 2723853f27..4f842cf142 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -37,8 +37,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; @@ -69,7 +67,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"; @@ -143,8 +140,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; @@ -339,7 +334,7 @@ public static bool IsNotAzureServer() // Ref: https://feedback.azure.com/forums/307516-azure-synapse-analytics/suggestions/17858869-support-always-encrypted-in-sql-data-warehouse public static bool IsAKVSetupAvailable() { - return !string.IsNullOrEmpty(AKVUrl) && !string.IsNullOrEmpty(UserManagedIdentityClientId) && !string.IsNullOrEmpty(AKVTenantId) && IsNotAzureSynapse(); + return AKVBaseUri != null && !string.IsNullOrEmpty(UserManagedIdentityClientId) && !string.IsNullOrEmpty(AKVTenantId) && IsNotAzureSynapse(); } private static readonly DefaultAzureCredential s_defaultCredential = new(new DefaultAzureCredentialOptions { ManagedIdentityClientId = UserManagedIdentityClientId }); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs index 977bc53257..b89cfda558 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 f570fad10f..7b8e551bc9 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 @@ -32,9 +32,8 @@ - - + @@ -50,6 +49,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 new file mode 100644 index 0000000000..bcc0d93c07 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs @@ -0,0 +1,308 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading; + +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; } + + public StoreLocation Location { get; } + + public StoreName Name { get; } + + public CertificateStoreContext(StoreLocation location, StoreName name) + { + Certificates = new List(); + Location = location; + Name = name; + } + } + + private readonly List _certificateStoreModifications = new List(); + + protected X509Certificate2 CreateCertificate(string subjectName, IEnumerable dnsNames, IEnumerable ipAddresses, bool forceCsp = false) + { + // This will always generate a certificate with: + // * Start date: 24hrs ago + // * End date: 24hrs in the future + // * Subject: {subjectName} + // * Subject alternative names: {dnsNames}, {ipAddresses} + // * Public key: 2048-bit RSA + // * Hash algorithm: SHA256 + // * Key usage: digital signature, key encipherment + // * Enhanced key usage: server authentication, client authentication + DateTimeOffset notBefore = DateTimeOffset.UtcNow.AddDays(-1); + DateTimeOffset notAfter = DateTimeOffset.UtcNow.AddDays(1); + byte[] passwordBytes = new byte[32]; + string password = null; + Random rnd = new Random(); + + rnd.NextBytes(passwordBytes); + password = Convert.ToBase64String(passwordBytes); +#if NET + X500DistinguishedNameBuilder subjectBuilder = new X500DistinguishedNameBuilder(); + SubjectAlternativeNameBuilder sanBuilder = new SubjectAlternativeNameBuilder(); + RSA rsaKey = CreateRSA(forceCsp); + bool hasSans = false; + + subjectBuilder.AddCommonName(subjectName); + foreach (string dnsName in dnsNames) + { + sanBuilder.AddDnsName(dnsName); + hasSans = true; + } + foreach (string ipAddress in ipAddresses) + { + sanBuilder.AddIpAddress(System.Net.IPAddress.Parse(ipAddress)); + hasSans = true; + } + + CertificateRequest request = new CertificateRequest(subjectBuilder.Build(), rsaKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + + request.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(request.PublicKey, false)); + request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment, false)); + request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection() { new Oid("1.3.6.1.5.5.7.3.1"), new Oid("1.3.6.1.5.5.7.3.2") }, true)); + + if (hasSans) + { + request.CertificateExtensions.Add(sanBuilder.Build()); + } + + // Generate an ephemeral certificate, then export it and return it as a new certificate with the correct key storage flags set. + // This is to ensure that it's imported into the certificate stores with its private key. + using (X509Certificate2 ephemeral = request.CreateSelfSigned(notBefore, notAfter)) + { + #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. + // Instead, fall back to running a PowerShell script which calls New-SelfSignedCertificate. This cmdlet also adds the certificate to a specific, + // certificate store, so remove it from there. + // Normally, the PowerShell script will return zero and print the base64-encoded certificate to stdout. If there's an exception, it'll return 1 and + // print the message instead. + const string PowerShellCommandTemplate = @"$notBefore = [DateTime]::ParseExact(""{0}"", ""O"", $null) +$notAfter = [DateTime]::ParseExact(""{1}"", ""O"", $null) +$subject = ""CN={2}"" +$sAN = @({3}) + +try +{{ + $x509 = PKI\New-SelfSignedCertificate -Subject $subject -TextExtension $sAN -KeyLength 2048 -KeyAlgorithm RSA ` + -CertStoreLocation ""Cert:\CurrentUser\My"" -NotBefore $notBefore -NotAfter $notAfter ` + -KeyExportPolicy Exportable -HashAlgorithm SHA256 -Provider ""{5}"" -KeySpec KeyExchange + + if ($x509 -eq $null) + {{ throw ""Certificate was null!"" }} + + $exportedArray = $x509.Export(""Pkcs12"", ""{4}"") + Write-Output $([Convert]::ToBase64String($exportedArray)) + + Remove-Item ""Cert:\CurrentUser\My\$($x509.Thumbprint)"" + + exit 0 +}} +catch [Exception] +{{ + Write-Output $_.Exception.Message + exit 1 +}}"; + const int PowerShellCommandTimeout = 15_000; + + string sanString = string.Empty; + bool hasSans = false; + + foreach (string dnsName in dnsNames) + { + sanString += string.Format("DNS={0}&", dnsName); + hasSans = true; + } + foreach (string ipAddress in ipAddresses) + { + sanString += string.Format("IPAddress={0}&", ipAddress); + hasSans = true; + } + + 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, CspProviderName); + + ProcessStartInfo startInfo = new() + { + FileName = "powershell.exe", + RedirectStandardOutput = true, + RedirectStandardError = false, + UseShellExecute = false, + CreateNoWindow = true, + // Pass the Base64-encoded command to remove the need to escape quote marks + Arguments = "-EncodedCommand " + Convert.ToBase64String(Encoding.Unicode.GetBytes(formattedCommand)), + // Run as Administrator, since we're manipulating the system + // certificate store. + Verb = "RunAs", + LoadUserProfile = true + }; + + // This command sometimes fails with: + // + // Access is denied. 0x80070005 (WIN32: 5 ERROR_ACCESS_DENIED) + // + // We will retry it a few times with a short delay to avoid spurious + // failures in CI pipeline runs. + // + // See ADO issue for more details: + // + // Issue 34304: #3223 Fix Functional test failures in CI + // + // https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/34304 + // + // Delay 5 seconds between retries, and retry 3 times. + const int delay = 5; + const int retries = 3; + + string commandOutput = string.Empty; + + for (int attempt = 1; attempt <= retries; ++attempt) + { + using Process psProcess = new() { StartInfo = startInfo }; + + psProcess.Start(); + commandOutput = psProcess.StandardOutput.ReadToEnd(); + + if (!psProcess.WaitForExit(PowerShellCommandTimeout)) + { + psProcess.Kill(); + throw new Exception("Process did not complete in time, exiting."); + } + + // Process completed successfully if it had an exit code of zero, the command output will be the base64-encoded certificate + var code = psProcess.ExitCode; + if (code == 0) + { + return new X509Certificate2(Convert.FromBase64String(commandOutput), password, X509KeyStorageFlags.Exportable); + } + + Console.WriteLine( + $"PowerShell command failed with exit code {code} on " + + $"attempt {attempt} of {retries}; " + + $"retrying in {delay} seconds..."); + + Thread.Sleep(TimeSpan.FromSeconds(delay)); + } + + throw new Exception( + "PowerShell command raised exception: " + + $"{commandOutput}; command was: {formattedCommand}"); +#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); + + if (storeContext == null) + { + storeContext = new(storeLocation, storeName); + _certificateStoreModifications.Add(storeContext); + } + + using X509Store store = new X509Store(storeContext.Name, storeContext.Location); + + store.Open(OpenFlags.ReadWrite); + if (store.Certificates.Contains(cert)) + { + store.Remove(cert); + } + store.Add(cert); + + storeContext.Certificates.Add(cert); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + foreach (CertificateStoreContext storeContext in _certificateStoreModifications) + { + using X509Store store = new X509Store(storeContext.Name, storeContext.Location); + + try + { + store.Open(OpenFlags.ReadWrite); + } + catch(Exception) + { + continue; + } + + foreach (X509Certificate2 cert in storeContext.Certificates) + { + try + { + if (store.Certificates.Contains(cert)) + { + store.Remove(cert); + } + } + catch (Exception) + { + continue; + } + + cert.Dispose(); + } + + storeContext.Certificates.Clear(); + } + } + } +} 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 3bd48830cc..1546ef13ee 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 @@ -11,6 +11,8 @@ PreserveNewest + + \ No newline at end of file From c5e191ca02cb10950cda4c69b42177ae74077cc7 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Tue, 7 Oct 2025 16:14:34 -0700 Subject: [PATCH 08/11] Update test utilities target frameworks. Fix compilation issues. --- .../tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs | 2 +- .../Fixtures/CertificateFixtureBase.cs | 5 ++--- .../Microsoft.Data.SqlClient.TestUtilities.csproj | 5 ++++- tools/props/Versions.props | 1 + 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs index f3e9bcd7fa..c93b770958 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs @@ -54,7 +54,7 @@ public void TestRoundTripWithCspAndCertStoreProvider() [MemberData(nameof(TestEncryptDecryptWithCsp_Data))] public void TestEncryptDecryptWithCsp(string connectionString, string providerName, int providerType) { - string keyIdentifier = DataTestUtility.GetUniqueNameForSqlServer("CSP"); + string keyIdentifier = DataTestUtility.GetLongName("CSP"); CspParameters namedCspParameters = new CspParameters(providerType, providerName, keyIdentifier); using SQLSetupStrategyCspProvider sqlSetupStrategyCsp = new SQLSetupStrategyCspProvider(namedCspParameters); 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 bcc0d93c07..85d5d70850 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 @@ -60,12 +60,11 @@ protected X509Certificate2 CreateCertificate(string subjectName, IEnumerable - netstandard2.0 + netfx + netcoreapp + win + win-$(Platform) $(ObjFolder)$(Configuration).$(Platform)\$(AssemblyName) $(BinFolder)$(Configuration).$(Platform)\$(AssemblyName) diff --git a/tools/props/Versions.props b/tools/props/Versions.props index a2b1d7273a..0aca13245e 100644 --- a/tools/props/Versions.props +++ b/tools/props/Versions.props @@ -66,6 +66,7 @@ 13.0.1 4.3.0 6.0.1 + 5.0.0 2.4.2 2.4.5 7.0.0-beta.22316.1 From 85f3a434bc9af31d92b42150b5296fa4c57bc7c4 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Tue, 7 Oct 2025 16:47:09 -0700 Subject: [PATCH 09/11] Construct valid X500 distinguished name. --- .../Fixtures/CertificateFixtureBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs index 85d5d70850..5c60436a3a 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 @@ -60,7 +60,7 @@ protected X509Certificate2 CreateCertificate(string subjectName, IEnumerable Date: Wed, 8 Oct 2025 11:10:21 -0700 Subject: [PATCH 10/11] Print rsa key type for debugging. --- .../Fixtures/CertificateFixtureBase.cs | 3 +++ 1 file changed, 3 insertions(+) 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 5c60436a3a..56c6c75162 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 @@ -63,6 +63,9 @@ protected X509Certificate2 CreateCertificate(string subjectName, IEnumerable Date: Wed, 8 Oct 2025 11:29:28 -0700 Subject: [PATCH 11/11] Clean up net version ifdefs to fix certificate exportability. --- .../TestFixtures/SQLSetupStrategyAzureKeyVault.cs | 6 +++--- .../Fixtures/CertificateFixtureBase.cs | 14 -------------- 2 files changed, 3 insertions(+), 17 deletions(-) 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 f1ebc3a93e..abc7c68d98 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs @@ -56,9 +56,9 @@ public static void RegisterGlobalProviders(SqlColumnEncryptionAzureKeyVaultProvi 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. +#if NET + // In .NET Framework, the key is exportable in plaintext. + // We need to manually handle this in .NET 6.0 and 8.0 with a non-plaintext export. RSA replacementKey = RSA.Create(rsa.KeySize); Span passwordBytes = stackalloc byte[32]; PbeParameters pbeParameters = new PbeParameters(PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA256, 10000); 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 56c6c75162..ba3ec7d9ed 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 @@ -63,8 +63,6 @@ protected X509Certificate2 CreateCertificate(string subjectName, IEnumerable