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

Commit 4cf9df1

Browse files
authored
[Package Signing] Add more Validate Certificate integration tests (#338)
1 parent ef79f0e commit 4cf9df1

12 files changed

Lines changed: 708 additions & 139 deletions

src/Validation.PackageSigning.ValidateCertificate/CertificateVerificationResult.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ public CertificateVerificationResult(
2626
DateTime? statusUpdateTime = null,
2727
DateTime? revocationTime = null)
2828
{
29-
if (status != EndCertificateStatus.Revoked && revocationTime.HasValue)
29+
if (revocationTime.HasValue &&
30+
status != EndCertificateStatus.Revoked &&
31+
status != EndCertificateStatus.Invalid)
3032
{
3133
throw new ArgumentException(
32-
$"End certificate revoked at {revocationTime} but status isn't {nameof(EndCertificateStatus.Revoked)}",
34+
$"End certificate revoked at {revocationTime} but status is {status}",
3335
nameof(status));
3436
}
3537

@@ -119,7 +121,7 @@ public override string ToString()
119121
return $"Good (StatusUpdateTime = {StatusUpdateTime})";
120122

121123
case EndCertificateStatus.Invalid:
122-
return $"Invalid (Flags = {StatusFlags}, StatusUpdateTime = {StatusUpdateTime})";
124+
return $"Invalid (Flags = {StatusFlags}, RevocationTime = {RevocationTime}, StatusUpdateTime = {StatusUpdateTime})";
123125

124126
case EndCertificateStatus.Revoked:
125127
return $"Revoked (Flags = {StatusFlags}, RevocationTime = {RevocationTime}, StatusUpdateTime = {StatusUpdateTime})";

src/Validation.PackageSigning.ValidateCertificate/SignatureDeciderFactory.cs

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,17 @@ public SignatureDecider MakeDeciderForInvalidatedCertificate(EndCertificate cert
6868
return RejectAllSignaturesDecider;
6969
}
7070

71-
// NotTimeValid and HasWeakSignature fail packages only at ingestion.
72-
else if (ResultHasOnlyFlags(result, X509ChainStatusFlags.NotTimeValid | X509ChainStatusFlags.HasWeakSignature))
71+
// NotTimeValid and HasWeakSignature fail packages only at ingestion. It is assumed that a chain with HasWeakSignature will
72+
// ALWAYS have NotSignatureValid.
73+
else if (result.StatusFlags == X509ChainStatusFlags.NotTimeValid ||
74+
result.StatusFlags == (X509ChainStatusFlags.HasWeakSignature | X509ChainStatusFlags.NotSignatureValid) ||
75+
result.StatusFlags == (X509ChainStatusFlags.NotTimeValid | X509ChainStatusFlags.HasWeakSignature | X509ChainStatusFlags.NotSignatureValid))
7376
{
7477
return RejectSignaturesAtIngestionDecider;
7578
}
7679

77-
// NotTimeNested does not affect signatures and should be ignored.
78-
else if (ResultHasOnlyFlags(result, X509ChainStatusFlags.NotTimeNested))
80+
// NotTimeNested does not affect signatures and should be ignored if is the only status.
81+
else if (result.StatusFlags == X509ChainStatusFlags.NotTimeNested)
7982
{
8083
return NoActionDecider;
8184
}
@@ -145,15 +148,5 @@ private SignatureDecision RejectSignaturesAtIngestionOtherwiseWarnDecider(Packag
145148
? SignatureDecision.Reject
146149
: SignatureDecision.Warn;
147150
}
148-
149-
private bool ResultHasOnlyFlags(CertificateVerificationResult result, X509ChainStatusFlags flags)
150-
{
151-
if (result.StatusFlags == X509ChainStatusFlags.NoError)
152-
{
153-
return flags == X509ChainStatusFlags.NoError;
154-
}
155-
156-
return (result.StatusFlags & flags) == result.StatusFlags;
157-
}
158151
}
159152
}

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

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections.Generic;
56
using System.Security.Cryptography.X509Certificates;
67
using System.Security.Principal;
78
using System.Threading.Tasks;
89
using Org.BouncyCastle.Asn1;
910
using Org.BouncyCastle.Asn1.X509;
1011
using Org.BouncyCastle.Crypto.Parameters;
1112
using Org.BouncyCastle.Security;
13+
using Org.BouncyCastle.X509;
1214
using Test.Utility.Signing;
1315
using Xunit;
1416
using GeneralName = Org.BouncyCastle.Asn1.X509.GeneralName;
17+
using BCCertificate = Org.BouncyCastle.X509.X509Certificate;
1518

1619
namespace Validation.PackageSigning.Core.Tests.Support
1720
{
@@ -22,6 +25,7 @@ namespace Validation.PackageSigning.Core.Tests.Support
2225
public class CertificateIntegrationTestFixture : IDisposable
2326
{
2427
private readonly Lazy<Task<SigningTestServer>> _testServer;
28+
private readonly Lazy<Task<CertificateAuthority>> _rootCertificateAuthority;
2529
private readonly Lazy<Task<CertificateAuthority>> _certificateAuthority;
2630
private readonly Lazy<Task<TimestampService>> _timestampService;
2731
private readonly Lazy<Task<Uri>> _timestampServiceUrl;
@@ -37,6 +41,7 @@ public CertificateIntegrationTestFixture()
3741
"This test must be executing with administrator privileges since it installs a trusted root.");
3842

3943
_testServer = new Lazy<Task<SigningTestServer>>(SigningTestServer.CreateAsync);
44+
_rootCertificateAuthority = new Lazy<Task<CertificateAuthority>>(CreateDefaultTrustedRootCertificateAuthorityAsync);
4045
_certificateAuthority = new Lazy<Task<CertificateAuthority>>(CreateDefaultTrustedCertificateAuthorityAsync);
4146
_timestampService = new Lazy<Task<TimestampService>>(CreateDefaultTrustedTimestampServiceAsync);
4247
_timestampServiceUrl = new Lazy<Task<Uri>>(CreateDefaultTrustedTimestampServiceUrlAsync);
@@ -50,6 +55,10 @@ public CertificateIntegrationTestFixture()
5055
public Task<X509Certificate2> GetSigningCertificateAsync() => _signingCertificate.Value;
5156
public Task<string> GetSigningCertificateThumbprintAsync() => _signingCertificateThumbprint.Value;
5257

58+
protected Task<CertificateAuthority> GetRootCertificateAuthority() => _rootCertificateAuthority.Value;
59+
protected Task<CertificateAuthority> GetCertificateAuthority() => _certificateAuthority.Value;
60+
protected DisposableList GetResponders() => _responders;
61+
5362
public void Dispose()
5463
{
5564
_trustedRoot?.Dispose();
@@ -61,11 +70,10 @@ public void Dispose()
6170
}
6271
}
6372

64-
private async Task<CertificateAuthority> CreateDefaultTrustedCertificateAuthorityAsync()
73+
private async Task<CertificateAuthority> CreateDefaultTrustedRootCertificateAuthorityAsync()
6574
{
6675
var testServer = await GetTestServerAsync();
6776
var rootCa = CertificateAuthority.Create(testServer.Url);
68-
var intermediateCa = rootCa.CreateIntermediateCertificateAuthority();
6977
var rootCertificate = new X509Certificate2(rootCa.Certificate.GetEncoded());
7078

7179
_trustedRoot = new TrustedTestCert<X509Certificate2>(
@@ -74,6 +82,15 @@ private async Task<CertificateAuthority> CreateDefaultTrustedCertificateAuthorit
7482
StoreName.Root,
7583
StoreLocation.LocalMachine);
7684

85+
return rootCa;
86+
}
87+
88+
private async Task<CertificateAuthority> CreateDefaultTrustedCertificateAuthorityAsync()
89+
{
90+
var testServer = await GetTestServerAsync();
91+
var rootCa = await GetRootCertificateAuthority();
92+
var intermediateCa = rootCa.CreateIntermediateCertificateAuthority();
93+
7794
_responders.AddRange(testServer.RegisterResponders(intermediateCa));
7895

7996
return intermediateCa;
@@ -103,30 +120,32 @@ private async Task<X509Certificate2> CreateDefaultTrustedSigningCertificateAsync
103120
}
104121

105122
public X509Certificate2 CreateSigningCertificate(CertificateAuthority ca)
123+
{
124+
void CustomizeAsSigningCertificate(X509V3CertificateGenerator generator)
125+
{
126+
generator.AddSigningEku();
127+
generator.AddAuthorityInfoAccess(ca, addOcsp: true, addCAIssuers: true);
128+
}
129+
130+
return IssueCertificate(ca, "Signing", CustomizeAsSigningCertificate).certificate;
131+
}
132+
133+
protected (BCCertificate publicCertificate, X509Certificate2 certificate) IssueCertificate(
134+
CertificateAuthority ca,
135+
string name,
136+
Action<X509V3CertificateGenerator> customizeCertificate)
106137
{
107138
var keyPair = SigningTestUtility.GenerateKeyPair(publicKeyLength: 2048);
108139
var publicCertificate = ca.IssueCertificate(
109140
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-
},
141+
new X509Name($"C=US,ST=WA,L=Redmond,O=NuGet,CN=NuGet Test ${name} Certificate ({Guid.NewGuid()})"),
142+
customizeCertificate,
124143
notBefore: DateTime.UtcNow.AddSeconds(-10));
125144

126145
var certificate = new X509Certificate2(publicCertificate.GetEncoded());
127146
certificate.PrivateKey = DotNetUtilities.ToRSA(keyPair.Private as RsaPrivateCrtKeyParameters);
128147

129-
return certificate;
148+
return (publicCertificate, certificate);
130149
}
131150

132151
private async Task<string> GetDefaultTrustedSigningCertificateThumbprintAsync()
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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.Collections.Generic;
5+
using Org.BouncyCastle.Asn1;
6+
using Org.BouncyCastle.Asn1.X509;
7+
using Test.Utility.Signing;
8+
9+
namespace Org.BouncyCastle.X509
10+
{
11+
public static class X509V3CertificateGeneratorExtensions
12+
{
13+
public static void MakeExpired(this X509V3CertificateGenerator generator)
14+
{
15+
SigningTestUtility.CertificateModificationGeneratorExpiredCert(generator);
16+
}
17+
18+
public static void AddSigningEku(this X509V3CertificateGenerator generator)
19+
{
20+
SigningTestUtility.CertificateModificationGeneratorForCodeSigningEkuCert(generator);
21+
}
22+
23+
public static void AddAuthorityInfoAccess(
24+
this X509V3CertificateGenerator generator,
25+
CertificateAuthority ca,
26+
bool addOcsp = false,
27+
bool addCAIssuers = false)
28+
{
29+
var vector = new List<Asn1Encodable>();
30+
31+
if (addOcsp)
32+
{
33+
vector.Add(
34+
new AccessDescription(
35+
AccessDescription.IdADOcsp,
36+
new GeneralName(GeneralName.UniformResourceIdentifier, ca.OcspResponderUri.OriginalString)));
37+
}
38+
39+
if (addCAIssuers)
40+
{
41+
vector.Add(
42+
new AccessDescription(
43+
AccessDescription.IdADCAIssuers,
44+
new GeneralName(GeneralName.UniformResourceIdentifier, ca.CertificateUri.OriginalString)));
45+
}
46+
47+
generator.AddExtension(
48+
X509Extensions.AuthorityInfoAccess,
49+
critical: false,
50+
extensionValue: new DerSequence(vector.ToArray()));
51+
}
52+
}
53+
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
<Compile Include="Support\TestDbAsyncEnumerable.cs" />
4545
<Compile Include="Support\TestDbAsyncEnumerator.cs" />
4646
<Compile Include="Support\TestDbAsyncQueryProvider.cs" />
47+
<Compile Include="Support\X509V3CertificateGeneratorExtensions.cs" />
4748
<Compile Include="Support\XunitLogger.cs" />
4849
<Compile Include="Support\XunitLoggerFactoryExtensions.cs" />
4950
<Compile Include="Support\XunitLoggerProvider.cs" />
@@ -62,6 +63,9 @@
6263
<PackageReference Include="Portable.BouncyCastle">
6364
<Version>1.8.1.3</Version>
6465
</PackageReference>
66+
<PackageReference Include="System.ValueTuple">
67+
<Version>4.4.0</Version>
68+
</PackageReference>
6569
<PackageReference Include="Test.Utility">
6670
<Version>4.7.0-preview1-4886</Version>
6771
</PackageReference>

tests/Validation.PackageSigning.ValidateCertificate.Tests/CertificateVerificationResultFacts.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ public void CanCreateInvalidResultWithStatusUpdateTime()
7272

7373
[Theory]
7474
[InlineData(EndCertificateStatus.Good, X509ChainStatusFlags.NoError)]
75-
[InlineData(EndCertificateStatus.Invalid, X509ChainStatusFlags.ExplicitDistrust)]
7675
[InlineData(EndCertificateStatus.Unknown, X509ChainStatusFlags.OfflineRevocation)]
7776
public void CannotCreateNonRevokedResultWithRevocationDate(EndCertificateStatus status, X509ChainStatusFlags flags)
7877
{
@@ -83,7 +82,7 @@ public void CannotCreateNonRevokedResultWithRevocationDate(EndCertificateStatus
8382
.WithRevocationTime(new DateTime(2000, 1, 2))
8483
.Build());
8584

86-
Assert.StartsWith("End certificate revoked at 1/2/2000 12:00:00 AM but status isn't Revoked", exception.Message);
85+
Assert.StartsWith($"End certificate revoked at 1/2/2000 12:00:00 AM but status is {status}", exception.Message);
8786
}
8887

8988
[Fact]

0 commit comments

Comments
 (0)