ring/pki/ocsp.cc
Bob Beck bc97b7a8e1 Bring in the core of chromium certificate verifier as libpki
Initially this leaves the canonical source in chrome, Additions
and fillins are committed directly, the chrome files are coverted
using the IMPORT script run from the pki directory for the moment.

The intention here is to continue frequent automatic conversion
(and avoid wholesale cosmetic changes in here for now) until
chrome converts to use these files in place of it's versions.
At that point these will become the definiative files, and the
IMPORT script can be tossed out.

A middle step along the way will be to change google3's verify.cc
in third_party/chromium_certificate_verifier to use this instead
of it's own extracted copy.

Status (and what is not done yet) being roughly tracked in README.md

Bug: chromium:1322914

Change-Id: Ibdb5479bc68985fa61ce6b10f98f31f6b3a7cbdf
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60285
Commit-Queue: Bob Beck <bbe@google.com>
Reviewed-by: Adam Langley <agl@google.com>
2023-06-22 19:34:39 +00:00

1051 lines
36 KiB
C++

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "webutil/url/url.h"
#include "ocsp.h"
#include "asn1_util.h"
#include "cert_errors.h"
#include "extended_key_usage.h"
#include "parsed_certificate.h"
#include "revocation_util.h"
#include "string_util.h"
#include "verify_name_match.h"
#include "verify_signed_data.h"
#include "fillins/x509_util.h"
#include <openssl/bytestring.h>
#include <openssl/digest.h>
#include <openssl/mem.h>
#include <openssl/sha.h>
#include "webutil/url/url.h"
namespace bssl {
OCSPCertID::OCSPCertID() = default;
OCSPCertID::~OCSPCertID() = default;
OCSPSingleResponse::OCSPSingleResponse() = default;
OCSPSingleResponse::~OCSPSingleResponse() = default;
OCSPResponseData::OCSPResponseData() = default;
OCSPResponseData::~OCSPResponseData() = default;
OCSPResponse::OCSPResponse() = default;
OCSPResponse::~OCSPResponse() = default;
// CertID ::= SEQUENCE {
// hashAlgorithm AlgorithmIdentifier,
// issuerNameHash OCTET STRING, -- Hash of issuer's DN
// issuerKeyHash OCTET STRING, -- Hash of issuer's public key
// serialNumber CertificateSerialNumber
// }
bool ParseOCSPCertID(const der::Input& raw_tlv, OCSPCertID* out) {
der::Parser outer_parser(raw_tlv);
der::Parser parser;
if (!outer_parser.ReadSequence(&parser))
return false;
if (outer_parser.HasMore())
return false;
der::Input sigalg_tlv;
if (!parser.ReadRawTLV(&sigalg_tlv))
return false;
if (!ParseHashAlgorithm(sigalg_tlv, &(out->hash_algorithm)))
return false;
if (!parser.ReadTag(der::kOctetString, &(out->issuer_name_hash)))
return false;
if (!parser.ReadTag(der::kOctetString, &(out->issuer_key_hash)))
return false;
if (!parser.ReadTag(der::kInteger, &(out->serial_number)))
return false;
CertErrors errors;
if (!VerifySerialNumber(out->serial_number, false /*warnings_only*/, &errors))
return false;
return !parser.HasMore();
}
namespace {
// Parses |raw_tlv| to extract an OCSP RevokedInfo (RFC 6960) and stores the
// result in the OCSPCertStatus |out|. Returns whether the parsing was
// successful.
//
// RevokedInfo ::= SEQUENCE {
// revocationTime GeneralizedTime,
// revocationReason [0] EXPLICIT CRLReason OPTIONAL
// }
bool ParseRevokedInfo(const der::Input& raw_tlv, OCSPCertStatus* out) {
der::Parser parser(raw_tlv);
if (!parser.ReadGeneralizedTime(&(out->revocation_time)))
return false;
der::Input reason_input;
if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(0), &reason_input,
&(out->has_reason))) {
return false;
}
if (out->has_reason) {
der::Parser reason_parser(reason_input);
der::Input reason_value_input;
uint8_t reason_value;
if (!reason_parser.ReadTag(der::kEnumerated, &reason_value_input))
return false;
if (!der::ParseUint8(reason_value_input, &reason_value))
return false;
if (reason_value >
static_cast<uint8_t>(OCSPCertStatus::RevocationReason::LAST)) {
return false;
}
out->revocation_reason =
static_cast<OCSPCertStatus::RevocationReason>(reason_value);
if (out->revocation_reason == OCSPCertStatus::RevocationReason::UNUSED)
return false;
if (reason_parser.HasMore())
return false;
}
return !parser.HasMore();
}
// Parses |raw_tlv| to extract an OCSP CertStatus (RFC 6960) and stores the
// result in the OCSPCertStatus |out|. Returns whether the parsing was
// successful.
//
// CertStatus ::= CHOICE {
// good [0] IMPLICIT NULL,
// revoked [1] IMPLICIT RevokedInfo,
// unknown [2] IMPLICIT UnknownInfo
// }
//
// UnknownInfo ::= NULL
bool ParseCertStatus(const der::Input& raw_tlv, OCSPCertStatus* out) {
der::Parser parser(raw_tlv);
der::Tag status_tag;
der::Input status;
if (!parser.ReadTagAndValue(&status_tag, &status))
return false;
out->has_reason = false;
if (status_tag == der::ContextSpecificPrimitive(0)) {
out->status = OCSPRevocationStatus::GOOD;
} else if (status_tag == der::ContextSpecificConstructed(1)) {
out->status = OCSPRevocationStatus::REVOKED;
if (!ParseRevokedInfo(status, out))
return false;
} else if (status_tag == der::ContextSpecificPrimitive(2)) {
out->status = OCSPRevocationStatus::UNKNOWN;
} else {
return false;
}
return !parser.HasMore();
}
// Writes the hash of |value| as an OCTET STRING to |cbb|, using |hash_type| as
// the algorithm. Returns true on success.
bool AppendHashAsOctetString(const EVP_MD* hash_type,
CBB* cbb,
const der::Input& value) {
CBB octet_string;
unsigned hash_len;
uint8_t hash_buffer[EVP_MAX_MD_SIZE];
return CBB_add_asn1(cbb, &octet_string, CBS_ASN1_OCTETSTRING) &&
EVP_Digest(value.UnsafeData(), value.Length(), hash_buffer, &hash_len,
hash_type, nullptr) &&
CBB_add_bytes(&octet_string, hash_buffer, hash_len) && CBB_flush(cbb);
}
} // namespace
// SingleResponse ::= SEQUENCE {
// certID CertID,
// certStatus CertStatus,
// thisUpdate GeneralizedTime,
// nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
// singleExtensions [1] EXPLICIT Extensions OPTIONAL
// }
bool ParseOCSPSingleResponse(const der::Input& raw_tlv,
OCSPSingleResponse* out) {
der::Parser outer_parser(raw_tlv);
der::Parser parser;
if (!outer_parser.ReadSequence(&parser))
return false;
if (outer_parser.HasMore())
return false;
if (!parser.ReadRawTLV(&(out->cert_id_tlv)))
return false;
der::Input status_tlv;
if (!parser.ReadRawTLV(&status_tlv))
return false;
if (!ParseCertStatus(status_tlv, &(out->cert_status)))
return false;
if (!parser.ReadGeneralizedTime(&(out->this_update)))
return false;
der::Input next_update_input;
if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(0),
&next_update_input, &(out->has_next_update))) {
return false;
}
if (out->has_next_update) {
der::Parser next_update_parser(next_update_input);
if (!next_update_parser.ReadGeneralizedTime(&(out->next_update)))
return false;
if (next_update_parser.HasMore())
return false;
}
if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(1),
&(out->extensions), &(out->has_extensions))) {
return false;
}
return !parser.HasMore();
}
namespace {
// Parses |raw_tlv| to extract a ResponderID (RFC 6960) and stores the
// result in the ResponderID |out|. Returns whether the parsing was successful.
//
// ResponderID ::= CHOICE {
// byName [1] Name,
// byKey [2] KeyHash
// }
bool ParseResponderID(const der::Input& raw_tlv,
OCSPResponseData::ResponderID* out) {
der::Parser parser(raw_tlv);
der::Tag id_tag;
der::Input id_input;
if (!parser.ReadTagAndValue(&id_tag, &id_input))
return false;
if (id_tag == der::ContextSpecificConstructed(1)) {
out->type = OCSPResponseData::ResponderType::NAME;
out->name = id_input;
} else if (id_tag == der::ContextSpecificConstructed(2)) {
der::Parser key_parser(id_input);
der::Input key_hash;
if (!key_parser.ReadTag(der::kOctetString, &key_hash))
return false;
if (key_parser.HasMore())
return false;
if (key_hash.Length() != SHA_DIGEST_LENGTH)
return false;
out->type = OCSPResponseData::ResponderType::KEY_HASH;
out->key_hash = key_hash;
} else {
return false;
}
return !parser.HasMore();
}
} // namespace
// ResponseData ::= SEQUENCE {
// version [0] EXPLICIT Version DEFAULT v1,
// responderID ResponderID,
// producedAt GeneralizedTime,
// responses SEQUENCE OF SingleResponse,
// responseExtensions [1] EXPLICIT Extensions OPTIONAL
// }
bool ParseOCSPResponseData(const der::Input& raw_tlv, OCSPResponseData* out) {
der::Parser outer_parser(raw_tlv);
der::Parser parser;
if (!outer_parser.ReadSequence(&parser))
return false;
if (outer_parser.HasMore())
return false;
der::Input version_input;
bool version_present;
if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(0),
&version_input, &version_present)) {
return false;
}
// For compatibilty, we ignore the restriction from X.690 Section 11.5 that
// DEFAULT values should be omitted for values equal to the default value.
// TODO: Add warning about non-strict parsing.
if (version_present) {
der::Parser version_parser(version_input);
if (!version_parser.ReadUint8(&(out->version)))
return false;
if (version_parser.HasMore())
return false;
} else {
out->version = 0;
}
if (out->version != 0)
return false;
der::Input responder_input;
if (!parser.ReadRawTLV(&responder_input))
return false;
if (!ParseResponderID(responder_input, &(out->responder_id)))
return false;
if (!parser.ReadGeneralizedTime(&(out->produced_at)))
return false;
der::Parser responses_parser;
if (!parser.ReadSequence(&responses_parser))
return false;
out->responses.clear();
while (responses_parser.HasMore()) {
der::Input single_response;
if (!responses_parser.ReadRawTLV(&single_response))
return false;
out->responses.push_back(single_response);
}
if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(1),
&(out->extensions), &(out->has_extensions))) {
return false;
}
return !parser.HasMore();
}
namespace {
// Parses |raw_tlv| to extract a BasicOCSPResponse (RFC 6960) and stores the
// result in the OCSPResponse |out|. Returns whether the parsing was
// successful.
//
// BasicOCSPResponse ::= SEQUENCE {
// tbsResponseData ResponseData,
// signatureAlgorithm AlgorithmIdentifier,
// signature BIT STRING,
// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL
// }
bool ParseBasicOCSPResponse(const der::Input& raw_tlv, OCSPResponse* out) {
der::Parser outer_parser(raw_tlv);
der::Parser parser;
if (!outer_parser.ReadSequence(&parser))
return false;
if (outer_parser.HasMore())
return false;
if (!parser.ReadRawTLV(&(out->data)))
return false;
der::Input sigalg_tlv;
if (!parser.ReadRawTLV(&sigalg_tlv))
return false;
// TODO(crbug.com/634443): Propagate the errors.
std::optional<SignatureAlgorithm> sigalg =
ParseSignatureAlgorithm(sigalg_tlv);
if (!sigalg)
return false;
out->signature_algorithm = sigalg.value();
std::optional<der::BitString> signature = parser.ReadBitString();
if (!signature)
return false;
out->signature = signature.value();
der::Input certs_input;
if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(0), &certs_input,
&(out->has_certs))) {
return false;
}
out->certs.clear();
if (out->has_certs) {
der::Parser certs_seq_parser(certs_input);
der::Parser certs_parser;
if (!certs_seq_parser.ReadSequence(&certs_parser))
return false;
if (certs_seq_parser.HasMore())
return false;
while (certs_parser.HasMore()) {
der::Input cert_tlv;
if (!certs_parser.ReadRawTLV(&cert_tlv))
return false;
out->certs.push_back(cert_tlv);
}
}
return !parser.HasMore();
}
} // namespace
// OCSPResponse ::= SEQUENCE {
// responseStatus OCSPResponseStatus,
// responseBytes [0] EXPLICIT ResponseBytes OPTIONAL
// }
//
// ResponseBytes ::= SEQUENCE {
// responseType OBJECT IDENTIFIER,
// response OCTET STRING
// }
bool ParseOCSPResponse(const der::Input& raw_tlv, OCSPResponse* out) {
der::Parser outer_parser(raw_tlv);
der::Parser parser;
if (!outer_parser.ReadSequence(&parser))
return false;
if (outer_parser.HasMore())
return false;
der::Input response_status_input;
uint8_t response_status;
if (!parser.ReadTag(der::kEnumerated, &response_status_input))
return false;
if (!der::ParseUint8(response_status_input, &response_status))
return false;
if (response_status >
static_cast<uint8_t>(OCSPResponse::ResponseStatus::LAST)) {
return false;
}
out->status = static_cast<OCSPResponse::ResponseStatus>(response_status);
if (out->status == OCSPResponse::ResponseStatus::UNUSED)
return false;
if (out->status == OCSPResponse::ResponseStatus::SUCCESSFUL) {
der::Parser outer_bytes_parser;
der::Parser bytes_parser;
if (!parser.ReadConstructed(der::ContextSpecificConstructed(0),
&outer_bytes_parser)) {
return false;
}
if (!outer_bytes_parser.ReadSequence(&bytes_parser))
return false;
if (outer_bytes_parser.HasMore())
return false;
der::Input type_oid;
if (!bytes_parser.ReadTag(der::kOid, &type_oid))
return false;
if (type_oid != der::Input(kBasicOCSPResponseOid))
return false;
// As per RFC 6960 Section 4.2.1, the value of |response| SHALL be the DER
// encoding of BasicOCSPResponse.
der::Input response;
if (!bytes_parser.ReadTag(der::kOctetString, &response))
return false;
if (!ParseBasicOCSPResponse(response, out))
return false;
if (bytes_parser.HasMore())
return false;
}
return !parser.HasMore();
}
namespace {
// Checks that the |type| hash of |value| is equal to |hash|
bool VerifyHash(const EVP_MD* type,
const der::Input& hash,
const der::Input& value) {
unsigned value_hash_len;
uint8_t value_hash[EVP_MAX_MD_SIZE];
if (!EVP_Digest(value.UnsafeData(), value.Length(), value_hash,
&value_hash_len, type, nullptr)) {
return false;
}
return hash == der::Input(value_hash, value_hash_len);
}
// Extracts the bytes of the SubjectPublicKey bit string given an SPKI. That is
// to say, the value of subjectPublicKey without the leading unused bit
// count octet.
//
// Returns true on success and fills |*spk_tlv| with the result.
//
// From RFC 5280, Section 4.1
// SubjectPublicKeyInfo ::= SEQUENCE {
// algorithm AlgorithmIdentifier,
// subjectPublicKey BIT STRING }
//
// AlgorithmIdentifier ::= SEQUENCE {
// algorithm OBJECT IDENTIFIER,
// parameters ANY DEFINED BY algorithm OPTIONAL }
//
bool GetSubjectPublicKeyBytes(const der::Input& spki_tlv, der::Input* spk_tlv) {
CBS outer, inner, alg, spk;
uint8_t unused_bit_count;
CBS_init(&outer, spki_tlv.UnsafeData(), spki_tlv.Length());
// The subjectPublicKey field includes the unused bit count. For this
// application, the unused bit count must be zero, and is not included in
// the result. We extract the subjectPubicKey bit string, verify the first
// byte is 0, and if so set |spk_tlv| to the remaining bytes.
if (!CBS_get_asn1(&outer, &inner, CBS_ASN1_SEQUENCE) ||
!CBS_get_asn1(&inner, &alg, CBS_ASN1_SEQUENCE) ||
!CBS_get_asn1(&inner, &spk, CBS_ASN1_BITSTRING) ||
!CBS_get_u8(&spk, &unused_bit_count) || unused_bit_count != 0) {
return false;
}
*spk_tlv = der::Input(CBS_data(&spk), CBS_len(&spk));
return true;
}
// Checks the OCSPCertID |id| identifies |certificate|.
bool CheckCertIDMatchesCertificate(
const OCSPCertID& id,
const ParsedCertificate* certificate,
const ParsedCertificate* issuer_certificate) {
const EVP_MD* type = nullptr;
switch (id.hash_algorithm) {
case DigestAlgorithm::Md2:
case DigestAlgorithm::Md4:
case DigestAlgorithm::Md5:
// Unsupported.
return false;
case DigestAlgorithm::Sha1:
type = EVP_sha1();
break;
case DigestAlgorithm::Sha256:
type = EVP_sha256();
break;
case DigestAlgorithm::Sha384:
type = EVP_sha384();
break;
case DigestAlgorithm::Sha512:
type = EVP_sha512();
break;
}
if (!VerifyHash(type, id.issuer_name_hash, certificate->tbs().issuer_tlv))
return false;
der::Input key_tlv;
if (!GetSubjectPublicKeyBytes(issuer_certificate->tbs().spki_tlv, &key_tlv))
return false;
if (!VerifyHash(type, id.issuer_key_hash, key_tlv))
return false;
return id.serial_number == certificate->tbs().serial_number;
}
// TODO(eroman): Revisit how certificate parsing is used by this file. Ideally
// would either pass in the parsed bits, or have a better abstraction for lazily
// parsing.
std::shared_ptr<const ParsedCertificate> OCSPParseCertificate(
std::string_view der) {
ParseCertificateOptions parse_options;
parse_options.allow_invalid_serial_numbers = true;
// TODO(eroman): Swallows the parsing errors. However uses a permissive
// parsing model.
CertErrors errors;
return ParsedCertificate::Create(
bssl::UniquePtr<CRYPTO_BUFFER>(
CRYPTO_BUFFER_new(reinterpret_cast<const uint8_t*>(der.data()),
der.size(), x509_util::GetBufferPool())),
{}, &errors);
}
// Checks that the ResponderID |id| matches the certificate |cert| either
// by verifying the name matches that of the certificate or that the hash
// matches the certificate's public key hash (RFC 6960, 4.2.2.3).
[[nodiscard]] bool CheckResponderIDMatchesCertificate(
const OCSPResponseData::ResponderID& id,
const ParsedCertificate* cert) {
switch (id.type) {
case OCSPResponseData::ResponderType::NAME: {
der::Input name_rdn;
der::Input cert_rdn;
if (!der::Parser(id.name).ReadTag(der::kSequence, &name_rdn) ||
!der::Parser(cert->tbs().subject_tlv)
.ReadTag(der::kSequence, &cert_rdn))
return false;
return VerifyNameMatch(name_rdn, cert_rdn);
}
case OCSPResponseData::ResponderType::KEY_HASH: {
der::Input key;
if (!GetSubjectPublicKeyBytes(cert->tbs().spki_tlv, &key))
return false;
return VerifyHash(EVP_sha1(), id.key_hash, key);
}
}
return false;
}
// Verifies that |responder_certificate| has been authority for OCSP signing,
// delegated to it by |issuer_certificate|.
//
// TODO(eroman): No revocation checks are done (see id-pkix-ocsp-nocheck in the
// spec). extension).
//
// TODO(eroman): Not all properties of the certificate are verified, only the
// signature and EKU. Can full RFC 5280 validation be used, or are there
// compatibility concerns?
[[nodiscard]] bool VerifyAuthorizedResponderCert(
const ParsedCertificate* responder_certificate,
const ParsedCertificate* issuer_certificate) {
// The Authorized Responder must be directly signed by the issuer of the
// certificate being checked.
// TODO(eroman): Must check the signature algorithm against policy.
if (!responder_certificate->signature_algorithm().has_value() ||
!VerifySignedData(*responder_certificate->signature_algorithm(),
responder_certificate->tbs_certificate_tlv(),
responder_certificate->signature_value(),
issuer_certificate->tbs().spki_tlv,
/*cache=*/nullptr)) {
return false;
}
// The Authorized Responder must include the value id-kp-OCSPSigning as
// part of the extended key usage extension.
if (!responder_certificate->has_extended_key_usage())
return false;
for (const auto& key_purpose_oid :
responder_certificate->extended_key_usage()) {
if (key_purpose_oid == der::Input(kOCSPSigning))
return true;
}
return false;
}
[[nodiscard]] bool VerifyOCSPResponseSignatureGivenCert(
const OCSPResponse& response,
const ParsedCertificate* cert) {
// TODO(eroman): Must check the signature algorithm against policy.
return VerifySignedData(response.signature_algorithm, response.data,
response.signature, cert->tbs().spki_tlv,
/*cache=*/nullptr);
}
// Verifies that the OCSP response has a valid signature using
// |issuer_certificate|, or an authorized responder issued by
// |issuer_certificate| for OCSP signing.
[[nodiscard]] bool VerifyOCSPResponseSignature(
const OCSPResponse& response,
const OCSPResponseData& response_data,
const ParsedCertificate* issuer_certificate) {
// In order to verify the OCSP signature, a valid responder matching the OCSP
// Responder ID must be located (RFC 6960, 4.2.2.2). The responder is allowed
// to be either the certificate issuer or a delegated authority directly
// signed by the issuer.
if (CheckResponderIDMatchesCertificate(response_data.responder_id,
issuer_certificate) &&
VerifyOCSPResponseSignatureGivenCert(response, issuer_certificate)) {
return true;
}
// Otherwise search through the provided certificates for the Authorized
// Responder. Want a certificate that:
// (1) Matches the OCSP Responder ID.
// (2) Has been given authority for OCSP signing by |issuer_certificate|.
// (3) Has signed the OCSP response using its public key.
for (const auto& responder_cert_tlv : response.certs) {
std::shared_ptr<const ParsedCertificate> cur_responder_certificate =
OCSPParseCertificate(responder_cert_tlv.AsStringView());
// If failed parsing the certificate, keep looking.
if (!cur_responder_certificate)
continue;
// If the certificate doesn't match the OCSP's responder ID, keep looking.
if (!CheckResponderIDMatchesCertificate(response_data.responder_id,
cur_responder_certificate.get())) {
continue;
}
// If the certificate isn't a valid Authorized Responder certificate, keep
// looking.
if (!VerifyAuthorizedResponderCert(cur_responder_certificate.get(),
issuer_certificate)) {
continue;
}
// If the certificate signed this OCSP response, have found a match.
// Otherwise keep looking.
if (VerifyOCSPResponseSignatureGivenCert(response,
cur_responder_certificate.get())) {
return true;
}
}
// Failed to confirm the validity of the OCSP signature using any of the
// candidate certificates.
return false;
}
// Parse ResponseData and return false if any unhandled critical extensions are
// found. No known critical ResponseData extensions exist.
bool ParseOCSPResponseDataExtensions(
const der::Input& response_extensions,
OCSPVerifyResult::ResponseStatus* response_details) {
std::map<der::Input, ParsedExtension> extensions;
if (!ParseExtensions(response_extensions, &extensions)) {
*response_details = OCSPVerifyResult::PARSE_RESPONSE_DATA_ERROR;
return false;
}
for (const auto& ext : extensions) {
// TODO: handle ResponseData extensions
if (ext.second.critical) {
*response_details = OCSPVerifyResult::UNHANDLED_CRITICAL_EXTENSION;
return false;
}
}
return true;
}
// Parse SingleResponse and return false if any unhandled critical extensions
// (other than the CT extension) are found. The CT-SCT extension is not required
// to be marked critical, but since it is handled by Chrome, we will overlook
// the flag setting.
bool ParseOCSPSingleResponseExtensions(
const der::Input& single_extensions,
OCSPVerifyResult::ResponseStatus* response_details) {
std::map<der::Input, ParsedExtension> extensions;
if (!ParseExtensions(single_extensions, &extensions)) {
*response_details = OCSPVerifyResult::PARSE_RESPONSE_DATA_ERROR;
return false;
}
// The wire form of the OID 1.3.6.1.4.1.11129.2.4.5 - OCSP SingleExtension for
// X.509v3 Certificate Transparency Signed Certificate Timestamp List, see
// Section 3.3 of RFC6962.
const uint8_t ct_ocsp_ext_oid[] = {0x2B, 0x06, 0x01, 0x04, 0x01,
0xD6, 0x79, 0x02, 0x04, 0x05};
der::Input ct_ext_oid(ct_ocsp_ext_oid);
for (const auto& ext : extensions) {
// The CT OCSP extension is handled in ct::ExtractSCTListFromOCSPResponse
if (ext.second.oid == ct_ext_oid)
continue;
// TODO: handle SingleResponse extensions
if (ext.second.critical) {
*response_details = OCSPVerifyResult::UNHANDLED_CRITICAL_EXTENSION;
return false;
}
}
return true;
}
// Loops through the OCSPSingleResponses to find the best match for |cert|.
OCSPRevocationStatus GetRevocationStatusForCert(
const OCSPResponseData& response_data,
const ParsedCertificate* cert,
const ParsedCertificate* issuer_certificate,
int64_t verify_time_epoch_seconds,
std::optional<int64_t> max_age_seconds,
OCSPVerifyResult::ResponseStatus* response_details) {
OCSPRevocationStatus result = OCSPRevocationStatus::UNKNOWN;
*response_details = OCSPVerifyResult::NO_MATCHING_RESPONSE;
for (const auto& single_response_der : response_data.responses) {
// In the common case, there should only be one SingleResponse in the
// ResponseData (matching the certificate requested and used on this
// connection). However, it is possible for the OCSP responder to provide
// multiple responses for multiple certificates. Look through all the
// provided SingleResponses, and check to see if any match the
// certificate. A SingleResponse matches a certificate if it has the same
// serial number, issuer name (hash), and issuer public key (hash).
OCSPSingleResponse single_response;
if (!ParseOCSPSingleResponse(single_response_der, &single_response))
return OCSPRevocationStatus::UNKNOWN;
// Reject unhandled critical extensions in SingleResponse
if (single_response.has_extensions &&
!ParseOCSPSingleResponseExtensions(single_response.extensions,
response_details)) {
return OCSPRevocationStatus::UNKNOWN;
}
OCSPCertID cert_id;
if (!ParseOCSPCertID(single_response.cert_id_tlv, &cert_id))
return OCSPRevocationStatus::UNKNOWN;
if (!CheckCertIDMatchesCertificate(cert_id, cert, issuer_certificate))
continue;
// The SingleResponse matches the certificate, but may be out of date. Out
// of date responses are noted seperate from responses with mismatched
// serial numbers. If an OCSP responder provides both an up to date
// response and an expired response, the up to date response takes
// precedence (PROVIDED > INVALID_DATE).
if (!CheckRevocationDateValid(single_response.this_update,
single_response.has_next_update
? &single_response.next_update
: nullptr,
verify_time_epoch_seconds, max_age_seconds)) {
if (*response_details != OCSPVerifyResult::PROVIDED)
*response_details = OCSPVerifyResult::INVALID_DATE;
continue;
}
// In the case with multiple matching and up to date responses, keep only
// the strictest status (REVOKED > UNKNOWN > GOOD).
if (*response_details != OCSPVerifyResult::PROVIDED ||
result == OCSPRevocationStatus::GOOD ||
single_response.cert_status.status == OCSPRevocationStatus::REVOKED) {
result = single_response.cert_status.status;
}
*response_details = OCSPVerifyResult::PROVIDED;
}
return result;
}
OCSPRevocationStatus CheckOCSP(
std::string_view raw_response,
std::string_view certificate_der,
const ParsedCertificate* certificate,
std::string_view issuer_certificate_der,
const ParsedCertificate* issuer_certificate,
int64_t verify_time_epoch_seconds,
std::optional<int64_t> max_age_seconds,
OCSPVerifyResult::ResponseStatus* response_details) {
*response_details = OCSPVerifyResult::NOT_CHECKED;
if (raw_response.empty()) {
*response_details = OCSPVerifyResult::MISSING;
return OCSPRevocationStatus::UNKNOWN;
}
der::Input response_der(raw_response);
OCSPResponse response;
if (!ParseOCSPResponse(response_der, &response)) {
*response_details = OCSPVerifyResult::PARSE_RESPONSE_ERROR;
return OCSPRevocationStatus::UNKNOWN;
}
// RFC 6960 defines all responses |response_status| != SUCCESSFUL as error
// responses. No revocation information is provided on error responses, and
// the OCSPResponseData structure is not set.
if (response.status != OCSPResponse::ResponseStatus::SUCCESSFUL) {
*response_details = OCSPVerifyResult::ERROR_RESPONSE;
return OCSPRevocationStatus::UNKNOWN;
}
// Actual revocation information is contained within the BasicOCSPResponse as
// a ResponseData structure. The BasicOCSPResponse was parsed above, and
// contains an unparsed ResponseData. From RFC 6960:
//
// BasicOCSPResponse ::= SEQUENCE {
// tbsResponseData ResponseData,
// signatureAlgorithm AlgorithmIdentifier,
// signature BIT STRING,
// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
//
// ResponseData ::= SEQUENCE {
// version [0] EXPLICIT Version DEFAULT v1,
// responderID ResponderID,
// producedAt GeneralizedTime,
// responses SEQUENCE OF SingleResponse,
// responseExtensions [1] EXPLICIT Extensions OPTIONAL }
OCSPResponseData response_data;
if (!ParseOCSPResponseData(response.data, &response_data)) {
*response_details = OCSPVerifyResult::PARSE_RESPONSE_DATA_ERROR;
return OCSPRevocationStatus::UNKNOWN;
}
// Process the OCSP ResponseData extensions. In particular, must reject if
// there are any critical extensions that are not understood.
if (response_data.has_extensions &&
!ParseOCSPResponseDataExtensions(response_data.extensions,
response_details)) {
return OCSPRevocationStatus::UNKNOWN;
}
std::shared_ptr<const ParsedCertificate> parsed_certificate;
std::shared_ptr<const ParsedCertificate> parsed_issuer_certificate;
if (!certificate) {
parsed_certificate = OCSPParseCertificate(certificate_der);
certificate = parsed_certificate.get();
}
if (!issuer_certificate) {
parsed_issuer_certificate = OCSPParseCertificate(issuer_certificate_der);
issuer_certificate = parsed_issuer_certificate.get();
}
if (!certificate || !issuer_certificate) {
*response_details = OCSPVerifyResult::NOT_CHECKED;
return OCSPRevocationStatus::UNKNOWN;
}
// If producedAt is outside of the certificate validity period, reject the
// response.
if (response_data.produced_at < certificate->tbs().validity_not_before ||
response_data.produced_at > certificate->tbs().validity_not_after) {
*response_details = OCSPVerifyResult::BAD_PRODUCED_AT;
return OCSPRevocationStatus::UNKNOWN;
}
// Look through all of the OCSPSingleResponses for a match (based on CertID
// and time).
OCSPRevocationStatus status = GetRevocationStatusForCert(
response_data, certificate, issuer_certificate, verify_time_epoch_seconds,
max_age_seconds, response_details);
// Check that the OCSP response has a valid signature. It must either be
// signed directly by the issuing certificate, or a valid authorized
// responder.
if (!VerifyOCSPResponseSignature(response, response_data,
issuer_certificate)) {
return OCSPRevocationStatus::UNKNOWN;
}
return status;
}
} // namespace
OCSPRevocationStatus CheckOCSP(
std::string_view raw_response,
std::string_view certificate_der,
std::string_view issuer_certificate_der,
int64_t verify_time_epoch_seconds,
std::optional<int64_t> max_age_seconds,
OCSPVerifyResult::ResponseStatus* response_details) {
return CheckOCSP(raw_response, certificate_der, nullptr,
issuer_certificate_der, nullptr, verify_time_epoch_seconds,
max_age_seconds, response_details);
}
OCSPRevocationStatus CheckOCSP(
std::string_view raw_response,
const ParsedCertificate* certificate,
const ParsedCertificate* issuer_certificate,
int64_t verify_time_epoch_seconds,
std::optional<int64_t> max_age_seconds,
OCSPVerifyResult::ResponseStatus* response_details) {
return CheckOCSP(raw_response, std::string_view(), certificate,
std::string_view(), issuer_certificate,
verify_time_epoch_seconds, max_age_seconds,
response_details);
}
bool CreateOCSPRequest(const ParsedCertificate* cert,
const ParsedCertificate* issuer,
std::vector<uint8_t>* request_der) {
request_der->clear();
bssl::ScopedCBB cbb;
// This initial buffer size is big enough for 20 octet long serial numbers
// (upper bound from RFC 5280) and then a handful of extra bytes. This
// number doesn't matter for correctness.
const size_t kInitialBufferSize = 100;
if (!CBB_init(cbb.get(), kInitialBufferSize))
return false;
// OCSPRequest ::= SEQUENCE {
// tbsRequest TBSRequest,
// optionalSignature [0] EXPLICIT Signature OPTIONAL }
//
// TBSRequest ::= SEQUENCE {
// version [0] EXPLICIT Version DEFAULT v1,
// requestorName [1] EXPLICIT GeneralName OPTIONAL,
// requestList SEQUENCE OF Request,
// requestExtensions [2] EXPLICIT Extensions OPTIONAL }
CBB ocsp_request;
if (!CBB_add_asn1(cbb.get(), &ocsp_request, CBS_ASN1_SEQUENCE))
return false;
CBB tbs_request;
if (!CBB_add_asn1(&ocsp_request, &tbs_request, CBS_ASN1_SEQUENCE))
return false;
// "version", "requestorName", and "requestExtensions" are omitted.
CBB request_list;
if (!CBB_add_asn1(&tbs_request, &request_list, CBS_ASN1_SEQUENCE))
return false;
CBB request;
if (!CBB_add_asn1(&request_list, &request, CBS_ASN1_SEQUENCE))
return false;
// Request ::= SEQUENCE {
// reqCert CertID,
// singleRequestExtensions [0] EXPLICIT Extensions OPTIONAL }
CBB req_cert;
if (!CBB_add_asn1(&request, &req_cert, CBS_ASN1_SEQUENCE))
return false;
// CertID ::= SEQUENCE {
// hashAlgorithm AlgorithmIdentifier,
// issuerNameHash OCTET STRING, -- Hash of issuer's DN
// issuerKeyHash OCTET STRING, -- Hash of issuer's public key
// serialNumber CertificateSerialNumber }
// TODO(eroman): Don't use SHA1.
const EVP_MD* md = EVP_sha1();
if (!EVP_marshal_digest_algorithm(&req_cert, md))
return false;
AppendHashAsOctetString(md, &req_cert, issuer->tbs().subject_tlv);
der::Input key_tlv;
if (!GetSubjectPublicKeyBytes(issuer->tbs().spki_tlv, &key_tlv))
return false;
AppendHashAsOctetString(md, &req_cert, key_tlv);
CBB serial_number;
if (!CBB_add_asn1(&req_cert, &serial_number, CBS_ASN1_INTEGER))
return false;
if (!CBB_add_bytes(&serial_number, cert->tbs().serial_number.UnsafeData(),
cert->tbs().serial_number.Length())) {
return false;
}
uint8_t* result_bytes;
size_t result_bytes_length;
if (!CBB_finish(cbb.get(), &result_bytes, &result_bytes_length))
return false;
bssl::UniquePtr<uint8_t> delete_tbs_cert_bytes(result_bytes);
request_der->assign(result_bytes, result_bytes + result_bytes_length);
return true;
}
// From RFC 2560 section A.1.1:
//
// An OCSP request using the GET method is constructed as follows:
//
// GET {url}/{url-encoding of base-64 encoding of the DER encoding of
// the OCSPRequest}
URL CreateOCSPGetURL(const ParsedCertificate* cert,
const ParsedCertificate* issuer,
std::string_view ocsp_responder_url) {
std::vector<uint8_t> ocsp_request_der;
if (!CreateOCSPRequest(cert, issuer, &ocsp_request_der)) {
// Unexpected (means BoringSSL failed an operation).
return URL();
}
// Base64 encode the request data.
size_t len;
if (!EVP_EncodedLength(&len, ocsp_request_der.size())) {
return URL();
}
std::vector<uint8_t> encoded(len);
len = EVP_EncodeBlock(encoded.data(), ocsp_request_der.data(),
ocsp_request_der.size());
std::string b64_encoded(encoded.begin(), encoded.begin() + len);
// In theory +, /, and = are valid in paths and don't need to be escaped.
// However from the example in RFC 5019 section 5 it is clear that the intent
// is to escape non-alphanumeric characters (the example conclusively escapes
// '/' and '=', but doesn't clarify '+').
b64_encoded = bssl::string_util::FindAndReplace(b64_encoded, "+", "%2B");
b64_encoded = bssl::string_util::FindAndReplace(b64_encoded, "/", "%2F");
b64_encoded = bssl::string_util::FindAndReplace(b64_encoded, "=", "%3D");
// No attempt is made to collapse double slashes for URLs that end in slash,
// since the spec doesn't do that.
return URL(std::string(ocsp_responder_url) + "/" + b64_encoded);
}
} // namespace net