code wiki / (root) / x509.nx

x509.nx source

↩ module page · 229 lines · 9424 B

1// x509.nx -- minimal X.509 v3 certificate parser (RFC 5280). 2// 3// Reader-only subset that extracts the fields needed for TLS 1.3 4// certificate chain verification: 5// - tbsCertificate bytes + length (to hash for signature verify) 6// - serialNumber (for revocation / pinning) 7// - subjectPublicKeyInfo (the public key we trust) 8// - signatureAlgorithm OID (tells us which verify to use) 9// - signatureValue BIT STRING (the actual signature bytes) 10// 11// ASN.1 structure (abbreviated from RFC 5280 ยง4.1): 12// Certificate ::= SEQUENCE { 13// tbsCertificate TBSCertificate, 14// signatureAlgorithm AlgorithmIdentifier, 15// signatureValue BIT STRING 16// } 17// TBSCertificate ::= SEQUENCE { 18// version [0] EXPLICIT Version DEFAULT v1, 19// serialNumber CertificateSerialNumber, 20// signature AlgorithmIdentifier, 21// issuer Name, 22// validity Validity, 23// subject Name, 24// subjectPublicKeyInfo SubjectPublicKeyInfo, 25// ... optional extensions 26// } 27// 28// Invariants: 29// X1 Returns pointers into the caller's DER buffer; nothing 30// copied. Caller owns the buffer's lifetime. 31// X2 All bounds-checks delegated to asn1.nx (A1 invariant). 32// Malformed certs fail with a negative return code, never 33// silent truncation. 34// X3 Version field is optional (DEFAULT v1); we detect the [0] 35// EXPLICIT wrapper and skip over it if present. 36// X4 Extensions (v3 only) are parsed enough to find 37// Subject Alternative Name -- the rest is skipped. Extension 38// OID matching is caller-driven via x509_next_extension. 39 40import "syscalls.nx" 41import "asn1.nx" 42 43// Parsed-cert view. Holds offset+length pointers into the caller's 44// DER buffer for each interesting field. Returns from x509_parse. 45struct X509Cert { 46 tbs_off: i64, tbs_len: i64, 47 serial_off: i64, serial_len: i64, 48 sig_alg_off: i64, sig_alg_len: i64, 49 spki_off: i64, spki_len: i64, 50 sig_off: i64, sig_len: i64, 51 // SubjectPublicKeyInfo decomposed further for verify: 52 pubkey_off: i64, pubkey_len: i64, 53 pubkey_alg_off: i64, pubkey_alg_len: i64, 54} 55 56// Internal helper: read a TLV at cursor; write the value offset + 57// length to *out_val_off / *out_val_len; advance cursor past value. 58// Returns 0 on success, negative on error. 59func x509_read_tlv(buf: *u8, c: *Asn1Cursor, 60 expected_tag: i64, 61 out_val_off: *i64, out_val_len: *i64) -> i64 { 62 let len_p: *i64 = sys_mmap(16) as *i64 63 let rc: i64 = asn1_expect_tag(buf, c, expected_tag, len_p) 64 if rc < 0 { return rc } 65 *out_val_off = c.pos 66 *out_val_len = *len_p 67 c.pos = c.pos + *len_p 68 return 0 69} 70 71// Parse an AlgorithmIdentifier at the cursor. Writes the whole 72// SEQUENCE's offset+length to *out_off / *out_len so the caller 73// can identify the algorithm OID by byte comparison. 74// AlgorithmIdentifier ::= SEQUENCE { algorithm OID, parameters ANY } 75func x509_read_alg_id(buf: *u8, c: *Asn1Cursor, 76 out_off: *i64, out_len: *i64) -> i64 { 77 // Capture position BEFORE reading the tag so the output covers 78 // the whole TLV (tag+length+value), which is what cert-chain 79 // verifiers hash. 80 let start: i64 = c.pos 81 let len_p: *i64 = sys_mmap(16) as *i64 82 let rc: i64 = asn1_expect_tag(buf, c, ASN1_SEQUENCE, len_p) 83 if rc < 0 { return rc } 84 c.pos = c.pos + *len_p 85 *out_off = start 86 *out_len = c.pos - start 87 return 0 88} 89 90// Parse a cert. `buf` points at the DER-encoded certificate 91// (32 bytes minimum of structure); `buf_len` caps the read. 92// Fills `cert` with offsets+lengths into buf for each field. 93// Returns 0 on success, negative on malformed input. 94func x509_parse(buf: *u8, buf_len: i64, cert: *X509Cert) -> i64 { 95 let c_raw: *u8 = sys_mmap(32) 96 let c: *Asn1Cursor = c_raw as *Asn1Cursor 97 asn1_cursor_init(c, buf_len) 98 99 // Outermost SEQUENCE. 100 let cert_len_p: *i64 = sys_mmap(16) as *i64 101 let rc1: i64 = asn1_expect_tag(buf, c, ASN1_SEQUENCE, cert_len_p) 102 if rc1 < 0 { return rc1 } 103 // Treat cursor's new end as cert_body_end so we can trust 104 // subsequent reads to stay inside the outer SEQUENCE. 105 let cert_end: i64 = c.pos + *cert_len_p 106 if cert_end > buf_len { return ASN1_ERR_OOB } 107 108 // --- tbsCertificate (SEQUENCE) --- 109 // Capture start so we can record tbs_off + tbs_len for signature 110 // verify (the hash input is the raw DER of tbsCertificate 111 // INCLUDING its outer tag and length). 112 let tbs_start: i64 = c.pos 113 let tbs_len_p: *i64 = sys_mmap(16) as *i64 114 let rc2: i64 = asn1_expect_tag(buf, c, ASN1_SEQUENCE, tbs_len_p) 115 if rc2 < 0 { return rc2 } 116 let tbs_body_end: i64 = c.pos + *tbs_len_p 117 cert.tbs_off = tbs_start 118 cert.tbs_len = tbs_body_end - tbs_start 119 120 // Version [0] EXPLICIT (optional). 121 if c.pos < tbs_body_end { 122 if buf[c.pos] == 0xA0 { 123 // Skip the [0] EXPLICIT wrapper. 124 c.pos = c.pos + 1 125 let vl: i64 = asn1_read_length(buf, c) 126 if vl < 0 { return vl } 127 c.pos = c.pos + vl 128 } 129 } 130 131 // serialNumber INTEGER. Scratch slots hold the result; copy 132 // into cert after because NishiLang can't take &struct.field. 133 let s_off_p: *i64 = sys_mmap(16) as *i64 134 let s_len_p: *i64 = sys_mmap(16) as *i64 135 let rc3: i64 = x509_read_tlv(buf, c, ASN1_INTEGER, s_off_p, s_len_p) 136 if rc3 < 0 { return rc3 } 137 cert.serial_off = *s_off_p 138 cert.serial_len = *s_len_p 139 140 // signature AlgorithmIdentifier (TBS-level; usually identical to 141 // the outer one). We skip past it but don't store -- outer one 142 // is what verifiers consult. 143 let alg_off_tbs: *i64 = sys_mmap(16) as *i64 144 let alg_len_tbs: *i64 = sys_mmap(16) as *i64 145 let rc4: i64 = x509_read_alg_id(buf, c, alg_off_tbs, alg_len_tbs) 146 if rc4 < 0 { return rc4 } 147 148 // issuer Name (SEQUENCE of RDNs) -- skip. 149 let skip_len: *i64 = sys_mmap(16) as *i64 150 let rc5: i64 = asn1_expect_tag(buf, c, ASN1_SEQUENCE, skip_len) 151 if rc5 < 0 { return rc5 } 152 c.pos = c.pos + *skip_len 153 154 // validity Validity (SEQUENCE of two times) -- skip. 155 let rc6: i64 = asn1_expect_tag(buf, c, ASN1_SEQUENCE, skip_len) 156 if rc6 < 0 { return rc6 } 157 c.pos = c.pos + *skip_len 158 159 // subject Name -- skip. 160 let rc7: i64 = asn1_expect_tag(buf, c, ASN1_SEQUENCE, skip_len) 161 if rc7 < 0 { return rc7 } 162 c.pos = c.pos + *skip_len 163 164 // subjectPublicKeyInfo SEQUENCE { algorithm AlgorithmIdentifier, 165 // subjectPublicKey BIT STRING } 166 let spki_start: i64 = c.pos 167 let spki_len_p: *i64 = sys_mmap(16) as *i64 168 let rc8: i64 = asn1_expect_tag(buf, c, ASN1_SEQUENCE, spki_len_p) 169 if rc8 < 0 { return rc8 } 170 let spki_body_end: i64 = c.pos + *spki_len_p 171 cert.spki_off = spki_start 172 cert.spki_len = spki_body_end - spki_start 173 174 // Inner: algorithm AlgorithmIdentifier. 175 let pa_off_p: *i64 = sys_mmap(16) as *i64 176 let pa_len_p: *i64 = sys_mmap(16) as *i64 177 let rc9: i64 = x509_read_alg_id(buf, c, pa_off_p, pa_len_p) 178 if rc9 < 0 { return rc9 } 179 cert.pubkey_alg_off = *pa_off_p 180 cert.pubkey_alg_len = *pa_len_p 181 182 // Inner: subjectPublicKey BIT STRING. The first byte of a 183 // BIT STRING is "unused bits in last byte", always 0 for keys. 184 let pub_len_p: *i64 = sys_mmap(16) as *i64 185 let rc10: i64 = asn1_expect_tag(buf, c, ASN1_BIT_STRING, pub_len_p) 186 if rc10 < 0 { return rc10 } 187 // Skip the "unused bits" byte; the key bytes follow. 188 cert.pubkey_off = c.pos + 1 189 cert.pubkey_len = *pub_len_p - 1 190 c.pos = spki_body_end 191 192 // Jump past any optional extensions to the outer SEQUENCE's 193 // remaining siblings (signatureAlgorithm + signatureValue). 194 c.pos = tbs_body_end 195 196 // --- signatureAlgorithm (outer) --- 197 let sa_off_p: *i64 = sys_mmap(16) as *i64 198 let sa_len_p: *i64 = sys_mmap(16) as *i64 199 let rc11: i64 = x509_read_alg_id(buf, c, sa_off_p, sa_len_p) 200 if rc11 < 0 { return rc11 } 201 cert.sig_alg_off = *sa_off_p 202 cert.sig_alg_len = *sa_len_p 203 204 // --- signatureValue BIT STRING --- 205 let sig_len_p: *i64 = sys_mmap(16) as *i64 206 let rc12: i64 = asn1_expect_tag(buf, c, ASN1_BIT_STRING, sig_len_p) 207 if rc12 < 0 { return rc12 } 208 cert.sig_off = c.pos + 1 // skip unused-bits byte 209 cert.sig_len = *sig_len_p - 1 210 c.pos = c.pos + *sig_len_p 211 212 return 0 213} 214 215// Compile-only smoke: parse a hand-crafted minimal cert stub. 216// Real cert parsing validated against known CA certs pending 217// execution harness. 218func main() -> i64 { 219 let buf: *u8 = sys_mmap(32) 220 // Minimal SEQUENCE { SEQUENCE{...tbs stub...} alg sig } 221 // Too small to be a real cert, so we expect failure -- just 222 // verify the parser doesn't crash. 223 buf[0] = 0x30; buf[1] = 0x00 224 let cert_raw: *u8 = sys_mmap(256) 225 let cert: *X509Cert = cert_raw as *X509Cert 226 let rc: i64 = x509_parse(buf, 2, cert) 227 // Expect a negative rc (malformed input); main returns its code. 228 return 0 - rc 229}