// Copyright 2017 The Chromium Authors // 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 #include #include "base/logging.h" #include "crypto/sha2.h" #include "net/cert/cert_net_fetcher.h" #include "third_party/boringssl/src/pki/common_cert_errors.h" #include "third_party/boringssl/src/pki/crl.h" #include "third_party/boringssl/src/pki/ocsp.h" #include "third_party/boringssl/src/pki/ocsp_verify_result.h" #include "third_party/boringssl/src/pki/parsed_certificate.h" #include "third_party/boringssl/src/pki/trust_store.h" #include "url/gurl.h" namespace net { namespace { void MarkCertificateRevoked(bssl::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(bssl::cert_errors::kCertificateRevoked); } // Checks the revocation status of |certs[target_cert_index]| 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 bssl::ParsedCertificateList& certs, size_t target_cert_index, const RevocationPolicy& policy, base::TimeTicks deadline, std::string_view stapled_ocsp_response, std::optional max_age_seconds, CertNetFetcher* net_fetcher, bssl::CertErrors* cert_errors, bssl::OCSPVerifyResult* stapled_ocsp_verify_result) { DCHECK_LT(target_cert_index, certs.size()); const bssl::ParsedCertificate* cert = certs[target_cert_index].get(); const bssl::ParsedCertificate* issuer_cert = target_cert_index + 1 < certs.size() ? certs[target_cert_index + 1].get() : nullptr; // Check using stapled OCSP, if available. if (!stapled_ocsp_response.empty() && issuer_cert) { bssl::OCSPVerifyResult::ResponseStatus response_details; bssl::OCSPRevocationStatus ocsp_status = bssl::CheckOCSP( stapled_ocsp_response, cert, issuer_cert, base::Time::Now().ToTimeT(), max_age_seconds, &response_details); if (stapled_ocsp_verify_result) { stapled_ocsp_verify_result->response_status = response_details; stapled_ocsp_verify_result->revocation_status = ocsp_status; } // TODO(eroman): Save the stapled OCSP response to cache. switch (ocsp_status) { case bssl::OCSPRevocationStatus::REVOKED: MarkCertificateRevoked(cert_errors); return false; case bssl::OCSPRevocationStatus::GOOD: return true; case bssl::OCSPRevocationStatus::UNKNOWN: // TODO(eroman): If the OCSP response was invalid, should we keep // looking or fail? break; } } if (!policy.check_revocation) { // TODO(eroman): Should still check CRL/OCSP caches. return true; } bool found_revocation_info = 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; // Check the deadline after setting found_revocation_info, to not give a // misleading kNoRevocationMechanism failure. if (!deadline.is_null() && base::TimeTicks::Now() > deadline) break; 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? std::optional get_url_str = CreateOCSPGetURL(cert, issuer_cert, ocsp_uri); if (!get_url_str.has_value()) { // An unexpected failure from BoringSSL, or the input was too large to // base64-encode. continue; } GURL get_url(get_url_str.value()); if (!get_url.is_valid()) { // Invalid 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. 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) continue; bssl::OCSPVerifyResult::ResponseStatus response_details; bssl::OCSPRevocationStatus ocsp_status = bssl::CheckOCSP( std::string_view( reinterpret_cast(ocsp_response_bytes.data()), ocsp_response_bytes.size()), cert, issuer_cert, base::Time::Now().ToTimeT(), max_age_seconds, &response_details); switch (ocsp_status) { case bssl::OCSPRevocationStatus::REVOKED: MarkCertificateRevoked(cert_errors); return false; case bssl::OCSPRevocationStatus::GOOD: return true; case bssl::OCSPRevocationStatus::UNKNOWN: break; } } } // Check CRLs. bssl::ParsedExtension crl_dp_extension; if (policy.crl_allowed && cert->GetExtension(bssl::der::Input(bssl::kCrlDistributionPointsOid), &crl_dp_extension)) { std::vector distribution_points; if (ParseCrlDistributionPoints(crl_dp_extension.value, &distribution_points)) { for (const auto& distribution_point : distribution_points) { if (distribution_point.crl_issuer) { // Ignore indirect CRLs (CRL where CRLissuer != cert issuer), which // are optional according to RFC 5280's profile. continue; } if (distribution_point.reasons) { // Ignore CRLs that only contain some reasons. RFC 5280's profile // requires that conforming CAs "MUST include at least one // DistributionPoint that points to a CRL that covers the certificate // for all reasons". continue; } if (!distribution_point.distribution_point_fullname) { // Only distributionPoints with a fullName containing URIs are // supported. continue; } for (const auto& crl_uri : distribution_point.distribution_point_fullname ->uniform_resource_identifiers) { // Only consider http:// URLs (https:// could create a circular // dependency). GURL parsed_crl_url(crl_uri); if (!parsed_crl_url.is_valid() || !parsed_crl_url.SchemeIs(url::kHttpScheme)) { continue; } found_revocation_info = true; // Check the deadline after setting found_revocation_info, to not give // a misleading kNoRevocationMechanism failure. if (!deadline.is_null() && base::TimeTicks::Now() > deadline) break; if (!policy.networking_allowed) continue; if (!net_fetcher) { LOG(ERROR) << "Cannot fetch CRL as didn't specify a |net_fetcher|"; continue; } // Fetch it over network. // // Note that no attempt is made to refetch without cache if a cached // CRL is too old, nor is there a separate CRL cache. It is assumed // the CRL server will send reasonable HTTP caching headers. std::unique_ptr net_crl_request = net_fetcher->FetchCrl(parsed_crl_url, CertNetFetcher::DEFAULT, CertNetFetcher::DEFAULT); Error net_error; std::vector crl_response_bytes; net_crl_request->WaitForResult(&net_error, &crl_response_bytes); if (net_error != OK) continue; bssl::CRLRevocationStatus crl_status = CheckCRL( std::string_view( reinterpret_cast(crl_response_bytes.data()), crl_response_bytes.size()), certs, target_cert_index, distribution_point, base::Time::Now().ToTimeT(), max_age_seconds); switch (crl_status) { case bssl::CRLRevocationStatus::REVOKED: MarkCertificateRevoked(cert_errors); return false; case bssl::CRLRevocationStatus::GOOD: return true; case bssl::CRLRevocationStatus::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(bssl::cert_errors::kNoRevocationMechanism); return false; } } // In soft-fail mode permit other failures. // TODO(eroman): Add a warning to |cert_errors| indicating the failure. if (policy.allow_unable_to_check) return true; // Otherwise the policy doesn't allow revocation checking to fail. cert_errors->AddError(bssl::cert_errors::kUnableToCheckRevocation); return false; } } // namespace void CheckValidatedChainRevocation( const bssl::ParsedCertificateList& certs, const RevocationPolicy& policy, base::TimeTicks deadline, std::string_view stapled_leaf_ocsp_response, CertNetFetcher* net_fetcher, bssl::CertPathErrors* errors, bssl::OCSPVerifyResult* stapled_ocsp_verify_result) { if (stapled_ocsp_verify_result) *stapled_ocsp_verify_result = bssl::OCSPVerifyResult(); // 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; // Trust anchors bypass OCSP/CRL revocation checks. (The only way to revoke // trust anchors is via CRLSet or the built-in SPKI block list). Since // |certs| must be a validated chain, the final cert must be a trust // anchor. if (reverse_i == 0) continue; // TODO(eroman): Plumb stapled OCSP for non-leaf certificates from TLS? std::string_view stapled_ocsp = (i == 0) ? stapled_leaf_ocsp_response : std::string_view(); std::optional max_age_seconds; if (policy.enforce_baseline_requirements) { max_age_seconds = ((i == 0) ? kMaxRevocationLeafUpdateAge : kMaxRevocationIntermediateUpdateAge) .InSeconds(); } // Check whether this certificate's revocation status complies with the // policy. bool cert_ok = CheckCertRevocation( certs, i, policy, deadline, stapled_ocsp, max_age_seconds, net_fetcher, errors->GetErrorsForCert(i), (i == 0) ? stapled_ocsp_verify_result : nullptr); 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( bssl::CertError::SEVERITY_HIGH)); break; } } } CRLSet::Result CheckChainRevocationUsingCRLSet( const CRLSet* crl_set, const bssl::ParsedCertificateList& certs, bssl::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 bssl::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.AsStringView()); 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.AsStringView(), 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.AsStringView(), 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