blob: 1a30b223334b2efbbbffae86087640e82e357d72 [file] [log] [blame]
// 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 "ocsp.h"
#include <openssl/bytestring.h>
#include <openssl/digest.h>
#include <openssl/mem.h>
#include <openssl/pool.h>
#include <openssl/sha.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"
BSSL_NAMESPACE_BEGIN
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(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(CBS_ASN1_OCTETSTRING, &out->issuer_name_hash)) {
return false;
}
if (!parser.ReadTag(CBS_ASN1_OCTETSTRING, &out->issuer_key_hash)) {
return false;
}
if (!parser.ReadTag(CBS_ASN1_INTEGER, &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(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(
CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 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(CBS_ASN1_ENUMERATED, &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(der::Input raw_tlv, OCSPCertStatus *out) {
der::Parser parser(raw_tlv);
CBS_ASN1_TAG status_tag;
der::Input status;
if (!parser.ReadTagAndValue(&status_tag, &status)) {
return false;
}
out->has_reason = false;
if (status_tag == (CBS_ASN1_CONTEXT_SPECIFIC | 0)) {
out->status = OCSPRevocationStatus::GOOD;
} else if (status_tag ==
(CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 1)) {
out->status = OCSPRevocationStatus::REVOKED;
if (!ParseRevokedInfo(status, out)) {
return false;
}
} else if (status_tag == (CBS_ASN1_CONTEXT_SPECIFIC | 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,
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.data(), value.size(), 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(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(
CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 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(
CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 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(der::Input raw_tlv, OCSPResponseData::ResponderID *out) {
der::Parser parser(raw_tlv);
CBS_ASN1_TAG id_tag;
der::Input id_input;
if (!parser.ReadTagAndValue(&id_tag, &id_input)) {
return false;
}
if (id_tag == (CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 1)) {
out->type = OCSPResponseData::ResponderType::NAME;
out->name = id_input;
} else if (id_tag == (CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 2)) {
der::Parser key_parser(id_input);
der::Input key_hash;
if (!key_parser.ReadTag(CBS_ASN1_OCTETSTRING, &key_hash)) {
return false;
}
if (key_parser.HasMore()) {
return false;
}
if (key_hash.size() != 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(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(
CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 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(
CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 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(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(
CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 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(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(CBS_ASN1_ENUMERATED, &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(
CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 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(CBS_ASN1_OBJECT, &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(CBS_ASN1_OCTETSTRING, &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, der::Input hash, der::Input value) {
unsigned value_hash_len;
uint8_t value_hash[EVP_MAX_MD_SIZE];
if (!EVP_Digest(value.data(), value.size(), 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(der::Input spki_tlv, der::Input *spk_tlv) {
CBS outer, inner, alg, spk;
uint8_t unused_bit_count;
CBS_init(&outer, spki_tlv.data(), spki_tlv.size());
// 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;
// The objects returned by this function only last for the duration of a
// single certificate verification, so there is no need to pool them to save
// memory.
//
// 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(), nullptr)),
{}, &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(CBS_ASN1_SEQUENCE, &name_rdn) ||
!der::Parser(cert->tbs().subject_tlv)
.ReadTag(CBS_ASN1_SEQUENCE, &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(BytesAsStringView(responder_cert_tlv));
// 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(
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(
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.data(),
cert->tbs().serial_number.size())) {
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}
std::optional<std::string> 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 std::nullopt;
}
// Base64 encode the request data.
size_t len;
if (!EVP_EncodedLength(&len, ocsp_request_der.size())) {
return std::nullopt;
}
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 std::string(ocsp_responder_url) + "/" + b64_encoded;
}
BSSL_NAMESPACE_END