#include "net/cert/internal/revocation_checker.h"
#include <optional>
#include <string>
#include <string_view>
#include "base/logging.h"
#include "base/strings/string_view_util.h"
#include "crypto/sha2.h"
#include "net/cert/cert_net_fetcher.h"
#include "third_party/boringssl/src/include/openssl/pki/ocsp.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/parsed_certificate.h"
#include "third_party/boringssl/src/pki/trust_store.h"
#include "url/gurl.h"
namespace net {
namespace {
void MarkCertificateRevoked(bssl::CertErrors* errors) {
errors->AddError(bssl::cert_errors::kCertificateRevoked);
}
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<int64_t> max_age_seconds,
base::Time current_time,
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;
time_t time_now = current_time.ToTimeT();
if (!stapled_ocsp_response.empty() && issuer_cert) {
bssl::OCSPVerifyResult::ResponseStatus response_details;
bssl::OCSPRevocationStatus ocsp_status =
bssl::CheckOCSP(stapled_ocsp_response, cert, issuer_cert, time_now,
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;
}
switch (ocsp_status) {
case bssl::OCSPRevocationStatus::REVOKED:
MarkCertificateRevoked(cert_errors);
return false;
case bssl::OCSPRevocationStatus::GOOD:
return true;
case bssl::OCSPRevocationStatus::UNKNOWN:
break;
}
}
if (!policy.check_revocation) {
return true;
}
bool found_revocation_info = false;
if (cert->has_authority_info_access()) {
for (const auto& ocsp_uri : cert->ocsp_uris()) {
GURL parsed_ocsp_url(ocsp_uri);
if (!parsed_ocsp_url.is_valid() ||
!parsed_ocsp_url.SchemeIs(url::kHttpScheme)) {
continue;
}
found_revocation_info = true;
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;
}
std::optional<std::string> get_url_str =
CreateOCSPGetURL(cert, issuer_cert, ocsp_uri);
if (!get_url_str.has_value()) {
continue;
}
GURL get_url(get_url_str.value());
if (!get_url.is_valid()) {
continue;
}
std::unique_ptr<CertNetFetcher::Request> net_ocsp_request =
net_fetcher->FetchOcsp(get_url, CertNetFetcher::DEFAULT,
CertNetFetcher::DEFAULT);
Error net_error;
std::vector<uint8_t> 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<const char*>(ocsp_response_bytes.data()),
ocsp_response_bytes.size()),
cert, issuer_cert, time_now, 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;
}
}
}
bssl::ParsedExtension crl_dp_extension;
if (policy.crl_allowed &&
cert->GetExtension(bssl::der::Input(bssl::kCrlDistributionPointsOid),
&crl_dp_extension)) {
std::vector<bssl::ParsedDistributionPoint> distribution_points;
if (ParseCrlDistributionPoints(crl_dp_extension.value,
&distribution_points)) {
for (const auto& distribution_point : distribution_points) {
if (distribution_point.crl_issuer) {
continue;
}
if (distribution_point.reasons) {
continue;
}
if (!distribution_point.distribution_point_fullname) {
continue;
}
for (const auto& crl_uri :
distribution_point.distribution_point_fullname
->uniform_resource_identifiers) {
GURL parsed_crl_url(crl_uri);
if (!parsed_crl_url.is_valid() ||
!parsed_crl_url.SchemeIs(url::kHttpScheme)) {
continue;
}
found_revocation_info = true;
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;
}
std::unique_ptr<CertNetFetcher::Request> net_crl_request =
net_fetcher->FetchCrl(parsed_crl_url, CertNetFetcher::DEFAULT,
CertNetFetcher::DEFAULT);
Error net_error;
std::vector<uint8_t> 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<const char*>(crl_response_bytes.data()),
crl_response_bytes.size()),
certs, target_cert_index, distribution_point, time_now,
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;
}
}
}
}
}
if (!found_revocation_info) {
if (policy.allow_missing_info) {
return true;
} else {
cert_errors->AddError(bssl::cert_errors::kNoRevocationMechanism);
return false;
}
}
if (policy.allow_unable_to_check)
return true;
cert_errors->AddError(bssl::cert_errors::kUnableToCheckRevocation);
return false;
}
}
void CheckValidatedChainRevocation(
const bssl::ParsedCertificateList& certs,
const RevocationPolicy& policy,
base::TimeTicks deadline,
std::string_view stapled_leaf_ocsp_response,
base::Time current_time,
CertNetFetcher* net_fetcher,
bssl::CertPathErrors* errors,
bssl::OCSPVerifyResult* stapled_ocsp_verify_result) {
if (stapled_ocsp_verify_result)
*stapled_ocsp_verify_result = bssl::OCSPVerifyResult();
for (size_t reverse_i = 0; reverse_i < certs.size(); ++reverse_i) {
size_t i = certs.size() - reverse_i - 1;
if (reverse_i == 0)
continue;
std::string_view stapled_ocsp =
(i == 0) ? stapled_leaf_ocsp_response : std::string_view();
std::optional<int64_t> max_age_seconds;
if (policy.enforce_baseline_requirements) {
max_age_seconds = ((i == 0) ? kMaxRevocationLeafUpdateAge
: kMaxRevocationIntermediateUpdateAge)
.InSeconds();
}
bool cert_ok = CheckCertRevocation(
certs, i, policy, deadline, stapled_ocsp, max_age_seconds, current_time,
net_fetcher, errors->GetErrorsForCert(i),
(i == 0) ? stapled_ocsp_verify_result : nullptr);
if (!cert_ok) {
DCHECK(errors->GetErrorsForCert(i)->ContainsAnyErrorWithSeverity(
bssl::CertError::SEVERITY_HIGH));
break;
}
}
}
CRLSet::Result CheckChainRevocationUsingCRLSet(
const CRLSet* crl_set,
const bssl::ParsedCertificateList& certs,
bssl::CertPathErrors* errors) {
std::array<uint8_t, 32> 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();
const bool is_root = reverse_i == 0;
const bool is_target = i == 0;
std::array<uint8_t, 32> spki_hash =
crypto::SHA256Hash(cert->tbs().spki_tlv);
CRLSet::Result result = crl_set->CheckSPKI(base::as_string_view(spki_hash));
if (result != CRLSet::REVOKED) {
result =
crl_set->CheckSubject(base::as_string_view(cert->tbs().subject_tlv),
base::as_string_view(spki_hash));
}
if (result != CRLSet::REVOKED && !is_root) {
result = crl_set->CheckSerial(cert->tbs().serial_number,
base::as_string_view(issuer_spki_hash));
}
issuer_spki_hash = spki_hash;
switch (result) {
case CRLSet::REVOKED:
MarkCertificateRevoked(errors->GetErrorsForCert(i));
return CRLSet::Result::REVOKED;
case CRLSet::UNKNOWN:
break;
case CRLSet::GOOD:
if (is_target && !crl_set->IsExpired()) {
return CRLSet::Result::GOOD;
}
break;
}
}
return CRLSet::Result::UNKNOWN;
}
}