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

Commit 0cf5377

Browse files
authored
Merge pull request #327 from NuGet/dev
[ReleasePrep][2018.02.05]RI of dev into master
2 parents d436073 + c153e2d commit 0cf5377

20 files changed

Lines changed: 1280 additions & 240 deletions

src/NuGet.Jobs.Common/JobRunner.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Linq;
99
using System.Net;
1010
using System.Threading.Tasks;
11+
using Microsoft.ApplicationInsights.Extensibility;
1112
using Microsoft.Extensions.Logging;
1213
using NuGet.Services.Logging;
1314

@@ -98,6 +99,9 @@ public static async Task Run(JobBase job, string[] commandLineArgs)
9899
{
99100
_logger.LogError("Job runner threw an exception: {Exception}", ex);
100101
}
102+
103+
Trace.Close();
104+
TelemetryConfiguration.Active.TelemetryChannel.Flush();
101105
}
102106

103107
private static ILoggerFactory ConfigureLogging(JobBase job)

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-4870</Version>
109+
<Version>4.7.0-preview1-4886</Version>
110110
</PackageReference>
111111
<PackageReference Include="NuGet.Services.Configuration">
112112
<Version>2.12.0</Version>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<configuration>
33
<startup>
4-
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
4+
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
55
</startup>
66
</configuration>

src/Validation.PackageSigning.ValidateCertificate/CertificateValidationService.cs

Lines changed: 92 additions & 104 deletions
Large diffs are not rendered by default.

src/Validation.PackageSigning.ValidateCertificate/CertificateVerificationResult.cs

Lines changed: 145 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
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.Security.Cryptography.X509Certificates;
56
using NuGet.Services.Validation;
67

78
namespace Validation.PackageSigning.ValidateCertificate
@@ -13,39 +14,166 @@ namespace Validation.PackageSigning.ValidateCertificate
1314
public class CertificateVerificationResult
1415
{
1516
/// <summary>
16-
/// Create a new non-revoked certificate verification result.
17+
/// Create a new verification result.
1718
/// </summary>
18-
/// <param name="status">The status of the <see cref="X509Certificate2"/></param>
19-
/// <param name="revocationTime">The time of </param>
20-
public CertificateVerificationResult(EndCertificateStatus status)
19+
/// <param name="status">The determined status of the verified certificate.</param>
20+
/// <param name="statusFlags">The flags explaining the certificate's status.</param>
21+
/// <param name="statusUpdateTime">The time the revocation info was published.</param>
22+
/// <param name="revocationTime">The date the certificate was revoked, if applicable.</param>
23+
public CertificateVerificationResult(
24+
EndCertificateStatus status,
25+
X509ChainStatusFlags statusFlags,
26+
DateTime? statusUpdateTime = null,
27+
DateTime? revocationTime = null)
2128
{
22-
if (status == EndCertificateStatus.Revoked)
29+
if (status != EndCertificateStatus.Revoked && revocationTime.HasValue)
2330
{
24-
throw new ArgumentException("Provide a revocation date for a revoked certificate result.", nameof(status));
31+
throw new ArgumentException(
32+
$"End certificate revoked at {revocationTime} but status isn't {nameof(EndCertificateStatus.Revoked)}",
33+
nameof(status));
34+
}
35+
36+
switch (status)
37+
{
38+
case EndCertificateStatus.Good:
39+
if (statusFlags != X509ChainStatusFlags.NoError)
40+
{
41+
throw new ArgumentException(
42+
$"Invalid flags '{statusFlags}' for status '{status}'",
43+
nameof(statusFlags));
44+
}
45+
46+
break;
47+
48+
case EndCertificateStatus.Invalid:
49+
if (statusFlags == X509ChainStatusFlags.NoError)
50+
{
51+
throw new ArgumentException(
52+
$"Invalid flags '{statusFlags}' for status '{status}'",
53+
nameof(statusFlags));
54+
}
55+
56+
break;
57+
58+
case EndCertificateStatus.Revoked:
59+
if ((statusFlags & X509ChainStatusFlags.Revoked) == 0)
60+
{
61+
throw new ArgumentException(
62+
$"Invalid flags '{statusFlags}' for status '{status}'",
63+
nameof(statusFlags));
64+
}
65+
break;
66+
67+
case EndCertificateStatus.Unknown:
68+
if ((statusFlags & (X509ChainStatusFlags.RevocationStatusUnknown | X509ChainStatusFlags.OfflineRevocation)) == 0)
69+
{
70+
throw new ArgumentException(
71+
$"Invalid flags '{statusFlags}' for status '{status}'",
72+
nameof(statusFlags));
73+
}
74+
75+
break;
76+
77+
default:
78+
throw new ArgumentException($"Unknown status '{status}'", nameof(status));
2579
}
2680

2781
Status = status;
82+
StatusFlags = statusFlags;
83+
StatusUpdateTime = statusUpdateTime;
84+
RevocationTime = revocationTime;
2885
}
2986

3087
/// <summary>
31-
/// Create a new revoked certificate verification result.
88+
/// The status of the end <see cref="X509Certificate2"/>.
3289
/// </summary>
33-
/// <param name="revocationTime">The start of the certificate's invalidity period.</param>
34-
public CertificateVerificationResult(DateTime revocationTime)
35-
{
36-
Status = EndCertificateStatus.Revoked;
37-
RevocationTime = revocationTime;
38-
}
90+
public EndCertificateStatus Status { get; }
3991

4092
/// <summary>
41-
/// The status of the <see cref="X509Certificate2"/>.
93+
/// The flattened flags for the <see cref="X509Certificate2"/>'s entire chain.
4294
/// </summary>
43-
public EndCertificateStatus Status { get; }
95+
public X509ChainStatusFlags StatusFlags { get; }
96+
97+
/// <summary>
98+
/// The time that the end <see cref="X509Certificate2"/>'s status was last updated, according to the
99+
/// Certificate Authority. This value may be <c>null</c> if the <see cref="Status"/> is
100+
/// <see cref="EndCertificateStatus.Unknown"/> or if the status could not be determined.
101+
/// </summary>
102+
public DateTime? StatusUpdateTime { get; }
44103

45104
/// <summary>
46-
/// The time at which the <see cref="X509Certificate2"/> was revoked. Null unless
47-
/// <see cref="Status"/> is <see cref="CertificateStatus.Revoked"/>.
105+
/// The time at which the end <see cref="X509Certificate2"/> was revoked. If <see cref="Status"/>
106+
/// is not <see cref="CertificateStatus.Revoked"/>, this will have a value of <c>null</c>.
48107
/// </summary>
49108
public DateTime? RevocationTime { get; }
109+
110+
/// <summary>
111+
/// Convert a verification to a human readable string.
112+
/// </summary>
113+
/// <returns>A human readable string that summarizes the verification result.</returns>
114+
public override string ToString()
115+
{
116+
switch (Status)
117+
{
118+
case EndCertificateStatus.Good:
119+
return $"Good (StatusUpdateTime = {StatusUpdateTime})";
120+
121+
case EndCertificateStatus.Invalid:
122+
return $"Invalid (Flags = {StatusFlags}, StatusUpdateTime = {StatusUpdateTime})";
123+
124+
case EndCertificateStatus.Revoked:
125+
return $"Revoked (Flags = {StatusFlags}, RevocationTime = {RevocationTime}, StatusUpdateTime = {StatusUpdateTime})";
126+
127+
case EndCertificateStatus.Unknown:
128+
return $"Unknown (Flags = {StatusFlags}, StatusUpdateTime = {StatusUpdateTime})";
129+
130+
default:
131+
throw new InvalidOperationException($"Unknown status {Status}");
132+
}
133+
}
134+
135+
/// <summary>
136+
/// Helper used to create valid <see cref="CertificateVerificationResult"/>s.
137+
/// </summary>
138+
public class Builder
139+
{
140+
private EndCertificateStatus _status;
141+
private X509ChainStatusFlags _statusFlags;
142+
private DateTime? _statusUpdateTime;
143+
private DateTime? _revocationTime;
144+
145+
public Builder WithStatus(EndCertificateStatus value)
146+
{
147+
_status = value;
148+
return this;
149+
}
150+
151+
public Builder WithStatusFlags(X509ChainStatusFlags value)
152+
{
153+
_statusFlags = value;
154+
return this;
155+
}
156+
157+
public Builder WithStatusUpdateTime(DateTime? value)
158+
{
159+
_statusUpdateTime = value;
160+
return this;
161+
}
162+
163+
public Builder WithRevocationTime(DateTime? value)
164+
{
165+
_revocationTime = value;
166+
return this;
167+
}
168+
169+
public CertificateVerificationResult Build()
170+
{
171+
return new CertificateVerificationResult(
172+
_status,
173+
_statusFlags,
174+
_statusUpdateTime,
175+
_revocationTime);
176+
}
177+
}
50178
}
51179
}

src/Validation.PackageSigning.ValidateCertificate/ITelemetryService.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,19 @@ namespace Validation.PackageSigning.ValidateCertificate
77
{
88
public interface ITelemetryService
99
{
10+
/// <summary>
11+
/// The event that tracks when a signature may be invalid and should be manually inspected.
12+
/// Unlike <see cref="TrackPackageSignatureShouldBeInvalidatedEvent(PackageSignature)"/>, this
13+
/// package MAY be found to still be valid!
14+
/// </summary>
15+
/// <param name="signature">The signature that should be invalidated.</param>
16+
/// <returns>A task that returns when the event has been recorded.</returns>
17+
void TrackPackageSignatureMayBeInvalidatedEvent(PackageSignature signature);
18+
1019
/// <summary>
1120
/// The event that tracks when a signature should be manually invalidated.
21+
/// Unlike <see cref="TrackPackageSignatureMayBeInvalidatedEvent(PackageSignature)"/>, this
22+
/// package SHOULD be invalidated.
1223
/// </summary>
1324
/// <param name="signature">The signature that should be invalidated.</param>
1425
/// <returns>A task that returns when the event has been recorded.</returns>
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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.Linq;
6+
using System.Security.Cryptography.X509Certificates;
7+
using NuGet.Services.Validation;
8+
9+
namespace Validation.PackageSigning.ValidateCertificate
10+
{
11+
/// <summary>
12+
/// Deciders are created from a given <see cref="CertificateVerificationResult"/> to decide
13+
/// how each of the certificate's dependent <see cref="PackageSignature"/>s should be affected.
14+
/// </summary>
15+
/// <param name="signature">A signature that depends on the <see cref="EndCertificate"/> that was verified.</param>
16+
/// <returns>How the signature should be handled.</returns>
17+
public delegate SignatureDecision SignatureDecider(PackageSignature signature);
18+
19+
/// <summary>
20+
/// Creates functions that decide how <see cref="PackageSignature"/>s should be affected by
21+
/// <see cref="EndCertificate.Status"/> changes.
22+
/// </summary>
23+
public class SignatureDeciderFactory
24+
{
25+
/// <summary>
26+
/// Create a function to decide how signatures should be affected by a revoked certificate.
27+
/// </summary>
28+
/// <param name="certificate">The certificate that was revoked.</param>
29+
/// <param name="result">The verification result that describes when the certificate was revoked.</param>
30+
/// <returns>The function that describes how dependent signatures should be affected by the certificate status change.</returns>
31+
public SignatureDecider MakeDeciderForRevokedCertificate(EndCertificate certificate, CertificateVerificationResult result)
32+
{
33+
switch (certificate.Use)
34+
{
35+
case EndCertificateUse.Timestamping:
36+
return RejectSignaturesAtIngestionOtherwiseWarnDecider;
37+
38+
case EndCertificateUse.CodeSigning:
39+
return MakeDeciderForRevokedCodeSigningCertificate(result.RevocationTime);
40+
41+
default:
42+
throw new InvalidOperationException($"Revoked certificate has unknown use: {certificate.Use}");
43+
}
44+
}
45+
46+
/// <summary>
47+
/// Create a function to decide how signatures should be affected by an invalidated certificate.
48+
/// </summary>
49+
/// <param name="certificate">The certificate that was invalidated.</param>
50+
/// <param name="result">The verification result that describes why the certificate was invalidated.</param>
51+
/// <returns>The function that describes how dependent signatures should be affected by the certificate status change.</returns>
52+
public SignatureDecider MakeDeciderForInvalidatedCertificate(EndCertificate certificate, CertificateVerificationResult result)
53+
{
54+
if (result.Status != EndCertificateStatus.Invalid)
55+
{
56+
throw new ArgumentException($"Result must have a status of {nameof(EndCertificateStatus.Invalid)}", nameof(result));
57+
}
58+
59+
if (result.StatusFlags == X509ChainStatusFlags.NoError ||
60+
(result.StatusFlags & (X509ChainStatusFlags.OfflineRevocation | X509ChainStatusFlags.RevocationStatusUnknown)) != 0)
61+
{
62+
throw new ArgumentException($"Invalid flags on invalid verification result: {result.StatusFlags}!", nameof(result));
63+
}
64+
65+
// If a certificate used for the primary signature is revoked, all dependent signatures should be invalidated.
66+
// Note that in this case the revoked certificate is a parent of the end certificate - NOT the end certificate.
67+
if (certificate.Use == EndCertificateUse.CodeSigning && (result.StatusFlags & X509ChainStatusFlags.Revoked) != 0)
68+
{
69+
return RejectAllSignaturesDecider;
70+
}
71+
72+
// NotTimeValid and HasWeakSignature fail packages only at ingestion.
73+
else if (ResultHasOnlyFlags(result, X509ChainStatusFlags.NotTimeValid | X509ChainStatusFlags.HasWeakSignature))
74+
{
75+
return RejectSignaturesAtIngestionDecider;
76+
}
77+
78+
// NotTimeNested does not affect signatures and should be ignored.
79+
else if (ResultHasOnlyFlags(result, X509ChainStatusFlags.NotTimeNested))
80+
{
81+
return NoActionDecider;
82+
}
83+
84+
// In all other cases, reject signatures at ingestion and warn on all other signatures.
85+
else
86+
{
87+
return RejectSignaturesAtIngestionOtherwiseWarnDecider;
88+
}
89+
}
90+
91+
private SignatureDecider MakeDeciderForRevokedCodeSigningCertificate(DateTime? revocationTime)
92+
{
93+
// If the time the certificate was revoked is unknown, assume the worst and reject all dependent signatures.
94+
if (!revocationTime.HasValue)
95+
{
96+
return RejectAllSignaturesDecider;
97+
}
98+
99+
return (PackageSignature signature) =>
100+
{
101+
// The revoked code signing certificate invalidates signatures with no valid timestamps.
102+
// TODO: This should skip trusted timestamps with invalid/revoked certs. This requires:
103+
// * Getting trusted timestamps' certificates and their statuses.
104+
if (!signature.TrustedTimestamps.Any())
105+
{
106+
return SignatureDecision.Reject;
107+
}
108+
109+
// The revoked code signing certificate invalidates signatures with at least one trusted
110+
// timestamp before the revocation date.
111+
if (signature.TrustedTimestamps.Any(t => revocationTime.Value <= t.Value))
112+
{
113+
return SignatureDecision.Reject;
114+
}
115+
116+
// This signature lives to see another day.
117+
return SignatureDecision.Ignore;
118+
};
119+
}
120+
121+
private SignatureDecision NoActionDecider(PackageSignature signature) => SignatureDecision.Ignore;
122+
123+
private SignatureDecision RejectAllSignaturesDecider(PackageSignature signature) => SignatureDecision.Reject;
124+
125+
private SignatureDecision RejectSignaturesAtIngestionDecider(PackageSignature signature)
126+
{
127+
return (signature.Status == PackageSignatureStatus.Unknown)
128+
? SignatureDecision.Reject
129+
: SignatureDecision.Ignore;
130+
}
131+
132+
private SignatureDecision RejectSignaturesAtIngestionOtherwiseWarnDecider(PackageSignature signature)
133+
{
134+
return (signature.Status == PackageSignatureStatus.Unknown)
135+
? SignatureDecision.Reject
136+
: SignatureDecision.Warn;
137+
}
138+
139+
private bool ResultHasOnlyFlags(CertificateVerificationResult result, X509ChainStatusFlags flags)
140+
{
141+
if (result.StatusFlags == X509ChainStatusFlags.NoError)
142+
{
143+
return flags == X509ChainStatusFlags.NoError;
144+
}
145+
146+
return (result.StatusFlags & flags) == result.StatusFlags;
147+
}
148+
}
149+
}

0 commit comments

Comments
 (0)