Ejemplo n.º 1
0
        /// <summary>
        /// The helper that processes how a certificate's status change affects its dependent signatures.
        /// </summary>
        /// <param name="certificate">The certificate whose dependent signatures should be processed.</param>
        /// <param name="certificateVerificationResult">The result of the certificate's verification.</param>
        /// <param name="signatureDecider">The delegate that decides how a dependent signature should be handled.</param>
        /// <param name="onAllSignaturesHandled">The action that will be called once all dependent signatures have been processed.</param>
        /// <returns></returns>
        private async Task ProcessDependentSignaturesAsync(
            EndCertificate certificate,
            CertificateVerificationResult certificateVerificationResult,
            SignatureDecider signatureDecider,
            Action onAllSignaturesHandled)
        {
            // A single certificate may be dependend on by many signatures. To ensure sanity, only up
            // to "MaxSignatureUpdatesPerTransaction" signatures will be invalidated at a time.
            List <PackageSignature> signatures = null;
            int page = 0;

            do
            {
                // If necessary, save the previous iteration's signature invalidations.
                if (page > 0)
                {
                    _logger.LogInformation(
                        "Persisting {Signatures} dependent signature updates for certificate {CertificateThumbprint} (page {Page})",
                        signatures.Count,
                        certificate.Thumbprint,
                        page);

                    await _context.SaveChangesAsync();

                    page++;
                }

                _logger.LogInformation(
                    "Finding more dependent signatures to update for certificate {CertificateThumbprint}... (page {Page})",
                    certificate.Thumbprint,
                    page);

                signatures = await FindSignaturesAsync(certificate, page);

                _logger.LogInformation(
                    "Updating {Signatures} signatures for certificate {CertificateThumbprint}... (page {Page})",
                    signatures.Count,
                    certificate.Thumbprint,
                    page);

                foreach (var signature in signatures)
                {
                    var decision = signatureDecider(signature);

                    HandleSignatureDecision(signature, decision, certificate, certificateVerificationResult);
                }
            }while (signatures.Count == MaxSignatureUpdatesPerTransaction);

            // All signatures have been invalidated. Do any necessary finalizations, and persist the results.
            _logger.LogInformation(
                "Finalizing {Signatures} dependent signature updates for certificate {CertificateThumbprint} (total pages: {Pages})",
                signatures.Count,
                certificate.Thumbprint,
                page + 1);

            onAllSignaturesHandled();

            await _context.SaveChangesAsync();
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Handle the decision on how to update the signature.
        /// </summary>
        /// <param name="signature">The signature that should be updated.</param>
        /// <param name="decision">How the signature should be updated.</param>
        /// <param name="certificate">The certificate that signature depends on that changed the signature's state.</param>
        /// <param name="certificateVerificationResult">The certificate verification that changed the signature's state.</param>
        private void HandleSignatureDecision(
            PackageSignature signature,
            SignatureDecision decision,
            EndCertificate certificate,
            CertificateVerificationResult certificateVerificationResult)
        {
            switch (decision)
            {
            case SignatureDecision.Ignore:
                _logger.LogInformation(
                    "Signature {SignatureKey} is not affected by certificate verification result: {CertificateVerificationResult}",
                    signature.Key,
                    certificateVerificationResult);
                break;

            case SignatureDecision.Warn:
                _logger.LogWarning(
                    "Invalidating signature {SignatureKey} due to certificate verification result: {CertificateVerificationResult}",
                    signature.Key,
                    certificateVerificationResult);

                InvalidateSignature(signature, certificate);

                _telemetryService.TrackPackageSignatureMayBeInvalidatedEvent(signature);

                break;

            case SignatureDecision.Reject:
                _logger.LogWarning(
                    "Rejecting signature {SignatureKey} due to certificate verification result: {CertificateVerificationResult}",
                    signature.Key,
                    certificateVerificationResult);

                InvalidateSignature(signature, certificate);

                _telemetryService.TrackPackageSignatureShouldBeInvalidatedEvent(signature);
                break;

            default:
                throw new InvalidOperationException(
                          $"Unknown signature decision '{decision}' for certificate verification result: {certificateVerificationResult}");
            }
        }
        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}");
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Create a function to decide how signatures should be affected by an invalidated certificate.
        /// </summary>
        /// <param name="certificate">The certificate that was invalidated.</param>
        /// <param name="result">The verification result that describes why the certificate was invalidated.</param>
        /// <returns>The function that describes how dependent signatures should be affected by the certificate status change.</returns>
        public SignatureDecider MakeDeciderForInvalidatedCertificate(EndCertificate certificate, CertificateVerificationResult result)
        {
            if (result.Status != EndCertificateStatus.Invalid)
            {
                throw new ArgumentException($"Result must have a status of {nameof(EndCertificateStatus.Invalid)}", nameof(result));
            }

            if (result.StatusFlags == X509ChainStatusFlags.NoError)
            {
                throw new ArgumentException($"Invalid flags on invalid verification result: {result.StatusFlags}!", nameof(result));
            }

            // If a certificate used for the primary signature is revoked, all dependent signatures should be invalidated.
            // NOTE: It is assumed that the revoked certificate is an ancestor certificate, but this may not be strictly true.
            if (certificate.Use == EndCertificateUse.CodeSigning && (result.StatusFlags & X509ChainStatusFlags.Revoked) != 0)
            {
                return(RejectAllSignaturesDecider);
            }

            // NotTimeValid and HasWeakSignature fail packages only at ingestion. It is assumed that a chain with HasWeakSignature will
            // ALWAYS have NotSignatureValid.
            else if (result.StatusFlags == X509ChainStatusFlags.NotTimeValid ||
                     result.StatusFlags == (X509ChainStatusFlags.HasWeakSignature | X509ChainStatusFlags.NotSignatureValid) ||
                     result.StatusFlags == (X509ChainStatusFlags.NotTimeValid | X509ChainStatusFlags.HasWeakSignature | X509ChainStatusFlags.NotSignatureValid))
            {
                return(RejectSignaturesAtIngestionDecider);
            }

            // NotTimeNested does not affect signatures and should be ignored if it is the only status.
            else if (result.StatusFlags == X509ChainStatusFlags.NotTimeNested)
            {
                return(NoActionDecider);
            }

            // In all other cases, reject signatures at ingestion and warn on all other signatures.
            else
            {
                return(RejectSignaturesAtIngestionOtherwiseWarnDecider);
            }
        }
Ejemplo n.º 5
0
        /// <summary>
        /// Create a function to decide how signatures should be affected by a revoked certificate.
        /// </summary>
        /// <param name="certificate">The certificate that was revoked.</param>
        /// <param name="result">The verification result that describes when the certificate was revoked.</param>
        /// <returns>The function that describes how dependent signatures should be affected by the certificate status change.</returns>
        public SignatureDecider MakeDeciderForRevokedCertificate(EndCertificate certificate, CertificateVerificationResult result)
        {
            switch (certificate.Use)
            {
            case EndCertificateUse.Timestamping:
                return(RejectSignaturesAtIngestionOtherwiseWarnDecider);

            case EndCertificateUse.CodeSigning:
                return(MakeDeciderForRevokedCodeSigningCertificate(result.RevocationTime));

            default:
                throw new InvalidOperationException($"Revoked certificate has unknown use: {certificate.Use}");
            }
        }
Ejemplo n.º 6
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);
            }
        }
Ejemplo n.º 7
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));
        }
Ejemplo n.º 8
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());
        }