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

Commit 8ae07d8

Browse files
committed
Add trust verifier to extract and validate job (#311)
Progress on NuGet/Engineering#785
1 parent d832e5b commit 8ae07d8

10 files changed

Lines changed: 239 additions & 164 deletions

src/Validation.PackageSigning.ExtractAndValidateSignature/PackageSignatureVerifierFactory.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,18 @@ public static IPackageSignatureVerifier CreateMinimal()
3838
/// </summary>
3939
public static IPackageSignatureVerifier CreateFull()
4040
{
41-
var verificationProviders = new[]
41+
var verificationProviders = new ISignatureVerificationProvider[]
4242
{
4343
new IntegrityVerificationProvider(),
44+
new SignatureTrustAndValidityVerificationProvider(),
4445
};
4546

46-
var settings = SignedPackageVerifierSettings.VerifyCommandDefaultPolicy;
47+
var settings = new SignedPackageVerifierSettings(
48+
allowUnsigned: false,
49+
allowUntrusted: false,
50+
allowIgnoreTimestamp: false,
51+
failWithMultipleTimestamps: true,
52+
allowNoTimestamp: false);
4753

4854
return new PackageSignatureVerifier(
4955
verificationProviders,

src/Validation.PackageSigning.ExtractAndValidateSignature/SignatureValidator.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,8 @@ private async Task<SignatureValidatorResult> HandleSignedPackageAsync(
130130
.SignerInfo
131131
.CounterSignerInfos
132132
.Cast<SignerInfo>()
133-
.Select(x => x.SignedAttributes.FirstOrDefault(Oids.CommitmentTypeIndication))
134-
.Where(x => x != null)
135-
.Select(x => AttributeUtility.GetCommitmentTypeIndication(x))
136-
.Count(x => x != SignatureType.Unknown);
133+
.Select(x => AttributeUtility.GetSignatureType(x.SignedAttributes))
134+
.Count(signatureType => signatureType != SignatureType.Unknown);
137135
if (authorOrRepositoryCounterSignatureCount > 0)
138136
{
139137
_logger.LogInformation(

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.6.0-rtm-4822</Version>
109+
<Version>4.7.0-preview1-4853</Version>
110110
</PackageReference>
111111
<PackageReference Include="NuGet.Services.Configuration">
112112
<Version>2.10.0</Version>

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

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,18 @@
2121
using NuGet.Services.Validation;
2222
using NuGet.Services.Validation.Issues;
2323
using NuGetGallery;
24+
using Test.Utility.Signing;
2425
using Xunit;
2526
using Xunit.Abstractions;
2627
using NuGetHashAlgorithmName = NuGet.Common.HashAlgorithmName;
2728

2829
namespace Validation.PackageSigning.ExtractAndValidateSignature.Tests
2930
{
31+
[Collection(CertificateIntegrationTestCollection.Name)]
3032
public class SignatureValidatorIntegrationTests : IDisposable
3133
{
34+
private readonly CertificateIntegrationTestFixture _fixture;
35+
private readonly ITestOutputHelper _output;
3236
private readonly Mock<IPackageSigningStateService> _packageSigningStateService;
3337
private readonly Mock<ISignaturePartsExtractor> _signaturePartsExtractor;
3438
private readonly Mock<IEntityRepository<Certificate>> _certificates;
@@ -42,8 +46,11 @@ public class SignatureValidatorIntegrationTests : IDisposable
4246
private readonly CancellationToken _token;
4347
private readonly SignatureValidator _target;
4448

45-
public SignatureValidatorIntegrationTests(ITestOutputHelper output)
49+
public SignatureValidatorIntegrationTests(CertificateIntegrationTestFixture fixture, ITestOutputHelper output)
4650
{
51+
_fixture = fixture ?? throw new ArgumentNullException(nameof(fixture));
52+
_output = output ?? throw new ArgumentNullException(nameof(output));
53+
4754
// These dependencies have their own dependencies on the database or blob storage, which don't have good
4855
// integration test infrastructure in the jobs yet. Therefore, we'll mock them for now.
4956
_packageSigningStateService = new Mock<IPackageSigningStateService>();
@@ -83,14 +90,23 @@ public SignatureValidatorIntegrationTests(ITestOutputHelper output)
8390
_logger);
8491
}
8592

86-
[Theory]
87-
[InlineData(TestResources.SignedPackageLeaf1, TestResources.Leaf1Thumbprint)]
88-
[InlineData(TestResources.SignedPackageLeaf2, TestResources.Leaf2Thumbprint)]
89-
public async Task SuccessfullyValidatesLeaves(string resourceName, string thumbprint)
93+
public async Task<SignedPackageArchive> GetSignedPackage1Async()
94+
{
95+
AllowCertificateThumbprint(_fixture.LeafCertificate1Thumbprint);
96+
return await _fixture.GetSignedPackage1Async(_output);
97+
}
98+
99+
public async Task<MemoryStream> GetSignedPackageStream1Async()
100+
{
101+
AllowCertificateThumbprint(_fixture.LeafCertificate1Thumbprint);
102+
return await _fixture.GetSignedPackageStream1Async(_output);
103+
}
104+
105+
[Fact]
106+
public async Task AcceptsValidSignedPackage()
90107
{
91108
// Arrange
92-
_package = TestResources.LoadPackage(resourceName);
93-
AllowCertificateThumbprint(thumbprint);
109+
_package = await GetSignedPackage1Async();
94110

95111
// Act
96112
var result = await _target.ValidateAsync(
@@ -108,8 +124,7 @@ public async Task SuccessfullyValidatesLeaves(string resourceName, string thumbp
108124
public async Task RejectsPackageWithAddedFile()
109125
{
110126
// Arrange
111-
AllowCertificateThumbprint(TestResources.Leaf1Thumbprint);
112-
var packageStream = TestResources.GetResourceStream(TestResources.SignedPackageLeaf1);
127+
var packageStream = await GetSignedPackageStream1Async();
113128

114129
try
115130
{
@@ -144,8 +159,7 @@ public async Task RejectsPackageWithAddedFile()
144159
public async Task RejectsPackageWithModifiedFile()
145160
{
146161
// Arrange
147-
AllowCertificateThumbprint(TestResources.Leaf1Thumbprint);
148-
var packageStream = TestResources.GetResourceStream(TestResources.SignedPackageLeaf1);
162+
var packageStream = await GetSignedPackageStream1Async();
149163

150164
try
151165
{
@@ -238,9 +252,9 @@ public async Task RejectsMultipleSignatures()
238252
public async Task RejectsAuthorAndRepositoryCounterSignatures(SignatureType counterSignatureType)
239253
{
240254
// Arrange
241-
AllowCertificateThumbprint(TestResources.Leaf1Thumbprint);
255+
var packageStream = await GetSignedPackageStream1Async();
242256
ModifySignatureContent(
243-
TestResources.GetResourceStream(TestResources.SignedPackageLeaf1),
257+
packageStream,
244258
configuredSignedCms: signedCms =>
245259
{
246260
using (var counterCertificate = SigningTestUtility.GenerateCertificate(subjectName: null, modifyGenerator: null))
@@ -273,9 +287,9 @@ public async Task RejectsAuthorAndRepositoryCounterSignatures(SignatureType coun
273287
public async Task RejectsMutuallyExclusiveCounterSignaturesCommitmentTypes(SignatureType[] counterSignatureTypes)
274288
{
275289
// Arrange
276-
AllowCertificateThumbprint(TestResources.Leaf1Thumbprint);
290+
var packageStream = await GetSignedPackageStream1Async();
277291
ModifySignatureContent(
278-
TestResources.GetResourceStream(TestResources.SignedPackageLeaf1),
292+
packageStream,
279293
configuredSignedCms: signedCms =>
280294
{
281295
using (var counterCertificate = SigningTestUtility.GenerateCertificate(subjectName: null, modifyGenerator: null))
@@ -314,9 +328,9 @@ public async Task RejectsMutuallyExclusiveCounterSignaturesCommitmentTypes(Signa
314328
public async Task AllowsNonAuthorAndRepositoryCounterSignatures(string commitmentTypeOidBase64)
315329
{
316330
// Arrange
317-
AllowCertificateThumbprint(TestResources.Leaf1Thumbprint);
331+
var packageStream = await GetSignedPackageStream1Async();
318332
ModifySignatureContent(
319-
TestResources.GetResourceStream(TestResources.SignedPackageLeaf1),
333+
packageStream,
320334
configuredSignedCms: signedCms =>
321335
{
322336
using (var counterCertificate = SigningTestUtility.GenerateCertificate(subjectName: null, modifyGenerator: null))
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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 Xunit;
5+
6+
namespace Validation.PackageSigning.ExtractAndValidateSignature.Tests
7+
{
8+
[CollectionDefinition(Name)]
9+
public class CertificateIntegrationTestCollection : ICollectionFixture<CertificateIntegrationTestFixture>
10+
{
11+
public const string Name = "Certificate integration test collection";
12+
}
13+
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
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.IO;
6+
using System.Security.Cryptography.X509Certificates;
7+
using System.Security.Principal;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using NuGet.Common;
11+
using NuGet.Packaging.Signing;
12+
using NuGet.Test.Utility;
13+
using Test.Utility.Signing;
14+
using Xunit;
15+
using Xunit.Abstractions;
16+
17+
namespace Validation.PackageSigning.ExtractAndValidateSignature.Tests
18+
{
19+
/// <summary>
20+
/// This test fixture trusts the root certificate of checked in signed packages. This handles adding and removing
21+
/// the root certificate from the local machine trusted roots. Any tests with this fixture require admin elevation.
22+
/// </summary>
23+
public class CertificateIntegrationTestFixture : IDisposable
24+
{
25+
private static readonly string _testTimestampServer = Environment.GetEnvironmentVariable("TIMESTAMP_SERVER_URL");
26+
27+
private readonly SemaphoreSlim _lock = new SemaphoreSlim(1);
28+
private byte[] _signedPackageBytes1;
29+
30+
public CertificateIntegrationTestFixture()
31+
{
32+
Assert.True(
33+
IsAdministrator(),
34+
"This test must be executing with administrator privileges since it installs a trusted root.");
35+
36+
LeafCertificate1 = TestCertificate
37+
.Generate(SigningTestUtility.CertificateModificationGeneratorForCodeSigningEkuCert)
38+
.WithPrivateKeyAndTrust(StoreName.Root, StoreLocation.LocalMachine);
39+
LeafCertificate1Thumbprint = LeafCertificate1.TrustedCert.ComputeSHA256Thumbprint();
40+
}
41+
42+
public TrustedTestCert<TestCertificate> LeafCertificate1 { get; }
43+
public string LeafCertificate1Thumbprint { get; }
44+
public Task<SignedPackageArchive> GetSignedPackage1Async(ITestOutputHelper output) => GetSignedPackageAsync(
45+
new Reference<byte[]>(
46+
() => _signedPackageBytes1,
47+
x => _signedPackageBytes1 = x),
48+
TestResources.SignedPackageLeaf1,
49+
LeafCertificate1,
50+
output);
51+
public Task<MemoryStream> GetSignedPackageStream1Async(ITestOutputHelper output) => GetSignedPackageStreamAsync(
52+
new Reference<byte[]>(
53+
() => _signedPackageBytes1,
54+
x => _signedPackageBytes1 = x),
55+
TestResources.SignedPackageLeaf1,
56+
LeafCertificate1,
57+
output);
58+
59+
public void Dispose()
60+
{
61+
LeafCertificate1?.Dispose();
62+
}
63+
64+
/// <summary>
65+
/// Source: https://stackoverflow.com/a/11660205
66+
/// </summary>
67+
private static bool IsAdministrator()
68+
{
69+
var identity = WindowsIdentity.GetCurrent();
70+
var principal = new WindowsPrincipal(identity);
71+
return principal.IsInRole(WindowsBuiltInRole.Administrator);
72+
}
73+
74+
private async Task<MemoryStream> GetSignedPackageStreamAsync(
75+
Reference<byte[]> reference,
76+
string resourceName,
77+
TrustedTestCert<TestCertificate> certificate,
78+
ITestOutputHelper output)
79+
{
80+
await _lock.WaitAsync();
81+
try
82+
{
83+
if (reference.Value == null)
84+
{
85+
reference.Value = await GenerateSignedPackageBytesAsync(
86+
resourceName,
87+
certificate,
88+
output);
89+
}
90+
91+
var memoryStream = new MemoryStream();
92+
memoryStream.Write(reference.Value, 0, reference.Value.Length);
93+
return memoryStream;
94+
}
95+
finally
96+
{
97+
_lock.Release();
98+
}
99+
}
100+
101+
private async Task<SignedPackageArchive> GetSignedPackageAsync(
102+
Reference<byte[]> reference,
103+
string resourceName,
104+
TrustedTestCert<TestCertificate> certificate,
105+
ITestOutputHelper output)
106+
{
107+
return new SignedPackageArchive(
108+
await GetSignedPackageStreamAsync(reference, resourceName, certificate, output),
109+
new MemoryStream());
110+
}
111+
112+
private static async Task<byte[]> GenerateSignedPackageBytesAsync(string resourceName, TrustedTestCert<TestCertificate> certificate, ITestOutputHelper output)
113+
{
114+
var testLogger = new TestLogger(output);
115+
var timestampProvider = new Rfc3161TimestampProvider(new Uri(_testTimestampServer));
116+
var signatureProvider = new X509SignatureProvider(timestampProvider);
117+
118+
var unsignedBytes = await OperateOnSignerAsync(
119+
TestResources.GetResourceStream(resourceName),
120+
signatureProvider,
121+
x => x.RemoveSignaturesAsync(testLogger, CancellationToken.None));
122+
123+
var signedBytes = await OperateOnSignerAsync(
124+
new MemoryStream(unsignedBytes),
125+
signatureProvider,
126+
x =>
127+
{
128+
var request = new SignPackageRequest(certificate.TrustedCert, HashAlgorithmName.SHA256);
129+
return x.SignAsync(request, testLogger, CancellationToken.None);
130+
});
131+
132+
return signedBytes;
133+
}
134+
135+
private static async Task<byte[]> OperateOnSignerAsync(
136+
Stream packageReadStream,
137+
X509SignatureProvider signatureProvider,
138+
Func<Signer, Task> executeAsync)
139+
{
140+
using (packageReadStream)
141+
using (var packageWriteStream = new MemoryStream())
142+
{
143+
packageReadStream.CopyTo(packageWriteStream);
144+
145+
using (var signedPackage = new SignedPackageArchive(packageReadStream, packageWriteStream))
146+
{
147+
var signer = new Signer(signedPackage, signatureProvider);
148+
149+
await executeAsync(signer);
150+
151+
return packageWriteStream.ToArray();
152+
}
153+
}
154+
}
155+
156+
/// <summary>
157+
/// This is a workaround for the lack of <code>ref</code> parameters in <code>async</code> methods.
158+
/// </summary>
159+
/// <typeparam name="T">The type of the reference.</typeparam>
160+
private class Reference<T>
161+
{
162+
private readonly Func<T> _getValue;
163+
private readonly Action<T> _setValue;
164+
165+
public Reference(Func<T> getValue, Action<T> setValue)
166+
{
167+
_getValue = getValue;
168+
_setValue = setValue;
169+
}
170+
171+
public T Value
172+
{
173+
get => _getValue();
174+
set => _setValue(value);
175+
}
176+
}
177+
}
178+
}

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

Lines changed: 0 additions & 26 deletions
This file was deleted.

0 commit comments

Comments
 (0)