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}