diff options
Diffstat (limited to 'net/cert/internal/parse_ocsp.cc')
-rw-r--r-- | net/cert/internal/parse_ocsp.cc | 532 |
1 files changed, 532 insertions, 0 deletions
diff --git a/net/cert/internal/parse_ocsp.cc b/net/cert/internal/parse_ocsp.cc new file mode 100644 index 0000000..e06b29a --- /dev/null +++ b/net/cert/internal/parse_ocsp.cc @@ -0,0 +1,532 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <algorithm> + +#include "base/sha1.h" +#include "crypto/sha2.h" +#include "net/cert/internal/parse_ocsp.h" + +namespace net { + +OCSPCertID::OCSPCertID() {} +OCSPCertID::~OCSPCertID() {} + +OCSPSingleResponse::OCSPSingleResponse() {} +OCSPSingleResponse::~OCSPSingleResponse() {} + +OCSPResponseData::OCSPResponseData() {} +OCSPResponseData::~OCSPResponseData() {} + +OCSPResponse::OCSPResponse() {} +OCSPResponse::~OCSPResponse() {} + +der::Input BasicOCSPResponseOid() { + // From RFC 6960: + // + // id-pkix-ocsp OBJECT IDENTIFIER ::= { id-ad-ocsp } + // id-pkix-ocsp-basic OBJECT IDENTIFIER ::= { id-pkix-ocsp 1 } + // + // In dotted notation: 1.3.6.1.5.5.7.48.1.1 + static const uint8_t oid[] = {0x2b, 0x06, 0x01, 0x05, 0x05, + 0x07, 0x30, 0x01, 0x01}; + return der::Input(oid); +} + +// 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; + if (!VerifySerialNumber(out->serial_number)) + 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 = OCSPCertStatus::Status::GOOD; + } else if (status_tag == der::ContextSpecificConstructed(1)) { + out->status = OCSPCertStatus::Status::REVOKED; + if (!ParseRevokedInfo(status, out)) + return false; + } else if (status_tag == der::ContextSpecificPrimitive(2)) { + out->status = OCSPCertStatus::Status::UNKNOWN; + } else { + return false; + } + + return !parser.HasMore(); +} + +} // 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 responder_key; + if (!key_parser.ReadTag(der::kOctetString, &responder_key)) + return false; + if (key_parser.HasMore()) + return false; + + SHA1HashValue key_hash; + if (responder_key.Length() != sizeof(key_hash.data)) + return false; + memcpy(key_hash.data, responder_key.UnsafeData(), sizeof(key_hash.data)); + out->type = OCSPResponseData::ResponderType::KEY_HASH; + out->key_hash = HashValue(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; + out->signature_algorithm = SignatureAlgorithm::CreateFromDer(sigalg_tlv); + if (!out->signature_algorithm) + return false; + if (!parser.ReadBitString(&(out->signature))) + return false; + 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 != BasicOCSPResponseOid()) + 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(HashValueTag type, + const der::Input& hash, + const der::Input& value) { + HashValue target(type); + if (target.size() != hash.Length()) + return false; + memcpy(target.data(), hash.UnsafeData(), target.size()); + + HashValue value_hash(type); + if (type == HASH_VALUE_SHA1) { + base::SHA1HashBytes(value.UnsafeData(), value.Length(), value_hash.data()); + } else if (type == HASH_VALUE_SHA256) { + std::string hash_string = crypto::SHA256HashString(value.AsString()); + memcpy(value_hash.data(), hash_string.data(), value_hash.size()); + } else { + return false; + } + + return target.Equals(value_hash); +} + +// Checks that the input |id_tlv| parses to a valid CertID and matches the +// issuer |issuer| name and key, as well as the serial number |serial_number|. +bool CheckCertID(const der::Input& id_tlv, + const ParsedTbsCertificate& certificate, + const ParsedTbsCertificate& issuer, + const der::Input& serial_number) { + OCSPCertID id; + if (!ParseOCSPCertID(id_tlv, &id)) + return false; + + HashValueTag type = HASH_VALUE_SHA1; + switch (id.hash_algorithm) { + case DigestAlgorithm::Sha1: + type = HASH_VALUE_SHA1; + break; + case DigestAlgorithm::Sha256: + type = HASH_VALUE_SHA256; + break; + case DigestAlgorithm::Sha384: + case DigestAlgorithm::Sha512: + NOTIMPLEMENTED(); + return false; + } + + if (!VerifyHash(type, id.issuer_name_hash, certificate.issuer_tlv)) + return false; + + // SubjectPublicKeyInfo ::= SEQUENCE { + // algorithm AlgorithmIdentifier, + // subjectPublicKey BIT STRING + // } + der::Parser outer_parser(issuer.spki_tlv); + der::Parser spki_parser; + der::BitString key_bits; + if (!outer_parser.ReadSequence(&spki_parser)) + return false; + if (outer_parser.HasMore()) + return false; + if (!spki_parser.SkipTag(der::kSequence)) + return false; + if (!spki_parser.ReadBitString(&key_bits)) + return false; + der::Input key_tlv = key_bits.bytes(); + if (!VerifyHash(type, id.issuer_key_hash, key_tlv)) + return false; + + return id.serial_number == serial_number; +} + +} // namespace + +bool GetOCSPCertStatus(const OCSPResponseData& response_data, + const ParsedCertificate& issuer, + const ParsedCertificate& cert, + OCSPCertStatus* out) { + out->status = OCSPCertStatus::Status::GOOD; + + ParsedTbsCertificate tbs_cert; + if (!ParseTbsCertificate(cert.tbs_certificate_tlv, &tbs_cert)) + return false; + ParsedTbsCertificate issuer_tbs_cert; + if (!ParseTbsCertificate(issuer.tbs_certificate_tlv, &issuer_tbs_cert)) + return false; + + bool found = false; + for (const auto& response : response_data.responses) { + OCSPSingleResponse single_response; + if (!ParseOCSPSingleResponse(response, &single_response)) + return false; + if (CheckCertID(single_response.cert_id_tlv, tbs_cert, issuer_tbs_cert, + tbs_cert.serial_number)) { + OCSPCertStatus new_status = single_response.cert_status; + found = true; + // In the case that we receive multiple responses, we keep only the + // strictest status (REVOKED > UNKNOWN > GOOD). + if (out->status == OCSPCertStatus::Status::GOOD || + new_status.status == OCSPCertStatus::Status::REVOKED) { + *out = new_status; + } + } + } + + if (!found) + out->status = OCSPCertStatus::Status::UNKNOWN; + + return found; +} + +} // namespace net |