| // Copyright 2026 The BoringSSL Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include <openssl/asn1.h> |
| #include <openssl/base.h> |
| #include <openssl/bytestring.h> |
| #include <openssl/digest.h> |
| #include <openssl/err.h> |
| #include <openssl/evp.h> |
| #include <openssl/nid.h> |
| #include <openssl/span.h> |
| #include <openssl/x509.h> |
| |
| #include <assert.h> |
| |
| #include <algorithm> |
| #include <optional> |
| |
| #include "../bytestring/internal.h" |
| #include "../internal.h" |
| #include "./internal.h" |
| |
| |
| BSSL_NAMESPACE_BEGIN |
| namespace { |
| |
| // Prefix for domain separation to denote a Merkle Tree leaf or interior node. |
| constexpr uint8_t kMTCLeafDomainSeparator[] = {0x00}; |
| constexpr uint8_t kMTCNodeDomainSeparator[] = {0x01}; |
| // DER encoding of AlgorithmIdentifier for alg-mtcProof-draft with absent |
| // parameters. |
| // SEQUENCE (12 bytes) { |
| // OBJECT IDENTIFIER 1.3.6.1.4.1.44363.47.0 (10 bytes) |
| // } |
| constexpr uint8_t kMTCAlgorithmIdentifier[] = { |
| 0x30, // SEQUENCE tag |
| 12, // tag + len + OID bytes |
| 0x06, // OBJECT tag |
| 10, // OID length |
| OBJ_ENC_alg_mtcProof_draft, |
| }; |
| // MTCLogEntryType enum value for tbs_cert_entry. |
| constexpr uint8_t kMTCLogEntryTypeTBSCertEntry[] = {0x00, 0x01}; |
| // ASN.1 tag for encoded OCTET STRING. |
| constexpr uint8_t kEncodedOctetStringTag[] = {0x04}; |
| // Prefix for domain separation used in CosignedMessage. |
| constexpr uint8_t kCosignedMessageLabel[12] = {'s', 'u', 'b', 't', 'r', 'e', |
| 'e', '/', 'v', '1', '\n', '\0'}; |
| // String prefix used to construct ASCII representation of trust anchor IDs for |
| // CosignedMessage fields. |
| constexpr uint8_t kASCIIOIDPrefix[16] = {'o', 'i', 'd', '/', '1', '.', |
| '3', '.', '6', '.', '1', '.', |
| '4', '.', '1', '.'}; |
| // DER encoded OID component found in trust anchor IDs for issuance logs. |
| constexpr uint8_t kEncOIDLogs = 0x00; |
| |
| constexpr CBS_ASN1_TAG kTBSVersionTag = |
| CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 0; |
| |
| // HashNode executes one hashing step in the evaluation of an inclusion proof. |
| void HashNode(Span<uint8_t> out, const EVP_MD *log_hash, |
| Span<const uint8_t> left_child, Span<const uint8_t> right_child) { |
| assert(out.size() == EVP_MD_size(log_hash)); |
| assert(left_child.size() == EVP_MD_size(log_hash)); |
| assert(right_child.size() == EVP_MD_size(log_hash)); |
| ScopedEVP_MD_CTX ctx; |
| EVP_DigestInit_ex(ctx.get(), log_hash, nullptr); |
| EVP_DigestUpdate(ctx.get(), kMTCNodeDomainSeparator, |
| sizeof(kMTCNodeDomainSeparator)); |
| EVP_DigestUpdate(ctx.get(), left_child.data(), left_child.size()); |
| EVP_DigestUpdate(ctx.get(), right_child.data(), right_child.size()); |
| EVP_DigestFinal_ex(ctx.get(), out.data(), nullptr); |
| } |
| |
| // lsb returns whether the least-significant bit of `n` is set. |
| inline bool lsb(uint64_t n) { return n & 1; } |
| |
| // is_mtc_proof returns whether `algor` is an mtcProof AlgorithmIdentifier. |
| bool is_mtc_proof(const X509_ALGOR *algor) { |
| return OBJ_obj2nid(algor->algorithm) == NID_alg_mtcProof_draft && |
| algor->parameter == nullptr; |
| } |
| |
| // Returns the first hash value (of size `log_hash_size`) remaining in |
| // `inclusion_proof` and advances `inclusion_proof` past the returned value. |
| // Returns an empty span if there are no more hash values of the appropriate |
| // size. |
| Span<const uint8_t> GetNextValueFromInclusionProof( |
| size_t log_hash_size, Span<const uint8_t> &inclusion_proof) { |
| if (inclusion_proof.size() < log_hash_size) { |
| return Span<const uint8_t>(); |
| } |
| Span<const uint8_t> value = inclusion_proof.first(log_hash_size); |
| inclusion_proof = inclusion_proof.subspan(log_hash_size); |
| return value; |
| } |
| |
| // Writes `data` to the digest in `ctx`, prepending an 8-bit length prefix. |
| bool DigestUpdateWithU8LengthPrefix(EVP_MD_CTX *ctx, Span<const uint8_t> data) { |
| BSSL_CHECK(data.size() < (1u << 8)); |
| const uint8_t length_prefix = static_cast<uint8_t>(data.size()); |
| return EVP_DigestUpdate(ctx, &length_prefix, sizeof(length_prefix)) && |
| EVP_DigestUpdate(ctx, data.data(), data.size()); |
| } |
| |
| // Writes `data` to the digest in `ctx`, prepending a 16-bit length prefix. |
| bool DigestUpdateWithU16LengthPrefix(EVP_MD_CTX *ctx, |
| Span<const uint8_t> data) { |
| BSSL_CHECK(data.size() < (1u << 16)); |
| const uint8_t length_prefix[2] = {static_cast<uint8_t>(data.size() >> 8), |
| static_cast<uint8_t>(data.size())}; |
| return EVP_DigestUpdate(ctx, length_prefix, sizeof(length_prefix)) && |
| EVP_DigestUpdate(ctx, data.data(), data.size()); |
| } |
| |
| // Reads the next ASN.1 element from `cbs` and writes it as-is to the digest in |
| // `ctx`, including ASN.1 header bytes. |
| bool DigestUpdateNextASN1Element(EVP_MD_CTX *ctx, CBS *cbs) { |
| CBS element; |
| if (!CBS_get_any_asn1_element(cbs, &element, /*out_tag=*/nullptr, |
| /*out_header_len=*/nullptr)) { |
| return false; |
| } |
| return EVP_DigestUpdate(ctx, CBS_data(&element), CBS_len(&element)); |
| } |
| |
| // MTCCACosigner represents a Merkle Tree CA and its corresponding CA cosigner |
| // (see draft-ietf-plants-merkle-tree-certs, section 5.5). |
| class MTCCACosigner { |
| public: |
| MTCCACosigner() = default; |
| ~MTCCACosigner() = default; |
| |
| // Init() must be called before this object can be used. |
| // Init parses parameters defining an MTC CA cosigner from `x509`, including |
| // its MTCCertificationAuthority extension. `pkey` is the public key |
| // previously parsed from `x509`. Returns true if successfully initialized an |
| // MTCCACosigner representing a valid MTC CA, or false on error. |
| bool Init(const X509 *x509, const EVP_PKEY *pkey) { |
| // Get and parse the MTCCertificationAuthority extension (see section 5.5). |
| int ext_index = |
| X509_get_ext_by_NID(x509, NID_pe_mtcCertificationAuthority_draft, -1); |
| const X509_EXTENSION *ext = X509_get_ext(x509, ext_index); |
| if (ext == nullptr || !X509_EXTENSION_get_critical(ext)) { |
| OPENSSL_PUT_ERROR(X509, X509_R_INVALID_MTC_CA); |
| return false; |
| } |
| const ASN1_STRING *value = X509_EXTENSION_get_data(ext); |
| CBS ext_value; |
| CBS_init(&ext_value, ASN1_STRING_get0_data(value), |
| ASN1_STRING_length(value)); |
| |
| CBS seq, log_hash; |
| if (!CBS_get_asn1(&ext_value, &seq, CBS_ASN1_SEQUENCE) || // |
| !CBS_get_asn1_element(&seq, &log_hash, CBS_ASN1_SEQUENCE) || // |
| !x509_parse_algorithm(&seq, cosign_sigalg_.get()) || // |
| !CBS_get_asn1_uint64(&seq, &min_serial_) || // |
| !CBS_get_asn1_uint64(&seq, &max_serial_) || // |
| CBS_len(&seq) != 0) { |
| OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR); |
| return false; |
| } |
| log_hash_ = EVP_parse_digest_algorithm(&log_hash); |
| if (log_hash_ == nullptr) { |
| return false; |
| } |
| if (CBS_len(&log_hash) != 0) { |
| OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR); |
| return false; |
| } |
| |
| // Extract the CA ID from the subject (see section 5.1). |
| const X509_NAME *subject = X509_get_subject_name(x509); |
| int tai_index = |
| X509_NAME_get_index_by_NID(subject, NID_rdna_trustAnchorID_draft, -1); |
| if (tai_index < 0 || X509_NAME_entry_count(subject) != 1) { |
| OPENSSL_PUT_ERROR(X509, X509_R_INVALID_MTC_CA); |
| return false; |
| } |
| // For initial experimentation, the attribute's value is a UTF8String |
| // containing the trust anchor ID's ASCII representation. |
| const ASN1_STRING *tai_ascii = |
| X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject, tai_index)); |
| if (ASN1_STRING_type(tai_ascii) != V_ASN1_UTF8STRING) { |
| OPENSSL_PUT_ERROR(X509, X509_R_WRONG_TYPE); |
| return false; |
| } |
| std::string_view tai_ascii_str = BytesAsStringView( |
| Span(ASN1_STRING_get0_data(tai_ascii), ASN1_STRING_length(tai_ascii))); |
| ScopedCBB ca_id_enc; |
| if (!CBB_init(ca_id_enc.get(), 64) || |
| !CBB_add_asn1_relative_oid_from_text( |
| ca_id_enc.get(), tai_ascii_str.data(), tai_ascii_str.size())) { |
| OPENSSL_PUT_ERROR(ASN1, ASN1_R_INVALID_OBJECT_ENCODING); |
| return false; |
| } |
| if (!CBBFinishArray(ca_id_enc.get(), &ca_id_enc_oid_)) { |
| OPENSSL_PUT_ERROR(X509, ERR_R_INTERNAL_ERROR); |
| return false; |
| } |
| |
| cosign_pubkey_.reset(EVP_PKEY_dup_ref(pkey)); |
| return true; |
| } |
| |
| bool IsSerialInRange(uint64_t serial) const { |
| return min_serial_ <= serial && serial <= max_serial_; |
| } |
| |
| // Constructs CosignedMessage and computes signature. |
| bool VerifyCACosignature(Span<const uint8_t> ca_cosignature, |
| uint64_t log_number, uint64_t subtree_start, |
| uint64_t subtree_end, |
| Span<const uint8_t> expected_subtree_hash) const { |
| ScopedCBB cosigned_message; |
| if (!CBB_init(cosigned_message.get(), 256) || |
| !CBB_add_bytes(cosigned_message.get(), kCosignedMessageLabel, |
| sizeof(kCosignedMessageLabel)) || |
| !WriteCosignerName(cosigned_message.get()) || |
| !CBB_add_u64(cosigned_message.get(), 0u) || // timestamp |
| !WriteLogOrigin(cosigned_message.get(), log_number) || |
| !CBB_add_u64(cosigned_message.get(), subtree_start) || |
| !CBB_add_u64(cosigned_message.get(), subtree_end) || |
| !CBB_add_bytes(cosigned_message.get(), expected_subtree_hash.data(), |
| expected_subtree_hash.size())) { |
| OPENSSL_PUT_ERROR(X509, ERR_R_INTERNAL_ERROR); |
| return false; |
| } |
| return x509_verify_signature_bytes(cosign_sigalg_.get(), ca_cosignature, |
| CBBAsSpan(cosigned_message.get()), |
| cosign_pubkey_.get()); |
| } |
| |
| // IsCACosigner returns whether `trust_anchor_id` equals the cosigner ID |
| // of this CA's CA cosigner. |
| bool IsCACosigner(Span<const uint8_t> trust_anchor_id) const { |
| return trust_anchor_id == ca_id_enc_oid_; |
| } |
| |
| const EVP_MD *log_hash() const { return log_hash_; } |
| |
| private: |
| // WriteTaiAscii concatenates the trust anchor ID prefix with the CA ID as a |
| // relative OID to get the CA ID as a full OID, then appends any additional |
| // OID components from `oid_suffix`, and writes the ASCII string |
| // representation of the full trust anchor ID to `out`. |
| bool WriteTaiAscii(CBB *out, Span<const uint64_t> oid_suffix = {}) const { |
| // Trust anchor IDs are constrained to 256 bytes by spec (and are typically |
| // much shorter). |
| uint8_t id[256]; |
| CBB id_cbb; |
| if (!CBB_init_fixed(&id_cbb, id, sizeof(id)) || |
| !CBB_add_bytes(&id_cbb, ca_id_enc_oid_.data(), ca_id_enc_oid_.size())) { |
| return false; |
| } |
| for (const uint64_t component : oid_suffix) { |
| if (!CBB_add_asn1_oid_component(&id_cbb, component)) { |
| return false; |
| } |
| } |
| return CBB_add_asn1_relative_oid_from_der_to_text(out, CBB_data(&id_cbb), |
| CBB_len(&id_cbb)); |
| } |
| |
| // WriteCosignerName writes the `cosigner_name` field for a CosignedMessage (a |
| // prefixed ASCII representation of the CA ID) to `out`. |
| bool WriteCosignerName(CBB *out) const { |
| CBB cosigner_name; |
| return CBB_add_u8_length_prefixed(out, &cosigner_name) && |
| CBB_add_bytes(&cosigner_name, kASCIIOIDPrefix, |
| sizeof(kASCIIOIDPrefix)) && |
| WriteTaiAscii(&cosigner_name) && // |
| CBB_flush(out); |
| } |
| |
| // WriteLogOrigin writes the `log_origin` field for a CosignedMessage (a |
| // prefixed ASCII representation of the CA's log ID for a given `log_number`) |
| // to `out`. |
| bool WriteLogOrigin(CBB *out, uint64_t log_number) const { |
| const uint64_t log_number_relative_oid[2] = {kEncOIDLogs, log_number}; |
| CBB log_origin; |
| return CBB_add_u8_length_prefixed(out, &log_origin) && |
| CBB_add_bytes(&log_origin, kASCIIOIDPrefix, |
| sizeof(kASCIIOIDPrefix)) && |
| WriteTaiAscii(&log_origin, Span(log_number_relative_oid)) && |
| CBB_flush(out); |
| } |
| |
| // The CA ID, which is equal to the CA cosigner's cosigner ID (see sections |
| // 5.1 and 5.4), in the form of a DER-encoded relative OID. This does not |
| // include any framing, only the element's contents. |
| Array<uint8_t> ca_id_enc_oid_; |
| // Log hash algorithm. |
| const EVP_MD *log_hash_ = nullptr; |
| // Public key of the CA cosigner. |
| UniquePtr<EVP_PKEY> cosign_pubkey_; |
| // Signature algorithm for CA cosigner signatures. |
| ScopedX509Algor cosign_sigalg_; |
| // Min and max allowed serial number, (0..2^64-1). |
| uint64_t min_serial_ = 0u; |
| uint64_t max_serial_ = 0u; |
| }; |
| |
| } // namespace |
| |
| bool x509_is_merkle_tree_ca(const X509 *x509) { |
| return X509_get_ext_by_NID(x509, NID_pe_mtcCertificationAuthority_draft, |
| -1) >= 0; |
| } |
| |
| bool x509_evaluate_mtc_subtree_inclusion_proof( |
| Span<uint8_t> out, const EVP_MD *log_hash, |
| Span<const uint8_t> inclusion_proof, uint64_t index, |
| Span<const uint8_t> entry_hash, uint64_t subtree_start, |
| uint64_t subtree_end) { |
| const size_t log_hash_size = EVP_MD_size(log_hash); |
| if (out.size() != log_hash_size || entry_hash.size() != log_hash_size) { |
| return false; |
| } |
| |
| // Check that `subtree_start` and `subtree_end` define a valid subtree. |
| if (subtree_start > subtree_end) { |
| return false; |
| } |
| // The subtree must be aligned and not have a ragged left edge, i.e. the size |
| // must not exceed the largest power of 2 that divides the start index. |
| const uint64_t subtree_size = subtree_end - subtree_start; |
| if (subtree_start != 0 && |
| // The expression `subtree_start & (~subtree_start + 1)` isolates the |
| // lowest set bit of `subtree_start`. |
| subtree_size > (subtree_start & (~subtree_start + 1))) { |
| return false; |
| } |
| // Check that `index` is in range for the subtree. |
| if (index < subtree_start || subtree_end <= index) { |
| return false; |
| } |
| |
| OPENSSL_memcpy(out.data(), entry_hash.data(), log_hash_size); |
| |
| // `fn` is the index of the entry if the subtree were re-numbered to start at |
| // 0, and `sn` is what the last entry of such a re-numbered subtree would be. |
| uint64_t fn = index - subtree_start; |
| uint64_t sn = subtree_size - 1; |
| while (!inclusion_proof.empty()) { |
| Span<const uint8_t> p = |
| GetNextValueFromInclusionProof(log_hash_size, inclusion_proof); |
| if (p.empty()) { |
| // Truncated hash in inclusion proof, or trailing data after last full |
| // hash. |
| return false; |
| } |
| assert(p.size() == log_hash_size); |
| if (sn == 0) { |
| // More hashes in the inclusion proof than expected. |
| return false; |
| } |
| if (lsb(fn) || fn == sn) { |
| HashNode(out, log_hash, /*left_child=*/p, /*right_child=*/out); |
| while (!lsb(fn)) { |
| fn >>= 1; |
| sn >>= 1; |
| } |
| } else { |
| HashNode(out, log_hash, /*left_child=*/out, /*right_child=*/p); |
| } |
| fn >>= 1; |
| sn >>= 1; |
| } |
| |
| if (sn != 0) { |
| // Not enough hashes in inclusion proof. |
| return false; |
| } |
| |
| return true; |
| } |
| |
| int x509_verify_mtc(const X509 *x509, const EVP_PKEY *pkey, |
| const X509 *issuer) { |
| MTCCACosigner issuer_mtc_ca; |
| if (!issuer_mtc_ca.Init(issuer, pkey)) { |
| return 0; |
| } |
| |
| auto *impl = FromOpaque(x509); |
| if (!is_mtc_proof(impl->sig_alg.get())) { |
| OPENSSL_PUT_ERROR(X509, X509_R_UNSUPPORTED_ALGORITHM); |
| return 0; |
| } |
| if (X509_ALGOR_cmp(impl->sig_alg.get(), impl->tbs_sig_alg.get())) { |
| OPENSSL_PUT_ERROR(X509, X509_R_SIGNATURE_ALGORITHM_MISMATCH); |
| return 0; |
| } |
| |
| if (impl->signature->type == V_ASN1_BIT_STRING && |
| ASN1_BIT_STRING_unused_bits(impl->signature.get()) != 0) { |
| OPENSSL_PUT_ERROR(X509, X509_R_INVALID_BIT_STRING_BITS_LEFT); |
| return 0; |
| } |
| |
| CBS mtc_proof; |
| CBS_init(&mtc_proof, ASN1_STRING_get0_data(impl->signature.get()), |
| ASN1_STRING_length(impl->signature.get())); |
| |
| CBS extensions, inclusion_proof, signatures; |
| uint64_t subtree_start, subtree_end; |
| if (!CBS_get_u16_length_prefixed(&mtc_proof, &extensions) || |
| !CBS_get_u48(&mtc_proof, &subtree_start) || |
| !CBS_get_u48(&mtc_proof, &subtree_end) || |
| !CBS_get_u16_length_prefixed(&mtc_proof, &inclusion_proof) || |
| !CBS_get_u16_length_prefixed(&mtc_proof, &signatures) || |
| CBS_len(&mtc_proof) != 0) { |
| OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR); |
| return 0; |
| } |
| |
| // Iterate through the subtree signatures array to check that they conform to |
| // spec, and identify the subtree signature from the CA cosigner. |
| CBS cosigner_id, signature; |
| std::optional<Span<const uint8_t>> prev_cosigner_id; |
| std::optional<Span<const uint8_t>> ca_cosignature; |
| while (CBS_len(&signatures) > 0) { |
| if (!CBS_get_u8_length_prefixed(&signatures, &cosigner_id) || |
| CBS_len(&cosigner_id) == 0 || |
| !CBS_get_u16_length_prefixed(&signatures, &signature) || |
| CBS_len(&signature) == 0) { |
| OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR); |
| return 0; |
| } |
| Span<const uint8_t> cosigner_id_span = cosigner_id; |
| // Cosignatures must be ordered by increasing cosigner ID length |
| // (lexicographical order if same length), and must not contain duplicate |
| // cosigner IDs. |
| if (prev_cosigner_id.has_value()) { |
| if (prev_cosigner_id->size() > cosigner_id_span.size()) { |
| OPENSSL_PUT_ERROR(X509, X509_R_INVALID_MTC_PROOF); |
| return 0; |
| } |
| if (prev_cosigner_id->size() == cosigner_id_span.size() && |
| !std::lexicographical_compare( |
| prev_cosigner_id->begin(), prev_cosigner_id->end(), |
| cosigner_id_span.begin(), cosigner_id_span.end())) { |
| OPENSSL_PUT_ERROR(X509, X509_R_INVALID_MTC_PROOF); |
| return 0; |
| } |
| } |
| prev_cosigner_id = cosigner_id_span; |
| if (issuer_mtc_ca.IsCACosigner(cosigner_id_span)) { |
| ca_cosignature = signature; |
| } |
| } |
| if (!ca_cosignature.has_value()) { |
| OPENSSL_PUT_ERROR(X509, X509_R_INVALID_MTC_PROOF); |
| return 0; |
| } |
| |
| CBS tbs_seq, tbs; |
| Array<uint8_t> tbs_scratch; |
| if (!x509_get_or_marshal_tbs_cert(&tbs_seq, &tbs_scratch, x509) || |
| !CBS_get_asn1(&tbs_seq, &tbs, CBS_ASN1_SEQUENCE)) { |
| OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR); |
| return 0; |
| } |
| |
| CBS version_field; |
| if (CBS_peek_asn1_tag(&tbs, kTBSVersionTag)) { |
| if (!CBS_get_asn1_element(&tbs, &version_field, kTBSVersionTag)) { |
| OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR); |
| return 0; |
| } |
| } else { |
| CBS_init(&version_field, nullptr, 0); |
| } |
| |
| // Check serial number is well-formed and within CA's range. (Caller is |
| // responsible for checking serial number against other revoked ranges.) |
| uint64_t serial; |
| if (!CBS_get_asn1_uint64(&tbs, &serial)) { |
| OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR); |
| return 0; |
| } |
| if (!issuer_mtc_ca.IsSerialInRange(serial)) { |
| OPENSSL_PUT_ERROR(X509, X509_R_INVALID_PARAMETER); |
| return 0; |
| } |
| // `index` is the least significant 48 bits of `serial`. |
| uint64_t index = serial & ((uint64_t{1} << 48) - 1); |
| uint64_t log_number = serial >> 48; |
| if (log_number == 0) { |
| OPENSSL_PUT_ERROR(X509, X509_R_INVALID_PARAMETER); |
| return 0; |
| } |
| |
| // The TBSCertificate's `signature` field must match the expected MTC |
| // signature algorithm identifier. |
| CBS tbs_sigalg; |
| if (!CBS_get_asn1_element(&tbs, &tbs_sigalg, CBS_ASN1_SEQUENCE) || |
| !CBS_mem_equal(&tbs_sigalg, kMTCAlgorithmIdentifier, |
| sizeof(kMTCAlgorithmIdentifier))) { |
| OPENSSL_PUT_ERROR(X509, X509_R_SIGNATURE_ALGORITHM_MISMATCH); |
| return 0; |
| } |
| |
| // Compute `entry_hash` in a single pass without storing the full |
| // TBSCertificateLogEntry or MTCLogEntry. |
| ScopedEVP_MD_CTX entry_hash_ctx; |
| if (!EVP_DigestInit_ex(entry_hash_ctx.get(), issuer_mtc_ca.log_hash(), |
| nullptr) || |
| !EVP_DigestUpdate(entry_hash_ctx.get(), kMTCLeafDomainSeparator, |
| sizeof(kMTCLeafDomainSeparator)) || |
| // MTCLogEntry `extensions` field (comes directly from the MTCProof). |
| !DigestUpdateWithU16LengthPrefix(entry_hash_ctx.get(), extensions) || |
| // MTCLogEntryType enum value for a tbs_cert_entry. |
| !EVP_DigestUpdate(entry_hash_ctx.get(), kMTCLogEntryTypeTBSCertEntry, |
| sizeof(kMTCLogEntryTypeTBSCertEntry)) || |
| // Contents of TBSCertificateLogEntry follow (excluding the initial |
| // identifier and length octets): |
| !EVP_DigestUpdate(entry_hash_ctx.get(), CBS_data(&version_field), |
| CBS_len(&version_field)) || |
| !DigestUpdateNextASN1Element(entry_hash_ctx.get(), &tbs) || // issuer |
| !DigestUpdateNextASN1Element(entry_hash_ctx.get(), &tbs) || // validity |
| !DigestUpdateNextASN1Element(entry_hash_ctx.get(), &tbs)) { // subject |
| OPENSSL_PUT_ERROR(X509, ERR_R_INTERNAL_ERROR); |
| return 0; |
| } |
| |
| // Hash the TBSCertificate's `subjectPublicKeyInfo` field. |
| CBS spki; |
| if (!CBS_get_asn1_element(&tbs, &spki, CBS_ASN1_SEQUENCE)) { |
| OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR); |
| return 0; |
| } |
| uint8_t spki_digest[EVP_MAX_MD_SIZE]; |
| unsigned int spki_digest_len; |
| if (!EVP_Digest(CBS_data(&spki), CBS_len(&spki), spki_digest, |
| &spki_digest_len, issuer_mtc_ca.log_hash(), nullptr) || |
| spki_digest_len == 0 || spki_digest_len > 0xff) { |
| OPENSSL_PUT_ERROR(X509, ERR_R_INTERNAL_ERROR); |
| return 0; |
| } |
| |
| // Add the AlgorithmIdentifier from the subjectPublicKeyInfo. |
| CBS spki_contents; |
| if (!CBS_get_asn1(&spki, &spki_contents, CBS_ASN1_SEQUENCE)) { |
| OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR); |
| return 0; |
| } |
| if (!DigestUpdateNextASN1Element(entry_hash_ctx.get(), &spki_contents) || |
| //`subjectPublicKeyInfoHash` field. |
| !EVP_DigestUpdate(entry_hash_ctx.get(), &kEncodedOctetStringTag, |
| sizeof(kEncodedOctetStringTag)) || |
| !DigestUpdateWithU8LengthPrefix(entry_hash_ctx.get(), |
| Span(spki_digest, spki_digest_len)) || |
| // Write the remainder of the TBSCertificate contents octets to the hash. |
| // This includes the optional fields `issuerUniqueID`, `subjectUniqueID`, |
| // and `extensions`. |
| !EVP_DigestUpdate(entry_hash_ctx.get(), CBS_data(&tbs), CBS_len(&tbs))) { |
| OPENSSL_PUT_ERROR(X509, ERR_R_INTERNAL_ERROR); |
| return 0; |
| } |
| |
| uint8_t entry_hash[EVP_MAX_MD_SIZE]; |
| unsigned entry_hash_len; |
| if (!EVP_DigestFinal_ex(entry_hash_ctx.get(), entry_hash, &entry_hash_len)) { |
| OPENSSL_PUT_ERROR(X509, ERR_R_INTERNAL_ERROR); |
| return 0; |
| } |
| |
| InplaceVector<uint8_t, EVP_MAX_MD_SIZE> expected_subtree_hash; |
| expected_subtree_hash.ResizeForOverwrite( |
| EVP_MD_size(issuer_mtc_ca.log_hash())); |
| |
| if (!x509_evaluate_mtc_subtree_inclusion_proof( |
| Span(expected_subtree_hash), issuer_mtc_ca.log_hash(), |
| inclusion_proof, index, Span(entry_hash, entry_hash_len), |
| subtree_start, subtree_end)) { |
| OPENSSL_PUT_ERROR(X509, X509_R_INVALID_MTC_PROOF); |
| return 0; |
| } |
| |
| return issuer_mtc_ca.VerifyCACosignature(ca_cosignature.value(), log_number, |
| subtree_start, subtree_end, |
| Span(expected_subtree_hash)); |
| } |
| |
| BSSL_NAMESPACE_END |