blob: 28c45c0135e455609065782c66cbfb13776ade09 [file]
// 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 "encode_certificate.h"
#include <openssl/bytestring.h>
#include <openssl/nid.h>
#include <openssl/pool.h>
#include "../crypto/bytestring/internal.h"
#include "encode_values.h"
BSSL_NAMESPACE_BEGIN
namespace {
// draft-ietf-plants-merkle-tree-certs-07, Section 5.5: mtcMinSerial.
constexpr uint64_t kMtcMinSerial = uint64_t{1} << 48;
// `EncodeMtcCaCertificate` produces a certificate that is not issued by anyone,
// so its serial number is arbitrary, but RFC 5280, Section 4.1.2.2, requires it
// to be positive.
constexpr uint64_t kMtcCaCertSerialNumber = 1;
constexpr uint8_t kRdnaTrustAnchorIDOid[] = {OBJ_ENC_rdna_trustAnchorID};
constexpr uint8_t kKeyUsageOid[] = {OBJ_ENC_key_usage};
constexpr uint8_t kBasicConstraintsOid[] = {OBJ_ENC_basic_constraints};
constexpr uint8_t kMtcCertificationAuthoritySha256Oid[] = {
OBJ_ENC_pe_mtcCertificationAuthority_SHA256};
// The validity for MTC CA certificates is as wide as possible, to never
// constrain path building.
// notBefore is the earliest time representable as a GeneralizedTime,
// 00000101000000Z. notAfter is the value from RFC 5280, Section 4.1.2.5, for
// a certificate with no well-defined expiration date, 99991231235959Z.
// clang-format off
constexpr uint8_t kMtcCaCertValidity[] = {
0x30, 34, // SEQUENCE (34 bytes)
0x18, 15, // GeneralizedTime (15 bytes)
'0', '0', '0', '0', '0', '1', '0', '1', '0', '0', '0', '0', '0', '0', 'Z',
0x18, 15, // GeneralizedTime (15 bytes)
'9', '9', '9', '9', '1', '2', '3', '1', '2', '3', '5', '9', '5', '9', 'Z',
};
// clang-format on
// KeyUsage extension for MTC CA certificates: a BIT STRING with keyCertSign
// (bit 5) and cRLSign (bit 6) set.
// clang-format off
constexpr uint8_t kMtcCaCertKeyUsage[] = {
0x03, 2, 1, 0b00000110, // BIT STRING (2 bytes, 1 unused bit)
};
// clang-format on
// BasicConstraints extension for CA certificates: a SEQUENCE with cA set to
// TRUE.
// clang-format off
constexpr uint8_t kMtcCaCertBasicConstraints[] = {
0x30, 3, // SEQUENCE (3 bytes)
0x01, 1, 0xff, // BOOLEAN (1 byte)
};
// clang-format on
} // namespace
UniquePtr<CRYPTO_BUFFER> EncodeMtcCaCertificate(
Span<const uint8_t> ca_id, SignatureAlgorithm ca_cosign_sigalg,
Span<const uint8_t> ca_cosign_key, uint64_t min_serial, uint64_t max_serial,
CRYPTO_BUFFER_POOL *pool) {
if (ca_cosign_sigalg == SignatureAlgorithm::kMtcProof) {
return nullptr;
}
// minSerial and maxSerial are INTEGER (mtcMinSerial..mtcMaxSerial), where
// mtcMaxSerial is the largest uint64_t. If `min_serial` > `max_serial`, the
// range is empty, and the MTC CA has no valid serial numbers.
if (min_serial < kMtcMinSerial || max_serial < kMtcMinSerial) {
return nullptr;
}
CBS key_cbs(ca_cosign_key), ca_id_cbs(ca_id);
// Ensure `ca_cosign_key` is a single SEQUENCE, so it cannot be misparsed as
// other Certificate fields.
if (!CBS_get_asn1(&key_cbs, nullptr, CBS_ASN1_SEQUENCE) ||
CBS_len(&key_cbs) != 0) {
return nullptr;
}
if (!CBS_is_valid_asn1_relative_oid(&ca_id_cbs)) {
return nullptr;
}
// MTCCertificationAuthority
ScopedCBB mtc_ca_cbb;
CBB mtc_ca;
if (!CBB_init(mtc_ca_cbb.get(), 32) ||
!CBB_add_asn1(mtc_ca_cbb.get(), &mtc_ca, CBS_ASN1_SEQUENCE) ||
// sigAlg
!AddSignatureAlgorithmIdentifier(&mtc_ca, ca_cosign_sigalg) ||
// minSerial, maxSerial
!CBB_add_asn1_uint64(&mtc_ca, min_serial) ||
!CBB_add_asn1_uint64(&mtc_ca, max_serial) ||
!CBB_flush(mtc_ca_cbb.get())) {
return nullptr;
}
ScopedCBB cbb;
CBB cert, tbs_cert, version, subject, rdn, attr, extensions_wrapper,
extensions, signature;
if (!CBB_init(cbb.get(), 256 + ca_cosign_key.size()) ||
!CBB_add_asn1(cbb.get(), &cert, CBS_ASN1_SEQUENCE) ||
!CBB_add_asn1(&cert, &tbs_cert, CBS_ASN1_SEQUENCE) ||
// version: v3
!CBB_add_asn1(&tbs_cert, &version,
CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||
!CBB_add_asn1_uint64(&version, 2) ||
// serialNumber
!CBB_add_asn1_uint64(&tbs_cert, kMtcCaCertSerialNumber) ||
// signature (TBSCertificate)
!der::EncodeUnsignedAlgorithmIdentifier(&tbs_cert) ||
// issuer
!der::EncodeUnsignedIssuerName(&tbs_cert) ||
// validity
!CBB_add_bytes(&tbs_cert, kMtcCaCertValidity,
sizeof(kMtcCaCertValidity)) ||
// subject
!CBB_add_asn1(&tbs_cert, &subject, CBS_ASN1_SEQUENCE) ||
!CBB_add_asn1(&subject, &rdn, CBS_ASN1_SET) ||
!CBB_add_asn1(&rdn, &attr, CBS_ASN1_SEQUENCE)) {
return nullptr;
}
// draft-ietf-plants-merkle-tree-certs, Section 5.1: The CA ID's distinguished
// name has a single attribute of type id-rdna-trustAnchorID, whose value is a
// RELATIVE-OID containing the CA ID.
if (!CBB_add_asn1_element(&attr, CBS_ASN1_OBJECT, kRdnaTrustAnchorIDOid,
sizeof(kRdnaTrustAnchorIDOid)) ||
!CBB_add_asn1_element(&attr, CBS_ASN1_RELATIVE_OID, ca_id.data(),
ca_id.size())) {
return nullptr;
}
// subjectPublicKeyInfo
if (!CBB_add_bytes(&tbs_cert, ca_cosign_key.data(), ca_cosign_key.size()) ||
// extensions
!CBB_add_asn1(&tbs_cert, &extensions_wrapper,
CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 3) ||
!CBB_add_asn1(&extensions_wrapper, &extensions, CBS_ASN1_SEQUENCE) ||
!der::EncodeExtension(&extensions, kKeyUsageOid, /*critical=*/true,
kMtcCaCertKeyUsage) ||
!der::EncodeExtension(&extensions, kBasicConstraintsOid,
/*critical=*/true, kMtcCaCertBasicConstraints) ||
!der::EncodeExtension(&extensions, kMtcCertificationAuthoritySha256Oid,
/*critical=*/true, CBBAsSpan(mtc_ca_cbb.get())) ||
// Outer signatureAlgorithm in the Certificate
!der::EncodeUnsignedAlgorithmIdentifier(&cert) ||
// signatureValue (BIT STRING of length zero, per RFC 9925)
!CBB_add_asn1(&cert, &signature, CBS_ASN1_BITSTRING) ||
!CBB_add_u8(&signature, 0)) {
return nullptr;
}
if (!CBB_flush(cbb.get())) {
return nullptr;
}
CBS der(CBBAsSpan(cbb.get()));
return UniquePtr<CRYPTO_BUFFER>(CRYPTO_BUFFER_new_from_CBS(&der, pool));
}
BSSL_NAMESPACE_END