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

Commit 71da3ea

Browse files
committed
Add validate certificate job with some straw man implementations (#233)
Progress on NuGet/Engineering#787
1 parent 93d7c7a commit 71da3ea

34 files changed

Lines changed: 1973 additions & 9 deletions

NuGet.Jobs.sln

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Validation.PackageSigning.E
103103
EndProject
104104
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Validation.PackageSigning.ExtractAndValidateSignature.Tests", "tests\Validation.PackageSigning.ExtractAndValidateSignature.Tests\Validation.PackageSigning.ExtractAndValidateSignature.Tests.csproj", "{26435822-8938-48C9-96FD-0DCCF8F7CE00}"
105105
EndProject
106+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Validation.PackageSigning.ValidateCertificate", "src\Validation.PackageSigning.ValidateCertificate\Validation.PackageSigning.ValidateCertificate.csproj", "{A245E448-8AE0-452B-9338-8C0E0B637D72}"
107+
EndProject
108+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Validation.PackageSigning.ValidateCertificate.Tests", "tests\Validation.PackageSigning.ValidateCertificate.Tests\Validation.PackageSigning.ValidateCertificate.Tests.csproj", "{5ACE7756-F8D0-4D90-9957-872DE4A1381E}"
109+
EndProject
110+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Validation.PackageSigning.Helpers", "tests\Validation.PackageSigning.Helpers\Validation.PackageSigning.Helpers.csproj", "{2C5BE067-ADFD-49E3-BA9F-13A74436E5DB}"
111+
EndProject
106112
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Validation.PackageSigning.Core.Tests", "tests\Validation.PackageSigning.Core.Tests\Validation.PackageSigning.Core.Tests.csproj", "{B4B7564A-965B-447B-927F-6749E2C08880}"
107113
EndProject
108114
Global
@@ -261,6 +267,18 @@ Global
261267
{26435822-8938-48C9-96FD-0DCCF8F7CE00}.Debug|Any CPU.Build.0 = Debug|Any CPU
262268
{26435822-8938-48C9-96FD-0DCCF8F7CE00}.Release|Any CPU.ActiveCfg = Release|Any CPU
263269
{26435822-8938-48C9-96FD-0DCCF8F7CE00}.Release|Any CPU.Build.0 = Release|Any CPU
270+
{A245E448-8AE0-452B-9338-8C0E0B637D72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
271+
{A245E448-8AE0-452B-9338-8C0E0B637D72}.Debug|Any CPU.Build.0 = Debug|Any CPU
272+
{A245E448-8AE0-452B-9338-8C0E0B637D72}.Release|Any CPU.ActiveCfg = Release|Any CPU
273+
{A245E448-8AE0-452B-9338-8C0E0B637D72}.Release|Any CPU.Build.0 = Release|Any CPU
274+
{5ACE7756-F8D0-4D90-9957-872DE4A1381E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
275+
{5ACE7756-F8D0-4D90-9957-872DE4A1381E}.Debug|Any CPU.Build.0 = Debug|Any CPU
276+
{5ACE7756-F8D0-4D90-9957-872DE4A1381E}.Release|Any CPU.ActiveCfg = Release|Any CPU
277+
{5ACE7756-F8D0-4D90-9957-872DE4A1381E}.Release|Any CPU.Build.0 = Release|Any CPU
278+
{2C5BE067-ADFD-49E3-BA9F-13A74436E5DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
279+
{2C5BE067-ADFD-49E3-BA9F-13A74436E5DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
280+
{2C5BE067-ADFD-49E3-BA9F-13A74436E5DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
281+
{2C5BE067-ADFD-49E3-BA9F-13A74436E5DB}.Release|Any CPU.Build.0 = Release|Any CPU
264282
{B4B7564A-965B-447B-927F-6749E2C08880}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
265283
{B4B7564A-965B-447B-927F-6749E2C08880}.Debug|Any CPU.Build.0 = Debug|Any CPU
266284
{B4B7564A-965B-447B-927F-6749E2C08880}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -306,6 +324,9 @@ Global
306324
{91C060DA-736F-4DA9-A57F-CB3AC0E6CB10} = {678D7B14-F8BC-4193-99AF-2EE8AA390A02}
307325
{DD043977-6BCD-475A-BEE2-8C34309EC622} = {678D7B14-F8BC-4193-99AF-2EE8AA390A02}
308326
{26435822-8938-48C9-96FD-0DCCF8F7CE00} = {6A776396-02B1-475D-A104-26940ADB04AB}
327+
{A245E448-8AE0-452B-9338-8C0E0B637D72} = {678D7B14-F8BC-4193-99AF-2EE8AA390A02}
328+
{5ACE7756-F8D0-4D90-9957-872DE4A1381E} = {6A776396-02B1-475D-A104-26940ADB04AB}
329+
{2C5BE067-ADFD-49E3-BA9F-13A74436E5DB} = {6A776396-02B1-475D-A104-26940ADB04AB}
309330
{B4B7564A-965B-447B-927F-6749E2C08880} = {6A776396-02B1-475D-A104-26940ADB04AB}
310331
EndGlobalSection
311332
GlobalSection(ExtensibilityGlobals) = postSolution

build.ps1

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ Invoke-BuildStep 'Set version metadata in AssemblyInfo.cs' { `
103103
"$PSScriptRoot\src\NuGetCDNRedirect\Properties\AssemblyInfo.g.cs",
104104
"$PSScriptRoot\src\NuGet.Services.Validation.Orchestrator\Properties\AssemblyInfo.g.cs",
105105
"$PSScriptRoot\src\Stats.CollectAzureChinaCDNLogs\Properties\AssemblyInfo.g.cs",
106-
"$PSScriptRoot\src\Validation.PackageSigning.ExtractAndValidateSignature\Properties\AssemblyInfo.g.cs"
107-
106+
"$PSScriptRoot\src\Validation.PackageSigning.ExtractAndValidateSignature\Properties\AssemblyInfo.g.cs",
107+
"$PSScriptRoot\src\Validation.PackageSigning.ValidateCertificate\Properties\AssemblyInfo.g.cs"
108108

109109
$versionMetadata | ForEach-Object {
110110
Set-VersionInfo -Path $_ -Version $SimpleVersion -Branch $Branch -Commit $CommitSHA
@@ -152,7 +152,8 @@ Invoke-BuildStep 'Creating artifacts' {
152152
"src/NuGetCDNRedirect/NuGetCDNRedirect.csproj", `
153153
"src/NuGet.Services.Validation.Orchestrator/NuGet.Services.Validation.Orchestrator.csproj", `
154154
"src/Stats.CollectAzureChinaCDNLogs/Stats.CollectAzureChinaCDNLogs.csproj", `
155-
"src/Validation.PackageSigning.ExtractAndValidateSignature/Validation.PackageSigning.ExtractAndValidateSignature.csproj"
155+
"src/Validation.PackageSigning.ExtractAndValidateSignature/Validation.PackageSigning.ExtractAndValidateSignature.csproj", `
156+
"src/Validation.PackageSigning.ValidateCertificate/Validation.PackageSigning.ValidateCertificate.csproj"
156157

157158
Foreach ($Project in $Projects) {
158159
New-Package (Join-Path $PSScriptRoot "$Project") -Configuration $Configuration -BuildNumber $BuildNumber -Version $SemanticVersion -Branch $Branch -MSBuildVersion "$msBuildVersion"
@@ -174,4 +175,4 @@ if ($BuildErrors) {
174175
Error-Log "Builds completed with $($BuildErrors.Count) error(s):`r`n$($ErrorLines -join "`r`n")" -Fatal
175176
}
176177

177-
Write-Host ("`r`n" * 3)
178+
Write-Host ("`r`n" * 3)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<configuration>
3+
<startup>
4+
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
5+
</startup>
6+
</configuration>
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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.Threading;
6+
using System.Threading.Tasks;
7+
using Microsoft.Extensions.Logging;
8+
using NuGet.Jobs.Validation.PackageSigning.Messages;
9+
using NuGet.Jobs.Validation.PackageSigning.Storage;
10+
using NuGet.Services.ServiceBus;
11+
using NuGet.Services.Validation;
12+
13+
namespace Validation.PackageSigning.ValidateCertificate
14+
{
15+
/// <summary>
16+
/// The handler for <see cref="CertificateValidationMessage"/>. Upon receiving a message,
17+
/// this will validate a <see cref="X509Certificate2"/> and perform online revocation checks.
18+
/// </summary>
19+
public sealed class CertificateValidationMessageHandler : IMessageHandler<CertificateValidationMessage>
20+
{
21+
private readonly ICertificateStore _certificateStore;
22+
private readonly ICertificateValidationService _certificateValidationService;
23+
private readonly ILogger<CertificateValidationMessageHandler> _logger;
24+
25+
private readonly int _maximumValidationFailures;
26+
27+
public CertificateValidationMessageHandler(
28+
ICertificateStore certificateStore,
29+
ICertificateValidationService certificateValidationService,
30+
ILogger<CertificateValidationMessageHandler> logger,
31+
int maximumValidationFailures = CertificateValidationService.DefaultMaximumValidationFailures)
32+
{
33+
_certificateStore = certificateStore ?? throw new ArgumentNullException(nameof(certificateStore));
34+
_certificateValidationService = certificateValidationService ?? throw new ArgumentNullException(nameof(certificateValidationService));
35+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
36+
37+
_maximumValidationFailures = maximumValidationFailures;
38+
}
39+
40+
/// <summary>
41+
/// Perform the certificate validation request, including online revocation checks.
42+
/// </summary>
43+
/// <param name="message">The message requesting the certificate validation.</param>
44+
/// <returns>Whether the validation completed. If false, the validation should be retried later.</returns>
45+
public async Task<bool> HandleAsync(CertificateValidationMessage message)
46+
{
47+
var validation = await _certificateValidationService.FindCertificateValidationAsync(message);
48+
49+
if (validation == null)
50+
{
51+
_logger.LogInformation(
52+
"Could not find a certificate validation entity, failing (certificate: {CertificateKey} validation: {ValidationId})",
53+
message.CertificateKey,
54+
message.ValidationId);
55+
56+
return false;
57+
}
58+
59+
if (validation.Status != null)
60+
{
61+
// A certificate validation should be queued with a Status of null, and once the certificate validation
62+
// completes, the Status should be updated to a non-null value. Hence, the Status here SHOULD be null.
63+
// A non-null Status may indicate message duplication.
64+
_logger.LogWarning(
65+
"Invalid certificate validation entity's status, dropping message (certificate: {CertificateThumbprint} validation: {ValidationId})",
66+
validation.EndCertificate.Thumbprint,
67+
validation.ValidationId);
68+
69+
return true;
70+
}
71+
72+
if (validation.EndCertificate.Status == EndCertificateStatus.Revoked)
73+
{
74+
if (message.RevalidateRevokedCertificate)
75+
{
76+
_logger.LogWarning(
77+
"Revalidating certificate that is known to be revoked " +
78+
"(certificate: {CertificateThumbprint} validation: {ValidationId})",
79+
validation.EndCertificate.Thumbprint,
80+
validation.ValidationId);
81+
}
82+
else
83+
{
84+
// Do NOT revalidate a certificate that is known to be revoked unless explicitly told to!
85+
// Certificate Authorities are not required to keep a certificate's revocation information
86+
// forever, therefore, revoked certificates should only be revalidated in special cases.
87+
_logger.LogError(
88+
"Certificate known to be revoked MUST be validated with the " +
89+
$"{nameof(CertificateValidationMessage.RevalidateRevokedCertificate)} flag enabled " +
90+
"(certificate: {CertificateThumbprint} validation: {ValidationId})",
91+
validation.EndCertificate.Thumbprint,
92+
validation.ValidationId);
93+
94+
return true;
95+
}
96+
}
97+
98+
// Download and verify the certificate.
99+
var certificate = await _certificateStore.LoadAsync(validation.EndCertificate.Thumbprint, CancellationToken.None);
100+
var result = await _certificateValidationService.VerifyAsync(certificate);
101+
102+
// Save the result. This may alert if packages are invalidated.
103+
if (!await _certificateValidationService.TrySaveResultAsync(validation, result))
104+
{
105+
_logger.LogWarning(
106+
"Failed to save certificate validation result " +
107+
"(certificate: {CertificateThumbprint} validation: {ValidationId}), " +
108+
"failing validation",
109+
validation.EndCertificate.Thumbprint,
110+
validation.ValidationId);
111+
112+
return false;
113+
}
114+
115+
return HasValidationCompleted(validation, result);
116+
}
117+
118+
private bool HasValidationCompleted(EndCertificateValidation validation, CertificateVerificationResult result)
119+
{
120+
// The validation is complete if the certificate was determined to be "Good", "Invalid", or "Revoked".
121+
if (result.Status == EndCertificateStatus.Good
122+
|| result.Status == EndCertificateStatus.Invalid
123+
|| result.Status == EndCertificateStatus.Revoked)
124+
{
125+
return true;
126+
}
127+
else if (result.Status == EndCertificateStatus.Unknown)
128+
{
129+
// Certificates whose status failed to be determined will have an "Unknown"
130+
// status. These certificates should be retried until "_maximumValidationFailures"
131+
// is reached.
132+
if (validation.EndCertificate.ValidationFailures >= _maximumValidationFailures)
133+
{
134+
_logger.LogWarning(
135+
"Certificate {CertificateThumbprint} has reached maximum of {MaximumValidationFailures} failed validation attempts",
136+
validation.EndCertificate.Thumbprint,
137+
_maximumValidationFailures);
138+
139+
return true;
140+
}
141+
else
142+
{
143+
_logger.LogWarning(
144+
"Could not validate certificate {CertificateThumbprint}, {RetriesLeft} retries left",
145+
validation.EndCertificate.Thumbprint,
146+
_maximumValidationFailures - validation.EndCertificate.ValidationFailures);
147+
148+
return false;
149+
}
150+
}
151+
152+
_logger.LogError(
153+
$"Unknown {nameof(EndCertificateStatus)} value: {{CertificateStatus}}, throwing to retry",
154+
result.Status);
155+
156+
throw new InvalidOperationException($"Unknown {nameof(EndCertificateStatus)} value: {result.Status}");
157+
}
158+
}
159+
}

0 commit comments

Comments
 (0)