Skip to content
This repository was archived by the owner on Jul 30, 2024. It is now read-only.

Commit 080c870

Browse files
authored
[Package Signing] Certificate Validation Testing Infrastructure (#332)
The focus of this change is to prepare the Validate Certificate job's test project for integration testing: * Moves common integration testing infrastructure from E&V's test project to the common project * Adds a basic integration test to verify a valid codesigning certificate Progress on https://github.com/NuGet/Engineering/issues/878
1 parent 02cbfdd commit 080c870

7 files changed

Lines changed: 279 additions & 135 deletions

File tree

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Security.Cryptography.X509Certificates;
6+
using System.Security.Principal;
7+
using System.Threading.Tasks;
8+
using Org.BouncyCastle.Asn1;
9+
using Org.BouncyCastle.Asn1.X509;
10+
using Org.BouncyCastle.Crypto.Parameters;
11+
using Org.BouncyCastle.Security;
12+
using Test.Utility.Signing;
13+
using Xunit;
14+
using GeneralName = Org.BouncyCastle.Asn1.X509.GeneralName;
15+
16+
namespace Validation.PackageSigning.Core.Tests.Support
17+
{
18+
/// <summary>
19+
/// This test fixture trusts the root certificate of checked in signed packages. This handles adding and removing
20+
/// the root certificate from the local machine trusted roots. Any tests with this fixture require admin elevation.
21+
/// </summary>
22+
public class CertificateIntegrationTestFixture : IDisposable
23+
{
24+
private readonly Lazy<Task<SigningTestServer>> _testServer;
25+
private readonly Lazy<Task<CertificateAuthority>> _certificateAuthority;
26+
private readonly Lazy<Task<TimestampService>> _timestampService;
27+
private readonly Lazy<Task<Uri>> _timestampServiceUrl;
28+
private readonly Lazy<Task<X509Certificate2>> _signingCertificate;
29+
private readonly Lazy<Task<string>> _signingCertificateThumbprint;
30+
private TrustedTestCert<X509Certificate2> _trustedRoot;
31+
private readonly DisposableList _responders;
32+
33+
public CertificateIntegrationTestFixture()
34+
{
35+
Assert.True(
36+
IsAdministrator(),
37+
"This test must be executing with administrator privileges since it installs a trusted root.");
38+
39+
_testServer = new Lazy<Task<SigningTestServer>>(SigningTestServer.CreateAsync);
40+
_certificateAuthority = new Lazy<Task<CertificateAuthority>>(CreateDefaultTrustedCertificateAuthorityAsync);
41+
_timestampService = new Lazy<Task<TimestampService>>(CreateDefaultTrustedTimestampServiceAsync);
42+
_timestampServiceUrl = new Lazy<Task<Uri>>(CreateDefaultTrustedTimestampServiceUrlAsync);
43+
_signingCertificate = new Lazy<Task<X509Certificate2>>(CreateDefaultTrustedSigningCertificateAsync);
44+
_signingCertificateThumbprint = new Lazy<Task<string>>(GetDefaultTrustedSigningCertificateThumbprintAsync);
45+
_responders = new DisposableList();
46+
}
47+
48+
public Task<SigningTestServer> GetTestServerAsync() => _testServer.Value;
49+
public Task<Uri> GetTimestampServiceUrlAsync() => _timestampServiceUrl.Value;
50+
public Task<X509Certificate2> GetSigningCertificateAsync() => _signingCertificate.Value;
51+
public Task<string> GetSigningCertificateThumbprintAsync() => _signingCertificateThumbprint.Value;
52+
53+
public void Dispose()
54+
{
55+
_trustedRoot?.Dispose();
56+
_responders.Dispose();
57+
58+
if (_testServer.IsValueCreated)
59+
{
60+
_testServer.Value.Result.Dispose();
61+
}
62+
}
63+
64+
private async Task<CertificateAuthority> CreateDefaultTrustedCertificateAuthorityAsync()
65+
{
66+
var testServer = await GetTestServerAsync();
67+
var rootCa = CertificateAuthority.Create(testServer.Url);
68+
var intermediateCa = rootCa.CreateIntermediateCertificateAuthority();
69+
var rootCertificate = new X509Certificate2(rootCa.Certificate.GetEncoded());
70+
71+
_trustedRoot = new TrustedTestCert<X509Certificate2>(
72+
rootCertificate,
73+
certificate => certificate,
74+
StoreName.Root,
75+
StoreLocation.LocalMachine);
76+
77+
_responders.AddRange(testServer.RegisterResponders(intermediateCa));
78+
79+
return intermediateCa;
80+
}
81+
82+
private async Task<TimestampService> CreateDefaultTrustedTimestampServiceAsync()
83+
{
84+
var testServer = await GetTestServerAsync();
85+
var ca = await _certificateAuthority.Value;
86+
var timestampService = TimestampService.Create(ca);
87+
88+
_responders.Add(testServer.RegisterResponder(timestampService));
89+
90+
return timestampService;
91+
}
92+
93+
private async Task<Uri> CreateDefaultTrustedTimestampServiceUrlAsync()
94+
{
95+
var timestampService = await _timestampService.Value;
96+
return timestampService.Url;
97+
}
98+
99+
private async Task<X509Certificate2> CreateDefaultTrustedSigningCertificateAsync()
100+
{
101+
var ca = await _certificateAuthority.Value;
102+
return CreateSigningCertificate(ca);
103+
}
104+
105+
public X509Certificate2 CreateSigningCertificate(CertificateAuthority ca)
106+
{
107+
var keyPair = SigningTestUtility.GenerateKeyPair(publicKeyLength: 2048);
108+
var publicCertificate = ca.IssueCertificate(
109+
keyPair.Public,
110+
new X509Name($"C=US,ST=WA,L=Redmond,O=NuGet,CN=NuGet Test Signing Certificate ({Guid.NewGuid()})"),
111+
generator =>
112+
{
113+
SigningTestUtility.CertificateModificationGeneratorForCodeSigningEkuCert(generator);
114+
115+
generator.AddExtension(
116+
X509Extensions.AuthorityInfoAccess,
117+
critical: false,
118+
extensionValue: new DerSequence(
119+
new AccessDescription(AccessDescription.IdADOcsp,
120+
new GeneralName(GeneralName.UniformResourceIdentifier, ca.OcspResponderUri.OriginalString)),
121+
new AccessDescription(AccessDescription.IdADCAIssuers,
122+
new GeneralName(GeneralName.UniformResourceIdentifier, ca.CertificateUri.OriginalString))));
123+
},
124+
notBefore: DateTime.UtcNow.AddSeconds(-10));
125+
126+
var certificate = new X509Certificate2(publicCertificate.GetEncoded());
127+
certificate.PrivateKey = DotNetUtilities.ToRSA(keyPair.Private as RsaPrivateCrtKeyParameters);
128+
129+
return certificate;
130+
}
131+
132+
private async Task<string> GetDefaultTrustedSigningCertificateThumbprintAsync()
133+
{
134+
var certificate = await GetSigningCertificateAsync();
135+
return certificate.ComputeSHA256Thumbprint();
136+
}
137+
138+
/// <summary>
139+
/// Source: https://stackoverflow.com/a/11660205
140+
/// </summary>
141+
private static bool IsAdministrator()
142+
{
143+
var identity = WindowsIdentity.GetCurrent();
144+
var principal = new WindowsPrincipal(identity);
145+
return principal.IsInRole(WindowsBuiltInRole.Administrator);
146+
}
147+
}
148+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Test.Utility.Signing;
5+
6+
namespace Validation.PackageSigning.Core.Tests.Support
7+
{
8+
public static class ExtensionMethods
9+
{
10+
public static DisposableList RegisterResponders(
11+
this ISigningTestServer testServer,
12+
CertificateAuthority ca,
13+
bool addCa = true,
14+
bool addOcsp = true)
15+
{
16+
var responders = new DisposableList();
17+
var currentCa = ca;
18+
19+
while (currentCa != null)
20+
{
21+
if (addCa)
22+
{
23+
responders.Add(testServer.RegisterResponder(currentCa));
24+
}
25+
26+
if (addOcsp)
27+
{
28+
responders.Add(testServer.RegisterResponder(currentCa.OcspResponder));
29+
}
30+
31+
currentCa = currentCa.Parent;
32+
}
33+
34+
return responders;
35+
}
36+
37+
public static DisposableList RegisterResponders(
38+
this ISigningTestServer testServer,
39+
TimestampService timestampService,
40+
bool addCa = true,
41+
bool addOcsp = true,
42+
bool addTimestamper = true)
43+
{
44+
var responders = testServer.RegisterResponders(
45+
timestampService.CertificateAuthority,
46+
addCa,
47+
addOcsp);
48+
49+
if (addTimestamper)
50+
{
51+
responders.Add(testServer.RegisterResponder(timestampService));
52+
}
53+
54+
return responders;
55+
}
56+
}
57+
}

tests/Validation.PackageSigning.Core.Tests/Validation.PackageSigning.Core.Tests.csproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@
3838
<Compile Include="Properties\AssemblyInfo.cs" />
3939
<Compile Include="Storage\CertificateStoreTests.cs" />
4040
<Compile Include="Storage\ValidatorStatusExtensionsFacts.cs" />
41+
<Compile Include="Support\CertificateIntegrationTestFixture.cs" />
4142
<Compile Include="Support\DbSetMockFactory.cs" />
43+
<Compile Include="Support\ExtensionMethods.cs" />
4244
<Compile Include="Support\TestDbAsyncEnumerable.cs" />
4345
<Compile Include="Support\TestDbAsyncEnumerator.cs" />
4446
<Compile Include="Support\TestDbAsyncQueryProvider.cs" />
@@ -57,6 +59,12 @@
5759
<PackageReference Include="Moq">
5860
<Version>4.7.145</Version>
5961
</PackageReference>
62+
<PackageReference Include="Portable.BouncyCastle">
63+
<Version>1.8.1.3</Version>
64+
</PackageReference>
65+
<PackageReference Include="Test.Utility">
66+
<Version>4.7.0-preview1-4886</Version>
67+
</PackageReference>
6068
<PackageReference Include="xunit">
6169
<Version>2.3.1</Version>
6270
</PackageReference>

tests/Validation.PackageSigning.ExtractAndValidateSignature.Tests/Support/CertificateIntegrationTestFixture.cs

Lines changed: 3 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -4,57 +4,26 @@
44
using System;
55
using System.IO;
66
using System.Security.Cryptography.X509Certificates;
7-
using System.Security.Principal;
87
using System.Threading;
98
using System.Threading.Tasks;
109
using NuGet.Common;
1110
using NuGet.Packaging.Signing;
1211
using NuGet.Test.Utility;
13-
using Org.BouncyCastle.Asn1;
14-
using Org.BouncyCastle.Asn1.X509;
15-
using Org.BouncyCastle.Crypto.Parameters;
16-
using Org.BouncyCastle.Security;
17-
using Test.Utility.Signing;
18-
using Validation.PackageSigning.ExtractAndValidateSignature.Tests.Support;
19-
using Xunit;
2012
using Xunit.Abstractions;
21-
using GeneralName = Org.BouncyCastle.Asn1.X509.GeneralName;
2213

2314
namespace Validation.PackageSigning.ExtractAndValidateSignature.Tests
2415
{
16+
using CoreCertificateIntegrationTestFixture = Core.Tests.Support.CertificateIntegrationTestFixture;
17+
2518
/// <summary>
2619
/// This test fixture trusts the root certificate of checked in signed packages. This handles adding and removing
2720
/// the root certificate from the local machine trusted roots. Any tests with this fixture require admin elevation.
2821
/// </summary>
29-
public class CertificateIntegrationTestFixture : IDisposable
22+
public class CertificateIntegrationTestFixture : CoreCertificateIntegrationTestFixture
3023
{
31-
private readonly Lazy<Task<SigningTestServer>> _testServer;
32-
private readonly Lazy<Task<CertificateAuthority>> _certificateAuthority;
33-
private readonly Lazy<Task<TimestampService>> _timestampService;
34-
private readonly Lazy<Task<Uri>> _timestampServiceUrl;
35-
private readonly Lazy<Task<X509Certificate2>> _signingCertificate;
36-
private readonly Lazy<Task<string>> _signingCertificateThumbprint;
37-
private TrustedTestCert<X509Certificate2> _trustedRoot;
38-
private readonly DisposableList _responders;
39-
4024
private readonly SemaphoreSlim _lock = new SemaphoreSlim(1);
4125
private byte[] _signedPackageBytes1;
4226

43-
public CertificateIntegrationTestFixture()
44-
{
45-
Assert.True(
46-
IsAdministrator(),
47-
"This test must be executing with administrator privileges since it installs a trusted root.");
48-
49-
_testServer = new Lazy<Task<SigningTestServer>>(SigningTestServer.CreateAsync);
50-
_certificateAuthority = new Lazy<Task<CertificateAuthority>>(CreateDefaultTrustedCertificateAuthorityAsync);
51-
_timestampService = new Lazy<Task<TimestampService>>(CreateDefaultTrustedTimestampServiceAsync);
52-
_timestampServiceUrl = new Lazy<Task<Uri>>(CreateDefaultTrustedTimestampServiceUrlAsync);
53-
_signingCertificate = new Lazy<Task<X509Certificate2>>(CreateDefaultTrustedSigningCertificateAsync);
54-
_signingCertificateThumbprint = new Lazy<Task<string>>(GetDefaultTrustedSigningCertificateThumbprintAsync);
55-
_responders = new DisposableList();
56-
}
57-
5827
public async Task<SignedPackageArchive> GetSignedPackage1Async(ITestOutputHelper output) => await GetSignedPackageAsync(
5928
new Reference<byte[]>(
6029
() => _signedPackageBytes1,
@@ -70,106 +39,6 @@ public async Task<MemoryStream> GetSignedPackageStream1Async(ITestOutputHelper o
7039
await GetSigningCertificateAsync(),
7140
output);
7241

73-
public Task<SigningTestServer> GetTestServerAsync() => _testServer.Value;
74-
public Task<Uri> GetTimestampServiceUrlAsync() => _timestampServiceUrl.Value;
75-
public Task<X509Certificate2> GetSigningCertificateAsync() => _signingCertificate.Value;
76-
public Task<string> GetSigningCertificateThumbprintAsync() => _signingCertificateThumbprint.Value;
77-
78-
public void Dispose()
79-
{
80-
_trustedRoot?.Dispose();
81-
_responders.Dispose();
82-
83-
if (_testServer.IsValueCreated)
84-
{
85-
_testServer.Value.Result.Dispose();
86-
}
87-
}
88-
89-
private async Task<CertificateAuthority> CreateDefaultTrustedCertificateAuthorityAsync()
90-
{
91-
var testServer = await GetTestServerAsync();
92-
var rootCa = CertificateAuthority.Create(testServer.Url);
93-
var intermediateCa = rootCa.CreateIntermediateCertificateAuthority();
94-
var rootCertificate = new X509Certificate2(rootCa.Certificate.GetEncoded());
95-
96-
_trustedRoot = new TrustedTestCert<X509Certificate2>(
97-
rootCertificate,
98-
certificate => certificate,
99-
StoreName.Root,
100-
StoreLocation.LocalMachine);
101-
102-
_responders.AddRange(testServer.RegisterResponders(intermediateCa));
103-
104-
return intermediateCa;
105-
}
106-
107-
private async Task<TimestampService> CreateDefaultTrustedTimestampServiceAsync()
108-
{
109-
var testServer = await GetTestServerAsync();
110-
var ca = await _certificateAuthority.Value;
111-
var timestampService = TimestampService.Create(ca);
112-
113-
_responders.Add(testServer.RegisterResponder(timestampService));
114-
115-
return timestampService;
116-
}
117-
118-
private async Task<Uri> CreateDefaultTrustedTimestampServiceUrlAsync()
119-
{
120-
var timestampService = await _timestampService.Value;
121-
return timestampService.Url;
122-
}
123-
124-
private async Task<X509Certificate2> CreateDefaultTrustedSigningCertificateAsync()
125-
{
126-
var ca = await _certificateAuthority.Value;
127-
return CreateSigningCertificate(ca);
128-
}
129-
130-
public X509Certificate2 CreateSigningCertificate(CertificateAuthority ca)
131-
{
132-
var keyPair = SigningTestUtility.GenerateKeyPair(publicKeyLength: 2048);
133-
var publicCertificate = ca.IssueCertificate(
134-
keyPair.Public,
135-
new X509Name($"C=US,ST=WA,L=Redmond,O=NuGet,CN=NuGet Test Signing Certificate ({Guid.NewGuid()})"),
136-
generator =>
137-
{
138-
SigningTestUtility.CertificateModificationGeneratorForCodeSigningEkuCert(generator);
139-
140-
generator.AddExtension(
141-
X509Extensions.AuthorityInfoAccess,
142-
critical: false,
143-
extensionValue: new DerSequence(
144-
new AccessDescription(AccessDescription.IdADOcsp,
145-
new GeneralName(GeneralName.UniformResourceIdentifier, ca.OcspResponderUri.OriginalString)),
146-
new AccessDescription(AccessDescription.IdADCAIssuers,
147-
new GeneralName(GeneralName.UniformResourceIdentifier, ca.CertificateUri.OriginalString))));
148-
},
149-
notBefore: DateTime.UtcNow.AddSeconds(-10));
150-
151-
var certificate = new X509Certificate2(publicCertificate.GetEncoded());
152-
certificate.PrivateKey = DotNetUtilities.ToRSA(keyPair.Private as RsaPrivateCrtKeyParameters);
153-
154-
return certificate;
155-
}
156-
157-
private async Task<string> GetDefaultTrustedSigningCertificateThumbprintAsync()
158-
{
159-
var certificate = await GetSigningCertificateAsync();
160-
return certificate.ComputeSHA256Thumbprint();
161-
}
162-
163-
/// <summary>
164-
/// Source: https://stackoverflow.com/a/11660205
165-
/// </summary>
166-
private static bool IsAdministrator()
167-
{
168-
var identity = WindowsIdentity.GetCurrent();
169-
var principal = new WindowsPrincipal(identity);
170-
return principal.IsInRole(WindowsBuiltInRole.Administrator);
171-
}
172-
17342
private async Task<MemoryStream> GetSignedPackageStreamAsync(
17443
Reference<byte[]> reference,
17544
string resourceName,

0 commit comments

Comments
 (0)