/* x509_ed25519.c -- see x509_ed25519.h. */ #include "starkernel/x509_ed25519.h" #include typedef struct { const uint8_t *p; size_t len; } der_span_t; /* * Parse a single DER TLV starting at *cursor (must be < limit). Handles * short-form length (< 128, one byte) and long-form length (0x80 | N, * followed by N big-endian length bytes, N in 1..4 -- a real X.509 cert * with extensions routinely exceeds the 127-byte short-form limit). * Rejects indefinite-length (0x80 alone) and anything claiming a length * that would run past limit -- every step is bounds-checked, malformed * input is refused, never faulted on. * * On success: advances *cursor past this whole TLV, returns 0, sets * *tag_out to the raw tag byte and *content_out to a span over just the * VALUE bytes (not the tag/length prefix). */ static int der_next(const uint8_t **cursor, const uint8_t *limit, uint8_t *tag_out, der_span_t *content_out) { const uint8_t *p = *cursor; if (p >= limit) return -1; uint8_t tag = *p++; if (p >= limit) return -1; uint8_t len_byte = *p++; size_t len; if (len_byte & 0x80u) { uint8_t nbytes = (uint8_t)(len_byte & 0x7Fu); if (nbytes == 0 || nbytes > 4) return -1; /* indefinite-length or unreasonably large */ if ((size_t)(limit - p) < nbytes) return -1; len = 0; for (uint8_t i = 0; i < nbytes; i++) len = (len << 8) | *p++; } else { len = len_byte; } if ((size_t)(limit - p) < len) return -1; *tag_out = tag; content_out->p = p; content_out->len = len; *cursor = p + len; return 0; } int x509_extract_ed25519_pubkey(const uint8_t *der, size_t der_len, uint8_t pubkey_out[32]) { if (!der || !pubkey_out) return -1; const uint8_t *cur = der; const uint8_t *end = der + der_len; uint8_t tag; der_span_t cert; /* Certificate ::= SEQUENCE { tbsCertificate, signatureAlgorithm, signatureValue } */ if (der_next(&cur, end, &tag, &cert) != 0 || tag != 0x30) return -1; /* tbsCertificate ::= SEQUENCE { ... } -- first child of Certificate */ const uint8_t *tbs_cur = cert.p; const uint8_t *tbs_limit = cert.p + cert.len; der_span_t tbs; if (der_next(&tbs_cur, tbs_limit, &tag, &tbs) != 0 || tag != 0x30) return -1; const uint8_t *cur2 = tbs.p; const uint8_t *limit2 = tbs.p + tbs.len; /* version [0] EXPLICIT Version DEFAULT v1 -- present (context- * specific constructed tag 0xA0) for v2/v3 certs, absent for v1. * Peek: if present, consume it; if not, leave cur2 where it is so * the next field (serialNumber) is read correctly either way. */ { const uint8_t *peek = cur2; uint8_t ptag; der_span_t pcontent; if (der_next(&peek, limit2, &ptag, &pcontent) == 0 && ptag == 0xA0) { cur2 = peek; } } /* Skip serialNumber, signature (AlgorithmIdentifier), issuer, * validity, subject -- exactly 5 fields, none of which this parser * needs the content of, only their length to skip past them. */ for (int i = 0; i < 5; i++) { der_span_t skip; if (der_next(&cur2, limit2, &tag, &skip) != 0) return -1; } /* subjectPublicKeyInfo ::= SEQUENCE { algorithm, subjectPublicKey } */ der_span_t spki; if (der_next(&cur2, limit2, &tag, &spki) != 0 || tag != 0x30) return -1; const uint8_t *cur3 = spki.p; const uint8_t *limit3 = spki.p + spki.len; /* algorithm ::= AlgorithmIdentifier ::= SEQUENCE { algorithm OID, parameters ANY OPTIONAL } */ der_span_t algid; if (der_next(&cur3, limit3, &tag, &algid) != 0 || tag != 0x30) return -1; const uint8_t *cur4 = algid.p; const uint8_t *limit4 = algid.p + algid.len; der_span_t oid; if (der_next(&cur4, limit4, &tag, &oid) != 0 || tag != 0x06) return -1; /* OID 1.3.101.112 (RFC 8410 Ed25519), DER-encoded: 43 (=40*1+3), 101, 112, * each arc after the first pair fitting in one byte (both < 128). */ static const uint8_t ED25519_OID[3] = { 0x2B, 0x65, 0x70 }; if (oid.len != sizeof(ED25519_OID) || memcmp(oid.p, ED25519_OID, sizeof(ED25519_OID)) != 0) { return -1; /* not an Ed25519 key -- refuse, don't guess */ } /* subjectPublicKey ::= BIT STRING -- 1 unused-bits byte (must be 0 * for a byte-aligned 32-byte key) followed by the raw key itself. */ der_span_t bitstr; if (der_next(&cur3, limit3, &tag, &bitstr) != 0 || tag != 0x03) return -1; if (bitstr.len != 33 || bitstr.p[0] != 0x00) return -1; memcpy(pubkey_out, bitstr.p + 1, 32); return 0; }