nx_tls13_auth.nx source
↩ module page · 274 lines · 11648 B
1// nx_tls13_auth.nx -- TLS 1.3 auth-related handshake message parsers.
2//
3// Phase 0b §H of the Nishi TLS 1.3 stack per
4// docs/NISHI_TLS13_GAP_AUDIT.md. Pure-byte parsers for the three
5// server-side handshake messages that follow ServerHello in a TLS
6// 1.3 1-RTT flow:
7//
8// 1. EncryptedExtensions (RFC 8446 §4.3.1)
9// Just an extensions blob; carries server-side extensions
10// that the spec wants encrypted (max_fragment_length, ALPN
11// pick, etc.).
12//
13// 2. Certificate (RFC 8446 §4.4.2)
14// certificate_request_context: opaque<0..255> (1 byte len)
15// certificate_list:
16// per entry:
17// opaque cert_data<1..2^24-1> (3 byte len)
18// Extension extensions<0..2^16-1> (2 byte len)
19//
20// 3. CertificateVerify (RFC 8446 §4.4.3)
21// SignatureScheme algorithm (2 bytes)
22// opaque signature<0..2^16-1> (2 byte len)
23//
24// All three sit inside the standard Handshake wrapper (msg_type +
25// uint24 length). This module does NO cryptographic validation --
26// signature verification, cert chain validation, hostname matching
27// all live in Gap I (nx_x509_validate, queued). Here we just turn
28// bytes into structure offsets the caller can hand to the verifier.
29//
30// What it does today:
31// - parse EncryptedExtensions; expose extensions blob offsets
32// - parse Certificate; expose FIRST cert's DER bytes + extensions
33// offsets (most TLS 1.3 deployments use a 1-3 cert chain;
34// iterating beyond the first is queued)
35// - parse CertificateVerify; expose scheme + signature offsets
36// - sealed verdict on every failure path
37//
38// What it doesn't do yet:
39// - iterate every cert in chain (caller can re-call into the
40// chain after the first; tls13_parse_certificate_chain_at queued)
41// - signature verification (composes against nx_ed25519 / future
42// nx_ecdsa / future nx_rsa_pss)
43// - cert chain validation (Gap I; nx_x509 ships DER parsing,
44// nx_x509_validate is the path-validation arc)
45// - OCSP stapling extension parse (RFC 6961 status_request_v2)
46//
47// KAT verified:
48// - hand-built EncryptedExtensions message round-trip
49// - hand-built Certificate with 1 cert + empty extensions roundtrip
50// - hand-built CertificateVerify with ed25519 scheme roundtrip
51// - tampered headers (wrong msg_type, truncated lengths) rejected
52//
53// Composes with:
54// - nx_tls13 (Handshake type constants + u16/u24 helpers)
55// - nx_x509 (existing; DER parse for the actual cert bytes)
56// - nx_ed25519 / future signature primitives (caller invokes after
57// extracting scheme + sig offsets)
58// - nx_tls13_client (queued; state machine consumes these)
59//
60// license_tier: INDEPENDENT_REDERIVE
61// genealogy_id: international-research-sources/ietf/rfc_8446
62// lineage_id: nishi_tls13_auth_parsers_q10
63
64// nx_safety_envelope:
65// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
66// sil_target: SIL1
67// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
68// verdict: NOT_YET_EVALUATED
69
70import "nx_syscalls.nx"
71import "nx_tls13.nx"
72
73const NX_TLS13_AUTH_VERDICT_OK: i64 = 1
74const NX_TLS13_AUTH_VERDICT_BAD_HEADER: i64 = 2
75const NX_TLS13_AUTH_VERDICT_TRUNCATED: i64 = 3
76const NX_TLS13_AUTH_VERDICT_EMPTY_CHAIN: i64 = 4
77const NX_TLS13_AUTH_VERDICT_TOO_LONG: i64 = 5
78const NX_TLS13_AUTH_VERDICT_TOO_MANY_CERTS: i64 = 6
79const NX_TLS13_AUTH_VERDICT_N: i64 = 7
80
81// Hard ceilings to defend against crafted oversize length-prefixes.
82// TLS 1.3 record cap is 2^14 = 16384. Cert chains rarely exceed
83// 10 KB. EncryptedExtensions blob ~1 KB typical, 8 KB worst.
84const NX_TLS13_AUTH_MAX_MESSAGE: i64 = 16384
85
86// Read the 4-byte Handshake header at the start of `buf` and verify
87// the type matches `expected_type`. Returns body offset (always 4)
88// on success or a negative verdict.
89func tls13_auth_check_handshake(buf: *u8, n: i64, expected_type: i64) -> i64 {
90 if n < 4 { return 0 - NX_TLS13_AUTH_VERDICT_TRUNCATED }
91 let msg_type: i64 = buf[0] & 0xff
92 if msg_type != expected_type { return 0 - NX_TLS13_AUTH_VERDICT_BAD_HEADER }
93 let body_len: i64 = ((buf[1] & 0xff) << 16) | ((buf[2] & 0xff) << 8) | (buf[3] & 0xff)
94 if body_len > NX_TLS13_AUTH_MAX_MESSAGE { return 0 - NX_TLS13_AUTH_VERDICT_TOO_LONG }
95 if 4 + body_len > n { return 0 - NX_TLS13_AUTH_VERDICT_TRUNCATED }
96 return 4
97}
98
99// ---- Parse EncryptedExtensions (RFC 8446 §4.3.1). ----
100//
101// Body: Extension extensions<0..2^16-1>; (2-byte list length + extensions)
102//
103// Outputs: byte offsets within `buf` to the extensions blob.
104func tls13_parse_encrypted_extensions(
105 buf: *u8, n: i64,
106 out_extensions_off: *i64,
107 out_extensions_len: *i64
108) -> i64 {
109 let body_off: i64 = tls13_auth_check_handshake(buf, n, HT_ENCRYPTED_EXTENSIONS)
110 if body_off < 0 { return 0 - body_off }
111 if body_off + 2 > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED }
112 let ext_list_len: i64 = tls_read_u16_be(buf, body_off)
113 if body_off + 2 + ext_list_len > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED }
114 *out_extensions_off = body_off + 2
115 *out_extensions_len = ext_list_len
116 return NX_TLS13_AUTH_VERDICT_OK
117}
118
119// ---- Parse Certificate message (RFC 8446 §4.4.2). ----
120//
121// Body:
122// opaque certificate_request_context<0..255>
123// (1 byte len + bytes; server-side this is always empty,
124// client-side responds with the bytes from CertificateRequest)
125// per entry in certificate_list (3-byte total list length):
126// opaque cert_data<1..2^24-1> (3 byte len + DER cert)
127// Extension extensions<0..2^16-1> (2 byte len + extensions)
128//
129// We expose the FIRST entry's cert DER offsets + extensions offsets.
130// Iterating beyond is queued (tls13_parse_certificate_chain_at).
131func tls13_parse_certificate_chain_first(
132 buf: *u8, n: i64,
133 out_ctx_off: *i64, out_ctx_len: *i64,
134 out_cert_off: *i64, out_cert_len: *i64,
135 out_cert_exts_off: *i64, out_cert_exts_len: *i64,
136 out_chain_total_len: *i64
137) -> i64 {
138 let body_off: i64 = tls13_auth_check_handshake(buf, n, HT_CERTIFICATE)
139 if body_off < 0 { return 0 - body_off }
140 var o: i64 = body_off
141 // certificate_request_context
142 if o + 1 > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED }
143 let ctx_len: i64 = buf[o] & 0xff
144 o = o + 1
145 if o + ctx_len > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED }
146 *out_ctx_off = o
147 *out_ctx_len = ctx_len
148 o = o + ctx_len
149 // certificate_list: 3-byte length
150 if o + 3 > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED }
151 let chain_len: i64 = ((buf[o] & 0xff) << 16) | ((buf[o + 1] & 0xff) << 8) | (buf[o + 2] & 0xff)
152 if chain_len < 1 { return NX_TLS13_AUTH_VERDICT_EMPTY_CHAIN }
153 if o + 3 + chain_len > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED }
154 *out_chain_total_len = chain_len
155 o = o + 3
156 // First CertificateEntry
157 if o + 3 > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED }
158 let cert_len: i64 = ((buf[o] & 0xff) << 16) | ((buf[o + 1] & 0xff) << 8) | (buf[o + 2] & 0xff)
159 if cert_len < 1 { return NX_TLS13_AUTH_VERDICT_EMPTY_CHAIN }
160 if o + 3 + cert_len > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED }
161 *out_cert_off = o + 3
162 *out_cert_len = cert_len
163 o = o + 3 + cert_len
164 // First entry's extensions
165 if o + 2 > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED }
166 let ce_len: i64 = tls_read_u16_be(buf, o)
167 if o + 2 + ce_len > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED }
168 *out_cert_exts_off = o + 2
169 *out_cert_exts_len = ce_len
170 return NX_TLS13_AUTH_VERDICT_OK
171}
172
173// ---- Parse CertificateVerify (RFC 8446 §4.4.3). ----
174//
175// Body:
176// SignatureScheme algorithm (2 bytes)
177// opaque signature<0..2^16-1> (2 byte len + signature)
178//
179// Outputs: chosen SignatureScheme + sig offset/length within buf.
180// Caller invokes the right signature primitive (nx_ed25519 /
181// nx_ecdsa / nx_rsa_pss) based on `*out_scheme`.
182func tls13_parse_certificate_verify(
183 buf: *u8, n: i64,
184 out_scheme: *i64,
185 out_sig_off: *i64,
186 out_sig_len: *i64
187) -> i64 {
188 let body_off: i64 = tls13_auth_check_handshake(buf, n, HT_CERTIFICATE_VERIFY)
189 if body_off < 0 { return 0 - body_off }
190 if body_off + 4 > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED }
191 *out_scheme = tls_read_u16_be(buf, body_off)
192 let sig_len: i64 = tls_read_u16_be(buf, body_off + 2)
193 if body_off + 4 + sig_len > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED }
194 *out_sig_off = body_off + 4
195 *out_sig_len = sig_len
196 return NX_TLS13_AUTH_VERDICT_OK
197}
198
199// ---- Parse ALL entries from a Certificate handshake message. ----
200//
201// Like tls13_parse_certificate_chain_first but emits offset/length
202// pairs for EVERY CertificateEntry in the chain, into caller-
203// supplied arrays. Stops early if the array runs out of space
204// (returns NX_TLS13_AUTH_VERDICT_TOO_MANY_CERTS).
205//
206// Output:
207// out_cert_offs[i] = byte offset of i-th cert's DER bytes
208// out_cert_lens[i] = byte length of i-th cert's DER bytes
209// *out_n_certs = number of certs parsed
210//
211// Used by the chain-walker integration: caller calls this to
212// build an array of cert offsets, then x509_parse on each, then
213// nx_x509_chain_verify on the resulting parsed-cert array.
214//
215// Per Cardinals 9 (single-responsibility -- parse, not verify),
216// 12 (defensive at boundaries -- bounded reads + cap-check on
217// max_certs).
218func tls13_parse_certificate_chain_all(
219 buf: *u8, n: i64,
220 out_cert_offs: *i64,
221 out_cert_lens: *i64,
222 out_n_certs: *i64,
223 max_certs: i64
224) -> i64 {
225 if max_certs < 1 { return NX_TLS13_AUTH_VERDICT_TOO_MANY_CERTS }
226
227 let body_off: i64 = tls13_auth_check_handshake(buf, n, HT_CERTIFICATE)
228 if body_off < 0 { return 0 - body_off }
229 var o: i64 = body_off
230
231 // certificate_request_context (skip)
232 if o + 1 > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED }
233 let ctx_len: i64 = buf[o] & 0xff
234 o = o + 1
235 if o + ctx_len > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED }
236 o = o + ctx_len
237
238 // certificate_list: 3-byte length, then entries.
239 if o + 3 > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED }
240 let chain_len: i64 = ((buf[o] & 0xff) << 16) | ((buf[o + 1] & 0xff) << 8) | (buf[o + 2] & 0xff)
241 if chain_len < 1 { return NX_TLS13_AUTH_VERDICT_EMPTY_CHAIN }
242 o = o + 3
243 let chain_end: i64 = o + chain_len
244 if chain_end > n { return NX_TLS13_AUTH_VERDICT_TRUNCATED }
245
246 var n_certs: i64 = 0
247 while o < chain_end {
248 if n_certs >= max_certs { return NX_TLS13_AUTH_VERDICT_TOO_MANY_CERTS }
249 // CertificateEntry: 3-byte cert_len + cert_data
250 if o + 3 > chain_end { return NX_TLS13_AUTH_VERDICT_TRUNCATED }
251 let cert_len: i64 = ((buf[o] & 0xff) << 16) | ((buf[o + 1] & 0xff) << 8) | (buf[o + 2] & 0xff)
252 if cert_len < 1 { return NX_TLS13_AUTH_VERDICT_EMPTY_CHAIN }
253 if o + 3 + cert_len > chain_end { return NX_TLS13_AUTH_VERDICT_TRUNCATED }
254 out_cert_offs[n_certs] = o + 3
255 out_cert_lens[n_certs] = cert_len
256 o = o + 3 + cert_len
257 // Then 2-byte ext_list_len + ext_data
258 if o + 2 > chain_end { return NX_TLS13_AUTH_VERDICT_TRUNCATED }
259 let ce_len: i64 = tls_read_u16_be(buf, o)
260 if o + 2 + ce_len > chain_end { return NX_TLS13_AUTH_VERDICT_TRUNCATED }
261 o = o + 2 + ce_len
262 n_certs = n_certs + 1
263 }
264
265 *out_n_certs = n_certs
266 return NX_TLS13_AUTH_VERDICT_OK
267}
268
269// Sealed-enum validity gate.
270func nx_tls13_auth_verdict_is_valid(v: i64) -> i64 {
271 if v < 0 { return 0 }
272 if v >= NX_TLS13_AUTH_VERDICT_N { return 0 }
273 return 1
274}