// SPDX-FileCopyrightText: © 2022—2026 Vladimir Zorin // SPDX-License-Identifier: LicenseRef-OWL-1.0-or-later // Licensed under OWL v1.0+. See LICENSE. /* * LITLS -- Lilush TLS Stack * X.509 certificate chain validation. */ #include "../litls.h" #include "asn1.h" #include /* Verify a single certificate's signature against an issuer's public key. */ static int verify_cert_signature(const litls_x509_cert_info *cert, int issuer_key_type, const uint8_t *issuer_pubkey, size_t issuer_pubkey_len, const uint8_t *issuer_rsa_e, size_t issuer_rsa_e_len) { uint8_t hash[64]; /* large enough for SHA-512 */ size_t hash_len; /* Ed25519 is "pure": no pre-hash, the verifier ingests the TBS message directly. */ if (cert->sig_algo == LITLS_SIG_ED25519) { if (issuer_key_type != LITLS_KEY_ED25519) return LITLS_X509_ERR_SIGNATURE; if (cert->signature_len != 64) return LITLS_X509_ERR_SIGNATURE; if (litls_ed25519_verify(cert->signature, cert->tbs, cert->tbs_len, issuer_pubkey) != 0) return LITLS_X509_ERR_SIGNATURE; return LITLS_X509_OK; } switch (cert->sig_algo) { case LITLS_SIG_ECDSA_SHA256: if (issuer_key_type != LITLS_KEY_ECDSA_P256 && issuer_key_type != LITLS_KEY_ECDSA_P384) return LITLS_X509_ERR_SIGNATURE; litls_sha256(cert->tbs, cert->tbs_len, hash); hash_len = 32; break; case LITLS_SIG_ECDSA_SHA384: if (issuer_key_type != LITLS_KEY_ECDSA_P256 && issuer_key_type != LITLS_KEY_ECDSA_P384) return LITLS_X509_ERR_SIGNATURE; litls_sha384(cert->tbs, cert->tbs_len, hash); hash_len = 48; break; case LITLS_SIG_RSA_SHA256: if (issuer_key_type != LITLS_KEY_RSA) return LITLS_X509_ERR_SIGNATURE; litls_sha256(cert->tbs, cert->tbs_len, hash); hash_len = 32; break; case LITLS_SIG_RSA_SHA384: if (issuer_key_type != LITLS_KEY_RSA) return LITLS_X509_ERR_SIGNATURE; litls_sha384(cert->tbs, cert->tbs_len, hash); hash_len = 48; break; case LITLS_SIG_RSA_SHA512: if (issuer_key_type != LITLS_KEY_RSA) return LITLS_X509_ERR_SIGNATURE; litls_sha512(cert->tbs, cert->tbs_len, hash); hash_len = 64; break; default: return LITLS_X509_ERR_SIGNATURE; } if (issuer_key_type == LITLS_KEY_ECDSA_P256) { uint8_t raw_sig[64]; if (litls_ecdsa_sig_der_to_raw(cert->signature, cert->signature_len, raw_sig, 32) != 0) return LITLS_X509_ERR_SIGNATURE; if (litls_p256_ecdsa_verify(hash, hash_len, issuer_pubkey, raw_sig, 64) != 0) return LITLS_X509_ERR_SIGNATURE; } else if (issuer_key_type == LITLS_KEY_ECDSA_P384) { uint8_t raw_sig[96]; if (litls_ecdsa_sig_der_to_raw(cert->signature, cert->signature_len, raw_sig, 48) != 0) return LITLS_X509_ERR_SIGNATURE; if (litls_p384_ecdsa_verify(hash, hash_len, issuer_pubkey, raw_sig, 96) != 0) return LITLS_X509_ERR_SIGNATURE; } else { /* RSA PKCS#1 v1.5 verify */ int hash_id; switch (cert->sig_algo) { case LITLS_SIG_RSA_SHA256: hash_id = LITLS_HASH_SHA256; break; case LITLS_SIG_RSA_SHA384: hash_id = LITLS_HASH_SHA384; break; case LITLS_SIG_RSA_SHA512: hash_id = LITLS_HASH_SHA512; break; default: return LITLS_X509_ERR_SIGNATURE; } if (litls_rsa_pkcs1_verify(hash, hash_len, hash_id, cert->signature, cert->signature_len, issuer_pubkey, issuer_pubkey_len, issuer_rsa_e, issuer_rsa_e_len) != 0) return LITLS_X509_ERR_SIGNATURE; } return LITLS_X509_OK; } int litls_x509_verify_chain(const uint8_t **chain_ders, const size_t *chain_der_lens, int chain_count, const litls_x509_trust_anchor *anchors, int anchor_count, int64_t now) { if (chain_count < 1 || chain_count > LITLS_X509_MAX_CHAIN) return LITLS_X509_ERR_CHAIN; litls_x509_cert_info certs[LITLS_X509_MAX_CHAIN]; /* Parse all certificates. */ for (int i = 0; i < chain_count; i++) { if (litls_x509_parse(chain_ders[i], chain_der_lens[i], &certs[i]) != 0) return LITLS_X509_ERR_PARSE; } /* Check validity periods. */ if (now > 0) { for (int i = 0; i < chain_count; i++) { if (now < certs[i].not_before || now > certs[i].not_after) return LITLS_X509_ERR_EXPIRED; } } /* Enforce X.509 v3 constraints (RFC 5280 §4.2.1.9 / §4.2.1.3 / §4.2.1.12). * Trust anchors themselves are not re-validated (they are trusted by virtue * of being configured), so anchor CA/KU flags are not checked here. * * RFC 5280 §4.2 mandates rejection of any unrecognised critical extension. * `unknown_critical_extension` covers extensions LITLS does not implement * (notably NameConstraints, 2.5.29.30) so they are not silently accepted. */ for (int i = 0; i < chain_count; i++) { int is_leaf = (i == 0); int is_issuer = (i > 0); /* intermediate in the presented chain */ if (certs[i].unknown_critical_extension) return LITLS_X509_ERR_CONSTRAINT; if (is_issuer) { if (!certs[i].basic_constraints_present || !certs[i].is_ca) return LITLS_X509_ERR_CONSTRAINT; if (certs[i].key_usage_present && !(certs[i].key_usage & LITLS_KU_KEY_CERT_SIGN)) return LITLS_X509_ERR_CONSTRAINT; /* pathLenConstraint: max number of *subordinate* CA certs that * may follow below this one. In a leaf-first chain certs[i] * signs certs[i-1], so (i-1) subordinate CAs precede this cert. */ if (certs[i].path_len_constraint >= 0 && (i - 1) > (int)certs[i].path_len_constraint) return LITLS_X509_ERR_CONSTRAINT; } if (is_leaf) { /* If EKU is present, it must permit serverAuth (or anyEKU). * If EKU is absent, accept -- many legacy/private CAs omit it. */ if (certs[i].eku_present && !(certs[i].eku_server_auth || certs[i].eku_any)) return LITLS_X509_ERR_CONSTRAINT; /* RFC 5280 §4.2.1.3 + RFC 8446 §4.4.3: the leaf's CertificateVerify * is a digital signature, so when KeyUsage is present it must * permit digitalSignature. (KeyAgreement-only certs are valid for * static-DH TLS 1.2 but not TLS 1.3.) */ if (certs[i].key_usage_present && !(certs[i].key_usage & LITLS_KU_DIGITAL_SIGNATURE)) return LITLS_X509_ERR_CONSTRAINT; } } /* Verify chain: each cert[i] is signed by cert[i+1]. */ for (int i = 0; i < chain_count - 1; i++) { int ret = verify_cert_signature(&certs[i], certs[i + 1].key_type, certs[i + 1].pubkey, certs[i + 1].pubkey_len, certs[i + 1].rsa_e, certs[i + 1].rsa_e_len); if (ret != LITLS_X509_OK) return ret; } /* Verify the last cert against a trust anchor. */ const litls_x509_cert_info *last = &certs[chain_count - 1]; for (int a = 0; a < anchor_count; a++) { int ret = verify_cert_signature(last, anchors[a].key_type, anchors[a].pubkey, anchors[a].pubkey_len, anchors[a].rsa_e, anchors[a].rsa_e_len); if (ret == LITLS_X509_OK) return LITLS_X509_OK; } return LITLS_X509_ERR_NO_ANCHOR; } /* ── Hostname verification (RFC 6125) ──────────────────── */ static int ascii_tolower(int c) { return (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; } /* Case-insensitive ASCII equality over two byte ranges. */ static int dns_label_eq(const char *a, size_t a_len, const char *b, size_t b_len) { if (a_len != b_len) return 0; for (size_t i = 0; i < a_len; i++) { if (ascii_tolower((unsigned char)a[i]) != ascii_tolower((unsigned char)b[i])) return 0; } return 1; } /* Match a single presented identifier (SAN entry or CN) against a hostname. * The presented name may start with "*." (full leftmost label wildcard); any * other use of '*' (middle/right labels or embedded in a label) is rejected. * Returns 1 on match, 0 otherwise. * * `host` is the reference hostname supplied by the caller (e.g. SNI). */ static int match_identifier(const char *presented, size_t presented_len, const char *host, size_t host_len) { if (presented_len == 0 || host_len == 0) return 0; /* Reject any '*' outside the leftmost-label position, or embedded in a * label alongside other characters. */ int has_wildcard = 0; for (size_t i = 0; i < presented_len; i++) { if (presented[i] != '*') continue; /* Must be the very first character and followed by '.'. */ if (i != 0 || presented_len < 3 || presented[1] != '.') return 0; has_wildcard = 1; } if (!has_wildcard) return dns_label_eq(presented, presented_len, host, host_len); /* Wildcard: "*.rest" must match "