Chromium Code Reviews| Index: net/cert/internal/verify_certificate_chain.cc |
| diff --git a/net/cert/internal/verify_certificate_chain.cc b/net/cert/internal/verify_certificate_chain.cc |
| index 4060bf0c12c50c0aac2dd137019bb1306890b80d..a309b6186e4321eae5ba8f935025fb7cd90f8bfe 100644 |
| --- a/net/cert/internal/verify_certificate_chain.cc |
| +++ b/net/cert/internal/verify_certificate_chain.cc |
| @@ -7,6 +7,7 @@ |
| #include <memory> |
| #include "base/logging.h" |
| +#include "net/cert/internal/cert_errors.h" |
| #include "net/cert/internal/name_constraints.h" |
| #include "net/cert/internal/parse_certificate.h" |
| #include "net/cert/internal/signature_algorithm.h" |
| @@ -18,17 +19,26 @@ |
| namespace net { |
| +using namespace verify_certificate_chain_errors; |
| + |
| namespace { |
| // Returns true if the certificate does not contain any unconsumed _critical_ |
| // extensions. |
| WARN_UNUSED_RESULT bool VerifyNoUnconsumedCriticalExtensions( |
| - const ParsedCertificate& cert) { |
| + const ParsedCertificate& cert, |
| + CertErrors* errors) { |
| + bool has_unconsumed_critical_extensions = false; |
| + |
| for (const auto& entry : cert.unparsed_extensions()) { |
| - if (entry.second.critical) |
| - return false; |
| + if (entry.second.critical) { |
| + has_unconsumed_critical_extensions = true; |
| + errors->AddWith2DerParams(kUnconsumedCriticalExtension, entry.second.oid, |
| + entry.second.value); |
| + } |
| } |
| - return true; |
| + |
| + return !has_unconsumed_critical_extensions; |
| } |
| // Returns true if |cert| was self-issued. The definition of self-issuance |
| @@ -54,9 +64,19 @@ WARN_UNUSED_RESULT bool IsSelfIssued(const ParsedCertificate& cert) { |
| // The validity period for a certificate is the period of time from |
| // notBefore through notAfter, inclusive. |
| WARN_UNUSED_RESULT bool VerifyTimeValidity(const ParsedCertificate& cert, |
| - const der::GeneralizedTime time) { |
| - return !(time < cert.tbs().validity_not_before) && |
| - !(cert.tbs().validity_not_after < time); |
| + const der::GeneralizedTime time, |
| + CertErrors* errors) { |
| + if (time < cert.tbs().validity_not_before) { |
| + errors->Add(kValidityFailedNotBefore); |
| + return false; |
| + } |
| + |
| + if (cert.tbs().validity_not_after < time) { |
| + errors->Add(kValidityFailedNotAfter); |
| + return false; |
| + } |
| + |
| + return true; |
| } |
| // Returns true if |signature_algorithm_tlv| is a valid algorithm encoding for |
| @@ -91,14 +111,27 @@ WARN_UNUSED_RESULT bool IsRsaWithSha1SignatureAlgorithm( |
| // specifying RSA with SHA1 (different OIDs). This is special-cased for |
| // compatibility sake. |
| WARN_UNUSED_RESULT bool VerifySignatureAlgorithmsMatch( |
| - const ParsedCertificate& cert) { |
| + const ParsedCertificate& cert, |
| + CertErrors* errors) { |
| const der::Input& alg1_tlv = cert.signature_algorithm_tlv(); |
| const der::Input& alg2_tlv = cert.tbs().signature_algorithm_tlv; |
| // Ensure that the two DER-encoded signature algorithms are byte-for-byte |
| - // equal, but make a compatibility concession for RSA with SHA1. |
| - return alg1_tlv == alg2_tlv || (IsRsaWithSha1SignatureAlgorithm(alg1_tlv) && |
| - IsRsaWithSha1SignatureAlgorithm(alg2_tlv)); |
| + // equal. |
| + if (alg1_tlv == alg2_tlv) |
| + return true; |
| + |
| + // But make a compatibility concession for RSA with SHA1. |
| + if (IsRsaWithSha1SignatureAlgorithm(alg1_tlv) && |
| + IsRsaWithSha1SignatureAlgorithm(alg2_tlv)) { |
| + errors->AddWith2DerParams(kSignatureAlgorithmsDifferentEncoding, alg1_tlv, |
| + alg2_tlv); |
| + return true; |
| + } |
| + |
| + errors->AddWith2DerParams(kSignatureAlgorithmMismatch, alg1_tlv, alg2_tlv); |
| + |
| + return false; |
| } |
| // This function corresponds to RFC 5280 section 6.1.3's "Basic Certificate |
| @@ -110,34 +143,43 @@ WARN_UNUSED_RESULT bool BasicCertificateProcessing( |
| const der::GeneralizedTime& time, |
| const der::Input& working_spki, |
| const der::Input& working_normalized_issuer_name, |
| - const std::vector<const NameConstraints*>& name_constraints_list) { |
| + const std::vector<const NameConstraints*>& name_constraints_list, |
| + CertErrors* errors) { |
| // Check that the signature algorithms in Certificate vs TBSCertificate |
| // match. This isn't part of RFC 5280 section 6.1.3, but is mandated by |
| // sections 4.1.1.2 and 4.1.2.3. |
| - if (!VerifySignatureAlgorithmsMatch(cert)) |
| + if (!VerifySignatureAlgorithmsMatch(cert, errors)) |
| return false; |
| // Verify the digital signature using the previous certificate's key (RFC |
| // 5280 section 6.1.3 step a.1). |
| - if (!cert.has_valid_supported_signature_algorithm() || |
| - !VerifySignedData(cert.signature_algorithm(), cert.tbs_certificate_tlv(), |
| + if (!cert.has_valid_supported_signature_algorithm()) { |
| + errors->AddWith1DerParam(kInvalidOrUnsupportedAlgorithm, |
| + cert.signature_algorithm_tlv()); |
| + return false; |
| + } |
| + |
| + if (!VerifySignedData(cert.signature_algorithm(), cert.tbs_certificate_tlv(), |
| cert.signature_value(), working_spki, |
| signature_policy)) { |
| + errors->Add(kSignatureVerificationFailed); |
| return false; |
| } |
| // Check the time range for the certificate's validity, ensuring it is valid |
| // at |time|. |
| // (RFC 5280 section 6.1.3 step a.2) |
| - if (!VerifyTimeValidity(cert, time)) |
| + if (!VerifyTimeValidity(cert, time, errors)) |
| return false; |
| // TODO(eroman): Check revocation (RFC 5280 section 6.1.3 step a.3) |
| // Verify the certificate's issuer name matches the issuing certificate's |
| // subject name. (RFC 5280 section 6.1.3 step a.4) |
| - if (cert.normalized_issuer() != working_normalized_issuer_name) |
| + if (cert.normalized_issuer() != working_normalized_issuer_name) { |
| + errors->Add(kSubjectDoesNotMatchIssuer); |
| return false; |
| + } |
| // Name constraints (RFC 5280 section 6.1.3 step b & c) |
| // If certificate i is self-issued and it is not the final certificate in the |
| @@ -147,6 +189,7 @@ WARN_UNUSED_RESULT bool BasicCertificateProcessing( |
| for (const NameConstraints* nc : name_constraints_list) { |
| if (!nc->IsPermittedCert(cert.normalized_subject(), |
| cert.subject_alt_names())) { |
| + errors->Add(kNotPermitterdByNameConstraints); |
| return false; |
| } |
| } |
| @@ -165,7 +208,8 @@ WARN_UNUSED_RESULT bool PrepareForNextCertificate( |
| size_t* max_path_length_ptr, |
| der::Input* working_spki, |
| der::Input* working_normalized_issuer_name, |
| - std::vector<const NameConstraints*>* name_constraints_list) { |
| + std::vector<const NameConstraints*>* name_constraints_list, |
| + CertErrors* errors) { |
| // TODO(crbug.com/634456): Steps a-b are omitted, as policy mappings are not |
| // yet implemented. |
| @@ -203,8 +247,15 @@ WARN_UNUSED_RESULT bool PrepareForNextCertificate( |
| // |
| // This code implicitly rejects non version 3 intermediates, since they |
| // can't contain a BasicConstraints extension. |
| - if (!cert.has_basic_constraints() || !cert.basic_constraints().is_ca) |
| + if (!cert.has_basic_constraints()) { |
| + errors->Add(kMissingBasicConstraints); |
| return false; |
| + } |
| + |
| + if (!cert.basic_constraints().is_ca) { |
| + errors->Add(kBasicConstraintsIndicatesNotCa); |
| + return false; |
| + } |
| // From RFC 5280 section 6.1.4 step l: |
| // |
| @@ -212,8 +263,10 @@ WARN_UNUSED_RESULT bool PrepareForNextCertificate( |
| // max_path_length is greater than zero and decrement |
| // max_path_length by 1. |
| if (!IsSelfIssued(cert)) { |
| - if (*max_path_length_ptr == 0) |
| + if (*max_path_length_ptr == 0) { |
| + errors->Add(kMaxPathLengthViolated); |
| return false; |
| + } |
| --(*max_path_length_ptr); |
| } |
| @@ -233,6 +286,7 @@ WARN_UNUSED_RESULT bool PrepareForNextCertificate( |
| // keyCertSign bit is set. |
| if (cert.has_key_usage() && |
| !cert.key_usage().AssertsBit(KEY_USAGE_BIT_KEY_CERT_SIGN)) { |
| + errors->Add(kKeyCertSignBitNotSet); |
| return false; |
| } |
| @@ -242,7 +296,7 @@ WARN_UNUSED_RESULT bool PrepareForNextCertificate( |
| // the certificate. Process any other recognized non-critical |
| // extension present in the certificate that is relevant to path |
| // processing. |
| - if (!VerifyNoUnconsumedCriticalExtensions(cert)) |
| + if (!VerifyNoUnconsumedCriticalExtensions(cert, errors)) |
| return false; |
| return true; |
| @@ -271,7 +325,8 @@ WARN_UNUSED_RESULT bool PrepareForNextCertificate( |
| // for compatibility reasons. Investigate if we need to similarly relax this |
| // constraint. |
| WARN_UNUSED_RESULT bool VerifyTargetCertHasConsistentCaBits( |
| - const ParsedCertificate& cert) { |
| + const ParsedCertificate& cert, |
| + CertErrors* errors) { |
| // Check if the certificate contains any property specific to CAs. |
| bool has_ca_property = |
| (cert.has_basic_constraints() && |
| @@ -283,9 +338,16 @@ WARN_UNUSED_RESULT bool VerifyTargetCertHasConsistentCaBits( |
| // If it "looks" like a CA because it has a CA-only property, then check that |
| // it sets ALL the properties expected of a CA. |
| if (has_ca_property) { |
| - return cert.has_basic_constraints() && cert.basic_constraints().is_ca && |
| - (!cert.has_key_usage() || |
| - cert.key_usage().AssertsBit(KEY_USAGE_BIT_KEY_CERT_SIGN)); |
| + bool success = cert.has_basic_constraints() && |
| + cert.basic_constraints().is_ca && |
| + (!cert.has_key_usage() || |
| + cert.key_usage().AssertsBit(KEY_USAGE_BIT_KEY_CERT_SIGN)); |
| + if (!success) { |
| + // TODO(eroman): Add DER for basic constraints and key usage. |
| + errors->Add(kTargetCertInconsistentCaBits); |
| + } |
| + |
| + return success; |
| } |
| return true; |
| @@ -293,7 +355,8 @@ WARN_UNUSED_RESULT bool VerifyTargetCertHasConsistentCaBits( |
| // This function corresponds with RFC 5280 section 6.1.5's "Wrap-Up Procedure". |
| // It does processing for the final certificate (the target cert). |
| -WARN_UNUSED_RESULT bool WrapUp(const ParsedCertificate& cert) { |
| +WARN_UNUSED_RESULT bool WrapUp(const ParsedCertificate& cert, |
| + CertErrors* errors) { |
| // TODO(crbug.com/634452): Steps a-b are omitted as policy constraints are not |
| // yet implemented. |
| @@ -309,7 +372,7 @@ WARN_UNUSED_RESULT bool WrapUp(const ParsedCertificate& cert) { |
| // |
| // Note that this is duplicated by PrepareForNextCertificate() so as to |
| // directly match the procedures in RFC 5280's section 6.1. |
| - if (!VerifyNoUnconsumedCriticalExtensions(cert)) |
| + if (!VerifyNoUnconsumedCriticalExtensions(cert, errors)) |
| return false; |
| // TODO(eroman): Step g is omitted, as policy constraints are not yet |
| @@ -317,7 +380,7 @@ WARN_UNUSED_RESULT bool WrapUp(const ParsedCertificate& cert) { |
| // The following check is NOT part of RFC 5280 6.1.5's "Wrap-Up Procedure", |
| // however is implied by RFC 5280 section 4.2.1.9. |
| - if (!VerifyTargetCertHasConsistentCaBits(cert)) |
| + if (!VerifyTargetCertHasConsistentCaBits(cert, errors)) |
| return false; |
| return true; |
| @@ -328,7 +391,11 @@ WARN_UNUSED_RESULT bool WrapUp(const ParsedCertificate& cert) { |
| WARN_UNUSED_RESULT bool ProcessTrustAnchorConstraints( |
| const TrustAnchor& trust_anchor, |
| size_t* max_path_length_ptr, |
| - std::vector<const NameConstraints*>* name_constraints_list) { |
| + std::vector<const NameConstraints*>* name_constraints_list, |
| + CertErrors* errors) { |
| + // Set the trust anchor as the current context for any subsequent errors. |
| + ScopedCertErrorsTrustAnchorContext error_context(errors, &trust_anchor); |
| + |
| // In RFC 5937 the enforcement of anchor constraints is governed by the input |
| // enforceTrustAnchorConstraints to path validation. In our implementation |
| // this is always on, and enforcement is controlled solely by whether or not |
| @@ -373,7 +440,7 @@ WARN_UNUSED_RESULT bool ProcessTrustAnchorConstraints( |
| // Extensions may be marked critical or not critical. When trust anchor |
| // constraints are enforced, clients MUST reject certification paths |
| // containing a trust anchor with unrecognized critical extensions. |
| - if (!VerifyNoUnconsumedCriticalExtensions(cert)) |
| + if (!VerifyNoUnconsumedCriticalExtensions(cert, errors)) |
| return false; |
| return true; |
| @@ -386,10 +453,15 @@ WARN_UNUSED_RESULT bool ProcessTrustAnchorConstraints( |
| bool VerifyCertificateChain(const ParsedCertificateList& certs, |
| const TrustAnchor* trust_anchor, |
| const SignaturePolicy* signature_policy, |
| - const der::GeneralizedTime& time) { |
| + const der::GeneralizedTime& time, |
| + CertErrors* errors) { |
| + DCHECK(trust_anchor); |
|
mattm
2016/08/29 22:15:11
DCHECK(errors) and signature_policy also?
eroman
2016/08/29 22:55:18
Done.
|
| + |
| // An empty chain is necessarily invalid. |
| - if (certs.empty()) |
| + if (certs.empty()) { |
| + errors->Add(kChainIsEmpty); |
| return false; |
| + } |
| // Will contain a NameConstraints for each previous cert in the chain which |
| // had nameConstraints. This corresponds to the permitted_subtrees and |
| @@ -432,7 +504,7 @@ bool VerifyCertificateChain(const ParsedCertificateList& certs, |
| // Apply any trust anchor constraints per RFC 5937. |
| if (!ProcessTrustAnchorConstraints(*trust_anchor, &max_path_length, |
| - &name_constraints_list)) { |
| + &name_constraints_list, errors)) { |
| return false; |
| } |
| @@ -454,6 +526,10 @@ bool VerifyCertificateChain(const ParsedCertificateList& certs, |
| const ParsedCertificate& cert = *certs[index_into_certs]; |
| + // Set the certificate anchor as the current context for any subsequent |
|
mattm
2016/08/29 22:15:11
Is "anchor" here a typo?
eroman
2016/08/29 22:55:18
Yes. Fixed.
|
| + // errors. |
| + ScopedCertErrorsCertContext error_context(errors, &cert, i); |
| + |
| // Per RFC 5280 section 6.1: |
| // * Do basic processing for each certificate |
| // * If it is the last certificate in the path (target certificate) |
| @@ -461,17 +537,17 @@ bool VerifyCertificateChain(const ParsedCertificateList& certs, |
| // - Otherwise run "Prepare for Next cert" |
| if (!BasicCertificateProcessing( |
| cert, is_target_cert, signature_policy, time, working_spki, |
| - working_normalized_issuer_name, name_constraints_list)) { |
| + working_normalized_issuer_name, name_constraints_list, errors)) { |
| return false; |
| } |
| if (!is_target_cert) { |
| if (!PrepareForNextCertificate(cert, &max_path_length, &working_spki, |
| &working_normalized_issuer_name, |
| - &name_constraints_list)) { |
| + &name_constraints_list, errors)) { |
| return false; |
| } |
| } else { |
| - if (!WrapUp(cert)) |
| + if (!WrapUp(cert, errors)) |
| return false; |
| } |
| } |
| @@ -484,4 +560,37 @@ bool VerifyCertificateChain(const ParsedCertificateList& certs, |
| return true; |
| } |
| +namespace verify_certificate_chain_errors { |
| + |
| +DEFINE_CERT_ERROR_TYPE( |
| + kSignatureAlgorithmMismatch, |
| + "Certificate.signatureAlgorithm != TBSCertificate.signature"); |
| +DEFINE_CERT_ERROR_TYPE(kInvalidOrUnsupportedAlgorithm, |
| + "Invalid or unsupported signature algorithm"); |
| +DEFINE_CERT_ERROR_TYPE(kChainIsEmpty, "Chain is empty"); |
| +DEFINE_CERT_ERROR_TYPE(kUnconsumedCriticalExtension, |
| + "Unconsumed critical extension"); |
| +DEFINE_CERT_ERROR_TYPE( |
| + kTargetCertInconsistentCaBits, |
| + "Target certificate looks like a CA but does not set all CA properties"); |
| +DEFINE_CERT_ERROR_TYPE(kKeyCertSignBitNotSet, "keyCertSign bit is not set"); |
| +DEFINE_CERT_ERROR_TYPE(kMaxPathLengthViolated, "max_path_length reached"); |
| +DEFINE_CERT_ERROR_TYPE(kBasicConstraintsIndicatesNotCa, |
| + "Basic Constraints indicates not a CA"); |
| +DEFINE_CERT_ERROR_TYPE(kMissingBasicConstraints, |
| + "Does not have Basic Constraints"); |
| +DEFINE_CERT_ERROR_TYPE(kNotPermitterdByNameConstraints, |
|
mattm
2016/08/29 22:15:11
s/Permitterd/Permitted/
eroman
2016/08/29 22:55:18
Good spot! Fixed.
|
| + "Not permitted by name constraints"); |
| +DEFINE_CERT_ERROR_TYPE(kSubjectDoesNotMatchIssuer, |
| + "subject does not match issuer"); |
| +DEFINE_CERT_ERROR_TYPE(kSignatureVerificationFailed, |
| + "Signature verification failed"); |
| +DEFINE_CERT_ERROR_TYPE(kValidityFailedNotAfter, "Time is after notAfter"); |
| +DEFINE_CERT_ERROR_TYPE(kValidityFailedNotBefore, "Time is before notBefore"); |
| +DEFINE_CERT_ERROR_TYPE(kSignatureAlgorithmsDifferentEncoding, |
| + "Certificate.signatureAlgorithm is encoded differently " |
| + "than TBSCertificate.signature"); |
| + |
| +} // verify_certificate_chain_errors |
| + |
| } // namespace net |