Skip to content

Commit 3aa4e18

Browse files
committed
Move fixtures to the Common tests project, update references
1 parent 81be890 commit 3aa4e18

17 files changed

+339
-342
lines changed

src/Microsoft.Data.SqlClient/tests/Common/Common.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,10 @@
5151
</ContentWithTargetPath>
5252
</ItemGroup>
5353

54+
<!-- Fixture-specific References -->
55+
<ItemGroup>
56+
<PackageReference Include="Azure.Identity" Version="$(AzureIdentityVersion)" />
57+
<PackageReference Include="Azure.Security.KeyVault.Keys" Version="$(AzureSecurityKeyVaultKeysVersion)" />
58+
</ItemGroup>
59+
5460
</Project>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.using System;
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using Azure.Core;
8+
using Azure.Security.KeyVault.Keys;
9+
10+
namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures;
11+
12+
public abstract class AzureKeyVaultKeyFixtureBase : IDisposable
13+
{
14+
private readonly KeyClient _keyClient;
15+
private readonly Random _randomGenerator;
16+
17+
private readonly List<KeyVaultKey> _createdKeys = new List<KeyVaultKey>();
18+
19+
protected AzureKeyVaultKeyFixtureBase(Uri keyVaultUri, TokenCredential keyVaultToken)
20+
{
21+
_keyClient = new KeyClient(keyVaultUri, keyVaultToken);
22+
_randomGenerator = new Random();
23+
}
24+
25+
protected Uri CreateKey(string name, int keySize)
26+
{
27+
CreateRsaKeyOptions createOptions = new CreateRsaKeyOptions(GenerateUniqueName(name)) { KeySize = keySize };
28+
KeyVaultKey created = _keyClient.CreateRsaKey(createOptions);
29+
30+
_createdKeys.Add(created);
31+
return created.Id;
32+
}
33+
34+
private string GenerateUniqueName(string name)
35+
{
36+
byte[] rndBytes = new byte[16];
37+
38+
_randomGenerator.NextBytes(rndBytes);
39+
return name + "-" + BitConverter.ToString(rndBytes);
40+
}
41+
42+
public void Dispose()
43+
{
44+
Dispose(true);
45+
GC.SuppressFinalize(this);
46+
}
47+
48+
protected virtual void Dispose(bool disposing)
49+
{
50+
foreach (KeyVaultKey key in _createdKeys)
51+
{
52+
try
53+
{
54+
_keyClient.StartDeleteKey(key.Name).WaitForCompletion();
55+
}
56+
catch (Exception)
57+
{
58+
continue;
59+
}
60+
}
61+
}
62+
}
Lines changed: 129 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -10,89 +10,89 @@
1010
using System.Text;
1111
using System.Threading;
1212

13-
namespace Microsoft.Data.SqlClient.TestUtilities.Fixtures
13+
namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures;
14+
15+
public abstract class CertificateFixtureBase : IDisposable
1416
{
15-
public abstract class CertificateFixtureBase : IDisposable
17+
/// <summary>
18+
/// Certificates must be created using this provider. Certificates created by PowerShell
19+
/// using another provider aren't accessible from RSACryptoServiceProvider, which means
20+
/// that we could not roundtrip between SqlColumnEncryptionCertificateStoreProvider and
21+
/// SqlColumnEncryptionCspProvider.
22+
/// </summary>
23+
private const string CspProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider";
24+
25+
private sealed class CertificateStoreContext
1626
{
17-
/// <summary>
18-
/// Certificates must be created using this provider. Certificates created by PowerShell
19-
/// using another provider aren't accessible from RSACryptoServiceProvider, which means
20-
/// that we could not roundtrip between SqlColumnEncryptionCertificateStoreProvider and
21-
/// SqlColumnEncryptionCspProvider.
22-
/// </summary>
23-
private const string CspProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider";
24-
25-
private sealed class CertificateStoreContext
26-
{
27-
public List<X509Certificate2> Certificates { get; }
27+
public List<X509Certificate2> Certificates { get; }
2828

29-
public StoreLocation Location { get; }
29+
public StoreLocation Location { get; }
3030

31-
public StoreName Name { get; }
31+
public StoreName Name { get; }
3232

33-
public CertificateStoreContext(StoreLocation location, StoreName name)
34-
{
35-
Certificates = new List<X509Certificate2>();
36-
Location = location;
37-
Name = name;
38-
}
33+
public CertificateStoreContext(StoreLocation location, StoreName name)
34+
{
35+
Certificates = new List<X509Certificate2>();
36+
Location = location;
37+
Name = name;
3938
}
39+
}
4040

41-
private readonly List<CertificateStoreContext> _certificateStoreModifications = new List<CertificateStoreContext>();
41+
private readonly List<CertificateStoreContext> _certificateStoreModifications = new List<CertificateStoreContext>();
4242

43-
protected X509Certificate2 CreateCertificate(string subjectName, IEnumerable<string> dnsNames, IEnumerable<string> ipAddresses, bool forceCsp = false)
44-
{
45-
// This will always generate a certificate with:
46-
// * Start date: 24hrs ago
47-
// * End date: 24hrs in the future
48-
// * Subject: {subjectName}
49-
// * Subject alternative names: {dnsNames}, {ipAddresses}
50-
// * Public key: 2048-bit RSA
51-
// * Hash algorithm: SHA256
52-
// * Key usage: digital signature, key encipherment
53-
// * Enhanced key usage: server authentication, client authentication
54-
DateTimeOffset notBefore = DateTimeOffset.UtcNow.AddDays(-1);
55-
DateTimeOffset notAfter = DateTimeOffset.UtcNow.AddDays(1);
56-
byte[] passwordBytes = new byte[32];
57-
string password = null;
58-
Random rnd = new Random();
59-
60-
rnd.NextBytes(passwordBytes);
61-
password = Convert.ToBase64String(passwordBytes);
43+
protected X509Certificate2 CreateCertificate(string subjectName, IEnumerable<string> dnsNames, IEnumerable<string> ipAddresses, bool forceCsp = false)
44+
{
45+
// This will always generate a certificate with:
46+
// * Start date: 24hrs ago
47+
// * End date: 24hrs in the future
48+
// * Subject: {subjectName}
49+
// * Subject alternative names: {dnsNames}, {ipAddresses}
50+
// * Public key: 2048-bit RSA
51+
// * Hash algorithm: SHA256
52+
// * Key usage: digital signature, key encipherment
53+
// * Enhanced key usage: server authentication, client authentication
54+
DateTimeOffset notBefore = DateTimeOffset.UtcNow.AddDays(-1);
55+
DateTimeOffset notAfter = DateTimeOffset.UtcNow.AddDays(1);
56+
byte[] passwordBytes = new byte[32];
57+
string password = null;
58+
Random rnd = new Random();
59+
60+
rnd.NextBytes(passwordBytes);
61+
password = Convert.ToBase64String(passwordBytes);
6262
#if NET
63-
X500DistinguishedNameBuilder subjectBuilder = new X500DistinguishedNameBuilder();
64-
SubjectAlternativeNameBuilder sanBuilder = new SubjectAlternativeNameBuilder();
65-
RSA rsaKey = CreateRSA(forceCsp);
66-
bool hasSans = false;
63+
X500DistinguishedNameBuilder subjectBuilder = new X500DistinguishedNameBuilder();
64+
SubjectAlternativeNameBuilder sanBuilder = new SubjectAlternativeNameBuilder();
65+
RSA rsaKey = CreateRSA(forceCsp);
66+
bool hasSans = false;
6767

68-
subjectBuilder.AddCommonName(subjectName);
69-
foreach (string dnsName in dnsNames)
70-
{
71-
sanBuilder.AddDnsName(dnsName);
72-
hasSans = true;
73-
}
74-
foreach (string ipAddress in ipAddresses)
75-
{
76-
sanBuilder.AddIpAddress(System.Net.IPAddress.Parse(ipAddress));
77-
hasSans = true;
78-
}
68+
subjectBuilder.AddCommonName(subjectName);
69+
foreach (string dnsName in dnsNames)
70+
{
71+
sanBuilder.AddDnsName(dnsName);
72+
hasSans = true;
73+
}
74+
foreach (string ipAddress in ipAddresses)
75+
{
76+
sanBuilder.AddIpAddress(System.Net.IPAddress.Parse(ipAddress));
77+
hasSans = true;
78+
}
7979

80-
CertificateRequest request = new CertificateRequest(subjectBuilder.Build(), rsaKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
80+
CertificateRequest request = new CertificateRequest(subjectBuilder.Build(), rsaKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
8181

82-
request.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(request.PublicKey, false));
83-
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment, false));
84-
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));
82+
request.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(request.PublicKey, false));
83+
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment, false));
84+
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));
8585

86-
if (hasSans)
87-
{
88-
request.CertificateExtensions.Add(sanBuilder.Build());
89-
}
86+
if (hasSans)
87+
{
88+
request.CertificateExtensions.Add(sanBuilder.Build());
89+
}
9090

91-
// Generate an ephemeral certificate, then export it and return it as a new certificate with the correct key storage flags set.
92-
// This is to ensure that it's imported into the certificate stores with its private key.
93-
using (X509Certificate2 ephemeral = request.CreateSelfSigned(notBefore, notAfter))
94-
{
95-
#if NET9_0_OR_GREATER
91+
// Generate an ephemeral certificate, then export it and return it as a new certificate with the correct key storage flags set.
92+
// This is to ensure that it's imported into the certificate stores with its private key.
93+
using (X509Certificate2 ephemeral = request.CreateSelfSigned(notBefore, notAfter))
94+
{
95+
#if NET9_0_OR_GREATER
9696
return X509CertificateLoader.LoadPkcs12(
9797
ephemeral.Export(X509ContentType.Pkcs12, password),
9898
password,
@@ -102,13 +102,13 @@ protected X509Certificate2 CreateCertificate(string subjectName, IEnumerable<str
102102
PreserveStorageProvider = true,
103103
PreserveKeyName = true
104104
});
105-
#else
106-
return new X509Certificate2(
107-
ephemeral.Export(X509ContentType.Pkcs12, password),
108-
password,
109-
X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
110-
#endif
111-
}
105+
#else
106+
return new X509Certificate2(
107+
ephemeral.Export(X509ContentType.Pkcs12, password),
108+
password,
109+
X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
110+
#endif
111+
}
112112
#else
113113
// 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.
114114
// Instead, fall back to running a PowerShell script which calls New-SelfSignedCertificate. This cmdlet also adds the certificate to a specific,
@@ -227,82 +227,81 @@ exit 1
227227
"PowerShell command raised exception: " +
228228
$"{commandOutput}; command was: {formattedCommand}");
229229
#endif
230-
}
230+
}
231231

232232
#if NET
233-
private static RSA CreateRSA(bool forceCsp)
234-
{
235-
const int KeySize = 2048;
236-
const int CspProviderType = 24;
233+
private static RSA CreateRSA(bool forceCsp)
234+
{
235+
const int KeySize = 2048;
236+
const int CspProviderType = 24;
237237

238-
return forceCsp && OperatingSystem.IsWindows()
239-
? new RSACryptoServiceProvider(KeySize, new CspParameters(CspProviderType, CspProviderName, Guid.NewGuid().ToString()))
240-
: RSA.Create(KeySize);
241-
}
238+
return forceCsp && OperatingSystem.IsWindows()
239+
? new RSACryptoServiceProvider(KeySize, new CspParameters(CspProviderType, CspProviderName, Guid.NewGuid().ToString()))
240+
: RSA.Create(KeySize);
241+
}
242242
#endif
243243

244-
protected void AddToStore(X509Certificate2 cert, StoreLocation storeLocation, StoreName storeName)
245-
{
246-
CertificateStoreContext storeContext = _certificateStoreModifications.Find(csc => csc.Location == storeLocation && csc.Name == storeName);
247-
248-
if (storeContext == null)
249-
{
250-
storeContext = new(storeLocation, storeName);
251-
_certificateStoreModifications.Add(storeContext);
252-
}
253-
254-
using X509Store store = new X509Store(storeContext.Name, storeContext.Location);
255-
256-
store.Open(OpenFlags.ReadWrite);
257-
if (store.Certificates.Contains(cert))
258-
{
259-
store.Remove(cert);
260-
}
261-
store.Add(cert);
244+
protected void AddToStore(X509Certificate2 cert, StoreLocation storeLocation, StoreName storeName)
245+
{
246+
CertificateStoreContext storeContext = _certificateStoreModifications.Find(csc => csc.Location == storeLocation && csc.Name == storeName);
262247

263-
storeContext.Certificates.Add(cert);
248+
if (storeContext == null)
249+
{
250+
storeContext = new(storeLocation, storeName);
251+
_certificateStoreModifications.Add(storeContext);
264252
}
265253

266-
public void Dispose()
254+
using X509Store store = new X509Store(storeContext.Name, storeContext.Location);
255+
256+
store.Open(OpenFlags.ReadWrite);
257+
if (store.Certificates.Contains(cert))
267258
{
268-
Dispose(true);
269-
GC.SuppressFinalize(this);
259+
store.Remove(cert);
270260
}
261+
store.Add(cert);
262+
263+
storeContext.Certificates.Add(cert);
264+
}
265+
266+
public void Dispose()
267+
{
268+
Dispose(true);
269+
GC.SuppressFinalize(this);
270+
}
271271

272-
protected virtual void Dispose(bool disposing)
272+
protected virtual void Dispose(bool disposing)
273+
{
274+
foreach (CertificateStoreContext storeContext in _certificateStoreModifications)
273275
{
274-
foreach (CertificateStoreContext storeContext in _certificateStoreModifications)
276+
using X509Store store = new X509Store(storeContext.Name, storeContext.Location);
277+
278+
try
279+
{
280+
store.Open(OpenFlags.ReadWrite);
281+
}
282+
catch (Exception)
275283
{
276-
using X509Store store = new X509Store(storeContext.Name, storeContext.Location);
284+
continue;
285+
}
277286

287+
foreach (X509Certificate2 cert in storeContext.Certificates)
288+
{
278289
try
279290
{
280-
store.Open(OpenFlags.ReadWrite);
291+
if (store.Certificates.Contains(cert))
292+
{
293+
store.Remove(cert);
294+
}
281295
}
282-
catch(Exception)
296+
catch (Exception)
283297
{
284298
continue;
285299
}
286300

287-
foreach (X509Certificate2 cert in storeContext.Certificates)
288-
{
289-
try
290-
{
291-
if (store.Certificates.Contains(cert))
292-
{
293-
store.Remove(cert);
294-
}
295-
}
296-
catch (Exception)
297-
{
298-
continue;
299-
}
300-
301-
cert.Dispose();
302-
}
303-
304-
storeContext.Certificates.Clear();
301+
cert.Dispose();
305302
}
303+
304+
storeContext.Certificates.Clear();
306305
}
307306
}
308307
}

0 commit comments

Comments
 (0)