nx_https_get_happy_test.nx source
↩ module page · 657 lines · 24381 B
1// nx_https_get_happy_test.nx -- REAL fork+child happy-path KAT
2// for the nx_https_get / nx_tls13_client_session_run state machine
3// over TCP loopback.
4//
5// Closes the verdict-only gap explicitly named in the
6// 2026-05-19 PM honest verification audit
7// (NISHI_HTTPS_ARC_STATUS_2026_05_19.md):
8//
9// "nx_https_get (commit 72771424): only tests BAD_URL paths.
10// The full handshake + GET round-trip against a real or
11// loopback HTTPS server is NOT yet a KAT. The lower wire
12// layer IS verified via the pre-existing nx_https_loopback_test
13// (fork+child TCP loopback running both TLS endpoints in
14// NishiLang), but that test bypasses the Tls13ClientSession
15// state machine wrapper..."
16//
17// This file builds the loopback test that DOES exercise the
18// session state machine wrapper. Architecture:
19//
20// Parent process (CLIENT):
21// - socket+bind+listen on 127.0.0.1:19454
22// - accept(); pass accepted fd to nx_tls13_client_session_run
23// - assert session_run returns POSITIVE pointer (= state
24// reached CONNECTED, all 4 dispatched recv_hs verdicts OK,
25// app keys derived)
26//
27// Child process (SERVER):
28// - socket+connect to loopback port
29// - read ClientHello as REAL TLS record (5-byte header)
30// - emit ServerHello as REAL TLS record (5-byte header)
31// - derive handshake keys
32// - emit AEAD-encrypted EE + Cert + CV + SF records
33// - read AEAD-encrypted CF record
34//
35// The CERT carried in the Certificate message is a REAL
36// 197-byte minimal v1 Ed25519 cert with SAN entry "localhost"
37// matching the SNI hostname used by the client. The cert's
38// outer sig is a REAL Ed25519 signature over its own TBS bytes
39// (now that SHA-512 K constants are fixed -- commit c9ffedfd --
40// Ed25519 sign produces canonical FIPS-compliant output).
41//
42// The cert is added to the client's TrustStore as its own
43// trust anchor (self-signed -> leaf is anchor). Cert chain
44// validation passes:
45// - Leaf SAN matches SNI hostname ("localhost")
46// - Validity period [2023..2026] covers now_epoch (2024-06-15)
47// - Self-signed: leaf verifies under leaf's own pub (the
48// pipeline walks: leaf's issuer-DN matches anchor's
49// subject-DN -> verify leaf sig under anchor's pubkey ->
50// ed25519_verify_full passes because SHA-512 now correct)
51//
52// The CV signature can stay garbage bytes because CV sig
53// verification is still STUBBED in
54// nx_tls13_client_dispatch_with_validation (the dispatcher only
55// validates the Cert message, not CV). This is documented in
56// nx_tls13_client.nx line 170-171 + the dispatcher wrapper.
57// The KAT exercises everything EXCEPT CV sig verification.
58//
59// The SF (server Finished) MUST have a correct HMAC -- the
60// client dispatcher's WAIT_SF branch checks it via
61// nx_tls13_finished_compute. We compute the MAC at test time.
62//
63// expect_exit: 0
64// license_tier: ORIGINAL
65
66import "nx_syscalls.nx"
67import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host
68import "nx_sha256.nx"
69import "nx_sha512.nx"
70import "nx_ed25519.nx"
71import "nx_ed25519_signature.nx"
72import "nx_ed25519_scalar.nx"
73import "nx_ed25519_arith.nx"
74import "nx_x25519.nx"
75import "nx_x509.nx"
76import "nx_x509_trust_store.nx"
77import "nx_tls13.nx"
78import "nx_tls13_ext.nx"
79import "nx_tls13_hello.nx"
80import "nx_tls13_finished.nx"
81import "nx_tls13_transcript.nx"
82import "nx_tls13_handshake.nx"
83import "nx_tls13_schedule.nx"
84import "nx_tls13_record.nx"
85import "nx_tls13_server.nx"
86import "nx_tls13_client_validate_certificate.nx"
87import "nx_tls13_client_session_run.nx"
88
89const HPORT: i64 = 19454
90const NOW_EPOCH: i64 = 1718452800 // 2024-06-15 within cert validity
91
92// ---------------------------------------------------------------
93// TCP + framing helpers
94// ---------------------------------------------------------------
95
96func hbuild_addr(out: *u8, port: i64) -> i64 {
97 out[0] = 2; out[1] = 0
98 out[2] = (port >> 8) & 0xff
99 out[3] = port & 0xff
100 out[4] = 127; out[5] = 0; out[6] = 0; out[7] = 1
101 var i: i64 = 8
102 while i < 16 { out[i] = 0; i = i + 1 }
103 return 16
104}
105
106func hread_n(fd: i64, buf: *u8, n: i64) -> i64 {
107 var got: i64 = 0
108 while got < n {
109 let r: i64 = sys_read(fd, buf + got, n - got)
110 if r <= 0 { return 0 - 1 }
111 got = got + r
112 }
113 return got
114}
115
116func hwrite_n(fd: i64, buf: *u8, n: i64) -> i64 {
117 var off: i64 = 0
118 while off < n {
119 let w: i64 = sys_write(fd, (buf as i64 + off) as *u8, n - off)
120 if w <= 0 { return 0 - 1 }
121 off = off + w
122 }
123 return 0
124}
125
126// Wrap handshake body bytes in a TLS plaintext record (type=22,
127// version=0x0303) and send to fd.
128func send_tls_plain_record(fd: i64, body: *u8, body_len: i64) -> i64 {
129 let hdr: *u8 = sys_mmap(16)
130 hdr[0] = NX_TLS13_CT_HANDSHAKE & 0xff
131 hdr[1] = 0x03
132 hdr[2] = 0x03
133 hdr[3] = (body_len >> 8) & 0xff
134 hdr[4] = body_len & 0xff
135 if hwrite_n(fd, hdr, 5) < 0 { return 0 - 1 }
136 if hwrite_n(fd, body, body_len) < 0 { return 0 - 2 }
137 return 0
138}
139
140// Read a full TLS plaintext record from fd; returns body length,
141// sets *out_body_off = 5 (skip header). out_buf must be sized
142// for the full record (header + body).
143func recv_tls_plain_record(fd: i64, out_buf: *u8, out_cap: i64) -> i64 {
144 if out_cap < 5 { return 0 - 1 }
145 if hread_n(fd, out_buf, 5) < 0 { return 0 - 2 }
146 let body_len: i64 = ((out_buf[3] & 0xff) << 8) | (out_buf[4] & 0xff)
147 if 5 + body_len > out_cap { return 0 - 3 }
148 if hread_n(fd, out_buf + 5, body_len) < 0 { return 0 - 4 }
149 return body_len
150}
151
152// AEAD record send (inner type 22 = handshake-after-keys).
153func send_aead_record(fd: i64, key: *u8, iv: *u8, seq: i64,
154 body: *u8, body_len: i64, inner_type: i64) -> i64 {
155 let hdr: *u8 = sys_mmap(16)
156 let ct: *u8 = sys_mmap(body_len + 64)
157 let tag: *u8 = sys_mmap(16)
158 let v: i64 = nx_tls13_record_encrypt(
159 key, iv, seq, body, body_len, inner_type, 0, hdr, ct, tag
160 )
161 if v != NX_TLS13_REC_VERDICT_OK { return 0 - 1 }
162 if hwrite_n(fd, hdr, 5) < 0 { return 0 - 2 }
163 if hwrite_n(fd, ct, body_len + 1) < 0 { return 0 - 3 }
164 if hwrite_n(fd, tag, 16) < 0 { return 0 - 4 }
165 return 0
166}
167
168// AEAD record recv. Returns decrypted plaintext length.
169func recv_aead_record(fd: i64, key: *u8, iv: *u8, seq: i64,
170 out: *u8, out_cap: i64) -> i64 {
171 let hdr: *u8 = sys_mmap(16)
172 if hread_n(fd, hdr, 5) < 0 { return 0 - 1 }
173 let total: i64 = ((hdr[3] & 0xff) << 8) | (hdr[4] & 0xff)
174 if total < 17 { return 0 - 2 }
175 let ct_len: i64 = total - 16
176 let buf: *u8 = sys_mmap(total + 16)
177 if hread_n(fd, buf, total) < 0 { return 0 - 3 }
178 let tag: *u8 = sys_mmap(16)
179 var ti: i64 = 0
180 while ti < 16 { tag[ti] = buf[ct_len + ti]; ti = ti + 1 }
181 let rct: *i64 = sys_mmap(8) as *i64
182 let rln: *i64 = sys_mmap(8) as *i64
183 let v: i64 = nx_tls13_record_decrypt(
184 key, iv, seq, hdr, buf, ct_len, tag, out, rct, rln
185 )
186 if v != NX_TLS13_REC_VERDICT_OK { return 0 - 4 }
187 return rln[0]
188}
189
190// ---------------------------------------------------------------
191// Ed25519 helpers: derive pub from priv via real arithmetic
192// ---------------------------------------------------------------
193
194// Compute s_scalar = SHA-512(priv)[0..32] clamped.
195func derive_s_scalar(priv: *u8, s_out: *u8) -> i64 {
196 let h: *u8 = sys_mmap(64)
197 ed25519_sha512(priv, 32, h)
198 var i: i64 = 0
199 while i < 32 { s_out[i] = h[i]; i = i + 1 }
200 s_out[0] = s_out[0] & 0xf8
201 s_out[31] = (s_out[31] & 0x7f) | 0x40
202 return 0
203}
204
205// Compute pub = compress(s_scalar * G).
206func derive_pub_from_priv(priv: *u8, pub_out: *u8) -> i64 {
207 let s: *u8 = sys_mmap(32)
208 derive_s_scalar(priv, s)
209 let bp: *GeP3 = ge_p3_alloc()
210 ed25519_basepoint_p3(bp)
211 let A: *GeP3 = ge_p3_alloc()
212 ge_scalar_mul(A, s, bp)
213 ge_p3_compress(pub_out, A)
214 return 0
215}
216
217// ---------------------------------------------------------------
218// Build a real Ed25519-signed 197-byte v1 cert with SAN
219// ---------------------------------------------------------------
220
221func emit_ed25519_algid(buf: *u8, off: i64) -> i64 {
222 buf[off + 0] = 0x30 as u8
223 buf[off + 1] = 5 as u8
224 buf[off + 2] = 0x06 as u8
225 buf[off + 3] = 3 as u8
226 buf[off + 4] = 0x2B as u8
227 buf[off + 5] = 0x65 as u8
228 buf[off + 6] = 0x70 as u8
229 return 7
230}
231
232func emit_utctime(buf: *u8, off: i64,
233 yyyy: i64, mo: i64, d: i64,
234 h: i64, mi: i64, s: i64) -> i64 {
235 buf[off + 0] = 0x17 as u8
236 buf[off + 1] = 13 as u8
237 let yy: i64 = yyyy - 2000
238 buf[off + 2] = (0x30 + (yy / 10)) as u8
239 buf[off + 3] = (0x30 + (yy % 10)) as u8
240 buf[off + 4] = (0x30 + (mo / 10)) as u8
241 buf[off + 5] = (0x30 + (mo % 10)) as u8
242 buf[off + 6] = (0x30 + (d / 10)) as u8
243 buf[off + 7] = (0x30 + (d % 10)) as u8
244 buf[off + 8] = (0x30 + (h / 10)) as u8
245 buf[off + 9] = (0x30 + (h % 10)) as u8
246 buf[off + 10] = (0x30 + (mi / 10)) as u8
247 buf[off + 11] = (0x30 + (mi % 10)) as u8
248 buf[off + 12] = (0x30 + (s / 10)) as u8
249 buf[off + 13] = (0x30 + (s % 10)) as u8
250 buf[off + 14] = 0x5A as u8
251 return 15
252}
253
254func emit_dnsname(buf: *u8, off: i64, name: *u8, name_len: i64) -> i64 {
255 buf[off] = 0x82 as u8
256 buf[off + 1] = name_len as u8
257 var i: i64 = 0
258 while i < name_len {
259 buf[off + 2 + i] = name[i]
260 i = i + 1
261 }
262 return 2 + name_len
263}
264
265// Build cert with SAN "localhost" (9 chars) + Ed25519 SPKI from
266// given pub bytes. Returns total cert length. TBS is at offset 3
267// (after outer SEQUENCE header) length TBS_TOTAL = 118. Caller
268// must sign tbs bytes + write into the cert at the sig position
269// (offset 3 + TBS_TOTAL + 7 alg id + 3 BIT STRING header).
270func build_cert_with_pub(buf: *u8, pub: *u8) -> i64 {
271 let SAN_INNER_LEN: i64 = 11 // 1 dNSName entry "localhost"
272 let SAN_SEQ_TOTAL: i64 = 2 + SAN_INNER_LEN // 13
273 let OS_TOTAL: i64 = 2 + SAN_SEQ_TOTAL // 15
274 let OID_TOTAL: i64 = 5
275 let EXT_BODY: i64 = OID_TOTAL + OS_TOTAL // 20
276 let EXT_TOTAL: i64 = 2 + EXT_BODY // 22
277 let EXTS_BODY: i64 = EXT_TOTAL // 22
278 let EXTS_TOTAL: i64 = 2 + EXTS_BODY // 24
279 let EXP_WRAP_TOTAL: i64 = 2 + EXTS_TOTAL // 26
280
281 let VAL_BODY: i64 = 30
282 let VAL_TOTAL: i64 = 2 + VAL_BODY
283
284 let SPKI_BODY: i64 = 7 + 35
285 let SPKI_TOTAL: i64 = 2 + SPKI_BODY
286
287 let TBS_BODY: i64 = 3 + 7 + 2 + VAL_TOTAL + 2 + SPKI_TOTAL + EXP_WRAP_TOTAL // 116
288 let TBS_TOTAL: i64 = 2 + TBS_BODY
289
290 let SIG_TOTAL: i64 = 67
291 let OUTER_BODY: i64 = TBS_TOTAL + 7 + SIG_TOTAL
292 let OUTER_TOTAL: i64 = 3 + OUTER_BODY
293
294 var o: i64 = 0
295 buf[o] = 0x30 as u8; o = o + 1
296 buf[o] = 0x81 as u8; o = o + 1
297 buf[o] = OUTER_BODY as u8; o = o + 1
298
299 let tbs_start: i64 = o
300 buf[o] = 0x30 as u8; o = o + 1
301 buf[o] = TBS_BODY as u8; o = o + 1
302
303 buf[o] = 0x02 as u8; buf[o+1] = 0x01 as u8; buf[o+2] = 0x01 as u8; o = o + 3
304 o = o + emit_ed25519_algid(buf, o)
305 buf[o] = 0x30 as u8; buf[o+1] = 0x00 as u8; o = o + 2
306 buf[o] = 0x30 as u8; o = o + 1
307 buf[o] = VAL_BODY as u8; o = o + 1
308 o = o + emit_utctime(buf, o, 2023, 1, 1, 0, 0, 0)
309 o = o + emit_utctime(buf, o, 2026, 1, 1, 0, 0, 0)
310 buf[o] = 0x30 as u8; buf[o+1] = 0x00 as u8; o = o + 2
311 buf[o] = 0x30 as u8; o = o + 1
312 buf[o] = SPKI_BODY as u8; o = o + 1
313 o = o + emit_ed25519_algid(buf, o)
314 buf[o] = 0x03 as u8; o = o + 1 // BIT STRING tag
315 buf[o] = 0x21 as u8; o = o + 1 // length 33
316 buf[o] = 0x00 as u8; o = o + 1 // unused bits
317 var ki: i64 = 0
318 while ki < 32 { buf[o + ki] = pub[ki]; ki = ki + 1 }
319 o = o + 32
320
321 buf[o] = 0xA3 as u8; o = o + 1
322 buf[o] = EXTS_TOTAL as u8; o = o + 1
323 buf[o] = 0x30 as u8; o = o + 1
324 buf[o] = EXTS_BODY as u8; o = o + 1
325 buf[o] = 0x30 as u8; o = o + 1
326 buf[o] = EXT_BODY as u8; o = o + 1
327 buf[o] = 0x06 as u8; buf[o+1] = 0x03 as u8; buf[o+2] = 0x55 as u8
328 buf[o+3] = 0x1D as u8; buf[o+4] = 0x11 as u8; o = o + 5
329 buf[o] = 0x04 as u8; o = o + 1
330 buf[o] = SAN_SEQ_TOTAL as u8; o = o + 1
331 buf[o] = 0x30 as u8; o = o + 1
332 buf[o] = SAN_INNER_LEN as u8; o = o + 1
333 let host: *u8 = sys_mmap(16)
334 host[0]=0x6c; host[1]=0x6f; host[2]=0x63; host[3]=0x61
335 host[4]=0x6c; host[5]=0x68; host[6]=0x6f; host[7]=0x73
336 host[8]=0x74 // "localhost"
337 o = o + emit_dnsname(buf, o, host, 9)
338
339 let tbs_end: i64 = o
340
341 o = o + emit_ed25519_algid(buf, o)
342
343 // signatureValue BIT STRING wrapper, sig bytes filled by caller
344 buf[o] = 0x03 as u8; o = o + 1
345 buf[o] = 0x41 as u8; o = o + 1
346 buf[o] = 0x00 as u8; o = o + 1
347 // 64 bytes placeholder for the sig (caller fills)
348 var si: i64 = 0
349 while si < 64 { buf[o + si] = 0 as u8; si = si + 1 }
350 o = o + 64
351
352 return o
353}
354
355// Sign TBS bytes at cert[3..3+TBS_TOTAL] and place sig at the
356// end (the 64 bytes after the BIT STRING wrapper).
357// TBS_TOTAL is the outer SEQUENCE of the TBS including its 2-byte
358// header. The TBS body bytes signed by Ed25519 are exactly those
359// outer bytes (DER's "tbsCertificate" SEQUENCE).
360func sign_cert(buf: *u8, priv: *u8, cert_len: i64) -> i64 {
361 let TBS_TOTAL: i64 = 118
362 let tbs_off: i64 = 3
363 let sig_off: i64 = cert_len - 64 // last 64 bytes = sig
364 let sig: *u8 = sys_mmap(64)
365 ed25519_sign_full(priv, buf + tbs_off, TBS_TOTAL, sig)
366 var i: i64 = 0
367 while i < 64 {
368 buf[sig_off + i] = sig[i]
369 i = i + 1
370 }
371 return 0
372}
373
374// ---------------------------------------------------------------
375// CLIENT entry point: hand to nx_tls13_client_session_run
376// ---------------------------------------------------------------
377
378func client_session_run(cfd: i64, cert_for_trust: *u8, cert_len: i64) -> i64 {
379 // Build TrustStore with our self-signed cert as anchor
380 let store: *TrustStore = trust_store_alloc(4)
381 let anchor: *X509Cert = sys_mmap(256) as *X509Cert
382 if x509_parse(cert_for_trust, cert_len, anchor) < 0 { return 200 }
383 if trust_store_add(store, cert_for_trust, anchor) != NX_TRUST_STORE_OK {
384 return 201
385 }
386
387 // Build val_ctx: sni="localhost", now within cert validity
388 let val_ctx: *TlsValidationContext = sys_mmap(64) as *TlsValidationContext
389 let host: *u8 = sys_mmap(16)
390 host[0]=0x6c; host[1]=0x6f; host[2]=0x63; host[3]=0x61
391 host[4]=0x6c; host[5]=0x68; host[6]=0x6f; host[7]=0x73
392 host[8]=0x74
393 val_ctx.store = store
394 val_ctx.sni_host = host
395 val_ctx.sni_host_len = 9
396 val_ctx.now_epoch = NOW_EPOCH
397
398 // Client entropy: deterministic test vectors
399 let cr: *u8 = sys_mmap(32)
400 var i: i64 = 0
401 while i < 32 { cr[i] = (0xC0 + i) as u8; i = i + 1 }
402 let priv: *u8 = sys_mmap(32)
403 i = 0
404 while i < 32 { priv[i] = (0xA0 + i) as u8; i = i + 1 }
405
406 let r: i64 = nx_tls13_client_session_run(
407 cfd, host, 9, cr, priv, val_ctx
408 )
409 if r <= 0 { return 0 - r }
410 let s: *Tls13ClientSession = r as *Tls13ClientSession
411 if s.state != NX_TLS13_CSESSION_STATE_CONNECTED { return 210 }
412 return 0
413}
414
415// ---------------------------------------------------------------
416// SERVER side: emit real TLS records throughout
417// ---------------------------------------------------------------
418
419// Parse CH and extract client X25519 pubkey + session_id offset+len.
420func server_parse_ch(ch: *u8, ch_len: i64,
421 sid_o: *i64, sid_l: *i64, pub_out: *i64) -> i64 {
422 let p_ro: *i64 = sys_mmap(16) as *i64
423 let p_eo: *i64 = sys_mmap(16) as *i64
424 let p_el: *i64 = sys_mmap(16) as *i64
425 let pv: i64 = tls13_server_parse_client_hello(
426 ch, ch_len, p_ro, sid_o, sid_l, p_eo, p_el
427 )
428 if pv != NX_TLS13_SERVER_VERDICT_OK { return pv }
429 let ks_off: *i64 = sys_mmap(16) as *i64
430 let ks_len: *i64 = sys_mmap(16) as *i64
431 let fv: i64 = tls13_ext_find(
432 ch + p_eo[0], p_el[0], EXT_KEY_SHARE, ks_off, ks_len
433 )
434 if fv != NX_TLS13_HELLO_VERDICT_OK { return fv }
435 // SH-style key_share parsing differs from CH: CH wraps the
436 // KeyShareEntry list in a 2-byte client_shares length prefix.
437 // CH KeyShareEntry: group(2) + ke_len(2) + ke(32)
438 let ke_off: i64 = p_eo[0] + ks_off[0] + 2 + 4
439 pub_out[0] = (ch as i64) + ke_off
440 return 0
441}
442
443// Derive handshake-traffic keys after CH+SH transcript snapshot.
444func server_derive_keys(s_priv: *u8, c_pub: *u8, th: *u8, eh: *u8,
445 chts_out: *u8, shts_out: *u8,
446 ck_out: *u8, civ_out: *u8,
447 sk_out: *u8, siv_out: *u8) -> i64 {
448 let hs: *u8 = sys_mmap(64)
449 tls13_handshake_compute_handshake_keys(
450 s_priv, c_pub, th, eh,
451 hs, chts_out, shts_out,
452 ck_out, civ_out, sk_out, siv_out
453 )
454 return 0
455}
456
457// Build a valid CV message (type=15) with garbage sig content
458// (CV verification is stubbed in client dispatcher).
459// handshake_type(1) + length(3) + scheme(2) + sig_len(2) + sig(64)
460// body length = 2 + 2 + 64 = 68
461func build_cv(out: *u8) -> i64 {
462 out[0] = 0x0F // HT_CERTIFICATE_VERIFY
463 out[1] = 0; out[2] = 0; out[3] = 68
464 out[4] = 0x08; out[5] = 0x07 // ed25519 SigScheme
465 out[6] = 0; out[7] = 64
466 var i: i64 = 0
467 while i < 64 { out[8 + i] = 0x99 as u8; i = i + 1 }
468 return 72
469}
470
471// Wrap our 197-byte cert in a TLS 1.3 Certificate handshake message:
472// handshake_type(1) + length(3) + cert_request_context_len(1) +
473// cert_request_context(0) + cert_chain_length(3) +
474// for each cert: cert_len(3) + cert_data + extensions_len(2) + extensions(0)
475//
476// Inner: 1(ctx_len) + 0 + 3(chain_len) + 3(cert_len) + cert_data +
477// 2(ext_len) + 0 = 9 + cert_len
478// Total body = inner + 0 (no extra wrapping)
479func build_cert_msg(out: *u8, cert: *u8, cert_len: i64) -> i64 {
480 let inner_len: i64 = 9 + cert_len
481 out[0] = 0x0B // HT_CERTIFICATE
482 out[1] = (inner_len >> 16) & 0xff
483 out[2] = (inner_len >> 8) & 0xff
484 out[3] = inner_len & 0xff
485 out[4] = 0 // ctx_len = 0
486 // cert_chain_length = 3 + cert_len + 2 (cert + ext_len)
487 let chain_len: i64 = 3 + cert_len + 2
488 out[5] = (chain_len >> 16) & 0xff
489 out[6] = (chain_len >> 8) & 0xff
490 out[7] = chain_len & 0xff
491 out[8] = (cert_len >> 16) & 0xff
492 out[9] = (cert_len >> 8) & 0xff
493 out[10] = cert_len & 0xff
494 var i: i64 = 0
495 while i < cert_len { out[11 + i] = cert[i]; i = i + 1 }
496 out[11 + cert_len] = 0 // ext_len = 0
497 out[11 + cert_len + 1] = 0
498 return 4 + inner_len
499}
500
501// EncryptedExtensions handshake message: type 8, body 2 bytes (empty ext list)
502func build_ee(out: *u8) -> i64 {
503 out[0] = 0x08 // HT_ENCRYPTED_EXTENSIONS
504 out[1] = 0; out[2] = 0; out[3] = 2
505 out[4] = 0; out[5] = 0
506 return 6
507}
508
509// Compute the server Finished handshake message.
510func build_sf(tx: *u8, shts: *u8, out: *u8) -> i64 {
511 let pre: *u8 = sys_mmap(64)
512 nx_tls13_transcript_snapshot(tx, pre)
513 let fk: *u8 = sys_mmap(64)
514 tls13_finished_key(shts, 32, fk)
515 let mac: *u8 = sys_mmap(64)
516 nx_tls13_finished_compute(fk, 32, pre, 32, mac)
517 out[0] = 0x14 // HT_FINISHED
518 out[1] = 0; out[2] = 0; out[3] = 32
519 var i: i64 = 0
520 while i < 32 { out[4 + i] = mac[i]; i = i + 1 }
521 return 36
522}
523
524func server_run(sfd: i64, cert_for_msg: *u8, cert_len: i64) -> i64 {
525 // ---- 1. Read ClientHello as real TLS record ----
526 let ch_rec: *u8 = sys_mmap(2048)
527 let ch_body_len: i64 = recv_tls_plain_record(sfd, ch_rec, 2048)
528 if ch_body_len < 0 { return 100 + (0 - ch_body_len) }
529 let ch_body: *u8 = ch_rec + 5
530
531 let sid_o: *i64 = sys_mmap(16) as *i64
532 let sid_l: *i64 = sys_mmap(16) as *i64
533 let c_pub_slot: *i64 = sys_mmap(8) as *i64
534 if server_parse_ch(ch_body, ch_body_len, sid_o, sid_l, c_pub_slot) != 0 {
535 return 110
536 }
537 let c_pub: *u8 = c_pub_slot[0] as *u8
538
539 // ---- 2. Generate server X25519 keypair + emit SH ----
540 let s_priv: *u8 = sys_mmap(32)
541 var pi: i64 = 0
542 while pi < 32 { s_priv[pi] = (0x80 + pi) as u8; pi = pi + 1 }
543 let s_pub: *u8 = sys_mmap(32)
544 let base: *u8 = sys_mmap(32)
545 base[0] = 9
546 var bi: i64 = 1
547 while bi < 32 { base[bi] = 0; bi = bi + 1 }
548 x25519(s_priv, base, s_pub)
549
550 let s_random: *u8 = sys_mmap(32)
551 var ri: i64 = 0
552 while ri < 32 { s_random[ri] = (0x70 + ri) as u8; ri = ri + 1 }
553 let sh: *u8 = sys_mmap(512)
554 let sh_len: i64 = tls13_server_emit_server_hello(
555 s_random, ch_body + sid_o[0], sid_l[0], s_pub, sh, 512
556 )
557 if sh_len < 0 { return 120 }
558 if send_tls_plain_record(sfd, sh, sh_len) != 0 { return 121 }
559
560 // ---- 3. Build transcript hash CH || SH ----
561 let tx: *u8 = nx_tls13_transcript_new()
562 nx_tls13_transcript_update(tx, ch_body, ch_body_len)
563 nx_tls13_transcript_update(tx, sh, sh_len)
564 let th: *u8 = sys_mmap(64)
565 nx_tls13_transcript_snapshot(tx, th)
566
567 // ---- 4. Derive handshake-traffic keys ----
568 let eh: *u8 = sys_mmap(64)
569 let empty: *u8 = sys_mmap(8)
570 sha256_digest(empty, 0, eh)
571
572 let chts: *u8 = sys_mmap(64)
573 let shts: *u8 = sys_mmap(64)
574 let ck: *u8 = sys_mmap(64)
575 let civ: *u8 = sys_mmap(64)
576 let sk: *u8 = sys_mmap(64)
577 let siv: *u8 = sys_mmap(64)
578 server_derive_keys(s_priv, c_pub, th, eh, chts, shts, ck, civ, sk, siv)
579
580 // ---- 5. Send AEAD-encrypted EE + Cert + CV + SF ----
581 let ee: *u8 = sys_mmap(64)
582 let ee_len: i64 = build_ee(ee)
583 nx_tls13_transcript_update(tx, ee, ee_len)
584 if send_aead_record(sfd, sk, siv, 0, ee, ee_len, 22) != 0 { return 130 }
585
586 let cert_msg: *u8 = sys_mmap(512)
587 let cert_msg_len: i64 = build_cert_msg(cert_msg, cert_for_msg, cert_len)
588 nx_tls13_transcript_update(tx, cert_msg, cert_msg_len)
589 if send_aead_record(sfd, sk, siv, 1, cert_msg, cert_msg_len, 22) != 0 { return 131 }
590
591 let cv: *u8 = sys_mmap(128)
592 let cv_len: i64 = build_cv(cv)
593 nx_tls13_transcript_update(tx, cv, cv_len)
594 if send_aead_record(sfd, sk, siv, 2, cv, cv_len, 22) != 0 { return 132 }
595
596 let sf: *u8 = sys_mmap(64)
597 let sf_len: i64 = build_sf(tx, shts, sf)
598 nx_tls13_transcript_update(tx, sf, sf_len)
599 if send_aead_record(sfd, sk, siv, 3, sf, sf_len, 22) != 0 { return 133 }
600
601 // ---- 6. Read encrypted CF record (client's Finished) ----
602 let cf: *u8 = sys_mmap(64)
603 let cf_n: i64 = recv_aead_record(sfd, ck, civ, 0, cf, 64)
604 if cf_n < 0 { return 140 + (0 - cf_n) }
605 if cf_n != 36 { return 150 }
606 if (cf[0] & 0xff) != 0x14 { return 151 } // expect HT_FINISHED
607
608 return 0
609}
610
611// ---------------------------------------------------------------
612// Main: fork + child
613// ---------------------------------------------------------------
614
615func main() -> i64 {
616 // ---- Prepare cert + key shared by both processes ----
617 let cert_priv: *u8 = sys_mmap(32)
618 var ki: i64 = 0
619 while ki < 32 { cert_priv[ki] = (0x10 + ki) as u8; ki = ki + 1 }
620 let cert_pub: *u8 = sys_mmap(32)
621 derive_pub_from_priv(cert_priv, cert_pub)
622
623 let cert: *u8 = sys_mmap(512)
624 let cert_len: i64 = build_cert_with_pub(cert, cert_pub)
625 sign_cert(cert, cert_priv, cert_len)
626
627 // ---- Socket setup ----
628 let addr: *u8 = sys_mmap(16)
629 hbuild_addr(addr, HPORT)
630 let lfd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0)
631 if lfd < 0 { return 1 }
632 if sys_bind(lfd, addr, 16) < 0 { return 2 }
633 if sys_listen(lfd, 4) < 0 { return 3 }
634
635 let pid: i64 = sys_fork()
636 if pid < 0 { return 4 }
637
638 if pid == 0 {
639 // CHILD = server
640 var spin: i64 = 0
641 while spin < 200000 { spin = spin + 1 }
642 let cfd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0)
643 if cfd < 0 { return 5 }
644 if nx_connect_bounded(cfd, addr, 16, NX_CONN_DEFAULT_MS) < 0 { return 6 }
645 return server_run(cfd, cert, cert_len)
646 }
647
648 // PARENT = client
649 let sfd: i64 = sys_accept(lfd)
650 if sfd < 0 { return 7 }
651 let cr: i64 = client_session_run(sfd, cert, cert_len)
652 if cr != 0 { return cr }
653
654 let status: *i64 = sys_mmap(8) as *i64
655 sys_wait4(pid, status, 0)
656 return wait_exit_code(*status)
657}