|
| 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.Collections.Generic; |
| 6 | +using System.Security.Cryptography.X509Certificates; |
| 7 | +using System.Threading; |
| 8 | +using System.Threading.Tasks; |
| 9 | +using Microsoft.Extensions.Logging; |
| 10 | +using Moq; |
| 11 | +using NuGet.Jobs.Validation.PackageSigning.Messages; |
| 12 | +using NuGet.Jobs.Validation.PackageSigning.Storage; |
| 13 | +using NuGet.Services.Validation; |
| 14 | +using Validation.PackageSigning.Helpers; |
| 15 | +using Validation.PackageSigning.ValidateCertificate.Tests.Support; |
| 16 | +using Xunit; |
| 17 | + |
| 18 | +namespace Validation.PackageSigning.ValidateCertificate.Tests |
| 19 | +{ |
| 20 | + [Collection(CertificateIntegrationTestCollection.Name)] |
| 21 | + public class CertificateValidationMessageHandlerIntegrationTests |
| 22 | + { |
| 23 | + private static readonly DateTime RevocationTime = DateTime.UtcNow.AddDays(-4); |
| 24 | + private static readonly DateTime BeforeRevocationTime = RevocationTime.AddDays(-1); |
| 25 | + private static readonly DateTime AfterRevocationTime = RevocationTime.AddDays(1); |
| 26 | + |
| 27 | + private readonly CertificateIntegrationTestFixture _fixture; |
| 28 | + |
| 29 | + private readonly Mock<IValidationEntitiesContext> _context; |
| 30 | + private readonly Mock<ITelemetryService> _telemetryService; |
| 31 | + private readonly Mock<ICertificateStore> _certificateStore; |
| 32 | + |
| 33 | + private readonly CertificateValidationMessageHandler _target; |
| 34 | + |
| 35 | + public CertificateValidationMessageHandlerIntegrationTests(CertificateIntegrationTestFixture fixture) |
| 36 | + { |
| 37 | + _fixture = fixture; |
| 38 | + |
| 39 | + _context = new Mock<IValidationEntitiesContext>(); |
| 40 | + _telemetryService = new Mock<ITelemetryService>(); |
| 41 | + _certificateStore = new Mock<ICertificateStore>(); |
| 42 | + |
| 43 | + var certificateValidationService = new CertificateValidationService( |
| 44 | + _context.Object, |
| 45 | + _telemetryService.Object, |
| 46 | + Mock.Of<ILogger<CertificateValidationService>>()); |
| 47 | + |
| 48 | + _target = new CertificateValidationMessageHandler( |
| 49 | + _certificateStore.Object, |
| 50 | + certificateValidationService, |
| 51 | + new OnlineCertificateVerifier(), |
| 52 | + Mock.Of<ILogger<CertificateValidationMessageHandler>>()); |
| 53 | + } |
| 54 | + |
| 55 | + [Theory] |
| 56 | + [MemberData(nameof(ValidateSigningCertificateData))] |
| 57 | + public async Task ValidateSigningCertificate( |
| 58 | + Func<CertificateIntegrationTestFixture, Task<X509Certificate2>> createCertificateFunc, |
| 59 | + DateTime signatureTime, |
| 60 | + EndCertificateStatus expectedCertificateStatus, |
| 61 | + PackageSignatureStatus expectedStatusForSignatureAtIngestion, |
| 62 | + PackageSignatureStatus expectedStatusForSignatureInGracePeriod, |
| 63 | + PackageSignatureStatus expectedStatusForSignatureAfterGracePeriod) |
| 64 | + { |
| 65 | + // Arrange |
| 66 | + var certificate = await createCertificateFunc(_fixture); |
| 67 | + |
| 68 | + var endCertificateKey = 123; |
| 69 | + var validationId = Guid.NewGuid(); |
| 70 | + |
| 71 | + var packageSigningState1 = new PackageSigningState { SigningStatus = PackageSigningStatus.Valid }; |
| 72 | + var packageSigningState2 = new PackageSigningState { SigningStatus = PackageSigningStatus.Valid }; |
| 73 | + var packageSigningState3 = new PackageSigningState { SigningStatus = PackageSigningStatus.Valid }; |
| 74 | + |
| 75 | + var signatureAtIngestion = new PackageSignature { Status = PackageSignatureStatus.Unknown }; |
| 76 | + var signatureInGracePeriod = new PackageSignature { Status = PackageSignatureStatus.InGracePeriod }; |
| 77 | + var signatureAfterGracePeriod = new PackageSignature { Status = PackageSignatureStatus.Valid }; |
| 78 | + |
| 79 | + var trustedTimestamp1 = new TrustedTimestamp { Status = TrustedTimestampStatus.Valid, Value = signatureTime }; |
| 80 | + var trustedTimestamp2 = new TrustedTimestamp { Status = TrustedTimestampStatus.Valid, Value = signatureTime }; |
| 81 | + var trustedTimestamp3 = new TrustedTimestamp { Status = TrustedTimestampStatus.Valid, Value = signatureTime }; |
| 82 | + |
| 83 | + var endCertificate = new EndCertificate |
| 84 | + { |
| 85 | + Key = endCertificateKey, |
| 86 | + Status = EndCertificateStatus.Unknown, |
| 87 | + Use = EndCertificateUse.CodeSigning, |
| 88 | + CertificateChainLinks = new CertificateChainLink[0], |
| 89 | + }; |
| 90 | + |
| 91 | + var validation = new EndCertificateValidation |
| 92 | + { |
| 93 | + EndCertificateKey = endCertificateKey, |
| 94 | + ValidationId = validationId, |
| 95 | + Status = null, |
| 96 | + EndCertificate = endCertificate |
| 97 | + }; |
| 98 | + |
| 99 | + signatureAtIngestion.PackageSigningState = packageSigningState1; |
| 100 | + signatureAtIngestion.EndCertificate = endCertificate; |
| 101 | + signatureAtIngestion.TrustedTimestamps = new[] { trustedTimestamp1 }; |
| 102 | + signatureInGracePeriod.PackageSigningState = packageSigningState2; |
| 103 | + signatureInGracePeriod.EndCertificate = endCertificate; |
| 104 | + signatureInGracePeriod.TrustedTimestamps = new[] { trustedTimestamp2 }; |
| 105 | + signatureAfterGracePeriod.PackageSigningState = packageSigningState3; |
| 106 | + signatureAfterGracePeriod.EndCertificate = endCertificate; |
| 107 | + signatureAfterGracePeriod.TrustedTimestamps = new[] { trustedTimestamp3 }; |
| 108 | + |
| 109 | + _context.Mock( |
| 110 | + packageSignatures: new[] { signatureAtIngestion, signatureInGracePeriod, signatureAfterGracePeriod }, |
| 111 | + endCertificates: new[] { endCertificate }, |
| 112 | + certificateValidations: new EndCertificateValidation[] { validation }); |
| 113 | + |
| 114 | + _certificateStore.Setup(s => s.LoadAsync(It.IsAny<string>(), It.IsAny<CancellationToken>())) |
| 115 | + .Returns(Task.FromResult(certificate)); |
| 116 | + |
| 117 | + // Act |
| 118 | + await _target.HandleAsync(new CertificateValidationMessage(certificateKey: endCertificateKey, validationId: validationId)); |
| 119 | + |
| 120 | + // Assert |
| 121 | + Assert.Equal(expectedCertificateStatus, validation.Status); |
| 122 | + Assert.Equal(expectedCertificateStatus, endCertificate.Status); |
| 123 | + Assert.Equal(expectedStatusForSignatureAtIngestion, signatureAtIngestion.Status); |
| 124 | + Assert.Equal(expectedStatusForSignatureInGracePeriod, signatureInGracePeriod.Status); |
| 125 | + Assert.Equal(expectedStatusForSignatureAfterGracePeriod, signatureAfterGracePeriod.Status); |
| 126 | + |
| 127 | + _context.Verify(c => c.SaveChangesAsync(), Times.Once); |
| 128 | + } |
| 129 | + |
| 130 | + public static IEnumerable<object[]> ValidateSigningCertificateData() |
| 131 | + { |
| 132 | + Func<CertificateIntegrationTestFixture, Task<X509Certificate2>> getGoodCert = |
| 133 | + (CertificateIntegrationTestFixture f) => f.GetSigningCertificateAsync(); |
| 134 | + |
| 135 | + yield return ValidateSigningCertificateArguments( |
| 136 | + createCertificateFunc: getGoodCert, |
| 137 | + signatureTime: DateTime.UtcNow, |
| 138 | + expectedCertificateStatus: EndCertificateStatus.Good, |
| 139 | + expectedStatusForSignatureAtIngestion: PackageSignatureStatus.Unknown, |
| 140 | + expectedStatusForSignatureInGracePeriod: PackageSignatureStatus.InGracePeriod, |
| 141 | + expectedStatusForSignatureAfterGracePeriod: PackageSignatureStatus.Valid); |
| 142 | + |
| 143 | + Func<CertificateIntegrationTestFixture, Task<X509Certificate2>> getRevokedParentCert = |
| 144 | + (CertificateIntegrationTestFixture f) => f.GetRevokedParentSigningCertificateAsync(); |
| 145 | + |
| 146 | + yield return ValidateSigningCertificateArguments( |
| 147 | + createCertificateFunc: getRevokedParentCert, |
| 148 | + signatureTime: DateTime.UtcNow, |
| 149 | + expectedCertificateStatus: EndCertificateStatus.Invalid, |
| 150 | + expectedStatusForSignatureAtIngestion: PackageSignatureStatus.Invalid, |
| 151 | + expectedStatusForSignatureInGracePeriod: PackageSignatureStatus.Invalid, |
| 152 | + expectedStatusForSignatureAfterGracePeriod: PackageSignatureStatus.Invalid); |
| 153 | + |
| 154 | + Func<CertificateIntegrationTestFixture, Task<X509Certificate2>> getWeakSignatureParentCert = |
| 155 | + (CertificateIntegrationTestFixture f) => f.GetWeakSignatureParentSigningCertificateAsync(); |
| 156 | + |
| 157 | + yield return ValidateSigningCertificateArguments( |
| 158 | + createCertificateFunc: getWeakSignatureParentCert, |
| 159 | + signatureTime: DateTime.UtcNow, |
| 160 | + expectedCertificateStatus: EndCertificateStatus.Invalid, |
| 161 | + expectedStatusForSignatureAtIngestion: PackageSignatureStatus.Invalid, |
| 162 | + expectedStatusForSignatureInGracePeriod: PackageSignatureStatus.InGracePeriod, |
| 163 | + expectedStatusForSignatureAfterGracePeriod: PackageSignatureStatus.Valid); |
| 164 | + |
| 165 | + Func<CertificateIntegrationTestFixture, Task<X509Certificate2>> getRevokedCert = |
| 166 | + (CertificateIntegrationTestFixture f) => f.GetRevokedSigningCertificateAsync(RevocationTime); |
| 167 | + |
| 168 | + yield return ValidateSigningCertificateArguments( |
| 169 | + createCertificateFunc: getRevokedCert, |
| 170 | + signatureTime: AfterRevocationTime, |
| 171 | + expectedCertificateStatus: EndCertificateStatus.Revoked, |
| 172 | + expectedStatusForSignatureAtIngestion: PackageSignatureStatus.Invalid, |
| 173 | + expectedStatusForSignatureInGracePeriod: PackageSignatureStatus.Invalid, |
| 174 | + expectedStatusForSignatureAfterGracePeriod: PackageSignatureStatus.Invalid); |
| 175 | + |
| 176 | + yield return ValidateSigningCertificateArguments( |
| 177 | + createCertificateFunc: getRevokedCert, |
| 178 | + signatureTime: BeforeRevocationTime, |
| 179 | + expectedCertificateStatus: EndCertificateStatus.Revoked, |
| 180 | + expectedStatusForSignatureAtIngestion: PackageSignatureStatus.Invalid, |
| 181 | + expectedStatusForSignatureInGracePeriod: PackageSignatureStatus.InGracePeriod, |
| 182 | + expectedStatusForSignatureAfterGracePeriod: PackageSignatureStatus.Valid); |
| 183 | + } |
| 184 | + |
| 185 | + private static object[] ValidateSigningCertificateArguments( |
| 186 | + Func<CertificateIntegrationTestFixture, Task<X509Certificate2>> createCertificateFunc, |
| 187 | + DateTime signatureTime, |
| 188 | + EndCertificateStatus expectedCertificateStatus, |
| 189 | + PackageSignatureStatus expectedStatusForSignatureAtIngestion, |
| 190 | + PackageSignatureStatus expectedStatusForSignatureInGracePeriod, |
| 191 | + PackageSignatureStatus expectedStatusForSignatureAfterGracePeriod) |
| 192 | + { |
| 193 | + return new object[] |
| 194 | + { |
| 195 | + createCertificateFunc, |
| 196 | + signatureTime, |
| 197 | + expectedCertificateStatus, |
| 198 | + expectedStatusForSignatureAtIngestion, |
| 199 | + expectedStatusForSignatureInGracePeriod, |
| 200 | + expectedStatusForSignatureAfterGracePeriod, |
| 201 | + }; |
| 202 | + } |
| 203 | + |
| 204 | + [Fact] |
| 205 | + public async Task ValidateTimestampingCertificate() |
| 206 | + { |
| 207 | + // Arrange |
| 208 | + var certificate = await _fixture.GetRevokedTimestampingCertificateAsync(RevocationTime); |
| 209 | + |
| 210 | + var endCertificateKey = 123; |
| 211 | + var validationId = Guid.NewGuid(); |
| 212 | + |
| 213 | + var packageSigningState = new PackageSigningState { SigningStatus = PackageSigningStatus.Valid }; |
| 214 | + |
| 215 | + var signatureAtIngestion = new PackageSignature { Status = PackageSignatureStatus.Unknown }; |
| 216 | + var signatureInGracePeriod = new PackageSignature { Status = PackageSignatureStatus.InGracePeriod }; |
| 217 | + var signatureAfterGracePeriod = new PackageSignature { Status = PackageSignatureStatus.Valid }; |
| 218 | + |
| 219 | + var endCertificate = new EndCertificate |
| 220 | + { |
| 221 | + Key = endCertificateKey, |
| 222 | + Status = EndCertificateStatus.Unknown, |
| 223 | + Use = EndCertificateUse.Timestamping, |
| 224 | + CertificateChainLinks = new CertificateChainLink[0], |
| 225 | + }; |
| 226 | + |
| 227 | + var trustedTimestamp = new TrustedTimestamp |
| 228 | + { |
| 229 | + EndCertificate = endCertificate, |
| 230 | + Status = TrustedTimestampStatus.Valid |
| 231 | + }; |
| 232 | + |
| 233 | + var validation = new EndCertificateValidation |
| 234 | + { |
| 235 | + EndCertificateKey = endCertificateKey, |
| 236 | + ValidationId = validationId, |
| 237 | + Status = null, |
| 238 | + EndCertificate = endCertificate |
| 239 | + }; |
| 240 | + |
| 241 | + signatureAtIngestion.PackageSigningState = packageSigningState; |
| 242 | + signatureAtIngestion.TrustedTimestamps = new[] { trustedTimestamp }; |
| 243 | + signatureInGracePeriod.PackageSigningState = packageSigningState; |
| 244 | + signatureInGracePeriod.TrustedTimestamps = new[] { trustedTimestamp }; |
| 245 | + signatureAfterGracePeriod.PackageSigningState = packageSigningState; |
| 246 | + signatureAfterGracePeriod.TrustedTimestamps = new[] { trustedTimestamp }; |
| 247 | + |
| 248 | + _context.Mock( |
| 249 | + packageSignatures: new[] { signatureAtIngestion, signatureInGracePeriod, signatureAfterGracePeriod }, |
| 250 | + endCertificates: new[] { endCertificate }, |
| 251 | + certificateValidations: new EndCertificateValidation[] { validation }); |
| 252 | + |
| 253 | + _certificateStore.Setup(s => s.LoadAsync(It.IsAny<string>(), It.IsAny<CancellationToken>())) |
| 254 | + .Returns(Task.FromResult(certificate)); |
| 255 | + |
| 256 | + // Act |
| 257 | + await _target.HandleAsync(new CertificateValidationMessage(certificateKey: endCertificateKey, validationId: validationId)); |
| 258 | + |
| 259 | + // Assert |
| 260 | + Assert.Equal(EndCertificateStatus.Revoked, validation.Status); |
| 261 | + Assert.Equal(EndCertificateStatus.Revoked, endCertificate.Status); |
| 262 | + Assert.Equal(PackageSignatureStatus.Invalid, signatureAtIngestion.Status); |
| 263 | + Assert.Equal(PackageSignatureStatus.Invalid, signatureInGracePeriod.Status); |
| 264 | + Assert.Equal(PackageSignatureStatus.Invalid, signatureAfterGracePeriod.Status); |
| 265 | + |
| 266 | + _context.Verify(c => c.SaveChangesAsync(), Times.Once); |
| 267 | + } |
| 268 | + } |
| 269 | +} |
0 commit comments