/* * Copyright (c) 2020, Ali Mohammad Pur * Copyright (c) 2023, stelar7 * * SPDX-License-Identifier: BSD-2-Clause */ #include "Certificate.h" #include #include #include #include #include #include #include namespace { // Used by ASN1 macros static String s_error_string; } namespace Crypto::Certificate { static ErrorOr parse_certificate_version(Crypto::ASN1::Decoder& decoder, Vector current_scope) { // Version ::= INTEGER {v1(0), v2(1), v3(2)} if (auto tag = decoder.peek(); !tag.is_error() && tag.value().type == Crypto::ASN1::Type::Constructed) { ENTER_SCOPE("Version"sv); READ_OBJECT(Integer, Crypto::UnsignedBigInteger, version); if (version > 3) { ERROR_WITH_SCOPE(TRY(String::formatted("Invalid version value at {}", current_scope))); } EXIT_SCOPE(); return version; } else { return Crypto::UnsignedBigInteger { 0 }; } } static ErrorOr parse_serial_number(Crypto::ASN1::Decoder& decoder, Vector current_scope) { // CertificateSerialNumber ::= INTEGER PUSH_SCOPE("CertificateSerialNumber"sv); READ_OBJECT(Integer, Crypto::UnsignedBigInteger, serial); POP_SCOPE(); return serial; } ErrorOr> parse_ec_parameters(Crypto::ASN1::Decoder& decoder, Vector current_scope) { // ECParameters ::= CHOICE { // namedCurve OBJECT IDENTIFIER // } PUSH_SCOPE("ECParameters"sv); READ_OBJECT(ObjectIdentifier, Vector, named_curve); POP_SCOPE(); constexpr static Array, 3> known_curve_identifiers { ASN1::secp256r1_oid, ASN1::secp384r1_oid, ASN1::secp521r1_oid }; bool is_known_curve = false; for (auto const& curves : known_curve_identifiers) { if (curves == named_curve.span()) { is_known_curve = true; break; } } if (!is_known_curve) { ERROR_WITH_SCOPE(TRY(String::formatted("Unknown named curve {}", named_curve))); } return named_curve; } static ErrorOr parse_algorithm_identifier(Crypto::ASN1::Decoder& decoder, Vector current_scope) { // AlgorithmIdentifier{ALGORITHM:SupportedAlgorithms} ::= SEQUENCE { // algorithm ALGORITHM.&id({SupportedAlgorithms}), // parameters ALGORITHM.&Type({SupportedAlgorithms}{@algorithm}) OPTIONAL, // ... } ENTER_TYPED_SCOPE(Sequence, "AlgorithmIdentifier"sv); PUSH_SCOPE("algorithm"sv); READ_OBJECT(ObjectIdentifier, Vector, algorithm); POP_SCOPE(); constexpr static Array, 12> known_algorithm_identifiers { ASN1::rsa_encryption_oid, ASN1::rsa_md5_encryption_oid, ASN1::rsa_sha1_encryption_oid, ASN1::rsa_sha256_encryption_oid, ASN1::rsa_sha384_encryption_oid, ASN1::rsa_sha512_encryption_oid, ASN1::ecdsa_with_sha256_encryption_oid, ASN1::ecdsa_with_sha384_encryption_oid, ASN1::ec_public_key_encryption_oid, ASN1::x25519_oid, ASN1::ed25519_oid, ASN1::x448_oid, }; bool is_known_algorithm = false; for (auto const& inner : known_algorithm_identifiers) { if (inner == algorithm.span()) { is_known_algorithm = true; break; } } if (!is_known_algorithm) { ERROR_WITH_SCOPE(TRY(String::formatted("Unknown algorithm {}", algorithm))); } // -- When the following OIDs are used in an AlgorithmIdentifier, the // -- parameters MUST be present and MUST be NULL. // sha256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 1 } // sha256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 4 } // sha256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 5 } // sha256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 11 } // sha384WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 12 } // sha512WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 13 } // sha224WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 14 } constexpr static Array, 8> rsa_null_algorithms = { ASN1::rsa_encryption_oid, ASN1::rsa_md5_encryption_oid, ASN1::rsa_sha1_encryption_oid, ASN1::rsa_sha256_encryption_oid, ASN1::rsa_sha384_encryption_oid, ASN1::rsa_sha512_encryption_oid, ASN1::rsa_sha224_encryption_oid, }; bool is_rsa_null_algorithm = false; for (auto const& inner : rsa_null_algorithms) { if (inner == algorithm.span()) { is_rsa_null_algorithm = true; break; } } if (is_rsa_null_algorithm) { PUSH_SCOPE("RSA null parameter"sv); READ_OBJECT(Null, void*, forced_null); (void)forced_null; POP_SCOPE(); EXIT_SCOPE(); return AlgorithmIdentifier(algorithm); } // https://www.ietf.org/rfc/rfc5758.txt // When the ecdsa-with-SHA224, ecdsa-with-SHA256, ecdsa-with-SHA384, or // ecdsa-with-SHA512 algorithm identifier appears in the algorithm field // as an AlgorithmIdentifier, the encoding MUST omit the parameters // field. // https://datatracker.ietf.org/doc/html/rfc8410#section-9 // For all of the OIDs, the parameters MUST be absent. constexpr static Array, 8> no_parameter_algorithms = { ASN1::ecdsa_with_sha224_encryption_oid, ASN1::ecdsa_with_sha256_encryption_oid, ASN1::ecdsa_with_sha384_encryption_oid, ASN1::ecdsa_with_sha512_encryption_oid, ASN1::x25519_oid, ASN1::x448_oid, ASN1::ed25519_oid, ASN1::ed448_oid }; bool is_no_parameter_algorithm = false; for (auto const& inner : no_parameter_algorithms) { if (inner == algorithm.span()) { is_no_parameter_algorithm = true; } } if (is_no_parameter_algorithm) { EXIT_SCOPE(); return AlgorithmIdentifier(algorithm); } if (algorithm.span() == ASN1::ec_public_key_encryption_oid.span()) { // The parameters associated with id-ecPublicKey SHOULD be absent or ECParameters, // and NULL is allowed to support legacy implementations. if (decoder.eof()) { EXIT_SCOPE(); return AlgorithmIdentifier(algorithm); } auto tag = TRY(decoder.peek()); if (tag.kind == Crypto::ASN1::Kind::Null) { PUSH_SCOPE("ecPublicKey null parameter"sv); READ_OBJECT(Null, void*, forced_null); (void)forced_null; POP_SCOPE(); EXIT_SCOPE(); return AlgorithmIdentifier(algorithm); } auto algorithm_identifier = AlgorithmIdentifier(algorithm); algorithm_identifier.ec_parameters = TRY(parse_ec_parameters(decoder, current_scope)); EXIT_SCOPE(); return algorithm_identifier; } ERROR_WITH_SCOPE(TRY(String::formatted("Unhandled parameters for algorithm {}", algorithm))); } static ErrorOr parse_name(Crypto::ASN1::Decoder& decoder, Vector current_scope) { RelativeDistinguishedName rdn {}; // Name ::= Choice { // rdn_sequence RDNSequence // } // NOTE: since this is the only alternative, there's no index // RDNSequence ::= Sequence OF RelativeDistinguishedName ENTER_TYPED_SCOPE(Sequence, "Name"sv); while (!decoder.eof()) { // RelativeDistinguishedName ::= Set OF AttributeTypeAndValue ENTER_TYPED_SCOPE(Set, "RDNSequence"sv); while (!decoder.eof()) { // AttributeTypeAndValue ::= Sequence { // type AttributeType, // value AttributeValue // } ENTER_TYPED_SCOPE(Sequence, "AttributeTypeAndValue"sv); // AttributeType ::= ObjectIdentifier PUSH_SCOPE("AttributeType"sv) READ_OBJECT(ObjectIdentifier, Vector, attribute_type_oid); POP_SCOPE(); // AttributeValue ::= Any PUSH_SCOPE("AttributeValue"sv) READ_OBJECT(PrintableString, StringView, attribute_value); POP_SCOPE(); auto attribute_type_string = TRY(String::join("."sv, attribute_type_oid)); auto attribute_value_string = TRY(String::from_utf8(attribute_value)); TRY(rdn.set(move(attribute_type_string), move(attribute_value_string))); EXIT_SCOPE(); } EXIT_SCOPE(); } EXIT_SCOPE(); return rdn; } static ErrorOr parse_time(Crypto::ASN1::Decoder& decoder, Vector current_scope) { // Time ::= Choice { // utc_time UTCTime, // general_time GeneralizedTime // } auto tag = TRY(decoder.peek()); if (tag.kind == Crypto::ASN1::Kind::UTCTime) { PUSH_SCOPE("UTCTime"sv); READ_OBJECT(UTCTime, StringView, utc_time); auto parse_result = Crypto::ASN1::parse_utc_time(utc_time); if (!parse_result.has_value()) { ERROR_WITH_SCOPE(TRY(String::formatted("Failed to parse UTCTime {}", utc_time))); } POP_SCOPE(); return parse_result.release_value(); } if (tag.kind == Crypto::ASN1::Kind::GeneralizedTime) { PUSH_SCOPE("GeneralizedTime"sv); READ_OBJECT(UTCTime, StringView, generalized_time); auto parse_result = Crypto::ASN1::parse_generalized_time(generalized_time); if (!parse_result.has_value()) { ERROR_WITH_SCOPE(TRY(String::formatted("Failed to parse GeneralizedTime {}", generalized_time))); } POP_SCOPE(); return parse_result.release_value(); } ERROR_WITH_SCOPE(TRY(String::formatted("Unrecognised Time format {}", kind_name(tag.kind)))); } static ErrorOr parse_validity(Crypto::ASN1::Decoder& decoder, Vector current_scope) { Validity validity {}; // Validity ::= SEQUENCE { // notBefore Time, // notAfter Time } ENTER_TYPED_SCOPE(Sequence, "Validity"sv); validity.not_before = TRY(parse_time(decoder, current_scope)); validity.not_after = TRY(parse_time(decoder, current_scope)); EXIT_SCOPE(); return validity; } ErrorOr parse_subject_public_key_info(Crypto::ASN1::Decoder& decoder, Vector current_scope) { // SubjectPublicKeyInfo ::= Sequence { // algorithm AlgorithmIdentifier, // subject_public_key BitString // } SubjectPublicKey public_key; ENTER_TYPED_SCOPE(Sequence, "SubjectPublicKeyInfo"sv); public_key.algorithm = TRY(parse_algorithm_identifier(decoder, current_scope)); PUSH_SCOPE("subjectPublicKey"sv); READ_OBJECT(BitString, Crypto::ASN1::BitStringView, value); POP_SCOPE(); public_key.raw_key = TRY(ByteBuffer::copy(TRY(value.raw_bytes()))); if (public_key.algorithm.identifier.span() == ASN1::rsa_encryption_oid.span()) { auto maybe_key = Crypto::PK::RSA::parse_rsa_key(public_key.raw_key, false, current_scope); if (maybe_key.is_error()) { ERROR_WITH_SCOPE(maybe_key.release_error()); } public_key.rsa = move(maybe_key.release_value().public_key); EXIT_SCOPE(); return public_key; } if (public_key.algorithm.identifier.span() == ASN1::ec_public_key_encryption_oid.span()) { auto maybe_key = Crypto::PK::EC::parse_ec_key(public_key.raw_key, false, current_scope); if (maybe_key.is_error()) { ERROR_WITH_SCOPE(maybe_key.release_error()); } public_key.ec = move(maybe_key.release_value().public_key); EXIT_SCOPE(); return public_key; } // https://datatracker.ietf.org/doc/html/rfc8410#section-9 // For all of the OIDs, the parameters MUST be absent. constexpr static Array, 5> no_parameter_algorithms = { ASN1::ec_public_key_encryption_oid, ASN1::x25519_oid, ASN1::x448_oid, ASN1::ed25519_oid, ASN1::ed448_oid }; for (auto const& inner : no_parameter_algorithms) { if (public_key.algorithm.identifier.span() == inner) { // Note: Raw key is already stored, so we can just exit out at this point. EXIT_SCOPE(); return public_key; } } String algo_oid = TRY(String::join("."sv, public_key.algorithm.identifier)); ERROR_WITH_SCOPE(TRY(String::formatted("Unhandled algorithm {}", algo_oid))); } // https://www.rfc-editor.org/rfc/rfc5208#section-5 ErrorOr parse_private_key_info(Crypto::ASN1::Decoder& decoder, Vector current_scope) { // PrivateKeyInfo ::= SEQUENCE { // version Version, // privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, // privateKey PrivateKey, // attributes [0] IMPLICIT Attributes OPTIONAL // } PrivateKey private_key; ENTER_TYPED_SCOPE(Sequence, "PrivateKeyInfo"sv); READ_OBJECT(Integer, Crypto::UnsignedBigInteger, version); if (version != 0) { ERROR_WITH_SCOPE(TRY(String::formatted("Invalid version value at {}", current_scope))); } private_key.algorithm = TRY(parse_algorithm_identifier(decoder, current_scope)); PUSH_SCOPE("privateKey"sv); READ_OBJECT(OctetString, StringView, value); POP_SCOPE(); private_key.raw_key = TRY(ByteBuffer::copy(value.bytes())); if (private_key.algorithm.identifier.span() == ASN1::rsa_encryption_oid.span()) { auto maybe_key = Crypto::PK::RSA::parse_rsa_key(value.bytes(), true, current_scope); if (maybe_key.is_error()) { ERROR_WITH_SCOPE(maybe_key.release_error()); } private_key.rsa = move(maybe_key.release_value().private_key); EXIT_SCOPE(); return private_key; } if (private_key.algorithm.identifier.span() == ASN1::ec_public_key_encryption_oid.span()) { auto maybe_key = Crypto::PK::EC::parse_ec_key(value.bytes(), true, current_scope); if (maybe_key.is_error()) { ERROR_WITH_SCOPE(maybe_key.release_error()); } private_key.ec = move(maybe_key.release_value().private_key); EXIT_SCOPE(); return private_key; } // https://datatracker.ietf.org/doc/html/rfc8410#section-9 // For all of the OIDs, the parameters MUST be absent. constexpr static Array, 5> no_parameter_algorithms = { ASN1::ec_public_key_encryption_oid, ASN1::x25519_oid, ASN1::x448_oid, ASN1::ed25519_oid, ASN1::ed448_oid }; for (auto const& inner : no_parameter_algorithms) { if (private_key.algorithm.identifier.span() == inner) { // Note: Raw key is already stored, so we can just exit out at this point. EXIT_SCOPE(); return private_key; } } String algo_oid = TRY(String::join("."sv, private_key.algorithm.identifier)); ERROR_WITH_SCOPE(TRY(String::formatted("Unhandled algorithm {}", algo_oid))); } static ErrorOr parse_unique_identifier(Crypto::ASN1::Decoder& decoder, Vector current_scope) { // UniqueIdentifier ::= BIT STRING PUSH_SCOPE("UniqueIdentifier"sv); READ_OBJECT(BitString, Crypto::ASN1::BitStringView, value); POP_SCOPE(); return value; } static ErrorOr parse_general_name(Crypto::ASN1::Decoder& decoder, Vector current_scope) { // GeneralName ::= CHOICE { // otherName [0] INSTANCE OF OTHER-NAME, // rfc822Name [1] IA5String, // dNSName [2] IA5String, // x400Address [3] ORAddress, // directoryName [4] Name, // ediPartyName [5] EDIPartyName, // uniformResourceIdentifier [6] IA5String, // iPAddress [7] OCTET STRING, // registeredID [8] OBJECT IDENTIFIER, // } auto tag = TRY(decoder.peek()); auto tag_value = static_cast(tag.kind); switch (tag_value) { case 0: // Note: We don't know how to use this. PUSH_SCOPE("otherName"sv) DROP_OBJECT(); POP_SCOPE(); break; case 1: { PUSH_SCOPE("rfc822Name"sv) READ_OBJECT(IA5String, StringView, name); POP_SCOPE(); return String::from_utf8(name); } case 2: { PUSH_SCOPE("dNSName"sv) READ_OBJECT(IA5String, StringView, name); POP_SCOPE(); return String::from_utf8(name); } case 3: // Note: We don't know how to use this. PUSH_SCOPE("x400Address"sv) DROP_OBJECT(); POP_SCOPE(); break; case 4: { PUSH_SCOPE("directoryName"sv); READ_OBJECT(OctetString, StringView, directory_name); Crypto::ASN1::Decoder decoder { directory_name.bytes() }; auto names = TRY(parse_name(decoder, current_scope)); POP_SCOPE(); return names.to_string(); } case 5: // Note: We don't know how to use this. PUSH_SCOPE("ediPartyName"); DROP_OBJECT(); POP_SCOPE(); break; case 6: { PUSH_SCOPE("uniformResourceIdentifier"sv); READ_OBJECT(IA5String, StringView, name); POP_SCOPE(); return String::from_utf8(name); } case 7: { PUSH_SCOPE("iPAddress"sv); READ_OBJECT(OctetString, StringView, ip_addr_sv); IPv4Address ip_addr { ip_addr_sv.bytes().data() }; POP_SCOPE(); return ip_addr.to_string(); } case 8: { PUSH_SCOPE("registeredID"sv); READ_OBJECT(ObjectIdentifier, Vector, identifier); POP_SCOPE(); return String::join("."sv, identifier); } default: ERROR_WITH_SCOPE("Unknown tag in GeneralNames choice"sv); } ERROR_WITH_SCOPE("Unknown tag in GeneralNames choice"sv); } static ErrorOr> parse_general_names(Crypto::ASN1::Decoder& decoder, Vector current_scope) { // GeneralNames ::= Sequence OF GeneralName ENTER_TYPED_SCOPE(Sequence, "GeneralNames"); Vector names; while (!decoder.eof()) { names.append(TRY(parse_general_name(decoder, current_scope))); } EXIT_SCOPE(); return names; } static ErrorOr> parse_subject_alternative_names(Crypto::ASN1::Decoder& decoder, Vector current_scope) { // SubjectAlternativeName ::= GeneralNames PUSH_SCOPE("SubjectAlternativeName"sv); auto values = TRY(parse_general_names(decoder, current_scope)); POP_SCOPE(); return values; } static ErrorOr> parse_issuer_alternative_names(Crypto::ASN1::Decoder& decoder, Vector current_scope) { // issuerAltName ::= GeneralNames PUSH_SCOPE("issuerAltName"sv); auto values = TRY(parse_general_names(decoder, current_scope)); POP_SCOPE(); return values; } static ErrorOr parse_key_usage(Crypto::ASN1::Decoder& decoder, Vector current_scope) { // KeyUsage ::= BIT STRING { // digitalSignature (0), // contentCommitment (1), // keyEncipherment (2), // dataEncipherment (3), // keyAgreement (4), // keyCertSign (5), // cRLSign (6), // encipherOnly (7), // decipherOnly (8) // } PUSH_SCOPE("KeyUsage"sv); READ_OBJECT(BitString, Crypto::ASN1::BitStringView, usage); POP_SCOPE(); return usage; } static ErrorOr parse_basic_constraints(Crypto::ASN1::Decoder& decoder, Vector current_scope) { // BasicConstraints ::= SEQUENCE { // cA BOOLEAN DEFAULT FALSE, // pathLenConstraint INTEGER (0..MAX) OPTIONAL // } BasicConstraints constraints {}; ENTER_TYPED_SCOPE(Sequence, "BasicConstraints"sv); if (decoder.eof()) { EXIT_SCOPE(); return constraints; } auto ca_tag = TRY(decoder.peek()); if (ca_tag.kind == Crypto::ASN1::Kind::Boolean) { PUSH_SCOPE("cA"sv); READ_OBJECT(Boolean, bool, is_certificate_authority); constraints.is_certificate_authority = is_certificate_authority; POP_SCOPE(); } if (decoder.eof()) { EXIT_SCOPE(); return constraints; } auto path_length_tag = TRY(decoder.peek()); if (path_length_tag.kind == Crypto::ASN1::Kind::Integer) { PUSH_SCOPE("pathLenConstraint"sv); READ_OBJECT(Integer, Crypto::UnsignedBigInteger, path_length_constraint); constraints.path_length_constraint = path_length_constraint; POP_SCOPE(); } EXIT_SCOPE(); return constraints; } static ErrorOr parse_extension(Crypto::ASN1::Decoder& decoder, Vector current_scope, Certificate& certificate) { // Extension ::= Sequence { // extension_id ObjectIdentifier, // critical Boolean DEFAULT false, // extension_value OctetString (DER-encoded) // } ENTER_TYPED_SCOPE(Sequence, "Extension"sv); PUSH_SCOPE("extension_id"sv); READ_OBJECT(ObjectIdentifier, Vector, extension_id); POP_SCOPE(); bool is_critical = false; auto peek = TRY(decoder.peek()); if (peek.kind == Crypto::ASN1::Kind::Boolean) { PUSH_SCOPE("critical"sv); READ_OBJECT(Boolean, bool, extension_critical); is_critical = extension_critical; POP_SCOPE(); } PUSH_SCOPE("extension_value"sv); READ_OBJECT(OctetString, StringView, extension_value); POP_SCOPE(); bool is_known_extension = false; Crypto::ASN1::Decoder extension_decoder { extension_value.bytes() }; Vector extension_scope {}; if (extension_id == ASN1::subject_alternative_name_oid) { is_known_extension = true; auto alternate_names = TRY(parse_subject_alternative_names(extension_decoder, extension_scope)); certificate.SAN = alternate_names; } if (extension_id == ASN1::key_usage_oid) { is_known_extension = true; auto usage = TRY(parse_key_usage(extension_decoder, extension_scope)); certificate.is_allowed_to_sign_certificate = usage.get(5); } if (extension_id == ASN1::basic_constraints_oid) { is_known_extension = true; auto constraints = TRY(parse_basic_constraints(extension_decoder, extension_scope)); certificate.is_certificate_authority = constraints.is_certificate_authority; certificate.path_length_constraint = constraints.path_length_constraint.to_u64(); } if (extension_id == ASN1::issuer_alternative_name_oid) { is_known_extension = true; auto alternate_names = TRY(parse_issuer_alternative_names(extension_decoder, extension_scope)); certificate.IAN = alternate_names; } EXIT_SCOPE(); if (is_critical && !is_known_extension) { ERROR_WITH_SCOPE(TRY(String::formatted("Extension {} is critical, but we do not support it", extension_id))); } if (!is_known_extension) { dbgln_if(TLS_DEBUG, TRY(String::formatted("{}: Unhandled extension: {}", current_scope, extension_id))); } return {}; } static ErrorOr parse_extensions(Crypto::ASN1::Decoder& decoder, Vector current_scope, Certificate& certificate) { // Extensions ::= Sequence OF Extension ENTER_TYPED_SCOPE(Sequence, "Extensions"sv); while (!decoder.eof()) { TRY(parse_extension(decoder, current_scope, certificate)); } EXIT_SCOPE(); return {}; } static ErrorOr parse_tbs_certificate(Crypto::ASN1::Decoder& decoder, Vector current_scope) { // TBSCertificate ::= SEQUENCE { // version [0] Version DEFAULT v1, // serialNumber CertificateSerialNumber, // signature AlgorithmIdentifier{{SupportedAlgorithms}}, // issuer Name, // validity Validity, // subject Name, // subjectPublicKeyInfo SubjectPublicKeyInfo, // issuerUniqueIdentifier [1] IMPLICIT UniqueIdentifier OPTIONAL, // ..., // [[2: -- if present, version shall be v2 or v3 // subjectUniqueIdentifier [2] IMPLICIT UniqueIdentifier OPTIONAL]], // [[3: -- if present, version shall be v2 or v3 // extensions [3] Extensions OPTIONAL]] // -- If present, version shall be v3]] // } // Note: Parse out the ASN.1 of this object, since its used for TLS verification. // To do this, we get the bytes of our parent, the size of ourself, and slice the parent buffer. auto pre_cert_buffer = TRY(decoder.peek_entry_bytes()); // FIXME: Dont assume this value. // Note: we assume this to be 4. 1 for the tag, and 3 for the length. auto entry_length_byte_count = 4; ENTER_TYPED_SCOPE(Sequence, "TBSCertificate"sv); auto post_cert_buffer = TRY(decoder.peek_entry_bytes()); if (pre_cert_buffer.size() < post_cert_buffer.size() + entry_length_byte_count) { ERROR_WITH_SCOPE("Unexpected end of file"); } Certificate certificate; certificate.version = TRY(parse_certificate_version(decoder, current_scope)).to_u64(); certificate.serial_number = TRY(parse_serial_number(decoder, current_scope)); certificate.algorithm = TRY(parse_algorithm_identifier(decoder, current_scope)); certificate.issuer = TRY(parse_name(decoder, current_scope)); certificate.validity = TRY(parse_validity(decoder, current_scope)); certificate.subject = TRY(parse_name(decoder, current_scope)); certificate.public_key = TRY(parse_subject_public_key_info(decoder, current_scope)); certificate.tbs_asn1 = TRY(ByteBuffer::copy(pre_cert_buffer.slice(0, post_cert_buffer.size() + entry_length_byte_count))); if (!decoder.eof()) { auto tag = TRY(decoder.peek()); if (static_cast(tag.kind) == 1) { REWRITE_TAG(BitString) TRY(parse_unique_identifier(decoder, current_scope)); } } if (!decoder.eof()) { auto tag = TRY(decoder.peek()); if (static_cast(tag.kind) == 2) { REWRITE_TAG(BitString) TRY(parse_unique_identifier(decoder, current_scope)); } } if (!decoder.eof()) { auto tag = TRY(decoder.peek()); if (static_cast(tag.kind) == 3) { REWRITE_TAG(Sequence) ENTER_TYPED_SCOPE(Sequence, "extensions"sv); TRY(parse_extensions(decoder, current_scope, certificate)); EXIT_SCOPE(); } } if (!decoder.eof()) { ERROR_WITH_SCOPE("Reached end of TBS parse with more data left"sv); } certificate.is_self_issued = TRY(certificate.issuer.to_string()) == TRY(certificate.subject.to_string()); EXIT_SCOPE(); return certificate; } ErrorOr Certificate::parse_certificate(ReadonlyBytes buffer, bool) { Crypto::ASN1::Decoder decoder { buffer }; Vector current_scope {}; // Certificate ::= SIGNED{TBSCertificate} // SIGNED{ToBeSigned} ::= SEQUENCE { // toBeSigned ToBeSigned, // COMPONENTS OF SIGNATURE{ToBeSigned}, // } // SIGNATURE{ToBeSigned} ::= SEQUENCE { // algorithmIdentifier AlgorithmIdentifier{{SupportedAlgorithms}}, // encrypted ENCRYPTED-HASH{ToBeSigned}, // } // ENCRYPTED-HASH{ToBeSigned} ::= BIT STRING (CONSTRAINED BY { // -- shall be the result of applying a hashing procedure to the DER-encoded (see 6.2) // -- octets of a value of -- ToBeSigned -- and then applying an encipherment procedure // -- to those octets -- } ) ENTER_TYPED_SCOPE(Sequence, "Certificate"sv); Certificate certificate = TRY(parse_tbs_certificate(decoder, current_scope)); certificate.original_asn1 = TRY(ByteBuffer::copy(buffer)); certificate.signature_algorithm = TRY(parse_algorithm_identifier(decoder, current_scope)); PUSH_SCOPE("signature"sv); READ_OBJECT(BitString, Crypto::ASN1::BitStringView, signature); certificate.signature_value = TRY(ByteBuffer::copy(TRY(signature.raw_bytes()))); POP_SCOPE(); if (!decoder.eof()) { ERROR_WITH_SCOPE("Reached end of Certificate parse with more data left"sv); } EXIT_SCOPE(); return certificate; } #undef PUSH_SCOPE #undef ENTER_SCOPE #undef ENTER_TYPED_SCOPE #undef POP_SCOPE #undef EXIT_SCOPE #undef READ_OBJECT #undef DROP_OBJECT #undef REWRITE_TAG ErrorOr RelativeDistinguishedName::to_string() const { #define ADD_IF_RECOGNIZED(identifier, shorthand_code) \ if (member_identifier == identifier) { \ cert_name.appendff("\\{}={}", shorthand_code, value); \ continue; \ } StringBuilder cert_name; for (auto const& [member_identifier, value] : m_members) { ADD_IF_RECOGNIZED(enum_value(ASN1::AttributeType::SerialNumber), "SERIALNUMBER"); ADD_IF_RECOGNIZED(enum_value(ASN1::AttributeType::Email), "MAIL"); ADD_IF_RECOGNIZED(enum_value(ASN1::AttributeType::Title), "T"); ADD_IF_RECOGNIZED(enum_value(ASN1::AttributeType::PostalCode), "PC"); ADD_IF_RECOGNIZED(enum_value(ASN1::AttributeType::DnQualifier), "DNQ"); ADD_IF_RECOGNIZED(enum_value(ASN1::AttributeType::GivenName), "GIVENNAME"); ADD_IF_RECOGNIZED(enum_value(ASN1::AttributeType::Surname), "SN"); ADD_IF_RECOGNIZED(enum_value(ASN1::AttributeType::Cn), "CN"); ADD_IF_RECOGNIZED(enum_value(ASN1::AttributeType::L), "L"); ADD_IF_RECOGNIZED(enum_value(ASN1::AttributeType::St), "ST"); ADD_IF_RECOGNIZED(enum_value(ASN1::AttributeType::O), "O"); ADD_IF_RECOGNIZED(enum_value(ASN1::AttributeType::Ou), "OU"); ADD_IF_RECOGNIZED(enum_value(ASN1::AttributeType::C), "C"); ADD_IF_RECOGNIZED(enum_value(ASN1::AttributeType::Street), "STREET"); ADD_IF_RECOGNIZED(enum_value(ASN1::AttributeType::Dc), "DC"); ADD_IF_RECOGNIZED(enum_value(ASN1::AttributeType::Uid), "UID"); cert_name.appendff("\\{}={}", member_identifier, value); } #undef ADD_IF_RECOGNIZED return cert_name.to_string(); } bool Certificate::is_valid() const { auto now = UnixDateTime::now(); if (now < validity.not_before) { dbgln("certificate expired (not yet valid, signed for {})", Core::DateTime::from_timestamp(validity.not_before.seconds_since_epoch())); return false; } if (validity.not_after < now) { dbgln("certificate expired (expiry date {})", Core::DateTime::from_timestamp(validity.not_after.seconds_since_epoch())); return false; } return true; } // https://www.ietf.org/rfc/rfc5280.html#page-12 bool Certificate::is_self_signed() { if (m_is_self_signed.has_value()) return *m_is_self_signed; // Self-signed certificates are self-issued certificates where the digital // signature may be verified by the public key bound into the certificate. if (!this->is_self_issued) m_is_self_signed.emplace(false); // FIXME: Actually check if we sign ourself m_is_self_signed.emplace(true); return *m_is_self_signed; } }