private Task SaveUnknownCertificateStatusAsync(EndCertificateValidation validation) { validation.EndCertificate.ValidationFailures++; if (validation.EndCertificate.ValidationFailures >= _maximumValidationFailures) { // The maximum number of validation failures has been reached. The certificate's // validation should not be retried as a NuGet Admin will need to investigate the issues. // If the certificate is found to be invalid, the Admin will need to invalidate packages // and timestamps that depend on this certificate! validation.EndCertificate.Status = EndCertificateStatus.Invalid; validation.EndCertificate.LastVerificationTime = DateTime.UtcNow; validation.Status = EndCertificateStatus.Invalid; _logger.LogWarning( "Certificate {CertificateThumbprint} has reached maximum of {MaximumValidationFailures} failed validation attempts " + "and requires manual investigation by NuGet Admin. Firing alert...", validation.EndCertificate.Thumbprint, _maximumValidationFailures); _telemetryService.TrackUnableToValidateCertificateEvent(validation.EndCertificate); } return(_context.SaveChangesAsync()); }
private async Task <bool> HandleUnknownResultAsync(int validationFailuresStart) { // Arrange // Return an "Unknown" status for the certificate's verification. The validation service should increment the number // of failures for the validation's certificate. var certificateValidation = new EndCertificateValidation { Status = null, EndCertificate = new EndCertificate { Status = EndCertificateStatus.Unknown, Use = EndCertificateUse.CodeSigning, ValidationFailures = validationFailuresStart, CertificateChainLinks = new CertificateChainLink[0], } }; var certificateVerificationResult = new CertificateVerificationResult( status: EndCertificateStatus.Unknown, statusFlags: X509ChainStatusFlags.RevocationStatusUnknown); _certificateValidationService .Setup(s => s.FindCertificateValidationAsync(It.IsAny <CertificateValidationMessage>())) .ReturnsAsync(certificateValidation); _certificateStore .Setup(s => s.LoadAsync(It.IsAny <string>(), It.IsAny <CancellationToken>())) .ReturnsAsync(new X509Certificate2()); _certificateVerifier .Setup(v => v.VerifyCodeSigningCertificate(It.IsAny <X509Certificate2>(), It.IsAny <X509Certificate2[]>())) .Returns(certificateVerificationResult); _certificateValidationService .Setup( s => s.TrySaveResultAsync( It.IsAny <EndCertificateValidation>(), It.Is <CertificateVerificationResult>(r => r.Status == EndCertificateStatus.Unknown))) .Callback <EndCertificateValidation, CertificateVerificationResult>((v, r) => v.EndCertificate.ValidationFailures++) .ReturnsAsync(true); // Act & Assert var result = await _target.HandleAsync(_message); Assert.Equal(validationFailuresStart + 1, certificateValidation.EndCertificate.ValidationFailures); _certificateValidationService .Verify( s => s.TrySaveResultAsync( It.IsAny <EndCertificateValidation>(), It.IsAny <CertificateVerificationResult>()), Times.Once); return(result); }
private Task SaveGoodCertificateStatusAsync(EndCertificateValidation validation, CertificateVerificationResult result) { validation.EndCertificate.Status = EndCertificateStatus.Good; validation.EndCertificate.StatusUpdateTime = result.StatusUpdateTime; validation.EndCertificate.NextStatusUpdateTime = null; validation.EndCertificate.LastVerificationTime = DateTime.UtcNow; validation.EndCertificate.RevocationTime = null; validation.EndCertificate.ValidationFailures = 0; validation.Status = EndCertificateStatus.Good; return(_context.SaveChangesAsync()); }
private async Task <LoadCertificatesResult> LoadCertificatesAsync(EndCertificateValidation validation) { // Create a list of all the thumbprints that need to be downloaded. The first thumbprint is the end certificate, // the rest are the end certificate's ancestors. var thumbprints = new List <string>(); thumbprints.Add(validation.EndCertificate.Thumbprint); thumbprints.AddRange(validation.EndCertificate.CertificateChainLinks.Select(l => l.ParentCertificate.Thumbprint)); var certificates = await Task.WhenAll(thumbprints.Select(t => _certificateStore.LoadAsync(t, CancellationToken.None))); return(new LoadCertificatesResult( endCertificate: certificates.First(), ancestorCertificates: certificates.Skip(1).ToArray())); }
private bool HasValidationCompleted(EndCertificateValidation validation, CertificateVerificationResult result) { // The validation is complete if the certificate was determined to be "Good", "Invalid", or "Revoked". if (result.Status == EndCertificateStatus.Good || result.Status == EndCertificateStatus.Invalid || result.Status == EndCertificateStatus.Revoked) { return(true); } else if (result.Status == EndCertificateStatus.Unknown) { // Certificates whose status failed to be determined will have an "Unknown" // status. These certificates should be retried until "_maximumValidationFailures" // is reached. if (validation.EndCertificate.ValidationFailures >= _maximumValidationFailures) { _logger.LogWarning( "Certificate {CertificateThumbprint} has reached maximum of {MaximumValidationFailures} failed validation attempts", validation.EndCertificate.Thumbprint, _maximumValidationFailures); return(true); } else { _logger.LogWarning( "Could not validate certificate {CertificateThumbprint}, {RetriesLeft} retries left", validation.EndCertificate.Thumbprint, _maximumValidationFailures - validation.EndCertificate.ValidationFailures); return(false); } } _logger.LogError( $"Unknown {nameof(EndCertificateStatus)} value: {{CertificateStatus}}, throwing to retry", result.Status); throw new InvalidOperationException($"Unknown {nameof(EndCertificateStatus)} value: {result.Status}"); }
private Task SaveRevokedCertificateStatusAsync(EndCertificateValidation validation, CertificateVerificationResult result) { var invalidationDecider = _signatureDeciderFactory.MakeDeciderForRevokedCertificate(validation.EndCertificate, result); void RevokeCertificate() { validation.EndCertificate.Status = EndCertificateStatus.Revoked; validation.EndCertificate.StatusUpdateTime = result.StatusUpdateTime; validation.EndCertificate.NextStatusUpdateTime = null; validation.EndCertificate.LastVerificationTime = DateTime.UtcNow; validation.EndCertificate.RevocationTime = result.RevocationTime; validation.EndCertificate.ValidationFailures = 0; validation.Status = EndCertificateStatus.Revoked; } return(ProcessDependentSignaturesAsync( validation.EndCertificate, result, invalidationDecider, onAllSignaturesHandled: RevokeCertificate)); }
public async Task ValidateSigningCertificate( Func <CertificateIntegrationTestFixture, Task <X509Certificate2> > createCertificateFunc, DateTime signatureTime, EndCertificateStatus expectedCertificateStatus, PackageSignatureStatus expectedStatusForSignatureAtIngestion, PackageSignatureStatus expectedStatusForSignatureInGracePeriod, PackageSignatureStatus expectedStatusForSignatureAfterGracePeriod) { // Arrange var certificate = await createCertificateFunc(_fixture); var endCertificateKey = 123; var validationId = Guid.NewGuid(); var packageSigningState1 = new PackageSigningState { SigningStatus = PackageSigningStatus.Valid }; var packageSigningState2 = new PackageSigningState { SigningStatus = PackageSigningStatus.Valid }; var packageSigningState3 = new PackageSigningState { SigningStatus = PackageSigningStatus.Valid }; var signatureAtIngestion = new PackageSignature { Status = PackageSignatureStatus.Unknown, Type = PackageSignatureType.Author, }; var signatureInGracePeriod = new PackageSignature { Status = PackageSignatureStatus.InGracePeriod, Type = PackageSignatureType.Author, }; var signatureAfterGracePeriod = new PackageSignature { Status = PackageSignatureStatus.Valid, Type = PackageSignatureType.Author, }; var trustedTimestamp1 = new TrustedTimestamp { Status = TrustedTimestampStatus.Valid, Value = signatureTime }; var trustedTimestamp2 = new TrustedTimestamp { Status = TrustedTimestampStatus.Valid, Value = signatureTime }; var trustedTimestamp3 = new TrustedTimestamp { Status = TrustedTimestampStatus.Valid, Value = signatureTime }; var endCertificate = new EndCertificate { Key = endCertificateKey, Status = EndCertificateStatus.Unknown, Use = EndCertificateUse.CodeSigning, CertificateChainLinks = new CertificateChainLink[0], }; var validation = new EndCertificateValidation { EndCertificateKey = endCertificateKey, ValidationId = validationId, Status = null, EndCertificate = endCertificate }; signatureAtIngestion.PackageSigningState = packageSigningState1; signatureAtIngestion.EndCertificate = endCertificate; signatureAtIngestion.TrustedTimestamps = new[] { trustedTimestamp1 }; signatureInGracePeriod.PackageSigningState = packageSigningState2; signatureInGracePeriod.EndCertificate = endCertificate; signatureInGracePeriod.TrustedTimestamps = new[] { trustedTimestamp2 }; signatureAfterGracePeriod.PackageSigningState = packageSigningState3; signatureAfterGracePeriod.EndCertificate = endCertificate; signatureAfterGracePeriod.TrustedTimestamps = new[] { trustedTimestamp3 }; _context.Mock( packageSignatures: new[] { signatureAtIngestion, signatureInGracePeriod, signatureAfterGracePeriod }, endCertificates: new[] { endCertificate }, certificateValidations: new EndCertificateValidation[] { validation }); _certificateStore.Setup(s => s.LoadAsync(It.IsAny <string>(), It.IsAny <CancellationToken>())) .Returns(Task.FromResult(certificate)); // Act await _target.HandleAsync(new CertificateValidationMessage(certificateKey : endCertificateKey, validationId : validationId)); // Assert Assert.Equal(expectedCertificateStatus, validation.Status); Assert.Equal(expectedCertificateStatus, endCertificate.Status); Assert.Equal(expectedStatusForSignatureAtIngestion, signatureAtIngestion.Status); Assert.Equal(expectedStatusForSignatureInGracePeriod, signatureInGracePeriod.Status); Assert.Equal(expectedStatusForSignatureAfterGracePeriod, signatureAfterGracePeriod.Status); _context.Verify(c => c.SaveChangesAsync(), Times.Once); }
public async Task ValidateTimestampingCertificate() { // Arrange var certificate = await _fixture.GetRevokedTimestampingCertificateAsync(RevocationTime); var endCertificateKey = 123; var validationId = Guid.NewGuid(); var packageSigningState = new PackageSigningState { SigningStatus = PackageSigningStatus.Valid }; var signatureAtIngestion = new PackageSignature { Status = PackageSignatureStatus.Unknown, Type = PackageSignatureType.Author, }; var signatureInGracePeriod = new PackageSignature { Status = PackageSignatureStatus.InGracePeriod, Type = PackageSignatureType.Author, }; var signatureAfterGracePeriod = new PackageSignature { Status = PackageSignatureStatus.Valid, Type = PackageSignatureType.Author, }; var endCertificate = new EndCertificate { Key = endCertificateKey, Status = EndCertificateStatus.Unknown, Use = EndCertificateUse.Timestamping, CertificateChainLinks = new CertificateChainLink[0], }; var trustedTimestamp = new TrustedTimestamp { EndCertificate = endCertificate, Status = TrustedTimestampStatus.Valid }; var validation = new EndCertificateValidation { EndCertificateKey = endCertificateKey, ValidationId = validationId, Status = null, EndCertificate = endCertificate }; signatureAtIngestion.PackageSigningState = packageSigningState; signatureAtIngestion.TrustedTimestamps = new[] { trustedTimestamp }; signatureInGracePeriod.PackageSigningState = packageSigningState; signatureInGracePeriod.TrustedTimestamps = new[] { trustedTimestamp }; signatureAfterGracePeriod.PackageSigningState = packageSigningState; signatureAfterGracePeriod.TrustedTimestamps = new[] { trustedTimestamp }; _context.Mock( packageSignatures: new[] { signatureAtIngestion, signatureInGracePeriod, signatureAfterGracePeriod }, endCertificates: new[] { endCertificate }, certificateValidations: new EndCertificateValidation[] { validation }); _certificateStore.Setup(s => s.LoadAsync(It.IsAny <string>(), It.IsAny <CancellationToken>())) .Returns(Task.FromResult(certificate)); // Act await _target.HandleAsync(new CertificateValidationMessage(certificateKey : endCertificateKey, validationId : validationId)); // Assert Assert.Equal(EndCertificateStatus.Revoked, validation.Status); Assert.Equal(EndCertificateStatus.Revoked, endCertificate.Status); Assert.Equal(PackageSignatureStatus.Invalid, signatureAtIngestion.Status); Assert.Equal(PackageSignatureStatus.Invalid, signatureInGracePeriod.Status); Assert.Equal(PackageSignatureStatus.Invalid, signatureAfterGracePeriod.Status); _context.Verify(c => c.SaveChangesAsync(), Times.Once); }
public async Task <bool> TrySaveResultAsync(EndCertificateValidation validation, CertificateVerificationResult result) { if (validation.EndCertificate.Status == EndCertificateStatus.Revoked && result.Status != EndCertificateStatus.Revoked) { _logger.LogWarning( "Updating previously revoked certificate {CertificateThumbprint} to status {NewStatus}", validation.EndCertificate.Thumbprint, result.Status); } else { _logger.LogInformation( "Updating certificate {CertificateThumbprint} to status {NewStatus}", validation.EndCertificate.Thumbprint, result.Status); } try { switch (result.Status) { case EndCertificateStatus.Good: await SaveGoodCertificateStatusAsync(validation, result); break; case EndCertificateStatus.Unknown: await SaveUnknownCertificateStatusAsync(validation); break; case EndCertificateStatus.Invalid: await SaveInvalidCertificateStatusAsync(validation, result); break; case EndCertificateStatus.Revoked: await SaveRevokedCertificateStatusAsync(validation, result); break; default: _logger.LogError( $"Unknown {nameof(EndCertificateStatus)} value: {{CertificateStatus}}, throwing to retry", result.Status); throw new NotSupportedException($"Unknown {nameof(EndCertificateStatus)} value: {result.Status}"); } return(true); } catch (DbUpdateConcurrencyException e) { // The update concurrency exception be triggered by either the Certificate record or one of the dependent // PackageSignature records. Regardless, retry the validation so that the Certificate is validated with // the new state. _logger.LogWarning( 0, e, "Failed to update certificate {CertificateThumbprint} to status {NewStatus} due to concurrency exception", validation.EndCertificate.Thumbprint, result.Status); return(false); } }