// Copyright (c) 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/cert/internal/revocation_checker.h" #include #include "base/strings/string_piece.h" #include "crypto/sha2.h" #include "net/cert/cert_net_fetcher.h" #include "net/cert/internal/common_cert_errors.h" #include "net/cert/internal/ocsp.h" #include "net/cert/internal/parsed_certificate.h" #include "net/cert/internal/trust_store.h" #include "net/cert/ocsp_verify_result.h" #include "url/gurl.h" namespace net { namespace { void MarkCertificateRevoked(CertErrors* errors) { // TODO(eroman): Add a parameter to the error indicating which mechanism // caused the revocation (i.e. CRLSet, OCSP, stapled OCSP, etc). errors->AddError(cert_errors::kCertificateRevoked); } // Checks the revocation status of |cert| according to |policy|. If the checks // failed, returns false and adds errors to |cert_errors|. // // TODO(eroman): Make the verification time an input. bool CheckCertRevocation(const ParsedCertificate* cert, const ParsedCertificate* issuer_cert, const RevocationPolicy& policy, base::StringPiece stapled_ocsp_response, base::TimeDelta max_age, CertNetFetcher* net_fetcher, CertErrors* cert_errors) { // Check using stapled OCSP, if available. if (!stapled_ocsp_response.empty() && issuer_cert) { // TODO(eroman): CheckOCSP() re-parses the certificates, perhaps just pass // ParsedCertificate into it. OCSPVerifyResult::ResponseStatus response_details; OCSPRevocationStatus ocsp_status = CheckOCSP(stapled_ocsp_response, cert->der_cert().AsStringPiece(), issuer_cert->der_cert().AsStringPiece(), base::Time::Now(), max_age, &response_details); // TODO(eroman): Save the stapled OCSP response to cache. switch (ocsp_status) { case OCSPRevocationStatus::REVOKED: MarkCertificateRevoked(cert_errors); return false; case OCSPRevocationStatus::GOOD: return true; case OCSPRevocationStatus::UNKNOWN: // TODO(eroman): If the OCSP response was invalid, should we keep // looking or fail? break; } } // TODO(eroman): Check CRL. if (!policy.check_revocation) { // TODO(eroman): Should still check CRL/OCSP caches. return true; } bool found_revocation_info = false; bool failed_network_fetch = false; // Check OCSP. if (cert->has_authority_info_access()) { // Try each of the OCSP URIs for (const auto& ocsp_uri : cert->ocsp_uris()) { // Only consider http:// URLs (https:// could create a circular // dependency). GURL parsed_ocsp_url(ocsp_uri); if (!parsed_ocsp_url.is_valid() || !parsed_ocsp_url.SchemeIs(url::kHttpScheme)) { continue; } found_revocation_info = true; if (!policy.networking_allowed) continue; if (!net_fetcher) { LOG(ERROR) << "Cannot fetch OCSP as didn't specify a |net_fetcher|"; continue; } // TODO(eroman): Duplication of work if there are multiple URLs to try. // TODO(eroman): Are there cases where we would need to POST instead? GURL get_url = CreateOCSPGetURL(cert, issuer_cert, ocsp_uri); if (!get_url.is_valid()) { // A failure here could mean an unexpected failure from BoringSSL, or a // problem concatenating the URL. continue; } // Fetch it over network. // // TODO(eroman): Issue POST instead of GET if request is larger than 255 // bytes? // TODO(eroman): Improve interplay with HTTP cache. // // TODO(eroman): Bound the maximum time allowed spent doing network // requests. std::unique_ptr net_ocsp_request = net_fetcher->FetchOcsp(get_url, CertNetFetcher::DEFAULT, CertNetFetcher::DEFAULT); Error net_error; std::vector ocsp_response_bytes; net_ocsp_request->WaitForResult(&net_error, &ocsp_response_bytes); if (net_error != OK) { failed_network_fetch = true; continue; } OCSPVerifyResult::ResponseStatus response_details; OCSPRevocationStatus ocsp_status = CheckOCSP( base::StringPiece( reinterpret_cast(ocsp_response_bytes.data()), ocsp_response_bytes.size()), cert->der_cert().AsStringPiece(), issuer_cert->der_cert().AsStringPiece(), base::Time::Now(), max_age, &response_details); switch (ocsp_status) { case OCSPRevocationStatus::REVOKED: MarkCertificateRevoked(cert_errors); return false; case OCSPRevocationStatus::GOOD: return true; case OCSPRevocationStatus::UNKNOWN: break; } } } // Reaching here means that revocation checking was inconclusive. Determine // whether failure to complete revocation checking constitutes an error. if (!found_revocation_info) { if (policy.allow_missing_info) { // If the certificate lacked any (recognized) revocation mechanisms, and // the policy permits it, consider revocation checking a success. return true; } else { // If the certificate lacked any (recognized) revocation mechanisms, and // the policy forbids it, fail revocation checking. cert_errors->AddError(cert_errors::kNoRevocationMechanism); return false; } } // In soft-fail mode permit failures due to network errors. // TODO(eroman): Add a warning to |cert_errors| indicating the failure. if (failed_network_fetch && policy.allow_network_failure) return true; // Otherwise the policy doesn't allow revocation checking to fail. cert_errors->AddError(cert_errors::kUnableToCheckRevocation); return false; } } // namespace // The default values specify a strict revocation checking mode, in case users // fail to fully set the parameters. RevocationPolicy::RevocationPolicy() : check_revocation(true), networking_allowed(false), allow_missing_info(false), allow_network_failure(false) {} void CheckCertChainRevocation(const ParsedCertificateList& certs, const CertificateTrust& last_cert_trust, const RevocationPolicy& policy, base::StringPiece stapled_leaf_ocsp_response, CertNetFetcher* net_fetcher, CertPathErrors* errors) { // Check each certificate for revocation using OCSP/CRL. Checks proceed // from the root certificate towards the leaf certificate. Revocation errors // are added to |errors|. for (size_t reverse_i = 0; reverse_i < certs.size(); ++reverse_i) { size_t i = certs.size() - reverse_i - 1; const ParsedCertificate* cert = certs[i].get(); const ParsedCertificate* issuer_cert = i + 1 < certs.size() ? certs[i + 1].get() : nullptr; // Trust anchors bypass OCSP/CRL revocation checks. (The only way to revoke // trust anchors is via CRLSet or the built-in SPKI blacklist). if (reverse_i == 0 && last_cert_trust.IsTrustAnchor()) continue; // TODO(eroman): Plumb stapled OCSP for non-leaf certificates from TLS? base::StringPiece stapled_ocsp = (i == 0) ? stapled_leaf_ocsp_response : base::StringPiece(); base::TimeDelta max_age = (i == 0) ? kMaxOCSPLeafUpdateAge : kMaxOCSPIntermediateUpdateAge; // Check whether this certificate's revocation status complies with the // policy. bool cert_ok = CheckCertRevocation(cert, issuer_cert, policy, stapled_ocsp, max_age, net_fetcher, errors->GetErrorsForCert(i)); if (!cert_ok) { // If any certificate in the chain fails revocation checks, the chain is // revoked and no need to check revocation status for the remaining // certificates. DCHECK(errors->GetErrorsForCert(i)->ContainsAnyErrorWithSeverity( CertError::SEVERITY_HIGH)); break; } } } CRLSet::Result CheckChainRevocationUsingCRLSet( const CRLSet* crl_set, const ParsedCertificateList& certs, CertPathErrors* errors) { // Iterate from the root certificate towards the leaf (the root certificate is // also checked for revocation by CRLSet). std::string issuer_spki_hash; for (size_t reverse_i = 0; reverse_i < certs.size(); ++reverse_i) { size_t i = certs.size() - reverse_i - 1; const ParsedCertificate* cert = certs[i].get(); // True if |cert| is the root of the chain. const bool is_root = reverse_i == 0; // True if |cert| is the leaf certificate of the chain. const bool is_target = i == 0; // Check for revocation using the certificate's SPKI. std::string spki_hash = crypto::SHA256HashString(cert->tbs().spki_tlv.AsStringPiece()); CRLSet::Result result = crl_set->CheckSPKI(spki_hash); // Check for revocation using the certificate's Subject. if (result != CRLSet::REVOKED) { result = crl_set->CheckSubject(cert->tbs().subject_tlv.AsStringPiece(), spki_hash); } // Check for revocation using the certificate's serial number and issuer's // SPKI. if (result != CRLSet::REVOKED && !is_root) { result = crl_set->CheckSerial(cert->tbs().serial_number.AsStringPiece(), issuer_spki_hash); } // Prepare for the next iteration. issuer_spki_hash = std::move(spki_hash); switch (result) { case CRLSet::REVOKED: MarkCertificateRevoked(errors->GetErrorsForCert(i)); return CRLSet::Result::REVOKED; case CRLSet::UNKNOWN: // If the status is unknown, advance to the subordinate certificate. break; case CRLSet::GOOD: if (is_target && !crl_set->IsExpired()) { // If the target is covered by the CRLSet and known good, consider // the entire chain to be valid (even though the revocation status // of the intermediates may have been UNKNOWN). // // Only the leaf certificate is considered for coverage because some // intermediates have CRLs with no revocations (after filtering) and // those CRLs are pruned from the CRLSet at generation time. return CRLSet::Result::GOOD; } break; } } // If no certificate was revoked, and the target was not known good, then // the revocation status is still unknown. return CRLSet::Result::UNKNOWN; } } // namespace net