Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(214)

Unified Diff: net/cert/internal/verify_certificate_chain.cc

Issue 2282183004: Add error information to VerifyCertificateChain(). (Closed)
Patch Set: Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698