| // 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" |
| |
| 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(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; |
| } |
| |
| } // namespace bssl |