// 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 DER parser. * * Extracts: CN, validity dates, SANs, public key, signature algorithm, * and raw TBS/signature regions for chain validation. */ #include "../litls.h" #include "asn1.h" #include /* ── Well-known OIDs (DER-encoded value bytes) ────────── */ /* 2.5.4.3 -- id-at-commonName */ static const uint8_t OID_CN[] = {0x55, 0x04, 0x03}; /* 2.5.29.17 -- id-ce-subjectAltName */ static const uint8_t OID_SAN[] = {0x55, 0x1D, 0x11}; /* 2.5.29.19 -- id-ce-basicConstraints */ static const uint8_t OID_BASIC_CONSTRAINTS[] = {0x55, 0x1D, 0x13}; /* 2.5.29.15 -- id-ce-keyUsage */ static const uint8_t OID_KEY_USAGE[] = {0x55, 0x1D, 0x0F}; /* 2.5.29.37 -- id-ce-extKeyUsage */ static const uint8_t OID_EXT_KEY_USAGE[] = {0x55, 0x1D, 0x25}; /* 1.3.6.1.5.5.7.3.1 -- id-kp-serverAuth */ static const uint8_t OID_SERVER_AUTH[] = {0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01}; /* 2.5.29.37.0 -- anyExtendedKeyUsage */ static const uint8_t OID_ANY_EKU[] = {0x55, 0x1D, 0x25, 0x00}; /* Signature algorithm OIDs */ /* 1.2.840.10045.4.3.2 -- ecdsa-with-SHA256 */ static const uint8_t OID_ECDSA_SHA256[] = {0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02}; /* 1.2.840.10045.4.3.3 -- ecdsa-with-SHA384 */ static const uint8_t OID_ECDSA_SHA384[] = {0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x03}; /* 1.2.840.113549.1.1.11 -- sha256WithRSAEncryption */ static const uint8_t OID_RSA_SHA256[] = {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B}; /* 1.2.840.113549.1.1.12 -- sha384WithRSAEncryption */ static const uint8_t OID_RSA_SHA384[] = {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C}; /* 1.2.840.113549.1.1.13 -- sha512WithRSAEncryption */ static const uint8_t OID_RSA_SHA512[] = {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D}; /* Public key algorithm OIDs */ /* 1.2.840.10045.2.1 -- id-ecPublicKey */ static const uint8_t OID_EC_PUBKEY[] = {0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01}; /* 1.2.840.113549.1.1.1 -- rsaEncryption */ static const uint8_t OID_RSA_PUBKEY[] = {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01}; /* 1.3.101.112 -- id-Ed25519 (used for both SPKI algorithm and signature algorithm) */ static const uint8_t OID_ED25519[] = {0x2B, 0x65, 0x70}; /* Named curve OIDs */ /* 1.2.840.10045.3.1.7 -- secp256r1 (P-256) */ static const uint8_t OID_SECP256R1[] = {0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07}; /* 1.3.132.0.34 -- secp384r1 (P-384) */ static const uint8_t OID_SECP384R1[] = {0x2B, 0x81, 0x04, 0x00, 0x22}; /* ── Helpers ──────────────────────────────────────────── */ static int parse_sig_algo(const litls_der_element *oid_elem) { if (litls_der_oid_eq(oid_elem, OID_ECDSA_SHA256, sizeof(OID_ECDSA_SHA256))) return LITLS_SIG_ECDSA_SHA256; if (litls_der_oid_eq(oid_elem, OID_ECDSA_SHA384, sizeof(OID_ECDSA_SHA384))) return LITLS_SIG_ECDSA_SHA384; if (litls_der_oid_eq(oid_elem, OID_RSA_SHA256, sizeof(OID_RSA_SHA256))) return LITLS_SIG_RSA_SHA256; if (litls_der_oid_eq(oid_elem, OID_RSA_SHA384, sizeof(OID_RSA_SHA384))) return LITLS_SIG_RSA_SHA384; if (litls_der_oid_eq(oid_elem, OID_RSA_SHA512, sizeof(OID_RSA_SHA512))) return LITLS_SIG_RSA_SHA512; if (litls_der_oid_eq(oid_elem, OID_ED25519, sizeof(OID_ED25519))) return LITLS_SIG_ED25519; return LITLS_SIG_UNKNOWN; } /* Walk a Name (SEQUENCE of SET of SEQUENCE { OID, value }) looking for CN. */ static int extract_cn(const litls_der_element *name_elem, char *cn, size_t cn_cap, size_t *cn_len) { litls_der_cursor nc; litls_der_enter(&nc, name_elem); litls_der_element set_elem; while (litls_der_next(&nc, &set_elem) == 0) { if (set_elem.tag != ASN1_SET) continue; litls_der_cursor sc; litls_der_enter(&sc, &set_elem); litls_der_element attr_seq; if (litls_der_next(&sc, &attr_seq) != 0 || attr_seq.tag != ASN1_SEQUENCE) continue; litls_der_cursor ac; litls_der_enter(&ac, &attr_seq); litls_der_element oid, val; if (litls_der_next(&ac, &oid) != 0) continue; if (!litls_der_oid_eq(&oid, OID_CN, sizeof(OID_CN))) continue; if (litls_der_next(&ac, &val) != 0) continue; size_t copy = val.length < cn_cap - 1 ? val.length : cn_cap - 1; memcpy(cn, val.value, copy); cn[copy] = '\0'; *cn_len = copy; return 0; } return -1; /* CN not found (not an error for the cert itself) */ } /* Parse SubjectPublicKeyInfo. */ static int parse_spki(const litls_der_element *spki_elem, litls_x509_cert_info *info) { litls_der_cursor sc; litls_der_enter(&sc, spki_elem); /* AlgorithmIdentifier SEQUENCE { OID, params } */ litls_der_element algo_seq; if (litls_der_next(&sc, &algo_seq) != 0 || algo_seq.tag != ASN1_SEQUENCE) return -1; litls_der_cursor ac; litls_der_enter(&ac, &algo_seq); litls_der_element algo_oid; if (litls_der_next(&ac, &algo_oid) != 0) return -1; /* BIT STRING containing the key */ litls_der_element bits; if (litls_der_next(&sc, &bits) != 0 || bits.tag != ASN1_BIT_STRING) return -1; if (bits.length < 2 || bits.value[0] != 0x00) return -1; /* unused bits must be 0 */ const uint8_t *key_data = bits.value + 1; size_t key_len = bits.length - 1; if (litls_der_oid_eq(&algo_oid, OID_EC_PUBKEY, sizeof(OID_EC_PUBKEY))) { /* Read the curve OID from algorithm parameters */ litls_der_element curve_oid; if (litls_der_next(&ac, &curve_oid) != 0) return -1; if (litls_der_oid_eq(&curve_oid, OID_SECP256R1, sizeof(OID_SECP256R1))) { info->key_type = LITLS_KEY_ECDSA_P256; if (key_len != 65) return -1; } else if (litls_der_oid_eq(&curve_oid, OID_SECP384R1, sizeof(OID_SECP384R1))) { info->key_type = LITLS_KEY_ECDSA_P384; if (key_len != 97) return -1; } else { info->key_type = LITLS_KEY_UNKNOWN; } if (key_len > sizeof(info->pubkey)) return -1; memcpy(info->pubkey, key_data, key_len); info->pubkey_len = key_len; } else if (litls_der_oid_eq(&algo_oid, OID_RSA_PUBKEY, sizeof(OID_RSA_PUBKEY))) { info->key_type = LITLS_KEY_RSA; /* key_data is a DER SEQUENCE { INTEGER n, INTEGER e } */ litls_der_cursor kc; litls_der_init(&kc, key_data, key_len); litls_der_element rsa_seq; if (litls_der_next(&kc, &rsa_seq) != 0 || rsa_seq.tag != ASN1_SEQUENCE) return -1; litls_der_cursor rc; litls_der_enter(&rc, &rsa_seq); litls_der_element n_elem, e_elem; if (litls_der_next(&rc, &n_elem) != 0 || n_elem.tag != ASN1_INTEGER) return -1; if (litls_der_next(&rc, &e_elem) != 0 || e_elem.tag != ASN1_INTEGER) return -1; /* Strip leading zero from n if present */ const uint8_t *n = n_elem.value; size_t n_len = n_elem.length; if (n_len > 0 && n[0] == 0x00) { n++; n_len--; } if (n_len > sizeof(info->pubkey)) return -1; memcpy(info->pubkey, n, n_len); info->pubkey_len = n_len; /* Strip leading zero from e if present */ const uint8_t *e = e_elem.value; size_t e_len = e_elem.length; if (e_len > 0 && e[0] == 0x00) { e++; e_len--; } if (e_len > sizeof(info->rsa_e)) return -1; memcpy(info->rsa_e, e, e_len); info->rsa_e_len = e_len; } else if (litls_der_oid_eq(&algo_oid, OID_ED25519, sizeof(OID_ED25519))) { info->key_type = LITLS_KEY_ED25519; if (key_len != 32) return -1; memcpy(info->pubkey, key_data, 32); info->pubkey_len = 32; } else { info->key_type = LITLS_KEY_UNKNOWN; } return 0; } /* Parse SubjectAltName extension's OCTET STRING body (GeneralNames SEQUENCE). * Returns 0 on success, -1 on malformed body. */ static int parse_san_body(const litls_der_element *octet, litls_x509_cert_info *info) { litls_der_cursor gc; litls_der_init(&gc, octet->value, octet->length); litls_der_element gn_seq; if (litls_der_next(&gc, &gn_seq) != 0 || gn_seq.tag != ASN1_SEQUENCE) return -1; litls_der_cursor gnc; litls_der_enter(&gnc, &gn_seq); litls_der_element gn; while (litls_der_next(&gnc, &gn) == 0) { /* dNSName is context tag [2] implicit */ if (gn.tag != 0x82) continue; if (info->san_count >= LITLS_X509_MAX_SANS) break; size_t copy = gn.length; if (copy >= LITLS_X509_MAX_SAN_LEN) copy = LITLS_X509_MAX_SAN_LEN - 1; memcpy(info->sans[info->san_count].name, gn.value, copy); info->sans[info->san_count].name[copy] = '\0'; info->sans[info->san_count].len = copy; info->san_count++; } return 0; } /* Parse BasicConstraints extension: * SEQUENCE { cA BOOLEAN DEFAULT FALSE, pathLenConstraint INTEGER OPTIONAL } * Returns 0 on success, -1 on malformed body. */ static int parse_basic_constraints_body(const litls_der_element *octet, litls_x509_cert_info *info) { litls_der_cursor bc; litls_der_init(&bc, octet->value, octet->length); litls_der_element bc_seq; if (litls_der_next(&bc, &bc_seq) != 0 || bc_seq.tag != ASN1_SEQUENCE) return -1; info->basic_constraints_present = 1; info->is_ca = 0; info->path_len_constraint = -1; litls_der_cursor sc; litls_der_enter(&sc, &bc_seq); uint8_t peek; if (litls_der_peek_tag(&sc, &peek) == 0 && peek == ASN1_BOOLEAN) { litls_der_element ca_elem; if (litls_der_next(&sc, &ca_elem) == 0 && ca_elem.length >= 1) info->is_ca = (ca_elem.value[0] != 0x00) ? 1 : 0; } if (litls_der_peek_tag(&sc, &peek) == 0 && peek == ASN1_INTEGER) { litls_der_element plc; if (litls_der_next(&sc, &plc) == 0 && plc.length >= 1 && plc.length <= 2) { /* Small non-negative integer; accept up to 2 bytes. */ int v = 0; for (size_t i = 0; i < plc.length; i++) v = (v << 8) | plc.value[i]; if (v >= 0 && v < 32767) info->path_len_constraint = (int16_t)v; } } return 0; } /* Parse KeyUsage extension (BIT STRING of up to 9 named bits, RFC 5280 §4.2.1.3). * DER BIT STRING encoding: first value byte is the count of unused trailing * bits; subsequent bytes hold the bits MSB-first. Named bits map: * bit 0 (MSB of first byte) = digitalSignature, ..., bit 5 = keyCertSign, ... * Returns 0 on success, -1 on malformed body. */ static int parse_key_usage_body(const litls_der_element *octet, litls_x509_cert_info *info) { litls_der_cursor kc; litls_der_init(&kc, octet->value, octet->length); litls_der_element bits; if (litls_der_next(&kc, &bits) != 0 || bits.tag != ASN1_BIT_STRING) return -1; if (bits.length < 1) return -1; uint8_t unused = bits.value[0]; size_t bit_bytes = bits.length - 1; const uint8_t *bp = bits.value + 1; if (bit_bytes == 0 || unused > 7) return -1; size_t total_bits = bit_bytes * 8 - unused; if (total_bits > 9) total_bits = 9; /* only 9 named bits defined */ uint16_t ku = 0; for (size_t i = 0; i < total_bits; i++) { uint8_t b = bp[i / 8]; if (b & (0x80 >> (i % 8))) ku |= (uint16_t)(1u << i); } info->key_usage_present = 1; info->key_usage = ku; return 0; } /* Parse ExtendedKeyUsage extension (SEQUENCE OF OID). Sets eku_present and * flags for serverAuth / anyEKU. Unknown purposes are silently ignored; the * leaf is rejected at verify time only when EKU is present and none of the * accepted purposes are seen. * Returns 0 on success, -1 on malformed body. */ static int parse_ext_key_usage_body(const litls_der_element *octet, litls_x509_cert_info *info) { litls_der_cursor ec; litls_der_init(&ec, octet->value, octet->length); litls_der_element eku_seq; if (litls_der_next(&ec, &eku_seq) != 0 || eku_seq.tag != ASN1_SEQUENCE) return -1; info->eku_present = 1; litls_der_cursor sc; litls_der_enter(&sc, &eku_seq); litls_der_element purpose; while (litls_der_next(&sc, &purpose) == 0) { if (purpose.tag != ASN1_OID) continue; if (litls_der_oid_eq(&purpose, OID_SERVER_AUTH, sizeof(OID_SERVER_AUTH))) info->eku_server_auth = 1; else if (litls_der_oid_eq(&purpose, OID_ANY_EKU, sizeof(OID_ANY_EKU))) info->eku_any = 1; } return 0; } /* Parse all X.509 v3 extensions we care about. */ static void parse_extensions(const litls_der_element *exts_outer, litls_x509_cert_info *info) { /* extensions is [3] EXPLICIT SEQUENCE { ... } */ litls_der_cursor oc; litls_der_enter(&oc, exts_outer); litls_der_element exts_seq; if (litls_der_next(&oc, &exts_seq) != 0 || exts_seq.tag != ASN1_SEQUENCE) return; litls_der_cursor ec; litls_der_enter(&ec, &exts_seq); litls_der_element ext; while (litls_der_next(&ec, &ext) == 0) { if (ext.tag != ASN1_SEQUENCE) continue; litls_der_cursor xc; litls_der_enter(&xc, &ext); litls_der_element ext_oid; if (litls_der_next(&xc, &ext_oid) != 0) continue; /* Optional BOOLEAN (critical). DER encodes TRUE as 0xFF (any non-zero * accepted defensively). */ int is_critical = 0; litls_der_element next; if (litls_der_next(&xc, &next) != 0) continue; const litls_der_element *octet = &next; if (next.tag == ASN1_BOOLEAN) { if (next.length >= 1 && next.value[0] != 0) is_critical = 1; if (litls_der_next(&xc, &next) != 0) continue; octet = &next; } if (octet->tag != ASN1_OCTET_STRING) continue; int rc = 0; int recognized = 1; if (litls_der_oid_eq(&ext_oid, OID_SAN, sizeof(OID_SAN))) { rc = parse_san_body(octet, info); } else if (litls_der_oid_eq(&ext_oid, OID_BASIC_CONSTRAINTS, sizeof(OID_BASIC_CONSTRAINTS))) { rc = parse_basic_constraints_body(octet, info); } else if (litls_der_oid_eq(&ext_oid, OID_KEY_USAGE, sizeof(OID_KEY_USAGE))) { rc = parse_key_usage_body(octet, info); } else if (litls_der_oid_eq(&ext_oid, OID_EXT_KEY_USAGE, sizeof(OID_EXT_KEY_USAGE))) { rc = parse_ext_key_usage_body(octet, info); } else { recognized = 0; } /* RFC 5280 §4.2: an unrecognised critical extension MUST cause the * certificate to be rejected (canonical case: NameConstraints * 2.5.29.30, which LITLS does not implement). A recognised but * malformed *critical* extension must also reject -- otherwise a * bad SAN/EKU/KU/BC silently downgrades to "extension absent", * which can flip verification decisions (e.g. SAN -> CN fallback). */ if (!recognized && is_critical) info->unknown_critical_extension = 1; if (rc != 0 && is_critical) info->unknown_critical_extension = 1; } } /* ── Main parser ──────────────────────────────────────── */ int litls_x509_parse(const uint8_t *der, size_t der_len, litls_x509_cert_info *info) { memset(info, 0, sizeof(*info)); /* Initial "absent" sentinel for pathLenConstraint (0 would mean "no * subordinate CAs allowed"; -1 means the field was not present). */ info->path_len_constraint = -1; /* Certificate SEQUENCE */ litls_der_cursor top; litls_der_init(&top, der, der_len); litls_der_element cert_seq; if (litls_der_next(&top, &cert_seq) != 0 || cert_seq.tag != ASN1_SEQUENCE) return -1; litls_der_cursor cc; litls_der_enter(&cc, &cert_seq); /* tbsCertificate SEQUENCE -- record raw bytes for signature verification */ const uint8_t *tbs_start = cc.buf + cc.pos; litls_der_element tbs_seq; if (litls_der_next(&cc, &tbs_seq) != 0 || tbs_seq.tag != ASN1_SEQUENCE) return -1; info->tbs = tbs_start; info->tbs_len = tbs_seq.total; /* signatureAlgorithm (outer) SEQUENCE { OID } */ litls_der_element outer_algo; if (litls_der_next(&cc, &outer_algo) != 0 || outer_algo.tag != ASN1_SEQUENCE) return -1; /* signatureValue BIT STRING */ litls_der_element sig_bits; if (litls_der_next(&cc, &sig_bits) != 0 || sig_bits.tag != ASN1_BIT_STRING) return -1; if (sig_bits.length < 2 || sig_bits.value[0] != 0x00) return -1; info->signature = sig_bits.value + 1; info->signature_len = sig_bits.length - 1; /* Now parse inside tbsCertificate. */ litls_der_cursor tc; litls_der_enter(&tc, &tbs_seq); /* version [0] EXPLICIT INTEGER -- optional, skip if present */ uint8_t peek_tag; if (litls_der_peek_tag(&tc, &peek_tag) == 0 && peek_tag == ASN1_CONTEXT(0)) litls_der_skip(&tc); /* serialNumber INTEGER -- skip */ litls_der_skip(&tc); /* signature AlgorithmIdentifier SEQUENCE { OID, ... } */ litls_der_element inner_algo_seq; if (litls_der_next(&tc, &inner_algo_seq) != 0 || inner_algo_seq.tag != ASN1_SEQUENCE) return -1; litls_der_cursor iac; litls_der_enter(&iac, &inner_algo_seq); litls_der_element algo_oid; if (litls_der_next(&iac, &algo_oid) != 0) return -1; info->sig_algo = parse_sig_algo(&algo_oid); /* issuer Name -- skip */ litls_der_skip(&tc); /* validity SEQUENCE { notBefore, notAfter } */ litls_der_element validity; if (litls_der_next(&tc, &validity) != 0 || validity.tag != ASN1_SEQUENCE) return -1; litls_der_cursor vc; litls_der_enter(&vc, &validity); litls_der_element nb, na; if (litls_der_next(&vc, &nb) != 0) return -1; if (litls_der_parse_time(&nb, &info->not_before) != 0) return -1; if (litls_der_next(&vc, &na) != 0) return -1; if (litls_der_parse_time(&na, &info->not_after) != 0) return -1; /* subject Name -- extract CN */ litls_der_element subject; if (litls_der_next(&tc, &subject) != 0 || subject.tag != ASN1_SEQUENCE) return -1; extract_cn(&subject, info->common_name, sizeof(info->common_name), &info->common_name_len); /* subjectPublicKeyInfo SEQUENCE */ litls_der_element spki; if (litls_der_next(&tc, &spki) != 0 || spki.tag != ASN1_SEQUENCE) return -1; if (parse_spki(&spki, info) != 0) return -1; /* Extensions [3] -- optional */ while (litls_der_peek_tag(&tc, &peek_tag) == 0) { if (peek_tag == ASN1_CONTEXT(3)) { litls_der_element exts; litls_der_next(&tc, &exts); parse_extensions(&exts, info); break; } litls_der_skip(&tc); } return 0; } /* ── ECDSA DER signature to raw r||s conversion ──────── */ int litls_ecdsa_sig_der_to_raw(const uint8_t *der_sig, size_t der_len, uint8_t *raw, size_t component_size) { litls_der_cursor c; litls_der_init(&c, der_sig, der_len); litls_der_element seq; if (litls_der_next(&c, &seq) != 0 || seq.tag != ASN1_SEQUENCE) return -1; litls_der_cursor sc; litls_der_enter(&sc, &seq); litls_der_element r_elem, s_elem; if (litls_der_next(&sc, &r_elem) != 0 || r_elem.tag != ASN1_INTEGER) return -1; if (litls_der_next(&sc, &s_elem) != 0 || s_elem.tag != ASN1_INTEGER) return -1; memset(raw, 0, component_size * 2); /* Copy r, right-aligned into raw[0..component_size-1] */ const uint8_t *r = r_elem.value; size_t r_len = r_elem.length; if (r_len > 0 && r[0] == 0x00) { r++; r_len--; } /* strip leading zero */ if (r_len > component_size) return -1; memcpy(raw + component_size - r_len, r, r_len); /* Copy s, right-aligned into raw[component_size..2*component_size-1] */ const uint8_t *s = s_elem.value; size_t s_len = s_elem.length; if (s_len > 0 && s[0] == 0x00) { s++; s_len--; } if (s_len > component_size) return -1; memcpy(raw + component_size * 2 - s_len, s, s_len); return 0; } /* ── PEM decoder ──────────────────────────────────────── */ int litls_pem_decode(const uint8_t *pem, size_t pem_len, uint8_t *der_out, size_t *der_out_len) { /* Find "-----BEGIN " */ const char *begin_marker = "-----BEGIN "; const char *end_marker = "-----END "; const char *p = (const char *)pem; const char *pend = p + pem_len; const char *start = NULL; for (const char *s = p; s < pend - 11; s++) { if (memcmp(s, begin_marker, 11) == 0) { /* Skip to end of line */ while (s < pend && *s != '\n') s++; if (s < pend) s++; start = s; break; } } if (!start) return -1; /* Find "-----END " */ const char *b64_end = NULL; for (const char *s = start; s < pend - 9; s++) { if (memcmp(s, end_marker, 9) == 0) { b64_end = s; break; } } if (!b64_end) return -1; /* Strip whitespace and decode base64 in-place into a temp region. * We decode directly into der_out. */ size_t b64_len = 0; uint8_t b64_buf[8192]; for (const char *s = start; s < b64_end; s++) { if (*s == '\n' || *s == '\r' || *s == ' ' || *s == '\t') continue; if (b64_len >= sizeof(b64_buf)) return -1; b64_buf[b64_len++] = (uint8_t)*s; } return litls_base64_decode((const char *)b64_buf, b64_len, der_out, der_out_len); } /* ── PEM private key parser ───────────────────────────── */ /* * Parse SEC 1 ECPrivateKey (inner structure for P-256): * SEQUENCE { INTEGER(1), OCTET STRING(32-byte scalar), [0]{OID}, [1]{BIT STRING(pubkey)} } */ static int parse_ec_private_key(const uint8_t *der, size_t der_len, uint8_t *private_key, size_t *private_key_len, uint8_t *public_key, size_t *public_key_len) { litls_der_cursor c; litls_der_init(&c, der, der_len); litls_der_element seq; if (litls_der_next(&c, &seq) != 0 || seq.tag != ASN1_SEQUENCE) return -1; litls_der_cursor sc; litls_der_enter(&sc, &seq); /* version INTEGER (must be 1) */ litls_der_element ver; if (litls_der_next(&sc, &ver) != 0 || ver.tag != ASN1_INTEGER) return -1; if (ver.length != 1 || ver.value[0] != 1) return -1; /* privateKey OCTET STRING */ litls_der_element priv; if (litls_der_next(&sc, &priv) != 0 || priv.tag != ASN1_OCTET_STRING) return -1; if (priv.length != 32) return -1; memcpy(private_key, priv.value, 32); *private_key_len = 32; /* Optional [0] curve OID -- skip */ /* Optional [1] public key */ *public_key_len = 0; uint8_t peek; while (litls_der_peek_tag(&sc, &peek) == 0) { litls_der_element elem; if (litls_der_next(&sc, &elem) != 0) break; if (peek == 0xA1) { /* [1] EXPLICIT */ litls_der_cursor pc; litls_der_enter(&pc, &elem); litls_der_element bits; if (litls_der_next(&pc, &bits) == 0 && bits.tag == ASN1_BIT_STRING) { if (bits.length == 66 && bits.value[0] == 0x00) { memcpy(public_key, bits.value + 1, 65); *public_key_len = 65; } } } } /* If no public key in PEM, derive from private key */ if (*public_key_len == 0) { if (litls_p256_keygen(NULL, NULL) != 0) { /* Can't derive -- not fatal, caller may not need pubkey for signing. * Actually litls_p256_ecdsa_sign only needs the 32-byte private scalar * and returns a raw signature. But for CertificateVerify we need * the public key in the identity struct for completeness. We'll generate * it via a scalar multiplication. For now, we use keygen as workaround. */ } /* Use a minimal derivation: multiply private key by generator. * p256_keygen generates a random key. We need scalar-mult by base point. * The sign function only needs private_key[32]. We'll leave public_key empty * and derive it when building the server identity. */ } return 0; } int litls_parse_private_key_pem(const uint8_t *pem, size_t pem_len, int *key_type, uint8_t *private_key, size_t *private_key_len, uint8_t *public_key, size_t *public_key_len) { /* Decode PEM to DER */ uint8_t der[4096]; size_t der_len = sizeof(der); if (litls_pem_decode(pem, pem_len, der, &der_len) != 0) return -1; /* Determine PEM type by checking the BEGIN line */ const char *p = (const char *)pem; int is_ec_private_key = 0; for (size_t i = 0; i + 11 <= pem_len; i++) { if (i + 30 <= pem_len && memcmp(p + i, "-----BEGIN EC PRIVATE KEY-----", 30) == 0) { is_ec_private_key = 1; break; } if (memcmp(p + i, "-----BEGIN ", 11) == 0) break; } if (is_ec_private_key) { /* SEC 1 EC Private Key (P-256) */ *key_type = LITLS_KEY_ECDSA_P256; return parse_ec_private_key(der, der_len, private_key, private_key_len, public_key, public_key_len); } /* PKCS#8 PrivateKeyInfo: * SEQUENCE { INTEGER(0), AlgorithmIdentifier SEQUENCE { OID, ... }, OCTET STRING { key } } */ litls_der_cursor c; litls_der_init(&c, der, der_len); litls_der_element seq; if (litls_der_next(&c, &seq) != 0 || seq.tag != ASN1_SEQUENCE) return -1; litls_der_cursor sc; litls_der_enter(&sc, &seq); /* version INTEGER (0) */ litls_der_element ver; if (litls_der_next(&sc, &ver) != 0 || ver.tag != ASN1_INTEGER) return -1; /* AlgorithmIdentifier SEQUENCE { OID, params? } */ litls_der_element algo_seq; if (litls_der_next(&sc, &algo_seq) != 0 || algo_seq.tag != ASN1_SEQUENCE) return -1; litls_der_cursor ac; litls_der_enter(&ac, &algo_seq); litls_der_element algo_oid; if (litls_der_next(&ac, &algo_oid) != 0) return -1; /* privateKey OCTET STRING */ litls_der_element priv_octet; if (litls_der_next(&sc, &priv_octet) != 0 || priv_octet.tag != ASN1_OCTET_STRING) return -1; if (litls_der_oid_eq(&algo_oid, OID_ED25519, sizeof(OID_ED25519))) { /* Ed25519 PKCS#8: OCTET STRING contains OCTET STRING(32-byte seed) */ *key_type = LITLS_KEY_ED25519; litls_der_cursor pc; litls_der_init(&pc, priv_octet.value, priv_octet.length); litls_der_element seed_elem; if (litls_der_next(&pc, &seed_elem) != 0 || seed_elem.tag != ASN1_OCTET_STRING) return -1; if (seed_elem.length != 32) return -1; /* Derive full keypair from seed */ litls_ed25519_keypair(public_key, private_key, seed_elem.value); *private_key_len = 64; *public_key_len = 32; return 0; } if (litls_der_oid_eq(&algo_oid, OID_EC_PUBKEY, sizeof(OID_EC_PUBKEY))) { /* Check curve parameter -- must be prime256v1 */ litls_der_element curve_oid; if (litls_der_next(&ac, &curve_oid) != 0) return -1; if (!litls_der_oid_eq(&curve_oid, OID_SECP256R1, sizeof(OID_SECP256R1))) return -1; /* PKCS#8 wraps a SEC 1 ECPrivateKey inside the OCTET STRING */ *key_type = LITLS_KEY_ECDSA_P256; return parse_ec_private_key(priv_octet.value, priv_octet.length, private_key, private_key_len, public_key, public_key_len); } return -1; /* Unsupported algorithm */ }