diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj b/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj
index d60c9440a9..4133622d20 100644
--- a/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj
@@ -51,4 +51,10 @@
+
+
+
+
+
+
diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/AzureKeyVaultKeyFixtureBase.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/AzureKeyVaultKeyFixtureBase.cs
new file mode 100644
index 0000000000..f70560be9e
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/AzureKeyVaultKeyFixtureBase.cs
@@ -0,0 +1,70 @@
+// 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.Tests.Common.Fixtures;
+
+///
+/// Provides a base class for managing Azure Key Vault keys in test fixtures.
+///
+///
+/// This class simplifies the creation and cleanup of RSA keys in an Azure Key Vault during testing
+/// scenarios. It ensures that any keys created during the fixture's lifetime are properly deleted when the fixture is
+/// disposed.
+///
+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/Common/Fixtures/CertificateFixtureBase.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs
new file mode 100644
index 0000000000..9007796013
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs
@@ -0,0 +1,320 @@
+// 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.Tests.Common.Fixtures;
+
+///
+/// Provides a base class for managing the creation, storage, and disposal of X.509 certificates used in cryptographic
+/// operations. This class is designed to facilitate scenarios where certificates need to be programmatically generated,
+/// added to certificate stores, and cleaned up after use.
+///
+///
+/// This class includes functionality for creating self-signed certificates with specific configurations,
+/// adding certificates to system certificate stores, and ensuring proper cleanup of certificates to avoid resource
+/// leaks. It is intended to be used as a base class for test fixtures or other scenarios requiring temporary
+/// certificate management.
+/// The class implements to ensure that any certificates added to
+/// certificate stores are removed and properly disposed of when the object is no longer needed.
+///
+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/Common/Fixtures/ColumnEncryptionCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs
new file mode 100644
index 0000000000..a4aa84842f
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs
@@ -0,0 +1,70 @@
+// 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;
+using System.Security.Principal;
+
+namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures;
+
+///
+/// Provides a fixture for managing certificates used in column encryption scenarios.
+///
+///
+/// This class creates and manages certificates for testing or operational purposes, including primary
+/// and secondary column encryption certificates, as well as a certificate without a private key. Certificates are
+/// added to the appropriate certificate stores based on the current user's permissions.
+///
+public sealed class ColumnEncryptionCertificateFixture : CertificateFixtureBase
+{
+ public X509Certificate2 PrimaryColumnEncryptionCertificate { get; }
+
+ public X509Certificate2 SecondaryColumnEncryptionCertificate { get; }
+
+ public X509Certificate2 CertificateWithoutPrivateKey { get; }
+
+ private readonly X509Certificate2 _currentUserCertificate;
+ private readonly X509Certificate2 _localMachineCertificate;
+
+ public ColumnEncryptionCertificateFixture()
+ {
+ PrimaryColumnEncryptionCertificate = CreateCertificate(nameof(PrimaryColumnEncryptionCertificate), Array.Empty(), Array.Empty());
+ SecondaryColumnEncryptionCertificate = CreateCertificate(nameof(SecondaryColumnEncryptionCertificate), Array.Empty(), Array.Empty());
+ _currentUserCertificate = CreateCertificate(nameof(_currentUserCertificate), Array.Empty(), Array.Empty());
+ using (X509Certificate2 createdCertificate = CreateCertificate(nameof(CertificateWithoutPrivateKey), Array.Empty(), Array.Empty()))
+ {
+ // This will strip the private key away from the created certificate
+#if NET9_0_OR_GREATER
+ CertificateWithoutPrivateKey = X509CertificateLoader.LoadCertificate(createdCertificate.Export(X509ContentType.Cert));
+#else
+ CertificateWithoutPrivateKey = new X509Certificate2(createdCertificate.Export(X509ContentType.Cert));
+#endif
+ AddToStore(CertificateWithoutPrivateKey, StoreLocation.CurrentUser, StoreName.My);
+ }
+
+ AddToStore(PrimaryColumnEncryptionCertificate, StoreLocation.CurrentUser, StoreName.My);
+ AddToStore(SecondaryColumnEncryptionCertificate, StoreLocation.CurrentUser, StoreName.My);
+ AddToStore(_currentUserCertificate, StoreLocation.CurrentUser, StoreName.My);
+
+ if (IsAdmin)
+ {
+ _localMachineCertificate = CreateCertificate(nameof(_localMachineCertificate), Array.Empty(), Array.Empty());
+
+ AddToStore(_localMachineCertificate, StoreLocation.LocalMachine, StoreName.My);
+ }
+ }
+
+ public X509Certificate2 GetCertificate(StoreLocation storeLocation)
+ {
+ return storeLocation == StoreLocation.CurrentUser
+ ? _currentUserCertificate
+ : storeLocation == StoreLocation.LocalMachine && IsAdmin
+ ? _localMachineCertificate
+ : throw new InvalidOperationException("Attempted to retrieve the certificate added to the local machine store; this requires administrator rights.");
+ }
+
+ public static bool IsAdmin
+ => Environment.OSVersion.Platform == PlatformID.Win32NT
+ && new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs
new file mode 100644
index 0000000000..a91ca7a0e0
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs
@@ -0,0 +1,35 @@
+// 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.Tests.Common.Fixtures;
+
+///
+/// Provides a test fixture for managing a column master key certificate.
+///
+///
+/// This class is intended to simplify the setup and teardown of a column master key certificate for
+/// testing purposes. It creates and optionally adds the certificate to the specified certificate store.
+///
+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/Common/Fixtures/CspCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs
new file mode 100644
index 0000000000..74c4ca0325
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs
@@ -0,0 +1,54 @@
+// 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.Tests.Common.Fixtures;
+
+///
+/// Provides a fixture for working with certificates backed by a Cryptographic Service Provider (CSP).
+///
+///
+/// This class creates and manages a certificate stored in the current user's certificate store, along
+/// with its associated CSP key path. It is intended to facilitate testing or scenarios requiring temporary
+/// certificates.
+///
+public class CspCertificateFixture : CertificateFixtureBase
+{
+ public CspCertificateFixture()
+ {
+ CspCertificate = CreateCertificate(nameof(CspCertificate), Array.Empty(), Array.Empty(), true);
+
+ AddToStore(CspCertificate, StoreLocation.CurrentUser, StoreName.My);
+
+ CspCertificatePath = $"{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/Directory.Build.props b/src/Microsoft.Data.SqlClient/tests/Directory.Build.props
index 4f982d4ed9..b470f899e1 100644
--- a/src/Microsoft.Data.SqlClient/tests/Directory.Build.props
+++ b/src/Microsoft.Data.SqlClient/tests/Directory.Build.props
@@ -22,7 +22,7 @@
net462
- net9.0
+ net8.0;net9.0
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs
index ba90ddf4f0..caa98cc686 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs
@@ -7,7 +7,7 @@
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Text;
-using Microsoft.Data.SqlClient.TestUtilities.Fixtures;
+using Microsoft.Data.SqlClient.Tests.Common.Fixtures;
using Xunit;
using static Microsoft.Data.SqlClient.Tests.AlwaysEncryptedTests.Utility;
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCertificateStoreProviderShould.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCertificateStoreProviderShould.cs
index 216afef784..e1fe6a0a09 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCertificateStoreProviderShould.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCertificateStoreProviderShould.cs
@@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using Microsoft.Data.SqlClient.TestUtilities.Fixtures;
+using Microsoft.Data.SqlClient.Tests.Common.Fixtures;
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs
index 13795fec44..216db7e0c3 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs
@@ -14,7 +14,7 @@
using System.Security.Cryptography.X509Certificates;
using Xunit;
using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup;
-using Microsoft.Data.SqlClient.TestUtilities.Fixtures;
+using Microsoft.Data.SqlClient.Tests.Common.Fixtures;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted
{
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs
index aade12b956..bdd48967b5 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs
@@ -8,7 +8,7 @@
using System.Security.Cryptography.X509Certificates;
using Xunit;
using System.Security.Cryptography;
-using Microsoft.Data.SqlClient.TestUtilities.Fixtures;
+using Microsoft.Data.SqlClient.Tests.Common.Fixtures;
using Microsoft.Win32;
#if NET
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AzureKeyVaultKeyFixture.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AzureKeyVaultKeyFixture.cs
index 4fea191d01..b59cab7e3b 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AzureKeyVaultKeyFixture.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AzureKeyVaultKeyFixture.cs
@@ -2,7 +2,7 @@
// 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;
+using Microsoft.Data.SqlClient.Tests.Common.Fixtures;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted
{
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 23ea1a9d79..d08d2a86be 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs
@@ -9,7 +9,7 @@
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;
+using Microsoft.Data.SqlClient.Tests.Common.Fixtures;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted
{
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
deleted file mode 100644
index 695e3ef2ea..0000000000
--- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/AzureKeyVaultKeyFixtureBase.cs
+++ /dev/null
@@ -1,63 +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.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
deleted file mode 100644
index bcc0d93c07..0000000000
--- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs
+++ /dev/null
@@ -1,308 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Collections.Generic;
-using System.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/ColumnEncryptionCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/ColumnEncryptionCertificateFixture.cs
deleted file mode 100644
index 7b486ccee0..0000000000
--- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/ColumnEncryptionCertificateFixture.cs
+++ /dev/null
@@ -1,63 +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.Security.Cryptography.X509Certificates;
-using System.Security.Principal;
-
-namespace Microsoft.Data.SqlClient.TestUtilities.Fixtures
-{
- public sealed class ColumnEncryptionCertificateFixture : CertificateFixtureBase
- {
- public X509Certificate2 PrimaryColumnEncryptionCertificate { get; }
-
- public X509Certificate2 SecondaryColumnEncryptionCertificate { get; }
-
- public X509Certificate2 CertificateWithoutPrivateKey { get; }
-
- private readonly X509Certificate2 _currentUserCertificate;
- private readonly X509Certificate2 _localMachineCertificate;
-
- public ColumnEncryptionCertificateFixture()
- {
- PrimaryColumnEncryptionCertificate = CreateCertificate(nameof(PrimaryColumnEncryptionCertificate), Array.Empty(), Array.Empty());
- SecondaryColumnEncryptionCertificate = CreateCertificate(nameof(SecondaryColumnEncryptionCertificate), Array.Empty(), Array.Empty());
- _currentUserCertificate = CreateCertificate(nameof(_currentUserCertificate), Array.Empty(), Array.Empty());
- using (X509Certificate2 createdCertificate = CreateCertificate(nameof(CertificateWithoutPrivateKey), Array.Empty(), Array.Empty()))
- {
- // This will strip the private key away from the created certificate
-#if NET9_0_OR_GREATER
- CertificateWithoutPrivateKey = X509CertificateLoader.LoadCertificate(createdCertificate.Export(X509ContentType.Cert));
-#else
- CertificateWithoutPrivateKey = new X509Certificate2(createdCertificate.Export(X509ContentType.Cert));
-#endif
- AddToStore(CertificateWithoutPrivateKey, StoreLocation.CurrentUser, StoreName.My);
- }
-
- AddToStore(PrimaryColumnEncryptionCertificate, StoreLocation.CurrentUser, StoreName.My);
- AddToStore(SecondaryColumnEncryptionCertificate, StoreLocation.CurrentUser, StoreName.My);
- AddToStore(_currentUserCertificate, StoreLocation.CurrentUser, StoreName.My);
-
- if (IsAdmin)
- {
- _localMachineCertificate = CreateCertificate(nameof(_localMachineCertificate), Array.Empty(), Array.Empty());
-
- AddToStore(_localMachineCertificate, StoreLocation.LocalMachine, StoreName.My);
- }
- }
-
- public X509Certificate2 GetCertificate(StoreLocation storeLocation)
- {
- return storeLocation == StoreLocation.CurrentUser
- ? _currentUserCertificate
- : storeLocation == StoreLocation.LocalMachine && IsAdmin
- ? _localMachineCertificate
- : throw new InvalidOperationException("Attempted to retrieve the certificate added to the local machine store; this requires administrator rights.");
- }
-
- public static bool IsAdmin
- => Environment.OSVersion.Platform == PlatformID.Win32NT
- && new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
- }
-}
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
deleted file mode 100644
index b6706be1c4..0000000000
--- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/ColumnMasterKeyCertificateFixture.cs
+++ /dev/null
@@ -1,29 +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.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
deleted file mode 100644
index 7fabaf1b9c..0000000000
--- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CspCertificateFixture.cs
+++ /dev/null
@@ -1,47 +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.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;
- }
- }
- }
-}