Exemple #1
0
        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);
            }
Exemple #3
0
        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}");
        }
Exemple #6
0
        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));
        }
Exemple #7
0
        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);
        }
Exemple #8
0
        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);
        }
Exemple #9
0
        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);
            }
        }