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 @@ - - + +