Index: net/cert/cert_verify_proc.cc |
diff --git a/net/cert/cert_verify_proc.cc b/net/cert/cert_verify_proc.cc |
index 7ce4eae4d89e22632f903e3a9e7dc5bc144710df..da4968850ad0f8621ddeab9bfac5191a2a7b2e2b 100644 |
--- a/net/cert/cert_verify_proc.cc |
+++ b/net/cert/cert_verify_proc.cc |
@@ -25,6 +25,7 @@ |
#include "net/cert/cert_verify_result.h" |
#include "net/cert/crl_set.h" |
#include "net/cert/internal/parse_ocsp.h" |
+#include "net/cert/internal/signature_algorithm.h" |
#include "net/cert/ocsp_revocation_status.h" |
#include "net/cert/x509_certificate.h" |
#include "net/der/encode_values.h" |
@@ -372,64 +373,129 @@ bool AreSHA1IntermediatesAllowed() { |
// Sets the "has_*" boolean members in |verify_result| that correspond with |
// the the presence of |hash| somewhere in the certificate chain (excluding the |
// trust anchor). |
-void MapHashAlgorithmToBool(X509Certificate::SignatureHashAlgorithm hash, |
- CertVerifyResult* verify_result) { |
+void MapAlgorithmToBool(DigestAlgorithm hash, CertVerifyResult* verify_result) { |
switch (hash) { |
- case X509Certificate::kSignatureHashAlgorithmMd2: |
+ case DigestAlgorithm::Md2: |
verify_result->has_md2 = true; |
break; |
- case X509Certificate::kSignatureHashAlgorithmMd4: |
+ case DigestAlgorithm::Md4: |
verify_result->has_md4 = true; |
break; |
- case X509Certificate::kSignatureHashAlgorithmMd5: |
+ case DigestAlgorithm::Md5: |
verify_result->has_md5 = true; |
break; |
- case X509Certificate::kSignatureHashAlgorithmSha1: |
+ case DigestAlgorithm::Sha1: |
verify_result->has_sha1 = true; |
break; |
- case X509Certificate::kSignatureHashAlgorithmOther: |
+ case DigestAlgorithm::Sha256: |
+ case DigestAlgorithm::Sha384: |
+ case DigestAlgorithm::Sha512: |
break; |
} |
} |
-// Sets to true the |verify_result->has_*| boolean members for the hash |
-// algorithms present in the certificate chain. |
+// Inspects the signature algorithms in a single certificate |cert|. |
// |
-// This considers the hash algorithms in all certificates except trusted |
-// certificates. |
+// * Sets |verify_result->has_md2| to true if the certificate uses MD2. |
+// * Sets |verify_result->has_md4| to true if the certificate uses MD4. |
+// * Sets |verify_result->has_md5| to true if the certificate uses MD5. |
+// * Sets |verify_result->has_sha1| to true if the certificate uses SHA1. |
// |
-// In the case of a successful verification the trust anchor is the final |
-// certificate in the chain (either the final intermediate, or the leaf |
-// certificate). |
+// Returns false if the signature algorithm was unknown or mismatched. |
+WARN_UNUSED_RESULT bool InspectSignatureAlgorithmForCert( |
+ X509Certificate::OSCertHandle cert, |
+ CertVerifyResult* verify_result) { |
+ std::string cert_der; |
+ base::StringPiece cert_algorithm_sequence; |
+ base::StringPiece tbs_algorithm_sequence; |
+ |
+ // Extract the AlgorithmIdentifier SEQUENCEs |
+ if (!X509Certificate::GetDEREncoded(cert, &cert_der) || |
+ !asn1::ExtractSignatureAlgorithmsFromDERCert( |
+ cert_der, &cert_algorithm_sequence, &tbs_algorithm_sequence)) { |
+ return false; |
+ } |
+ |
+ if (!SignatureAlgorithm::IsEquivalent(der::Input(cert_algorithm_sequence), |
+ der::Input(tbs_algorithm_sequence))) { |
+ return false; |
+ } |
+ |
+ std::unique_ptr<SignatureAlgorithm> algorithm = |
+ SignatureAlgorithm::Create(der::Input(cert_algorithm_sequence), nullptr); |
+ if (!algorithm) |
+ return false; |
+ |
+ MapAlgorithmToBool(algorithm->digest(), verify_result); |
+ |
+ // Check algorithm-specific parameters. |
+ switch (algorithm->algorithm()) { |
+ case SignatureAlgorithmId::RsaPkcs1: |
+ case SignatureAlgorithmId::Ecdsa: |
+ DCHECK(!algorithm->has_params()); |
+ break; |
+ case SignatureAlgorithmId::RsaPss: |
+ MapAlgorithmToBool(algorithm->ParamsForRsaPss()->mgf1_hash(), |
+ verify_result); |
+ break; |
+ } |
+ |
+ return true; |
+} |
+ |
+// InspectSignatureAlgorithmsInChain() sets |verify_result->has_*| based on |
+// the signature algorithms used in the chain, and also checks that certificates |
+// don't have contradictory signature algorithms. |
// |
-// Whereas if verification was uncessful, the chain may be partial, and the |
-// final certificate may not be a trust anchor. This heuristic is used |
-// in both successful and failed verifications, despite this ambiguity (failure |
-// to tag one of the signature algorithms should only affect the final error). |
-void ComputeSignatureHashAlgorithms(CertVerifyResult* verify_result) { |
+// Returns false if any signature algorithm in the chain is unknown or |
+// mismatched. |
+// |
+// Background: |
+// |
+// X.509 certificates contain two redundant descriptors for the signature |
+// algorithm; one is covered by the signature, but in order to verify the |
+// signature, the other signature algorithm is untrusted. |
+// |
+// RFC 5280 states that the two should be equal, in order to mitigate risk of |
+// signature substitution attacks, but also discourages verifiers from enforcing |
+// the profile of RFC 5280. |
+// |
+// System verifiers are inconsistent - some use the unsigned signature, some use |
+// the signed signature, and they generally do not enforce that both match. This |
+// creates confusion, as it's possible that the signature itself may be checked |
+// using algorithm A, but if subsequent consumers report the certificate |
+// algorithm, they may end up reporting algorithm B, which was not used to |
+// verify the certificate. This function enforces that the two signatures match |
+// in order to prevent such confusion. |
+WARN_UNUSED_RESULT bool InspectSignatureAlgorithmsInChain( |
+ CertVerifyResult* verify_result) { |
const X509Certificate::OSCertHandles& intermediates = |
verify_result->verified_cert->GetIntermediateCertificates(); |
// If there are no intermediates, then the leaf is trusted or verification |
// failed. |
if (intermediates.empty()) |
- return; |
+ return true; |
DCHECK(!verify_result->has_sha1); |
// Fill in hash algorithms for the leaf certificate. |
- MapHashAlgorithmToBool(X509Certificate::GetSignatureHashAlgorithm( |
- verify_result->verified_cert->os_cert_handle()), |
- verify_result); |
+ if (!InspectSignatureAlgorithmForCert( |
+ verify_result->verified_cert->os_cert_handle(), verify_result)) { |
+ return false; |
+ } |
+ |
verify_result->has_sha1_leaf = verify_result->has_sha1; |
// Fill in hash algorithms for the intermediate cerificates, excluding the |
- // final one (which is the trust anchor). |
+ // final one (which is presumably the trust anchor; may be incorrect for |
+ // partial chains). |
for (size_t i = 0; i + 1 < intermediates.size(); ++i) { |
- MapHashAlgorithmToBool( |
- X509Certificate::GetSignatureHashAlgorithm(intermediates[i]), |
- verify_result); |
+ if (!InspectSignatureAlgorithmForCert(intermediates[i], verify_result)) |
+ return false; |
} |
+ |
+ return true; |
} |
} // namespace |
@@ -483,7 +549,13 @@ int CertVerifyProc::Verify(X509Certificate* cert, |
int rv = VerifyInternal(cert, hostname, ocsp_response, flags, crl_set, |
additional_trust_anchors, verify_result); |
- ComputeSignatureHashAlgorithms(verify_result); |
+ // Check for mismatched signature algorithms and unknown signature algorithms |
+ // in the chain. Also fills in the has_* booleans for the digest algorithms |
+ // present in the chain. |
+ if (!InspectSignatureAlgorithmsInChain(verify_result)) { |
+ verify_result->cert_status |= CERT_STATUS_INVALID; |
+ rv = MapCertStatusToNetError(verify_result->cert_status); |
+ } |
bool allow_common_name_fallback = |
!verify_result->is_issued_by_known_root && |