From 3503f0074e9015dd98aa97c3798e924bfc4a068f Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Sat, 7 Jun 2025 14:44:37 +0100
Subject: [PATCH 1/6] Add Common project to solution
---
src/Microsoft.Data.SqlClient.sln | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/src/Microsoft.Data.SqlClient.sln b/src/Microsoft.Data.SqlClient.sln
index 178d71db10..585e58a4f1 100644
--- a/src/Microsoft.Data.SqlClient.sln
+++ b/src/Microsoft.Data.SqlClient.sln
@@ -304,6 +304,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "steps", "steps", "{AD738BD4
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.SqlClient.UnitTests", "Microsoft.Data.SqlClient\tests\UnitTests\Microsoft.Data.SqlClient.UnitTests.csproj", "{4461063D-2F2B-274C-7E6F-F235119D258E}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Microsoft.Data.SqlClient\tests\Common\Common.csproj", "{67128EC0-30F5-6A98-448B-55F88A1DE707}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -582,6 +584,18 @@ Global
{4461063D-2F2B-274C-7E6F-F235119D258E}.Release|x64.Build.0 = Release|x64
{4461063D-2F2B-274C-7E6F-F235119D258E}.Release|x86.ActiveCfg = Release|x86
{4461063D-2F2B-274C-7E6F-F235119D258E}.Release|x86.Build.0 = Release|x86
+ {67128EC0-30F5-6A98-448B-55F88A1DE707}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {67128EC0-30F5-6A98-448B-55F88A1DE707}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {67128EC0-30F5-6A98-448B-55F88A1DE707}.Debug|x64.ActiveCfg = Debug|x64
+ {67128EC0-30F5-6A98-448B-55F88A1DE707}.Debug|x64.Build.0 = Debug|x64
+ {67128EC0-30F5-6A98-448B-55F88A1DE707}.Debug|x86.ActiveCfg = Debug|x86
+ {67128EC0-30F5-6A98-448B-55F88A1DE707}.Debug|x86.Build.0 = Debug|x86
+ {67128EC0-30F5-6A98-448B-55F88A1DE707}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {67128EC0-30F5-6A98-448B-55F88A1DE707}.Release|Any CPU.Build.0 = Release|Any CPU
+ {67128EC0-30F5-6A98-448B-55F88A1DE707}.Release|x64.ActiveCfg = Release|x64
+ {67128EC0-30F5-6A98-448B-55F88A1DE707}.Release|x64.Build.0 = Release|x64
+ {67128EC0-30F5-6A98-448B-55F88A1DE707}.Release|x86.ActiveCfg = Release|x86
+ {67128EC0-30F5-6A98-448B-55F88A1DE707}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -632,6 +646,7 @@ Global
{09352F1D-878F-4F55-8AA2-6E47F1AD37D5} = {4CAE9195-4F1A-4D48-854C-1C9FBC512C66}
{AD738BD4-6A02-4B88-8F93-FBBBA49A74C8} = {4CAE9195-4F1A-4D48-854C-1C9FBC512C66}
{4461063D-2F2B-274C-7E6F-F235119D258E} = {0CC4817A-12F3-4357-912C-09315FAAD008}
+ {67128EC0-30F5-6A98-448B-55F88A1DE707} = {0CC4817A-12F3-4357-912C-09315FAAD008}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {01D48116-37A2-4D33-B9EC-94793C702431}
From 81be890446f139695758e88a2ae9d25b59dfc7b6 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Sat, 7 Jun 2025 16:19:03 +0100
Subject: [PATCH 2/6] Enable default testing of net8.0
---
src/Microsoft.Data.SqlClient/tests/Directory.Build.props | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Microsoft.Data.SqlClient/tests/Directory.Build.props b/src/Microsoft.Data.SqlClient/tests/Directory.Build.props
index e0916258e1..afd05e6847 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
From 3aa4e1861b9c5025cdd4e0f632b37822c1c40ee8 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Sat, 7 Jun 2025 17:24:18 +0100
Subject: [PATCH 3/6] Move fixtures to the Common tests project, update
references
---
.../tests/Common/Common.csproj | 6 +
.../Fixtures/AzureKeyVaultKeyFixtureBase.cs | 62 +++++
.../Fixtures/CertificateFixtureBase.cs | 259 +++++++++---------
.../ColumnEncryptionCertificateFixture.cs | 62 +++++
.../ColumnMasterKeyCertificateFixture.cs | 28 ++
.../Common/Fixtures/CspCertificateFixture.cs | 46 ++++
.../ExceptionsAlgorithmErrors.cs | 2 +-
...ncryptionCertificateStoreProviderShould.cs | 2 +-
.../AlwaysEncrypted/ConversionTests.cs | 2 +-
.../AlwaysEncrypted/CspProviderExt.cs | 2 +-
.../TestFixtures/AzureKeyVaultKeyFixture.cs | 2 +-
.../TestFixtures/SQLSetupStrategy.cs | 2 +-
.../Fixtures/AzureKeyVaultKeyFixtureBase.cs | 63 -----
.../ColumnEncryptionCertificateFixture.cs | 63 -----
.../ColumnMasterKeyCertificateFixture.cs | 29 --
.../Fixtures/CspCertificateFixture.cs | 47 ----
...rosoft.Data.SqlClient.TestUtilities.csproj | 4 -
17 files changed, 339 insertions(+), 342 deletions(-)
create mode 100644 src/Microsoft.Data.SqlClient/tests/Common/Fixtures/AzureKeyVaultKeyFixtureBase.cs
rename src/Microsoft.Data.SqlClient/tests/{tools/Microsoft.Data.SqlClient.TestUtilities => Common}/Fixtures/CertificateFixtureBase.cs (51%)
create mode 100644 src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs
create mode 100644 src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs
create mode 100644 src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs
delete mode 100644 src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/AzureKeyVaultKeyFixtureBase.cs
delete mode 100644 src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/ColumnEncryptionCertificateFixture.cs
delete mode 100644 src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/ColumnMasterKeyCertificateFixture.cs
delete mode 100644 src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CspCertificateFixture.cs
diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj b/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj
index 0850798f5c..b0f1075460 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..ad25bd787b
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/AzureKeyVaultKeyFixtureBase.cs
@@ -0,0 +1,62 @@
+// 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;
+
+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/Common/Fixtures/CertificateFixtureBase.cs
similarity index 51%
rename from src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs
rename to src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs
index bcc0d93c07..8949be850d 100644
--- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Fixtures/CertificateFixtureBase.cs
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs
@@ -10,89 +10,89 @@
using System.Text;
using System.Threading;
-namespace Microsoft.Data.SqlClient.TestUtilities.Fixtures
+namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures;
+
+public abstract class CertificateFixtureBase : IDisposable
{
- 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
{
- ///
- /// 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 List Certificates { get; }
- public StoreLocation Location { get; }
+ public StoreLocation Location { get; }
- public StoreName Name { get; }
+ public StoreName Name { get; }
- public CertificateStoreContext(StoreLocation location, StoreName name)
- {
- Certificates = new List();
- Location = location;
- Name = name;
- }
+ public CertificateStoreContext(StoreLocation location, StoreName name)
+ {
+ Certificates = new List();
+ Location = location;
+ Name = name;
}
+ }
- private readonly List _certificateStoreModifications = new List();
+ 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);
+ 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;
+ 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;
- }
+ 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);
+ 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));
+ 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());
- }
+ 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
+ // 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,
@@ -102,13 +102,13 @@ protected X509Certificate2 CreateCertificate(string subjectName, IEnumerable 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);
+ protected void AddToStore(X509Certificate2 cert, StoreLocation storeLocation, StoreName storeName)
+ {
+ CertificateStoreContext storeContext = _certificateStoreModifications.Find(csc => csc.Location == storeLocation && csc.Name == storeName);
- storeContext.Certificates.Add(cert);
+ if (storeContext == null)
+ {
+ storeContext = new(storeLocation, storeName);
+ _certificateStoreModifications.Add(storeContext);
}
- public void Dispose()
+ using X509Store store = new X509Store(storeContext.Name, storeContext.Location);
+
+ store.Open(OpenFlags.ReadWrite);
+ if (store.Certificates.Contains(cert))
{
- Dispose(true);
- GC.SuppressFinalize(this);
+ store.Remove(cert);
}
+ store.Add(cert);
+
+ storeContext.Certificates.Add(cert);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
- protected virtual void Dispose(bool disposing)
+ protected virtual void Dispose(bool disposing)
+ {
+ foreach (CertificateStoreContext storeContext in _certificateStoreModifications)
{
- foreach (CertificateStoreContext storeContext in _certificateStoreModifications)
+ using X509Store store = new X509Store(storeContext.Name, storeContext.Location);
+
+ try
+ {
+ store.Open(OpenFlags.ReadWrite);
+ }
+ catch (Exception)
{
- using X509Store store = new X509Store(storeContext.Name, storeContext.Location);
+ continue;
+ }
+ foreach (X509Certificate2 cert in storeContext.Certificates)
+ {
try
{
- store.Open(OpenFlags.ReadWrite);
+ if (store.Certificates.Contains(cert))
+ {
+ store.Remove(cert);
+ }
}
- catch(Exception)
+ 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();
+ 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..4df3ab23ab
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs
@@ -0,0 +1,62 @@
+// 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;
+
+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..1b58af7458
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs
@@ -0,0 +1,28 @@
+// 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;
+
+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..9858554e48
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs
@@ -0,0 +1,46 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+
+namespace Microsoft.Data.SqlClient.Tests.Common.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/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/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;
- }
- }
- }
-}
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 bcd0bdb27b..058e8190ed 100644
--- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Microsoft.Data.SqlClient.TestUtilities.csproj
+++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Microsoft.Data.SqlClient.TestUtilities.csproj
@@ -15,10 +15,6 @@
PreserveNewest
-
-
-
-
From d8722b6757866b1a367fb20b8e25b9aa0cb83f7d Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Sat, 7 Jun 2025 17:35:57 +0100
Subject: [PATCH 4/6] Add high-level summary to cryptographic fixtures
---
.../Common/Fixtures/AzureKeyVaultKeyFixtureBase.cs | 8 ++++++++
.../tests/Common/Fixtures/CertificateFixtureBase.cs | 13 +++++++++++++
.../Fixtures/ColumnEncryptionCertificateFixture.cs | 8 ++++++++
.../Fixtures/ColumnMasterKeyCertificateFixture.cs | 7 +++++++
.../tests/Common/Fixtures/CspCertificateFixture.cs | 8 ++++++++
5 files changed, 44 insertions(+)
diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/AzureKeyVaultKeyFixtureBase.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/AzureKeyVaultKeyFixtureBase.cs
index ad25bd787b..f70560be9e 100644
--- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/AzureKeyVaultKeyFixtureBase.cs
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/AzureKeyVaultKeyFixtureBase.cs
@@ -9,6 +9,14 @@
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;
diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs
index 8949be850d..9007796013 100644
--- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs
@@ -12,6 +12,19 @@
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
{
///
diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs
index 4df3ab23ab..a4aa84842f 100644
--- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs
@@ -8,6 +8,14 @@
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; }
diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs
index 1b58af7458..a91ca7a0e0 100644
--- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs
@@ -7,6 +7,13 @@
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()
diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs
index 9858554e48..01b6048fa4 100644
--- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs
@@ -8,6 +8,14 @@
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()
From 87a461c054bfa32ab8b8ae4c603601c46d57cad1 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Sat, 7 Jun 2025 17:39:17 +0100
Subject: [PATCH 5/6] Use interpolated string in CspCertificateFixture
---
.../tests/Common/Fixtures/CspCertificateFixture.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs
index 01b6048fa4..74c4ca0325 100644
--- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs
@@ -24,7 +24,7 @@ public CspCertificateFixture()
AddToStore(CspCertificate, StoreLocation.CurrentUser, StoreName.My);
- CspCertificatePath = string.Format("{0}/{1}/{2}", StoreLocation.CurrentUser, StoreName.My, CspCertificate.Thumbprint);
+ CspCertificatePath = $"{StoreLocation.CurrentUser}/{StoreName.My}/{CspCertificate.Thumbprint}";
CspKeyPath = GetCspPathFromCertificate();
}
From 15c5860f9131acd7e38aace11e5ac0ba1c925010 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Tue, 17 Jun 2025 20:50:18 +0100
Subject: [PATCH 6/6] Correct merge of Common.csproj
---
src/Microsoft.Data.SqlClient/tests/Common/Common.csproj | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj b/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj
index aaf1ba4411..4133622d20 100644
--- a/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj
+++ b/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj
@@ -53,8 +53,8 @@
-
-
+
+