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

Commit b1cd817

Browse files
committed
Add integration test for untrusted and unavailable revocation TSA certificates (#323)
Progress on NuGet/Engineering#878
1 parent 33df23d commit b1cd817

6 files changed

Lines changed: 295 additions & 83 deletions

File tree

src/Validation.PackageSigning.ExtractAndValidateSignature/Validation.PackageSigning.ExtractAndValidateSignature.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@
106106
<Version>1.1.2</Version>
107107
</PackageReference>
108108
<PackageReference Include="NuGet.Packaging">
109-
<Version>4.7.0-preview1-4883</Version>
109+
<Version>4.7.0-preview1-4886</Version>
110110
</PackageReference>
111111
<PackageReference Include="NuGet.Services.Configuration">
112112
<Version>2.12.0</Version>

tests/Validation.PackageSigning.ExtractAndValidateSignature.Tests/SignatureValidatorIntegrationTests.cs

Lines changed: 147 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
using NuGet.Services.Validation.Issues;
2323
using NuGetGallery;
2424
using Test.Utility.Signing;
25+
using Validation.PackageSigning.ExtractAndValidateSignature.Tests.Support;
2526
using Xunit;
2627
using Xunit.Abstractions;
2728
using NuGetHashAlgorithmName = NuGet.Common.HashAlgorithmName;
@@ -92,13 +93,13 @@ public SignatureValidatorIntegrationTests(CertificateIntegrationTestFixture fixt
9293

9394
public async Task<SignedPackageArchive> GetSignedPackage1Async()
9495
{
95-
AllowCertificateThumbprint(_fixture.LeafCertificate1Thumbprint);
96+
AllowCertificateThumbprint(await _fixture.GetSigningCertificateThumbprintAsync());
9697
return await _fixture.GetSignedPackage1Async(_output);
9798
}
9899

99100
public async Task<MemoryStream> GetSignedPackageStream1Async()
100101
{
101-
AllowCertificateThumbprint(_fixture.LeafCertificate1Thumbprint);
102+
AllowCertificateThumbprint(await _fixture.GetSigningCertificateThumbprintAsync());
102103
return await _fixture.GetSignedPackageStream1Async(_output);
103104
}
104105

@@ -136,31 +137,43 @@ public async Task RejectsUntrustedSigningCertificate()
136137

137138
// Assert
138139
VerifyPackageSigningStatus(result, ValidationStatus.Failed, PackageSigningStatus.Invalid);
139-
Assert.NotEmpty(result.Issues);
140-
var clientIssues = result
141-
.Issues
142-
.OfType<ClientSigningVerificationFailure>()
143-
.Where(x => x.ClientCode == "NU3021")
144-
.ToList();
145-
var untrustedIssue = Assert.Single(clientIssues);
140+
var issue = Assert.Single(result.Issues);
141+
var clientIssue = Assert.IsType<ClientSigningVerificationFailure>(issue);
142+
Assert.Equal("NU3021", clientIssue.ClientCode);
146143
Assert.Equal(
147144
"A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider.",
148-
untrustedIssue.ClientMessage);
145+
clientIssue.ClientMessage);
149146
}
150147

151148
[Fact]
152-
public async Task AcceptsTrustedCertificateWithUnavailableRevocation()
149+
public async Task RejectsUntrustedTimestampingCertificate()
153150
{
154151
// Arrange
155-
using (var trustedTestCert = new TrustedTestCert<X509Certificate2>(
156-
await TestResources.GetTestRootCertificateAsync(),
157-
x => x,
158-
StoreName.Root,
159-
StoreLocation.LocalMachine,
160-
maximumValidityPeriod: TimeSpan.MaxValue))
152+
var testServer = await _fixture.GetTestServerAsync();
153+
var untrustedRootCa = CertificateAuthority.Create(testServer.Url);
154+
var untrustedRootCertficate = new X509Certificate2(untrustedRootCa.Certificate.GetEncoded());
155+
var timestampService = TimestampService.Create(untrustedRootCa);
156+
using (testServer.RegisterDefaultResponders(timestampService))
161157
{
162-
_package = TestResources.SignedPackageLeaf1Reader;
163-
AllowCertificateThumbprint(TestResources.Leaf1Thumbprint);
158+
byte[] packageBytes;
159+
using (var temporaryTrust = new TrustedTestCert<X509Certificate2>(
160+
untrustedRootCertficate,
161+
x => x,
162+
StoreName.Root,
163+
StoreLocation.LocalMachine))
164+
{
165+
packageBytes = await _fixture.GenerateSignedPackageBytesAsync(
166+
TestResources.SignedPackageLeaf1,
167+
await _fixture.GetSigningCertificateAsync(),
168+
timestampService.Url,
169+
_output);
170+
}
171+
172+
AllowCertificateThumbprint(await _fixture.GetSigningCertificateThumbprintAsync());
173+
174+
_package = new SignedPackageArchive(
175+
new MemoryStream(packageBytes),
176+
new MemoryStream());
164177

165178
// Act
166179
var result = await _target.ValidateAsync(
@@ -169,9 +182,124 @@ await TestResources.GetTestRootCertificateAsync(),
169182
_message,
170183
_token);
171184

185+
// Assert
186+
VerifyPackageSigningStatus(result, ValidationStatus.Failed, PackageSigningStatus.Invalid);
187+
var issue = Assert.Single(result.Issues);
188+
var clientIssue = Assert.IsType<ClientSigningVerificationFailure>(issue);
189+
Assert.Equal("NU3028", clientIssue.ClientCode);
190+
Assert.Equal(
191+
"A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider.",
192+
clientIssue.ClientMessage);
193+
}
194+
}
195+
196+
[Fact]
197+
public async Task AcceptsTrustedTimestampingCertificateWithUnavailableRevocation()
198+
{
199+
// Arrange
200+
var testServer = await _fixture.GetTestServerAsync();
201+
var trustedRootCa = CertificateAuthority.Create(testServer.Url);
202+
var trustedRootCertficate = new X509Certificate2(trustedRootCa.Certificate.GetEncoded());
203+
var timestampService = TimestampService.Create(trustedRootCa);
204+
using (var trust = new TrustedTestCert<X509Certificate2>(
205+
trustedRootCertficate,
206+
x => x,
207+
StoreName.Root,
208+
StoreLocation.LocalMachine))
209+
{
210+
byte[] packageBytes;
211+
using (testServer.RegisterDefaultResponders(timestampService))
212+
{
213+
packageBytes = await _fixture.GenerateSignedPackageBytesAsync(
214+
TestResources.SignedPackageLeaf1,
215+
await _fixture.GetSigningCertificateAsync(),
216+
timestampService.Url,
217+
_output);
218+
}
219+
220+
// Wait one second for the OCSP response cached by the operating system during signing to get stale.
221+
// This can be mitigated by leaving the OCSP unavailable during signing once this work item is done:
222+
// https://github.com/NuGet/Home/issues/6508
223+
await Task.Delay(TimeSpan.FromSeconds(1));
224+
225+
AllowCertificateThumbprint(await _fixture.GetSigningCertificateThumbprintAsync());
226+
227+
_package = new SignedPackageArchive(
228+
new MemoryStream(packageBytes),
229+
new MemoryStream());
230+
231+
SignatureValidatorResult result;
232+
using (testServer.RegisterResponders(timestampService, addOcsp: false))
233+
{
234+
// Act
235+
result = await _target.ValidateAsync(
236+
_packageKey,
237+
_package,
238+
_message,
239+
_token);
240+
}
241+
172242
// Assert
173243
VerifyPackageSigningStatus(result, ValidationStatus.Succeeded, PackageSigningStatus.Valid);
174244
Assert.Empty(result.Issues);
245+
246+
var allMessages = string.Join(Environment.NewLine, _logger.Messages);
247+
Assert.Contains("NU3028: The revocation function was unable to check revocation because the revocation server was offline.", allMessages);
248+
Assert.Contains("NU3028: The revocation function was unable to check revocation for the certificate.", allMessages);
249+
}
250+
}
251+
252+
[Fact]
253+
public async Task AcceptsTrustedSigningCertificateWithUnavailableRevocation()
254+
{
255+
// Arrange
256+
var testServer = await _fixture.GetTestServerAsync();
257+
var rootCa = CertificateAuthority.Create(testServer.Url);
258+
var intermediateCa = rootCa.CreateIntermediateCertificateAuthority();
259+
var rootCertificate = new X509Certificate2(rootCa.Certificate.GetEncoded());
260+
var signingCertificate = _fixture.CreateSigningCertificate(intermediateCa);
261+
using (var trust = new TrustedTestCert<X509Certificate2>(
262+
rootCertificate,
263+
x => x,
264+
StoreName.Root,
265+
StoreLocation.LocalMachine))
266+
{
267+
byte[] packageBytes;
268+
using (testServer.RegisterResponders(intermediateCa))
269+
{
270+
packageBytes = await _fixture.GenerateSignedPackageBytesAsync(
271+
TestResources.SignedPackageLeaf1,
272+
signingCertificate,
273+
await _fixture.GetTimestampServiceUrlAsync(),
274+
_output);
275+
}
276+
277+
// Wait one second for the OCSP response cached by the operating system during signing to get stale.
278+
// This can be mitigated by leaving the OCSP unavailable during signing once this work item is done:
279+
// https://github.com/NuGet/Home/issues/6508
280+
await Task.Delay(TimeSpan.FromSeconds(1));
281+
282+
AllowCertificateThumbprint(signingCertificate.ComputeSHA256Thumbprint());
283+
284+
_package = new SignedPackageArchive(
285+
new MemoryStream(packageBytes),
286+
new MemoryStream());
287+
288+
SignatureValidatorResult result;
289+
using (testServer.RegisterResponders(intermediateCa, addOcsp: false))
290+
{
291+
// Act
292+
result = await _target.ValidateAsync(
293+
_packageKey,
294+
_package,
295+
_message,
296+
_token);
297+
}
298+
299+
// Assert
300+
VerifyPackageSigningStatus(result, ValidationStatus.Succeeded, PackageSigningStatus.Valid);
301+
Assert.Empty(result.Issues);
302+
175303
var allMessages = string.Join(Environment.NewLine, _logger.Messages);
176304
Assert.Contains("NU3018: The revocation function was unable to check revocation because the revocation server was offline.", allMessages);
177305
Assert.Contains("NU3018: The revocation function was unable to check revocation for the certificate.", allMessages);

0 commit comments

Comments
 (0)