Forráskód Böngészése

LibTLS: Change Certificate parsing to use ErrorOr

Loads of changes that are tightly connected... :/
* Change lambdas to static functions
* Add spec docs to those functions
* Keep the current scope around as a parameter
* Add wrapping classes for some Certificate members
* Parse ec and ecdsa data from certificates
stelar7 2 éve
szülő
commit
d527edf0ab

+ 1 - 1
Meta/Lagom/Fuzzers/FuzzASN1.cpp

@@ -10,7 +10,7 @@
 
 extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
 {
-    (void)TLS::Certificate::parse_asn1({ data, size });
+    (void)TLS::Certificate::parse_certificate({ data, size });
 
     return 0;
 }

+ 26 - 7
Userland/Applications/CertificateSettings/CertificateStore.cpp

@@ -5,6 +5,7 @@
  */
 
 #include "CertificateStore.h"
+#include <AK/String.h>
 #include <Applications/CertificateSettings/CertificateStoreGML.h>
 #include <LibCrypto/ASN1/PEM.h>
 #include <LibFileSystem/FileSystem.h>
@@ -66,12 +67,24 @@ GUI::Variant CertificateStoreModel::data(GUI::ModelIndex const& index, GUI::Mode
     auto cert = m_certificates.at(index.row());
 
     switch (index.column()) {
-    case Column::IssuedTo:
-        return cert.subject.subject.is_empty() ? cert.subject.unit : cert.subject.subject;
-    case Column::IssuedBy:
-        return cert.issuer.subject.is_empty() ? cert.issuer.unit : cert.issuer.subject;
+    case Column::IssuedTo: {
+        auto issued_to = cert.subject.common_name();
+        if (issued_to.is_empty()) {
+            issued_to = cert.subject.organizational_unit();
+        }
+
+        return issued_to;
+    }
+    case Column::IssuedBy: {
+        auto issued_by = cert.issuer.common_name();
+        if (issued_by.is_empty()) {
+            issued_by = cert.issuer.organizational_unit();
+        }
+
+        return issued_by;
+    }
     case Column::Expire:
-        return cert.not_after.to_deprecated_string("%Y-%m-%d"sv);
+        return cert.validity.not_after.to_deprecated_string("%Y-%m-%d"sv);
     default:
         VERIFY_NOT_REACHED();
     }
@@ -128,8 +141,14 @@ ErrorOr<void> CertificateStoreWidget::export_pem()
     auto real_index = m_root_ca_proxy_model->map_to_source(index);
     auto cert = m_root_ca_model->get(real_index.row());
 
-    auto filename = cert.subject.subject.is_empty() ? cert.subject.unit : cert.subject.subject;
-    auto file = FileSystemAccessClient::Client::the().save_file(window(), filename.replace(" "sv, "_"sv), "pem"sv);
+    String filename = cert.subject.common_name();
+    if (filename.is_empty()) {
+        filename = cert.subject.organizational_unit();
+    }
+
+    filename = TRY(filename.replace(" "sv, "_"sv, ReplaceMode::All));
+
+    auto file = FileSystemAccessClient::Client::the().save_file(window(), filename.to_deprecated_string(), "pem"sv);
     if (file.is_error())
         return {};
 

+ 807 - 464
Userland/Libraries/LibTLS/Certificate.cpp

@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
+ * Copyright (c) 2023, stelar7 <dudedbz@gmail.com>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -10,536 +11,878 @@
 #include <LibCrypto/ASN1/ASN1.h>
 #include <LibCrypto/ASN1/DER.h>
 #include <LibCrypto/ASN1/PEM.h>
+#include <LibTLS/CipherSuite.h>
 
 namespace TLS {
 
-constexpr static Array<int, 4>
-    common_name_oid { 2, 5, 4, 3 },
-    country_name_oid { 2, 5, 4, 6 },
-    locality_name_oid { 2, 5, 4, 7 },
-    organization_name_oid { 2, 5, 4, 10 },
-    organizational_unit_name_oid { 2, 5, 4, 11 };
-
 constexpr static Array<int, 7>
     rsa_encryption_oid { 1, 2, 840, 113549, 1, 1, 1 },
     rsa_md5_encryption_oid { 1, 2, 840, 113549, 1, 1, 4 },
     rsa_sha1_encryption_oid { 1, 2, 840, 113549, 1, 1, 5 },
     rsa_sha256_encryption_oid { 1, 2, 840, 113549, 1, 1, 11 },
     rsa_sha384_encryption_oid { 1, 2, 840, 113549, 1, 1, 12 },
-    rsa_sha512_encryption_oid { 1, 2, 840, 113549, 1, 1, 13 };
+    rsa_sha512_encryption_oid { 1, 2, 840, 113549, 1, 1, 13 },
+    rsa_sha224_encryption_oid { 1, 2, 840, 113549, 1, 1, 14 },
+    ecdsa_with_sha224_encryption_oid { 1, 2, 840, 10045, 4, 3, 1 },
+    ecdsa_with_sha256_encryption_oid { 1, 2, 840, 10045, 4, 3, 2 },
+    ecdsa_with_sha384_encryption_oid { 1, 2, 840, 10045, 4, 3, 3 },
+    ecdsa_with_sha512_encryption_oid { 1, 2, 840, 10045, 4, 3, 3 },
+    ec_public_key_encryption_oid { 1, 2, 840, 10045, 2, 1 };
+
+constexpr static Array<Array<int, 7>, 9> known_algorithm_identifiers {
+    rsa_encryption_oid,
+    rsa_md5_encryption_oid,
+    rsa_sha1_encryption_oid,
+    rsa_sha256_encryption_oid,
+    rsa_sha384_encryption_oid,
+    rsa_sha512_encryption_oid,
+    ecdsa_with_sha256_encryption_oid,
+    ecdsa_with_sha384_encryption_oid,
+    ec_public_key_encryption_oid
+};
+
+constexpr static Array<int, 7>
+    curve_ansip384r1 { 1, 3, 132, 0, 34 },
+    curve_prime256 { 1, 2, 840, 10045, 3, 1, 7 };
+
+constexpr static Array<Array<int, 7>, 9> known_curve_identifiers {
+    curve_ansip384r1,
+    curve_prime256
+};
 
 constexpr static Array<int, 4>
     key_usage_oid { 2, 5, 29, 15 },
     subject_alternative_name_oid { 2, 5, 29, 17 },
+    issuer_alternative_name_oid { 2, 5, 29, 18 },
     basic_constraints_oid { 2, 5, 29, 19 };
 
-Optional<Certificate> Certificate::parse_asn1(ReadonlyBytes buffer, bool)
-{
-#define ENTER_SCOPE_WITHOUT_TYPECHECK(scope)                                               \
-    do {                                                                                   \
-        if (auto result = decoder.enter(); result.is_error()) {                            \
-            dbgln_if(TLS_DEBUG, "Failed to enter object (" scope "): {}", result.error()); \
-            return {};                                                                     \
-        }                                                                                  \
+#define ERROR_WITH_SCOPE(error)                                                                 \
+    do {                                                                                        \
+        return Error::from_string_view(TRY(String::formatted("{}: {}", current_scope, error))); \
     } while (0)
 
-#define ENTER_SCOPE_OR_FAIL(kind_name, scope)                                                                 \
-    do {                                                                                                      \
-        if (auto tag = decoder.peek(); tag.is_error() || tag.value().kind != Crypto::ASN1::Kind::kind_name) { \
-            if constexpr (TLS_DEBUG) {                                                                        \
-                if (tag.is_error())                                                                           \
-                    dbgln(scope " data was invalid: {}", tag.error());                                        \
-                else                                                                                          \
-                    dbgln(scope " data was not of kind " #kind_name);                                         \
-            }                                                                                                 \
-            return {};                                                                                        \
-        }                                                                                                     \
-        ENTER_SCOPE_WITHOUT_TYPECHECK(scope);                                                                 \
+#define ENTER_TYPED_SCOPE(tag_kind_name, scope)                                                                                                               \
+    do {                                                                                                                                                      \
+        if (auto tag = decoder.peek(); tag.is_error() || tag.value().kind != Crypto::ASN1::Kind::tag_kind_name) {                                             \
+            if (tag.is_error())                                                                                                                               \
+                ERROR_WITH_SCOPE(TRY(String::formatted(scope " data was invalid: {}", tag.error())));                                                         \
+            else                                                                                                                                              \
+                ERROR_WITH_SCOPE(TRY(String::formatted(scope " data was not of kind " #tag_kind_name " was {}", Crypto::ASN1::kind_name(tag.value().kind)))); \
+        }                                                                                                                                                     \
+        ENTER_SCOPE(scope);                                                                                                                                   \
     } while (0)
 
-#define EXIT_SCOPE(scope)                                                                  \
-    do {                                                                                   \
-        if (auto error = decoder.leave(); error.is_error()) {                              \
-            dbgln_if(TLS_DEBUG, "Error while exiting scope " scope ": {}", error.error()); \
-            return {};                                                                     \
-        }                                                                                  \
+#define ENTER_SCOPE(scope)                                                                \
+    do {                                                                                  \
+        if (auto result = decoder.enter(); result.is_error()) {                           \
+            ERROR_WITH_SCOPE(TRY(String::formatted("Failed to enter scope: {}", scope))); \
+        }                                                                                 \
+        PUSH_SCOPE(scope)                                                                 \
     } while (0)
 
-#define ENSURE_OBJECT_KIND(_kind_name, scope)                                                                                   \
-    do {                                                                                                                        \
-        if (auto tag = decoder.peek(); tag.is_error() || tag.value().kind != Crypto::ASN1::Kind::_kind_name) {                  \
-            if constexpr (TLS_DEBUG) {                                                                                          \
-                if (tag.is_error())                                                                                             \
-                    dbgln(scope " data was invalid: {}", tag.error());                                                          \
-                else                                                                                                            \
-                    dbgln(scope " data was not of kind " #_kind_name ", it was {}", Crypto::ASN1::kind_name(tag.value().kind)); \
-            }                                                                                                                   \
-            return {};                                                                                                          \
-        }                                                                                                                       \
+#define PUSH_SCOPE(scope) current_scope.append(#scope##sv);
+
+#define EXIT_SCOPE()                                                                             \
+    do {                                                                                         \
+        if (auto error = decoder.leave(); error.is_error()) {                                    \
+            ERROR_WITH_SCOPE(TRY(String::formatted("Failed to exit scope: {}", error.error()))); \
+        }                                                                                        \
+        POP_SCOPE();                                                                             \
     } while (0)
 
-#define READ_OBJECT_OR_FAIL(kind_name, type_name, value_name, scope)                                                   \
-    auto value_name##_result = decoder.read<type_name>(Crypto::ASN1::Class::Universal, Crypto::ASN1::Kind::kind_name); \
-    if (value_name##_result.is_error()) {                                                                              \
-        dbgln_if(TLS_DEBUG, scope " read of kind " #kind_name " failed: {}", value_name##_result.error());             \
-        return {};                                                                                                     \
-    }                                                                                                                  \
+#define POP_SCOPE() current_scope.remove(current_scope.size() - 1);
+
+#define READ_OBJECT(kind_name, type_name, value_name)                                                                    \
+    auto value_name##_result = decoder.read<type_name>(Crypto::ASN1::Class::Universal, Crypto::ASN1::Kind::kind_name);   \
+    if (value_name##_result.is_error()) {                                                                                \
+        ERROR_WITH_SCOPE(TRY(String::formatted("Read of kind " #kind_name " failed: {}", value_name##_result.error()))); \
+    }                                                                                                                    \
     auto value_name = value_name##_result.release_value();
 
-#define DROP_OBJECT_OR_FAIL(scope)                                        \
-    do {                                                                  \
-        if (auto error = decoder.drop(); error.is_error()) {              \
-            dbgln_if(TLS_DEBUG, scope " read failed: {}", error.error()); \
-        }                                                                 \
+#define REWRITE_TAG(kind_name)                                                                                              \
+    auto value_name##_result = decoder.rewrite_tag(Crypto::ASN1::Kind::kind_name);                                          \
+    if (value_name##_result.is_error()) {                                                                                   \
+        ERROR_WITH_SCOPE(TRY(String::formatted("Rewrite of kind " #kind_name " failed: {}", value_name##_result.error()))); \
+    }
+
+#define DROP_OBJECT()                                                                   \
+    do {                                                                                \
+        if (auto error = decoder.drop(); error.is_error()) {                            \
+            ERROR_WITH_SCOPE(TRY(String::formatted("Drop failed: {}", error.error()))); \
+        }                                                                               \
     } while (0)
 
-    Certificate certificate;
-    auto copy_buffer_result = ByteBuffer::copy(buffer.data(), buffer.size());
-    if (copy_buffer_result.is_error())
-        return {};
-    certificate.original_asn1 = copy_buffer_result.release_value();
+static ErrorOr<NamedCurve> oid_to_curve(Vector<int> curve)
+{
+    if (curve == curve_ansip384r1)
+        return NamedCurve::secp384r1;
+    else if (curve == curve_prime256)
+        return NamedCurve::secp256r1;
 
-    Crypto::ASN1::Decoder decoder { buffer };
-    // Certificate ::= Sequence {
-    //     certificate          TBSCertificate,
-    //     signature_algorithm  AlgorithmIdentifier,
-    //     signature_value      BitString
-    // }
-    ENTER_SCOPE_OR_FAIL(Sequence, "Certificate");
-
-    // TBSCertificate ::= Sequence {
-    //     version                  (0) EXPLICIT Version DEFAULT v1,
-    //     serial_number                CertificateSerialNumber,
-    //     signature                    AlgorithmIdentifier,
-    //     issuer                       Name,
-    //     validity                     Validity,
-    //     subject                      Name,
-    //     subject_public_key_info      SubjectPublicKeyInfo,
-    //     issuer_unique_id         (1) IMPLICIT UniqueIdentifier OPTIONAL (if present, version > v1),
-    //     subject_unique_id        (2) IMPLICIT UniqueIdentifier OPTIONAL (if present, version > v1),
-    //     extensions               (3) EXPLICIT Extensions OPTIONAL      (if present, version > v2)
+    return Error::from_string_view(TRY(String::formatted("Unknown curve oid {}", curve)));
+}
+
+static ErrorOr<CertificateKeyAlgorithm> oid_to_algorithm(Vector<int> algorithm)
+{
+    if (algorithm == rsa_encryption_oid)
+        return CertificateKeyAlgorithm::RSA_RSA;
+    else if (algorithm == rsa_md5_encryption_oid)
+        return CertificateKeyAlgorithm::RSA_MD5;
+    else if (algorithm == rsa_sha1_encryption_oid)
+        return CertificateKeyAlgorithm::RSA_SHA1;
+    else if (algorithm == rsa_sha256_encryption_oid)
+        return CertificateKeyAlgorithm::RSA_SHA256;
+    else if (algorithm == rsa_sha384_encryption_oid)
+        return CertificateKeyAlgorithm::RSA_SHA384;
+    else if (algorithm == rsa_sha512_encryption_oid)
+        return CertificateKeyAlgorithm::RSA_SHA512;
+    else if (algorithm == rsa_sha224_encryption_oid)
+        return CertificateKeyAlgorithm::RSA_SHA224;
+    else if (algorithm == ecdsa_with_sha224_encryption_oid)
+        return CertificateKeyAlgorithm::ECDSA_SHA224;
+    else if (algorithm == ecdsa_with_sha256_encryption_oid)
+        return CertificateKeyAlgorithm::ECDSA_SHA256;
+    else if (algorithm == ecdsa_with_sha384_encryption_oid)
+        return CertificateKeyAlgorithm::ECDSA_SHA384;
+    else if (algorithm == ecdsa_with_sha512_encryption_oid)
+        return CertificateKeyAlgorithm::ECDSA_SHA512;
+
+    return Error::from_string_view(TRY(String::formatted("Unknown algorithm oid {}", algorithm)));
+}
+
+static ErrorOr<Crypto::UnsignedBigInteger> parse_version(Crypto::ASN1::Decoder& decoder, Vector<StringView> 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<Crypto::UnsignedBigInteger> parse_serial_number(Crypto::ASN1::Decoder& decoder, Vector<StringView> current_scope)
+{
+    // CertificateSerialNumber ::= INTEGER
+    PUSH_SCOPE("CertificateSerialNumber"sv);
+    READ_OBJECT(Integer, Crypto::UnsignedBigInteger, serial);
+    POP_SCOPE();
+    return serial;
+}
+
+static ErrorOr<NamedCurve> parse_ec_parameters(Crypto::ASN1::Decoder& decoder, Vector<StringView> current_scope)
+{
+    // ECParameters ::= CHOICE {
+    //     namedCurve      OBJECT IDENTIFIER
     // }
-    ENTER_SCOPE_OR_FAIL(Sequence, "Certificate::TBSCertificate");
-
-    // version
-    {
-        // Version :: Integer { v1(0), v2(1), v3(2) } (Optional)
-        if (auto tag = decoder.peek(); !tag.is_error() && tag.value().type == Crypto::ASN1::Type::Constructed) {
-            ENTER_SCOPE_WITHOUT_TYPECHECK("Certificate::version");
-            READ_OBJECT_OR_FAIL(Integer, Crypto::UnsignedBigInteger, value, "Certificate::version");
-            if (!(value < 3)) {
-                dbgln_if(TLS_DEBUG, "Certificate::version Invalid value for version: {}", value.to_base_deprecated(10));
-                return {};
-            }
-            certificate.version = value.words()[0];
-            EXIT_SCOPE("Certificate::version");
-        } else {
-            certificate.version = 0;
+    PUSH_SCOPE("ECParameters"sv);
+    READ_OBJECT(ObjectIdentifier, Vector<int>, named_curve);
+    // Note: namedCurve sometimes has 5 nodes, but we need 7 for the comparison below to work.
+    while (named_curve.size() < 7) {
+        named_curve.append(0);
+    }
+    POP_SCOPE();
+
+    bool is_known_curve = false;
+    for (auto const& curves : known_curve_identifiers) {
+        if (curves.span() == named_curve.span()) {
+            is_known_curve = true;
+            break;
+        }
+    }
+
+    if (!is_known_curve) {
+        ERROR_WITH_SCOPE(TRY(String::formatted("Unknown named curve {}", named_curve)));
+    }
+
+    return oid_to_curve(named_curve);
+}
+
+static ErrorOr<CertificateKeyAlgorithm> parse_algorithm_identifier(Crypto::ASN1::Decoder& decoder, Vector<StringView> 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<int>, algorithm);
+    // Note: ecPublicKey only has 6 nodes, but we need 7 for the comparison below to work.
+    while (algorithm.size() < 7) {
+        algorithm.append(0);
+    }
+    POP_SCOPE();
+
+    bool is_known_algorithm = false;
+    for (auto const& inner : known_algorithm_identifiers) {
+        if (inner.span() == algorithm.span()) {
+            is_known_algorithm = true;
+            break;
         }
     }
 
-    // serial_number
-    {
-        // CertificateSerialNumber :: Integer
-        READ_OBJECT_OR_FAIL(Integer, Crypto::UnsignedBigInteger, value, "Certificate::serial_number");
-        certificate.serial_number = move(value);
-    }
-
-    auto parse_algorithm_identifier = [&](CertificateKeyAlgorithm& field) -> Optional<bool> {
-        // AlgorithmIdentifier ::= Sequence {
-        //     algorithm   ObjectIdentifier,
-        //     parameters  ANY OPTIONAL
-        // }
-        ENTER_SCOPE_OR_FAIL(Sequence, "AlgorithmIdentifier");
-        READ_OBJECT_OR_FAIL(ObjectIdentifier, Vector<int>, identifier, "AlgorithmIdentifier::algorithm");
-        if (identifier == rsa_encryption_oid)
-            field = CertificateKeyAlgorithm ::RSA_RSA;
-        else if (identifier == rsa_md5_encryption_oid)
-            field = CertificateKeyAlgorithm ::RSA_MD5;
-        else if (identifier == rsa_sha1_encryption_oid)
-            field = CertificateKeyAlgorithm ::RSA_SHA1;
-        else if (identifier == rsa_sha256_encryption_oid)
-            field = CertificateKeyAlgorithm ::RSA_SHA256;
-        else if (identifier == rsa_sha384_encryption_oid)
-            field = CertificateKeyAlgorithm ::RSA_SHA384;
-        else if (identifier == rsa_sha512_encryption_oid)
-            field = CertificateKeyAlgorithm ::RSA_SHA512;
-        else
-            return {};
-
-        EXIT_SCOPE("AlgorithmIdentifier");
-        return true;
+    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 }
+    Array<Array<int, 7>, 8> rsa_null_algorithms = {
+        rsa_encryption_oid,
+        rsa_md5_encryption_oid,
+        rsa_sha1_encryption_oid,
+        rsa_sha256_encryption_oid,
+        rsa_sha384_encryption_oid,
+        rsa_sha512_encryption_oid,
+        rsa_sha224_encryption_oid,
     };
 
-    // signature
-    {
-        if (!parse_algorithm_identifier(certificate.algorithm).has_value())
-            return {};
+    bool is_rsa_null_algorithm = false;
+    for (auto const& inner : rsa_null_algorithms) {
+        if (inner.span() == 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 oid_to_algorithm(algorithm);
+    }
+
+    // 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.
+    Array<Array<int, 7>, 8> no_parameter_algorithms = {
+        ecdsa_with_sha224_encryption_oid,
+        ecdsa_with_sha256_encryption_oid,
+        ecdsa_with_sha384_encryption_oid,
+        ecdsa_with_sha512_encryption_oid,
+    };
+
+    bool is_no_parameter_algorithm = false;
+    for (auto const& inner : no_parameter_algorithms) {
+        if (inner.span() == algorithm.span()) {
+            is_no_parameter_algorithm = true;
+        }
+    }
+
+    if (is_no_parameter_algorithm) {
+        EXIT_SCOPE();
+
+        return oid_to_algorithm(algorithm);
     }
 
-    auto parse_name = [&](auto& name_struct) -> Optional<bool> {
-        // Name ::= Choice {
-        //     rdn_sequence RDNSequence
-        // } // NOTE: since this is the only alternative, there's no index
-        // RDNSequence ::= Sequence OF RelativeDistinguishedName
-        ENTER_SCOPE_OR_FAIL(Sequence, "Certificate::TBSCertificate::issuer/subject");
+    if (algorithm.span() == 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 oid_to_algorithm(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 oid_to_algorithm(algorithm);
+        }
+
+        auto ec_parameters = TRY(parse_ec_parameters(decoder, current_scope));
+        EXIT_SCOPE();
+
+        if (ec_parameters == NamedCurve::secp256r1)
+            return CertificateKeyAlgorithm::ECDSA_SECP256R1;
+        else if (ec_parameters == NamedCurve::secp384r1)
+            return CertificateKeyAlgorithm::ECDSA_SECP384R1;
+    }
+
+    ERROR_WITH_SCOPE(TRY(String::formatted("Unhandled parameters for algorithm {}", algorithm)));
+}
+
+static ErrorOr<RelativeDistinguishedName> parse_name(Crypto::ASN1::Decoder& decoder, Vector<StringView> 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
-        // AttributeTypeAndValue ::= Sequence {
-        //     type   AttributeType,
-        //     value  AttributeValue
-        // }
-        // AttributeType ::= ObjectIdentifier
-        // AttributeValue ::= Any
+        ENTER_TYPED_SCOPE(Set, "RDNSequence"sv);
         while (!decoder.eof()) {
-            // Parse only the required fields, and ignore the rest.
-            ENTER_SCOPE_OR_FAIL(Set, "Certificate::TBSCertificate::issuer/subject::$::RelativeDistinguishedName");
-            while (!decoder.eof()) {
-                ENTER_SCOPE_OR_FAIL(Sequence, "Certificate::TBSCertificate::issuer/subject::$::RelativeDistinguishedName::$::AttributeTypeAndValue");
-                ENSURE_OBJECT_KIND(ObjectIdentifier, "Certificate::TBSCertificate::issuer/subject::$::RelativeDistinguishedName::$::AttributeTypeAndValue::type");
-
-                if (auto type_identifier_or_error = decoder.read<Vector<int>>(); !type_identifier_or_error.is_error()) {
-                    // Figure out what type of identifier this is
-                    auto& identifier = type_identifier_or_error.value();
-                    if (identifier == common_name_oid) {
-                        READ_OBJECT_OR_FAIL(PrintableString, StringView, name,
-                            "Certificate::TBSCertificate::issuer/subject::$::RelativeDistinguishedName::$::AttributeTypeAndValue::Value");
-                        name_struct.subject = name;
-                    } else if (identifier == country_name_oid) {
-                        READ_OBJECT_OR_FAIL(PrintableString, StringView, name,
-                            "Certificate::TBSCertificate::issuer/subject::$::RelativeDistinguishedName::$::AttributeTypeAndValue::Value");
-                        name_struct.country = name;
-                    } else if (identifier == locality_name_oid) {
-                        READ_OBJECT_OR_FAIL(PrintableString, StringView, name,
-                            "Certificate::TBSCertificate::issuer/subject::$::RelativeDistinguishedName::$::AttributeTypeAndValue::Value");
-                        name_struct.location = name;
-                    } else if (identifier == organization_name_oid) {
-                        READ_OBJECT_OR_FAIL(PrintableString, StringView, name,
-                            "Certificate::TBSCertificate::issuer/subject::$::RelativeDistinguishedName::$::AttributeTypeAndValue::Value");
-                        name_struct.entity = name;
-                    } else if (identifier == organizational_unit_name_oid) {
-                        READ_OBJECT_OR_FAIL(PrintableString, StringView, name,
-                            "Certificate::TBSCertificate::issuer/subject::$::RelativeDistinguishedName::$::AttributeTypeAndValue::Value");
-                        name_struct.unit = name;
-                    }
-                } else {
-                    dbgln_if(TLS_DEBUG, "Certificate::TBSCertificate::issuer/subject::$::RelativeDistinguishedName::$::AttributeTypeAndValue::type data was invalid: {}", type_identifier_or_error.error());
-                    return {};
-                }
-
-                EXIT_SCOPE("Certificate::TBSCertificate::issuer/subject::$::RelativeDistinguishedName::$::AttributeTypeAndValue");
-            }
-            EXIT_SCOPE("Certificate::TBSCertificate::issuer/subject::$::RelativeDistinguishedName");
+            // AttributeTypeAndValue ::= Sequence {
+            //     type   AttributeType,
+            //     value  AttributeValue
+            // }
+            ENTER_TYPED_SCOPE(Sequence, "AttributeTypeAndValue"sv);
+            // AttributeType ::= ObjectIdentifier
+            PUSH_SCOPE("AttributeType"sv)
+            READ_OBJECT(ObjectIdentifier, Vector<int>, 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(attribute_type_string, attribute_value_string));
+
+            EXIT_SCOPE();
         }
+        EXIT_SCOPE();
+    }
+    EXIT_SCOPE();
 
-        EXIT_SCOPE("Certificate::TBSCertificate::issuer/subject");
-        return true;
-    };
+    return rdn;
+}
 
-    // issuer
-    {
-        if (!parse_name(certificate.issuer).has_value())
-            return {};
+static ErrorOr<Core::DateTime> parse_time(Crypto::ASN1::Decoder& decoder, Vector<StringView> 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();
     }
 
-    // validity
-    {
-        ENTER_SCOPE_OR_FAIL(Sequence, "Certificate::TBSCertificate::Validity");
+    if (tag.kind == Crypto::ASN1::Kind::GeneralizedTime) {
+        PUSH_SCOPE("GeneralizedTime"sv);
 
-        auto parse_time = [&](Core::DateTime& datetime) -> Optional<bool> {
-            // Time ::= Choice {
-            //     utc_time     UTCTime,
-            //     general_time GeneralizedTime
-            // }
-            auto tag = decoder.peek();
-            if (tag.is_error()) {
-                dbgln_if(1, "Certificate::TBSCertificate::Validity::$::Time failed to read tag: {}", tag.error());
-                return {};
-            };
-
-            if (tag.value().kind == Crypto::ASN1::Kind::UTCTime) {
-                READ_OBJECT_OR_FAIL(UTCTime, StringView, time, "Certificate::TBSCertificate::Validity::$");
-                auto result = Crypto::ASN1::parse_utc_time(time);
-                if (!result.has_value()) {
-                    dbgln_if(1, "Certificate::TBSCertificate::Validity::$::Time Invalid UTC Time: {}", time);
-                    return {};
-                }
-                datetime = result.release_value();
-                return true;
-            }
-
-            if (tag.value().kind == Crypto::ASN1::Kind::GeneralizedTime) {
-                READ_OBJECT_OR_FAIL(UTCTime, StringView, time, "Certificate::TBSCertificate::Validity::$");
-                auto result = Crypto::ASN1::parse_generalized_time(time);
-                if (!result.has_value()) {
-                    dbgln_if(1, "Certificate::TBSCertificate::Validity::$::Time Invalid Generalized Time: {}", time);
-                    return {};
-                }
-                datetime = result.release_value();
-                return true;
-            }
-
-            dbgln_if(1, "Unrecognised Time format {}", Crypto::ASN1::kind_name(tag.value().kind));
-            return {};
-        };
-
-        if (!parse_time(certificate.not_before).has_value())
-            return {};
-
-        if (!parse_time(certificate.not_after).has_value())
-            return {};
-
-        EXIT_SCOPE("Certificate::TBSCertificate::Validity");
-    }
-
-    // subject
-    {
-        if (!parse_name(certificate.subject).has_value())
-            return {};
-    }
-
-    // subject_public_key_info
-    {
-        // SubjectPublicKeyInfo ::= Sequence {
-        //     algorithm           AlgorithmIdentifier,
-        //     subject_public_key  BitString
-        // }
-        ENTER_SCOPE_OR_FAIL(Sequence, "Certificate::TBSCertificate::subject_public_key_info");
-
-        if (!parse_algorithm_identifier(certificate.key_algorithm).has_value())
-            return {};
-
-        READ_OBJECT_OR_FAIL(BitString, Crypto::ASN1::BitStringView, value, "Certificate::TBSCertificate::subject_public_key_info::subject_public_key_info");
-        // Note: Once we support other kinds of keys, make sure to check the kind here!
+        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<Validity> parse_validity(Crypto::ASN1::Decoder& decoder, Vector<StringView> 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;
+}
+
+static ErrorOr<SubjectPublicKey> parse_subject_public_key_info(Crypto::ASN1::Decoder& decoder, Vector<StringView> 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();
+
+    switch (public_key.algorithm) {
+    case CertificateKeyAlgorithm::ECDSA_SECP256R1:
+    case CertificateKeyAlgorithm::ECDSA_SECP384R1: {
+        public_key.raw_key = TRY(ByteBuffer::copy(value.raw_bytes()));
+        break;
+    }
+    case CertificateKeyAlgorithm::RSA_RSA: {
+        public_key.raw_key = TRY(ByteBuffer::copy(value.raw_bytes()));
         auto key = Crypto::PK::RSA::parse_rsa_key(value.raw_bytes());
         if (!key.public_key.length()) {
-            dbgln_if(TLS_DEBUG, "Certificate::TBSCertificate::subject_public_key_info::subject_public_key_info: Invalid key");
-            return {};
+            return Error::from_string_literal("Invalid RSA key");
         }
-        certificate.public_key = move(key.public_key);
-        EXIT_SCOPE("Certificate::TBSCertificate::subject_public_key_info");
+
+        public_key.rsa = move(key.public_key);
+        break;
+    }
+    default: {
+        ERROR_WITH_SCOPE(TRY(String::formatted("Unknown algorithm {}", static_cast<u8>(public_key.algorithm))));
+    }
     }
 
-    auto parse_unique_identifier = [&]() -> Optional<bool> {
-        if (certificate.version == 0)
-            return true;
+    EXIT_SCOPE();
+    return public_key;
+}
 
-        auto tag = decoder.peek();
-        if (tag.is_error()) {
-            dbgln_if(TLS_DEBUG, "Certificate::TBSCertificate::*::UniqueIdentifier could not read tag: {}", tag.error());
-            return {};
-        }
+static ErrorOr<Crypto::ASN1::BitStringView> parse_unique_identifier(Crypto::ASN1::Decoder& decoder, Vector<StringView> current_scope)
+{
+    // UniqueIdentifier  ::=  BIT STRING
+    PUSH_SCOPE("UniqueIdentifier"sv);
+    READ_OBJECT(BitString, Crypto::ASN1::BitStringView, value);
+    POP_SCOPE();
 
-        // The spec says to just ignore these.
-        if (static_cast<u8>(tag.value().kind) == 1 || static_cast<u8>(tag.value().kind) == 2)
-            DROP_OBJECT_OR_FAIL("UniqueIdentifier");
+    return value;
+}
 
-        return true;
-    };
+static ErrorOr<String> parse_general_name(Crypto::ASN1::Decoder& decoder, Vector<StringView> 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<u8>(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<int>, identifier);
+        POP_SCOPE();
+        return String::join("."sv, identifier);
+    }
+    default:
+        ERROR_WITH_SCOPE("Unknown tag in GeneralNames choice"sv);
+    }
 
-    // issuer_unique_identifier
-    {
-        if (!parse_unique_identifier().has_value())
-            return {};
-    }
-
-    // subject_unique_identifier
-    {
-        if (!parse_unique_identifier().has_value())
-            return {};
-    }
-
-    // self issued
-    {
-        certificate.is_self_issued = certificate.issuer_identifier_string() == certificate.subject_identifier_string();
-    }
-
-    // extensions
-    {
-        if (certificate.version == 2) {
-            auto tag = decoder.peek();
-            if (tag.is_error()) {
-                dbgln_if(TLS_DEBUG, "Certificate::TBSCertificate::*::UniqueIdentifier could not read tag: {}", tag.error());
-                return {};
-            }
-            if (static_cast<u8>(tag.value().kind) == 3) {
-                // Extensions ::= Sequence OF Extension
-                // Extension ::= Sequence {
-                //     extension_id     ObjectIdentifier,
-                //     critical         Boolean DEFAULT false,
-                //     extension_value  OctetString (DER-encoded)
-                // }
-                ENTER_SCOPE_WITHOUT_TYPECHECK("Certificate::TBSCertificate::Extensions(IMPLICIT)");
-                ENTER_SCOPE_OR_FAIL(Sequence, "Certificate::TBSCertificate::Extensions");
-
-                while (!decoder.eof()) {
-                    ENTER_SCOPE_OR_FAIL(Sequence, "Certificate::TBSCertificate::Extensions::$::Extension");
-                    READ_OBJECT_OR_FAIL(ObjectIdentifier, Vector<int>, extension_id, "Certificate::TBSCertificate::Extensions::$::Extension::extension_id");
-                    bool is_critical = false;
-                    if (auto tag = decoder.peek(); !tag.is_error() && tag.value().kind == Crypto::ASN1::Kind::Boolean) {
-                        // Read the 'critical' property
-                        READ_OBJECT_OR_FAIL(Boolean, bool, critical, "Certificate::TBSCertificate::Extensions::$::Extension::critical");
-                        is_critical = critical;
-                    }
-                    READ_OBJECT_OR_FAIL(OctetString, StringView, extension_value, "Certificate::TBSCertificate::Extensions::$::Extension::extension_value");
-
-                    // Figure out what this extension is.
-                    if (extension_id == subject_alternative_name_oid) {
-                        Crypto::ASN1::Decoder decoder { extension_value.bytes() };
-                        // SubjectAlternativeName ::= GeneralNames
-                        // GeneralNames ::= Sequence OF GeneralName
-                        // GeneralName ::= CHOICE {
-                        //     other_name     (0) OtherName,
-                        //     rfc_822_name   (1) IA5String,
-                        //     dns_name       (2) IA5String,
-                        //     x400Address    (3) ORAddress,
-                        //     directory_name (4) Name,
-                        //     edi_party_name (5) EDIPartyName,
-                        //     uri            (6) IA5String,
-                        //     ip_address     (7) OctetString,
-                        //     registered_id  (8) ObjectIdentifier,
-                        // }
-                        ENTER_SCOPE_OR_FAIL(Sequence, "Certificate::TBSCertificate::Extensions::$::Extension::extension_value::SubjectAlternativeName");
-
-                        while (!decoder.eof()) {
-                            auto tag = decoder.peek();
-                            if (tag.is_error()) {
-                                dbgln_if(TLS_DEBUG, "Certificate::TBSCertificate::Extensions::$::Extension::extension_value::SubjectAlternativeName::$ could not read tag: {}", tag.error());
-                                return {};
-                            }
-
-                            auto tag_value = static_cast<u8>(tag.value().kind);
-                            switch (tag_value) {
-                            case 0:
-                                // OtherName
-                                // We don't know how to use this.
-                                DROP_OBJECT_OR_FAIL("Certificate::TBSCertificate::Extensions::$::Extension::extension_value::SubjectAlternativeName::$::OtherName");
-                                break;
-                            case 1:
-                                // RFC 822 name
-                                // We don't know how to use this.
-                                DROP_OBJECT_OR_FAIL("Certificate::TBSCertificate::Extensions::$::Extension::extension_value::SubjectAlternativeName::$::RFC822Name");
-                                break;
-                            case 2: {
-                                // DNS Name
-                                READ_OBJECT_OR_FAIL(IA5String, StringView, name, "Certificate::TBSCertificate::Extensions::$::Extension::extension_value::SubjectAlternativeName::$::Name");
-                                certificate.SAN.append(name);
-                                break;
-                            }
-                            case 3:
-                                // x400Address
-                                // We don't know how to use this.
-                                DROP_OBJECT_OR_FAIL("Certificate::TBSCertificate::Extensions::$::Extension::extension_value::SubjectAlternativeName::$::X400Address");
-                                break;
-                            case 4:
-                                // Directory name
-                                // We don't know how to use this.
-                                DROP_OBJECT_OR_FAIL("Certificate::TBSCertificate::Extensions::$::Extension::extension_value::SubjectAlternativeName::$::DirectoryName");
-                                break;
-                            case 5:
-                                // edi party name
-                                // We don't know how to use this.
-                                DROP_OBJECT_OR_FAIL("Certificate::TBSCertificate::Extensions::$::Extension::extension_value::SubjectAlternativeName::$::EDIPartyName");
-                                break;
-                            case 6: {
-                                // URI
-                                READ_OBJECT_OR_FAIL(IA5String, StringView, name, "Certificate::TBSCertificate::Extensions::$::Extension::extension_value::SubjectAlternativeName::$::URI");
-                                certificate.SAN.append(name);
-                                break;
-                            }
-                            case 7: {
-                                // IP Address
-                                READ_OBJECT_OR_FAIL(OctetString, StringView, ip_addr_sv, "Certificate::TBSCertificate::Extensions::$::Extension::extension_value::SubjectAlternativeName::$::IPAddress");
-                                IPv4Address ip_addr { ip_addr_sv.bytes().data() };
-                                certificate.SAN.append(ip_addr.to_deprecated_string());
-                                break;
-                            }
-                            case 8:
-                                // Registered ID
-                                // We can't handle these.
-                                DROP_OBJECT_OR_FAIL("Certificate::TBSCertificate::Extensions::$::Extension::extension_value::SubjectAlternativeName::$::RegisteredID");
-                                break;
-                            default:
-                                dbgln_if(TLS_DEBUG, "Unknown tag in SAN choice {}", tag_value);
-                                if (is_critical)
-                                    return {};
-                                else
-                                    DROP_OBJECT_OR_FAIL("Certificate::TBSCertificate::Extensions::$::Extension::extension_value::SubjectAlternativeName::$::???");
-                            }
-                        }
-                    } else if (extension_id == key_usage_oid) {
-                        // RFC5280 section 4.2.1.3: The keyCertSign bit is asserted when the subject public key is used
-                        // for verifying signatures on public key certificates. If the keyCertSign bit is asserted,
-                        // then the cA bit in the basic constraints extension MUST also be asserted.
-                        Crypto::ASN1::Decoder decoder { extension_value.bytes() };
-                        READ_OBJECT_OR_FAIL(BitString, Crypto::ASN1::BitStringView, usage, "Certificate::TBSCertificate::Extensions::$::Extension::extension_value::KeyUsage");
-
-                        // keyCertSign (5)
-                        certificate.is_allowed_to_sign_certificate = usage.get(5);
-                    } else if (extension_id == basic_constraints_oid) {
-                        // RFC5280 section 4.2.1.9: The cA boolean indicates whether the certified public key may be
-                        // used to verify certificate signatures. If the cA boolean is not asserted, then the keyCertSign
-                        // bit in the key usage extension MUST NOT be asserted. If the basic constraints extension is
-                        // not present in a version 3 certificate, or the extension is present but the cA boolean is
-                        // not asserted, then the certified public key MUST NOT be used to verify certificate signatures.
-                        Crypto::ASN1::Decoder decoder { extension_value.bytes() };
-                        ENTER_SCOPE_OR_FAIL(Sequence, "Certificate::TBSCertificate::Extensions::$::Extension::extension_value::BasicConstraints");
-
-                        if (auto tag = decoder.peek(); !tag.is_error() && tag.value().kind == Crypto::ASN1::Kind::Boolean) {
-                            READ_OBJECT_OR_FAIL(Boolean, bool, is_certificate_authority, "Certificate::TBSCertificate::Extensions::$::Extension::extension_value::BasicConstraints::cA");
-                            certificate.is_certificate_authority = is_certificate_authority;
-
-                            if (auto tag = decoder.peek(); !tag.is_error() && tag.value().kind == Crypto::ASN1::Kind::Integer) {
-                                READ_OBJECT_OR_FAIL(Integer, Crypto::UnsignedBigInteger, path_length_constraint, "Certificate::TBSCertificate::Extensions::$::Extension::extension_value::BasicConstraints::pathLenConstraint");
-                                certificate.path_length_constraint = path_length_constraint.to_u64();
-                            }
-                        }
-                    } else {
-                        dbgln_if(TLS_DEBUG, "Certificate::TBSCertificate::Extensions::$::Extension::extension_id: unknown extension {} (critical: {})", extension_id, is_critical);
-                        if (is_critical)
-                            return {};
-                    }
-
-                    EXIT_SCOPE("Certificate::TBSCertificate::Extensions::$::Extension");
-                }
-
-                EXIT_SCOPE("Certificate::TBSCertificate::Extensions");
-                EXIT_SCOPE("Certificate::TBSCertificate::Extensions(IMPLICIT)");
-            }
-        }
+    ERROR_WITH_SCOPE("Unknown tag in GeneralNames choice"sv);
+}
+
+static ErrorOr<Vector<String>> parse_general_names(Crypto::ASN1::Decoder& decoder, Vector<StringView> current_scope)
+{
+    // GeneralNames ::= Sequence OF GeneralName
+    ENTER_TYPED_SCOPE(Sequence, "GeneralNames");
+
+    Vector<String> names;
+    while (!decoder.eof()) {
+        names.append(TRY(parse_general_name(decoder, current_scope)));
+    }
+
+    EXIT_SCOPE();
+
+    return names;
+}
+
+static ErrorOr<Vector<String>> parse_subject_alternative_names(Crypto::ASN1::Decoder& decoder, Vector<StringView> current_scope)
+{
+    // SubjectAlternativeName ::= GeneralNames
+    PUSH_SCOPE("SubjectAlternativeName"sv);
+    auto values = TRY(parse_general_names(decoder, current_scope));
+    POP_SCOPE();
+
+    return values;
+}
+
+static ErrorOr<Vector<String>> parse_issuer_alternative_names(Crypto::ASN1::Decoder& decoder, Vector<StringView> current_scope)
+{
+    // issuerAltName ::= GeneralNames
+    PUSH_SCOPE("issuerAltName"sv);
+    auto values = TRY(parse_general_names(decoder, current_scope));
+    POP_SCOPE();
+
+    return values;
+}
+
+static ErrorOr<Crypto::ASN1::BitStringView> parse_key_usage(Crypto::ASN1::Decoder& decoder, Vector<StringView> 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<BasicConstraints> parse_basic_constraints(Crypto::ASN1::Decoder& decoder, Vector<StringView> 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<void> parse_extension(Crypto::ASN1::Decoder& decoder, Vector<StringView> 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<int>, 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<StringView, 8> extension_scope {};
+    if (extension_id == 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 == 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 == 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();
     }
 
-    EXIT_SCOPE("Certificate::TBSCertificate");
+    if (extension_id == 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<void> parse_extensions(Crypto::ASN1::Decoder& decoder, Vector<StringView> current_scope, Certificate& certificate)
+{
+    // Extensions ::= Sequence OF Extension
+    ENTER_TYPED_SCOPE(Sequence, "Extensions"sv);
 
-    // signature_algorithm
-    {
-        if (!parse_algorithm_identifier(certificate.signature_algorithm).has_value())
-            return {};
+    while (!decoder.eof()) {
+        TRY(parse_extension(decoder, current_scope, certificate));
     }
 
-    // signature_value
-    {
-        READ_OBJECT_OR_FAIL(BitString, Crypto::ASN1::BitStringView, value, "Certificate");
-        auto signature_data_result = ByteBuffer::copy(value.raw_bytes());
-        if (signature_data_result.is_error()) {
-            dbgln("Certificate::signature_value: out of memory");
-            return {};
+    EXIT_SCOPE();
+
+    return {};
+}
+
+static ErrorOr<Certificate> parse_tbs_certificate(Crypto::ASN1::Decoder& decoder, Vector<StringView> 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]]
+    // }
+
+    ENTER_TYPED_SCOPE(Sequence, "TBSCertificate"sv);
+
+    Certificate certificate;
+    certificate.version = TRY(parse_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));
+
+    if (!decoder.eof()) {
+        auto tag = TRY(decoder.peek());
+        if (static_cast<u8>(tag.kind) == 1) {
+            REWRITE_TAG(BitString)
+            TRY(parse_unique_identifier(decoder, current_scope));
+        }
+    }
+
+    if (!decoder.eof()) {
+        auto tag = TRY(decoder.peek());
+        if (static_cast<u8>(tag.kind) == 2) {
+            REWRITE_TAG(BitString)
+            TRY(parse_unique_identifier(decoder, current_scope));
         }
-        certificate.signature_value = signature_data_result.release_value();
     }
 
-    EXIT_SCOPE("Certificate");
+    if (!decoder.eof()) {
+        auto tag = TRY(decoder.peek());
+        if (static_cast<u8>(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);
+    }
 
-    dbgln_if(TLS_DEBUG, "Certificate issued for {} by {}", certificate.subject.subject, certificate.issuer.subject);
+    certificate.is_self_issued = TRY(certificate.issuer.to_string()) == TRY(certificate.subject.to_string());
+
+    EXIT_SCOPE();
 
     return certificate;
+}
 
-#undef DROP_OBJECT_OR_FAIL
-#undef ENSURE_OBJECT_KIND
-#undef ENTER_SCOPE_OR_FAIL
-#undef ENTER_SCOPE_WITHOUT_TYPECHECK
-#undef EXIT_SCOPE
-#undef READ_OBJECT_OR_FAIL
+ErrorOr<Certificate> Certificate::parse_certificate(ReadonlyBytes buffer, bool)
+{
+    Crypto::ASN1::Decoder decoder { buffer };
+    Vector<StringView, 8> 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));
+
+    CertificateKeyAlgorithm signature_algorithm = TRY(parse_algorithm_identifier(decoder, current_scope));
+    certificate.signature_algorithm = signature_algorithm;
+
+    PUSH_SCOPE("signature"sv);
+    READ_OBJECT(BitString, Crypto::ASN1::BitStringView, signature);
+    certificate.signature_value = TRY(ByteBuffer::copy(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<String> RelativeDistinguishedName::to_string()
+{
+#define ADD_IF_RECOGNIZED(identifier, shorthand_code)                 \
+    do {                                                              \
+        if (it->key == identifier) {                                  \
+            cert_name.appendff("\\{}={}", shorthand_code, it->value); \
+            continue;                                                 \
+        }                                                             \
+    } while (0);
+
+    StringBuilder cert_name;
+
+    for (auto it = m_members.begin(); it != m_members.end(); ++it) {
+        ADD_IF_RECOGNIZED(enum_value(AttributeType::SerialNumber), "SERIALNUMBER");
+        ADD_IF_RECOGNIZED(enum_value(AttributeType::Email), "MAIL");
+        ADD_IF_RECOGNIZED(enum_value(AttributeType::Title), "T");
+        ADD_IF_RECOGNIZED(enum_value(AttributeType::PostalCode), "PC");
+        ADD_IF_RECOGNIZED(enum_value(AttributeType::DnQualifier), "DNQ");
+        ADD_IF_RECOGNIZED(enum_value(AttributeType::GivenName), "GIVENNAME");
+        ADD_IF_RECOGNIZED(enum_value(AttributeType::Surname), "SN");
+
+        ADD_IF_RECOGNIZED(enum_value(AttributeType::Cn), "CN");
+        ADD_IF_RECOGNIZED(enum_value(AttributeType::L), "L");
+        ADD_IF_RECOGNIZED(enum_value(AttributeType::St), "ST");
+        ADD_IF_RECOGNIZED(enum_value(AttributeType::O), "O");
+        ADD_IF_RECOGNIZED(enum_value(AttributeType::Ou), "OU");
+        ADD_IF_RECOGNIZED(enum_value(AttributeType::C), "C");
+        ADD_IF_RECOGNIZED(enum_value(AttributeType::Street), "STREET");
+        ADD_IF_RECOGNIZED(enum_value(AttributeType::Dc), "DC");
+        ADD_IF_RECOGNIZED(enum_value(AttributeType::Uid), "UID");
+
+        cert_name.appendff("\\{}={}", it->key, it->value);
+    }
+#undef ADD_IF_RECOGNIZED
+
+    return cert_name.to_string();
+}
 }

+ 199 - 76
Userland/Libraries/LibTLS/Certificate.h

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, the SerenityOS developers.
+ * Copyright (c) 2020-2023, the SerenityOS developers.
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -18,36 +18,219 @@
 
 namespace TLS {
 
-enum class CertificateKeyAlgorithm {
+#define _ENUM(key, value) key,
+
+#define __ENUM_OBJECT_CLASS                   \
+    _ENUM(ApplicationProcess, "2.5.6.11"sv)   \
+    _ENUM(Country, "2.5.6.2"sv)               \
+    _ENUM(DcObject, "1.3.6.1.4.1.1466.344"sv) \
+    _ENUM(Device, "2.5.6.14"sv)               \
+    _ENUM(GroupOfNames, "2.5.6.9"sv)          \
+    _ENUM(GroupOfUniqueNames, "2.5.6.17"sv)   \
+    _ENUM(Locality, "2.5.6.3"sv)              \
+    _ENUM(Organization, "2.5.6.4"sv)          \
+    _ENUM(OrganizationalPerson, "2.5.6.7"sv)  \
+    _ENUM(OrganizationalRole, "2.5.6.8"sv)    \
+    _ENUM(OrganizationalUnit, "2.5.6.5"sv)    \
+    _ENUM(Person, "2.5.6.6"sv)                \
+    _ENUM(ResidentialPerson, "2.5.6.10"sv)    \
+    _ENUM(UidObject, "1.3.6.1.1.3.1"sv)
+
+// NOTE: Type = O
+// NOTE: This list is not exhaustive. If more members are needed, find them at the link below.
+// https://www.iana.org/assignments/ldap-parameters/ldap-parameters.xhtml#ldap-parameters-3
+enum class ObjectClass {
+    __ENUM_OBJECT_CLASS
+};
+
+#define __ENUM_ATTRIBUTE_TYPE                       \
+    _ENUM(BusinessCategory, "2.5.4.15"sv)           \
+    _ENUM(C, "2.5.4.6"sv)                           \
+    _ENUM(Cn, "2.5.4.3"sv)                          \
+    _ENUM(Dc, "0.9.2342.19200300.100.1.25"sv)       \
+    _ENUM(Description, "2.5.4.13"sv)                \
+    _ENUM(DestinationIndicator, "2.5.4.27"sv)       \
+    _ENUM(DistinguishedName, "2.5.4.49"sv)          \
+    _ENUM(DnQualifier, "2.5.4.46"sv)                \
+    _ENUM(EnhancedSearchGuide, "2.5.4.47"sv)        \
+    _ENUM(Email, "1.2.840.113549.1.9.1"sv)          \
+    _ENUM(FacsimileTelephoneNumber, "2.5.4.23"sv)   \
+    _ENUM(GenerationQualifier, "2.5.4.44"sv)        \
+    _ENUM(GivenName, "2.5.4.42"sv)                  \
+    _ENUM(HouseIdentifier, "2.5.4.51"sv)            \
+    _ENUM(Initials, "2.5.4.43"sv)                   \
+    _ENUM(InternationalISDNNumber, "2.5.4.25"sv)    \
+    _ENUM(L, "2.5.4.7"sv)                           \
+    _ENUM(Member, "2.5.4.31"sv)                     \
+    _ENUM(Name, "2.5.4.41"sv)                       \
+    _ENUM(O, "2.5.4.10"sv)                          \
+    _ENUM(Ou, "2.5.4.11"sv)                         \
+    _ENUM(Owner, "2.5.4.32"sv)                      \
+    _ENUM(PhysicalDeliveryOfficeName, "2.5.4.19"sv) \
+    _ENUM(PostalAddress, "2.5.4.16"sv)              \
+    _ENUM(PostalCode, "2.5.4.17"sv)                 \
+    _ENUM(PostOfficeBox, "2.5.4.18"sv)              \
+    _ENUM(PreferredDeliveryMethod, "2.5.4.28"sv)    \
+    _ENUM(RegisteredAddress, "2.5.4.26"sv)          \
+    _ENUM(RoleOccupant, "2.5.4.33"sv)               \
+    _ENUM(SearchGuide, "2.5.4.14"sv)                \
+    _ENUM(SeeAlso, "2.5.4.34"sv)                    \
+    _ENUM(SerialNumber, "2.5.4.5"sv)                \
+    _ENUM(Sn, "2.5.4.4"sv)                          \
+    _ENUM(St, "2.5.4.8"sv)                          \
+    _ENUM(Street, "2.5.4.9"sv)                      \
+    _ENUM(Surname, "2.5.4.4"sv)                     \
+    _ENUM(TelephoneNumber, "2.5.4.20"sv)            \
+    _ENUM(TeletexTerminalIdentifier, "2.5.4.22"sv)  \
+    _ENUM(TelexNumber, "2.5.4.21"sv)                \
+    _ENUM(Title, "2.5.4.12"sv)                      \
+    _ENUM(Uid, "0.9.2342.19200300.100.1.1"sv)       \
+    _ENUM(UniqueMember, "2.5.4.50"sv)               \
+    _ENUM(UserPassword, "2.5.4.35"sv)               \
+    _ENUM(X121Address, "2.5.4.24"sv)                \
+    _ENUM(X500UniqueIdentifier, "2.5.4.45"sv)
+
+// NOTE: Type = A
+// NOTE: This list is not exhaustive. If more members are needed, find them at the link below.
+// https://www.iana.org/assignments/ldap-parameters/ldap-parameters.xhtml#ldap-parameters-3
+enum class AttributeType {
+    __ENUM_ATTRIBUTE_TYPE
+};
+
+#undef _ENUM
+
+constexpr static StringView enum_value(ObjectClass object_class)
+{
+#define _ENUM(key, value)  \
+    case ObjectClass::key: \
+        return value;
+
+    switch (object_class) {
+        __ENUM_OBJECT_CLASS
+    }
+
+    return "Unknown"sv;
+#undef _ENUM
+#undef __ENUM_OBJECT_CLASS
+}
+
+constexpr static StringView enum_value(AttributeType object_class)
+{
+#define _ENUM(key, value)    \
+    case AttributeType::key: \
+        return value;
+
+    switch (object_class) {
+        __ENUM_ATTRIBUTE_TYPE
+    }
+
+    return "Unknown"sv;
+#undef _ENUM
+#undef __ENUM_ATTRIBUTE_TYPE
+}
+
+enum class CertificateKeyAlgorithm : u8 {
     Unsupported = 0x00,
     RSA_RSA = 0x01,
+    RSA_MD2 = 0x2,
+    RSA_MD4 = 0x3,
     RSA_MD5 = 0x04,
     RSA_SHA1 = 0x05,
+    RSA_OAEP = 0x6,
+    RSAES_OAEP = 0x7,
+    RSA_MGF1 = 0x8,
+    RSA_SPECIFIED = 0x9,
+    RSA_PSS = 0xa,
     RSA_SHA256 = 0x0b,
     RSA_SHA384 = 0x0c,
     RSA_SHA512 = 0x0d,
+    RSA_SHA224 = 0xe,
+    ECDSA_SHA224 = 0x10,
+    ECDSA_SHA256 = 0x11,
+    ECDSA_SHA384 = 0x12,
+    ECDSA_SHA512 = 0x13,
+    ECDSA_SECP256R1 = 0x14,
+    ECDSA_SECP384R1 = 0x15,
+};
+
+struct BasicConstraints {
+    bool is_certificate_authority;
+    Crypto::UnsignedBigInteger path_length_constraint;
+};
+
+class RelativeDistinguishedName {
+public:
+    ErrorOr<String> to_string();
+
+    ErrorOr<AK::HashSetResult> set(String key, String value)
+    {
+        return m_members.try_set(key, value);
+    }
+
+    Optional<String> get(StringView key)
+    {
+        return m_members.get(key);
+    }
+
+    Optional<String> get(AttributeType key)
+    {
+        return m_members.get(enum_value(key));
+    }
+
+    Optional<String> get(ObjectClass key)
+    {
+        return m_members.get(enum_value(key));
+    }
+
+    String common_name()
+    {
+        auto entry = get(AttributeType::Cn);
+        if (entry.has_value()) {
+            return entry.value();
+        }
+
+        return String();
+    }
+
+    String organizational_unit()
+    {
+        auto entry = get(AttributeType::Ou);
+        if (entry.has_value()) {
+            return entry.value();
+        }
+
+        return String();
+    }
+
+private:
+    HashMap<String, String> m_members;
+};
+
+struct Validity {
+    Core::DateTime not_before;
+    Core::DateTime not_after;
+};
+
+class SubjectPublicKey {
+public:
+    Crypto::PK::RSAPublicKey<Crypto::UnsignedBigInteger> rsa;
+
+    CertificateKeyAlgorithm algorithm { CertificateKeyAlgorithm::Unsupported };
+    ByteBuffer raw_key;
 };
 
 class Certificate {
 public:
     u16 version { 0 };
     CertificateKeyAlgorithm algorithm { CertificateKeyAlgorithm::Unsupported };
-    CertificateKeyAlgorithm key_algorithm { CertificateKeyAlgorithm::Unsupported };
     CertificateKeyAlgorithm ec_algorithm { CertificateKeyAlgorithm::Unsupported };
+    SubjectPublicKey public_key {};
     ByteBuffer exponent {};
-    Crypto::PK::RSAPublicKey<Crypto::UnsignedBigInteger> public_key {};
     Crypto::PK::RSAPrivateKey<Crypto::UnsignedBigInteger> private_key {};
-    struct Name {
-        DeprecatedString country;
-        DeprecatedString state;
-        DeprecatedString location;
-        DeprecatedString entity;
-        DeprecatedString subject;
-        DeprecatedString unit;
-    } issuer, subject;
-    Core::DateTime not_before;
-    Core::DateTime not_after;
-    Vector<DeprecatedString> SAN;
+    RelativeDistinguishedName issuer, subject;
+    Validity validity {};
+    Vector<String> SAN;
+    Vector<String> IAN;
     u8* ocsp { nullptr };
     Crypto::UnsignedBigInteger serial_number;
     ByteBuffer sign_key {};
@@ -62,71 +245,11 @@ public:
     Optional<size_t> path_length_constraint {};
     bool is_self_issued { false };
 
-    static Optional<Certificate> parse_asn1(ReadonlyBytes, bool client_cert = false);
+    static ErrorOr<Certificate> parse_certificate(ReadonlyBytes, bool client_cert = false);
 
     bool is_self_signed();
     bool is_valid() const;
 
-    DeprecatedString subject_identifier_string() const
-    {
-        StringBuilder cert_name;
-        if (!subject.country.is_empty()) {
-            cert_name.append("/C="sv);
-            cert_name.append(subject.country);
-        }
-        if (!subject.state.is_empty()) {
-            cert_name.append("/ST="sv);
-            cert_name.append(subject.state);
-        }
-        if (!subject.location.is_empty()) {
-            cert_name.append("/L="sv);
-            cert_name.append(subject.location);
-        }
-        if (!subject.entity.is_empty()) {
-            cert_name.append("/O="sv);
-            cert_name.append(subject.entity);
-        }
-        if (!subject.unit.is_empty()) {
-            cert_name.append("/OU="sv);
-            cert_name.append(subject.unit);
-        }
-        if (!subject.subject.is_empty()) {
-            cert_name.append("/CN="sv);
-            cert_name.append(subject.subject);
-        }
-        return cert_name.to_deprecated_string();
-    }
-
-    DeprecatedString issuer_identifier_string() const
-    {
-        StringBuilder cert_name;
-        if (!issuer.country.is_empty()) {
-            cert_name.append("/C="sv);
-            cert_name.append(issuer.country);
-        }
-        if (!issuer.state.is_empty()) {
-            cert_name.append("/ST="sv);
-            cert_name.append(issuer.state);
-        }
-        if (!issuer.location.is_empty()) {
-            cert_name.append("/L="sv);
-            cert_name.append(issuer.location);
-        }
-        if (!issuer.entity.is_empty()) {
-            cert_name.append("/O="sv);
-            cert_name.append(issuer.entity);
-        }
-        if (!issuer.unit.is_empty()) {
-            cert_name.append("/OU="sv);
-            cert_name.append(issuer.unit);
-        }
-        if (!issuer.subject.is_empty()) {
-            cert_name.append("/CN="sv);
-            cert_name.append(issuer.subject);
-        }
-        return cert_name.to_deprecated_string();
-    }
-
 private:
     Optional<bool> m_is_self_signed;
 };

+ 6 - 2
Userland/Libraries/LibTLS/HandshakeCertificate.cpp

@@ -76,10 +76,14 @@ ssize_t TLSv12::handle_certificate(ReadonlyBytes buffer)
             }
             remaining -= certificate_size_specific;
 
-            auto certificate = Certificate::parse_asn1(buffer.slice(res_cert, certificate_size_specific), false);
-            if (certificate.has_value()) {
+            auto certificate = Certificate::parse_certificate(buffer.slice(res_cert, certificate_size_specific), false);
+            if (!certificate.is_error()) {
                 m_context.certificates.append(certificate.value());
                 valid_certificate = true;
+            } else {
+                dbgln("Failed to parse client cert: {}", certificate.error());
+                dbgln("{:hex-dump}", buffer.slice(res_cert, certificate_size_specific));
+                dbgln("");
             }
             res_cert += certificate_size_specific;
         } while (remaining > 0);

+ 1 - 1
Userland/Libraries/LibTLS/HandshakeClient.cpp

@@ -186,7 +186,7 @@ void TLSv12::build_rsa_pre_master_secret(PacketBuilder& builder)
         print_buffer(m_context.premaster_key);
     }
 
-    Crypto::PK::RSA_PKCS1_EME rsa(certificate.public_key.modulus(), 0, certificate.public_key.public_exponent());
+    Crypto::PK::RSA_PKCS1_EME rsa(certificate.public_key.rsa.modulus(), 0, certificate.public_key.rsa.public_exponent());
 
     Vector<u8, 32> out;
     out.resize(rsa.output_size());

+ 1 - 1
Userland/Libraries/LibTLS/HandshakeServer.cpp

@@ -362,7 +362,7 @@ ssize_t TLSv12::verify_rsa_server_key_exchange(ReadonlyBytes server_key_info_buf
     // RFC5246 section 7.4.2: The sender's certificate MUST come first in the list.
     auto certificate_public_key = m_context.certificates.first().public_key;
     Crypto::PK::RSAPrivateKey dummy_private_key;
-    auto rsa = Crypto::PK::RSA(certificate_public_key, dummy_private_key);
+    auto rsa = Crypto::PK::RSA(certificate_public_key.rsa, dummy_private_key);
 
     auto signature_verify_buffer_result = ByteBuffer::create_uninitialized(signature_length);
     if (signature_verify_buffer_result.is_error()) {

+ 20 - 18
Userland/Libraries/LibTLS/TLSv12.cpp

@@ -102,13 +102,13 @@ bool Certificate::is_valid() const
 {
     auto now = Core::DateTime::now();
 
-    if (now < not_before) {
-        dbgln("certificate expired (not yet valid, signed for {})", not_before.to_deprecated_string());
+    if (now < validity.not_before) {
+        dbgln("certificate expired (not yet valid, signed for {})", validity.not_before.to_deprecated_string());
         return false;
     }
 
-    if (not_after < now) {
-        dbgln("certificate expired (expiry date {})", not_after.to_deprecated_string());
+    if (validity.not_after < now) {
+        dbgln("certificate expired (expiry date {})", validity.not_after.to_deprecated_string());
         return false;
     }
 
@@ -201,11 +201,12 @@ void TLSv12::set_root_certificates(Vector<Certificate> certificates)
     }
 
     for (auto& cert : certificates) {
-        if (!cert.is_valid())
-            dbgln("Certificate for {} by {} is invalid, things may or may not work!", cert.subject.subject, cert.issuer.subject);
+        if (!cert.is_valid()) {
+            dbgln("Certificate for {} by {} is invalid, things may or may not work!", cert.subject.common_name(), cert.issuer.common_name());
+        }
         // FIXME: Figure out what we should do when our root certs are invalid.
 
-        m_context.root_certificates.set(cert.subject_identifier_string(), cert);
+        m_context.root_certificates.set(MUST(cert.subject.to_string()).to_deprecated_string(), cert);
     }
     dbgln_if(TLS_DEBUG, "{}: Set {} root certificates", this, m_context.root_certificates.size());
 }
@@ -228,7 +229,7 @@ static bool wildcard_matches(StringView host, StringView subject)
 
 static bool certificate_subject_matches_host(Certificate& cert, StringView host)
 {
-    if (wildcard_matches(host, cert.subject.subject))
+    if (wildcard_matches(host, cert.subject.common_name()))
         return true;
 
     for (auto& san : cert.SAN) {
@@ -279,15 +280,15 @@ bool Context::verify_chain(StringView host) const
     for (size_t cert_index = 0; cert_index < local_chain->size(); ++cert_index) {
         auto cert = local_chain->at(cert_index);
 
-        auto subject_string = cert.subject_identifier_string();
-        auto issuer_string = cert.issuer_identifier_string();
+        auto subject_string = MUST(cert.subject.to_string());
+        auto issuer_string = MUST(cert.issuer.to_string());
 
         if (!cert.is_valid()) {
             dbgln("verify_chain: Certificate is not valid {}", subject_string);
             return false;
         }
 
-        auto maybe_root_certificate = root_certificates.get(issuer_string);
+        auto maybe_root_certificate = root_certificates.get(issuer_string.to_deprecated_string());
         if (maybe_root_certificate.has_value()) {
             auto& root_certificate = *maybe_root_certificate;
             auto verification_correct = verify_certificate_pair(cert, root_certificate);
@@ -312,7 +313,7 @@ bool Context::verify_chain(StringView host) const
         }
 
         auto parent_certificate = local_chain->at(cert_index + 1);
-        if (issuer_string != parent_certificate.subject_identifier_string()) {
+        if (issuer_string != MUST(parent_certificate.subject.to_string())) {
             dbgln("verify_chain: Next certificate in the chain is not the issuer of this certificate");
             return false;
         }
@@ -359,7 +360,7 @@ bool Context::verify_certificate_pair(Certificate const& subject, Certificate co
     }
 
     Crypto::PK::RSAPrivateKey dummy_private_key;
-    Crypto::PK::RSAPublicKey public_key_copy { issuer.public_key };
+    Crypto::PK::RSAPublicKey public_key_copy { issuer.public_key.rsa };
     auto rsa = Crypto::PK::RSA(public_key_copy, dummy_private_key);
     auto verification_buffer_result = ByteBuffer::create_uninitialized(subject.signature_value.size());
     if (verification_buffer_result.is_error()) {
@@ -471,8 +472,8 @@ Vector<Certificate> TLSv12::parse_pem_certificate(ReadonlyBytes certificate_pem_
         return {};
     }
 
-    auto maybe_certificate = Certificate::parse_asn1(decoded_certificate);
-    if (!maybe_certificate.has_value()) {
+    auto maybe_certificate = Certificate::parse_certificate(decoded_certificate);
+    if (!maybe_certificate.is_error()) {
         dbgln("Invalid certificate");
         return {};
     }
@@ -516,19 +517,20 @@ ErrorOr<Vector<Certificate>> DefaultRootCACertificates::reload_certificates(Byte
     auto certs = TRY(Crypto::decode_pems(data));
 
     for (auto& cert : certs) {
-        auto certificate_result = Certificate::parse_asn1(cert.bytes());
+        auto certificate_result = Certificate::parse_certificate(cert.bytes());
         // If the certificate does not parse it is likely using elliptic curve keys/signatures, which are not
         // supported right now. It might make sense to cleanup cacert.pem before adding it to the system.
-        if (!certificate_result.has_value()) {
+        if (certificate_result.is_error()) {
             // FIXME: It would be nice to have more informations about the certificate we failed to parse.
             //        Like: Issuer, Algorithm, CN, etc
+            dbgln("Failed to load certificate: {}", certificate_result.error());
             continue;
         }
         auto certificate = certificate_result.release_value();
         if (certificate.is_certificate_authority && certificate.is_self_signed()) {
             TRY(certificates.try_append(move(certificate)));
         } else {
-            dbgln("Skipped '{}' because it is not a valid root CA", certificate.subject_identifier_string());
+            dbgln("Skipped '{}' because it is not a valid root CA", MUST(certificate.subject.to_string()));
         }
     }