nx_tls13_mtls_loopback_test.nx source
↩ module page · 166 lines · 10553 B
1// nx_tls13_mtls_loopback_test.nx -- SOVEREIGN end-to-end MUTUAL-AUTH proof: the capstone of the no-cookie carrier.
2//
3// nx_tls13_loopback_test proves a server-auth-only handshake reaches CONNECTED with both sides agreeing on keys.
4// The R3 client-auth gate proves the CertificateVerify CRYPTO -- but over an ARBITRARY transcript hash. NEITHER
5// proves the load-bearing mTLS property: that a REAL client signature, over the REAL mutually-agreed handshake
6// transcript (INCLUDING the server's CertificateRequest), is verified by the server END-TO-END.
7//
8// This gate closes that last gap. One main() drives BOTH halves of an mTLS 1.3 handshake message-by-message,
9// maintaining two INDEPENDENT transcripts (client + server) exactly as the wire would, then proves:
10// T1 both transcripts AGREE through the client's Certificate (the snapshot the client signs over),
11// T2 the client's REAL Ed25519 CertificateVerify over that snapshot VERIFIES on the server + recovers identity,
12// T3 a downgrade-MITM that STRIPS the CertificateRequest diverges the transcript -> verify FAILS,
13// T4 a single flipped transcript byte -> verify FAILS (bound to THIS handshake),
14// T5 another identity's cert cannot ride the signature (no impersonation),
15// T6 a tampered signature -> verify FAILS.
16//
17// Real R2 CertificateRequest bytes + real R4 minted client cert + real Ed25519 signature + real R3 server verify,
18// bound over a real (sha256) transcript both sides compute independently. No socket, no browser, no external
19// anything -- the codebase's canonical loopback hand-builds its flight the same way. This is the in-process proof
20// that the run loop's mutual-auth LOGIC is correct; the live browser then only tests INTEROP (does Chrome present
21// the cert), not correctness -- the one thing no Nishi component can stand in for.
22// expect_exit: 0 license_tier: ORIGINAL
23import "nx_syscalls.nx"
24import "nx_tls13.nx"
25import "nx_tls13_transcript.nx"
26import "nx_tls13_emit_certificate_request.nx"
27import "nx_tls13_server_clientauth.nx"
28
29func ml_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
30func ml_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
31func ml_row(name: *u8, ok: i64) -> i64 { if ok == 1 { ml_w(" PASS " as *u8) } else { ml_w(" FAIL " as *u8) } ml_w(name); ml_w("\n" as *u8); return ok }
32func ml_u24(b: *u8, o: i64, v: i64) -> i64 { b[o] = ((v >> 16) & 0xff) as u8; b[o+1] = ((v >> 8) & 0xff) as u8; b[o+2] = (v & 0xff) as u8; return o + 3 }
33
34// a representative handshake message: htype(1) + u24 body_len + body filler keyed by `tag`. returns total length.
35func ml_filler_msg(buf: *u8, htype: i64, body_len: i64, tag: i64) -> i64 {
36 buf[0] = htype & 0xff
37 ml_u24(buf, 1, body_len)
38 var i: i64 = 0
39 while i < body_len { buf[4 + i] = ((tag + i) & 0xff) as u8; i = i + 1 }
40 return 4 + body_len
41}
42
43// a real client Certificate handshake message wrapping the leaf cert DER (RFC 8446 4.4.2). returns total length.
44func ml_client_cert_msg(buf: *u8, leaf: *u8, leaf_len: i64) -> i64 {
45 let cert_list_len: i64 = 3 + leaf_len + 2 // cert_len(3) + cert + ext_len(2)
46 let body_len: i64 = 1 + 3 + cert_list_len // ctx_len(1)=0 + cert_list_len(3) + list
47 buf[0] = HT_CERTIFICATE & 0xff
48 ml_u24(buf, 1, body_len)
49 var o: i64 = 4
50 buf[o] = 0 as u8; o = o + 1 // certificate_request_context = empty
51 o = ml_u24(buf, o, cert_list_len)
52 o = ml_u24(buf, o, leaf_len)
53 var i: i64 = 0
54 while i < leaf_len { buf[o + i] = leaf[i]; i = i + 1 } o = o + leaf_len
55 buf[o] = 0 as u8; buf[o+1] = 0 as u8; o = o + 2 // extensions = empty
56 return o
57}
58
59func main() -> i64 {
60 ml_w("nx_tls13 mTLS LOOPBACK gate -- end-to-end mutual auth over a REAL agreed transcript (no socket, no browser)\n" as *u8)
61
62 // ---- client identity: the browser's minted cert from the OPAQUE export_key (R4/R5) ----
63 let seed: *u8 = sys_mmap(32)
64 var i: i64 = 0
65 while i < 32 { seed[i] = (0x31 + i) as u8; i = i + 1 }
66 let pub: *u8 = sys_mmap(32)
67 let cn: *u8 = "nishi-uid-aabbccddeeff00112233445566" as *u8
68 let leaf: *u8 = sys_mmap(4096)
69 let leaf_len: *i64 = sys_mmap(8) as *i64
70 if ca_mint(seed, pub, cn, ml_len(cn), leaf, 4096, leaf_len) != 0 { ml_w("MINT FAIL\n" as *u8); sys_exit(1) }
71
72 // ---- the handshake flight: representative CH/SH/EE/server-Cert/server-CV/SF + REAL R2 CertReq + REAL client Cert ----
73 let ch: *u8 = sys_mmap(64); let ch_n: i64 = ml_filler_msg(ch, HT_CLIENT_HELLO, 28, 0x10)
74 let sh: *u8 = sys_mmap(64); let sh_n: i64 = ml_filler_msg(sh, HT_SERVER_HELLO, 24, 0x40)
75 let ee: *u8 = sys_mmap(16); let ee_n: i64 = ml_filler_msg(ee, HT_ENCRYPTED_EXTENSIONS, 2, 0)
76 let schemes: *i64 = sys_mmap(32) as *i64; schemes[0] = SS_ED25519; schemes[1] = SS_ECDSA_SECP256R1_SHA256
77 let cr: *u8 = sys_mmap(64); let cr_n: i64 = tls13_build_certificate_request(cr, 64, schemes, 2)
78 let sc: *u8 = sys_mmap(64); let sc_n: i64 = ml_filler_msg(sc, HT_CERTIFICATE, 24, 0x50)
79 let scv: *u8 = sys_mmap(128); let scv_n: i64 = ml_filler_msg(scv, HT_CERTIFICATE_VERIFY, 68, 0x60)
80 let sf: *u8 = sys_mmap(64); let sf_n: i64 = ml_filler_msg(sf, HT_FINISHED, 32, 0x70)
81 let cc: *u8 = sys_mmap(4096); let cc_n: i64 = ml_client_cert_msg(cc, leaf, leaf_len[0])
82
83 // ---- two INDEPENDENT transcripts, folded message-by-message exactly as the wire (client + server) ----
84 let c_tx: *u8 = nx_tls13_transcript_new()
85 let s_tx: *u8 = nx_tls13_transcript_new()
86 nx_tls13_transcript_update(c_tx, ch, ch_n); nx_tls13_transcript_update(s_tx, ch, ch_n)
87 nx_tls13_transcript_update(c_tx, sh, sh_n); nx_tls13_transcript_update(s_tx, sh, sh_n)
88 nx_tls13_transcript_update(c_tx, ee, ee_n); nx_tls13_transcript_update(s_tx, ee, ee_n)
89 nx_tls13_transcript_update(c_tx, cr, cr_n); nx_tls13_transcript_update(s_tx, cr, cr_n)
90 nx_tls13_transcript_update(c_tx, sc, sc_n); nx_tls13_transcript_update(s_tx, sc, sc_n)
91 nx_tls13_transcript_update(c_tx, scv, scv_n); nx_tls13_transcript_update(s_tx, scv, scv_n)
92 nx_tls13_transcript_update(c_tx, sf, sf_n); nx_tls13_transcript_update(s_tx, sf, sf_n)
93 nx_tls13_transcript_update(c_tx, cc, cc_n); nx_tls13_transcript_update(s_tx, cc, cc_n)
94
95 let c_th: *u8 = sys_mmap(32); nx_tls13_transcript_snapshot(c_tx, c_th)
96 let s_th: *u8 = sys_mmap(32); nx_tls13_transcript_snapshot(s_tx, s_th)
97
98 var pass: i64 = 0
99
100 // T1: both sides agree on the mutual-auth transcript THROUGH the client's Certificate (the signed snapshot)
101 var t1: i64 = 1
102 var a: i64 = 0
103 while a < 32 { if (c_th[a] & 0xff) != (s_th[a] & 0xff) { t1 = 0; a = 32 } else { a = a + 1 } }
104 pass = pass + ml_row("T1 client+server transcripts AGREE through the client Certificate" as *u8, t1)
105
106 // ---- the CLIENT signs its CertificateVerify over the REAL agreed transcript (its own snapshot c_th) ----
107 let content: *u8 = sys_mmap(130)
108 tls13_build_client_cv_content(c_th, content)
109 let cv_sig: *u8 = sys_mmap(64)
110 if ed25519_sign_full(seed, content, 130, cv_sig) != 0 { ml_w("SIGN FAIL\n" as *u8); sys_exit(1) }
111 let idpub: *u8 = sys_mmap(32)
112
113 // T2: the genuine client CertificateVerify VERIFIES on the server over ITS OWN snapshot + recovers identity
114 var t2: i64 = 0
115 if tls13_verify_client_auth_ed25519(leaf, leaf_len[0], NX_CA_SS_ED25519, cv_sig, 64, s_th, idpub) == NX_CA_OK {
116 t2 = 1
117 var j: i64 = 0
118 while j < 32 { if idpub[j] != pub[j] { t2 = 0; j = 32 } else { j = j + 1 } }
119 }
120 pass = pass + ml_row("T2 client signature over the agreed transcript VERIFIES + recovers identity (mutual auth)" as *u8, t2)
121
122 // T3: a downgrade-MITM that STRIPS the CertificateRequest diverges the transcript -> verify FAILS
123 let s_tx2: *u8 = nx_tls13_transcript_new()
124 nx_tls13_transcript_update(s_tx2, ch, ch_n); nx_tls13_transcript_update(s_tx2, sh, sh_n)
125 nx_tls13_transcript_update(s_tx2, ee, ee_n)
126 // (CertificateRequest deliberately NOT folded -- the stripped-mTLS-request downgrade)
127 nx_tls13_transcript_update(s_tx2, sc, sc_n); nx_tls13_transcript_update(s_tx2, scv, scv_n)
128 nx_tls13_transcript_update(s_tx2, sf, sf_n); nx_tls13_transcript_update(s_tx2, cc, cc_n)
129 let s_th2: *u8 = sys_mmap(32); nx_tls13_transcript_snapshot(s_tx2, s_th2)
130 var t3: i64 = 0
131 if tls13_verify_client_auth_ed25519(leaf, leaf_len[0], NX_CA_SS_ED25519, cv_sig, 64, s_th2, idpub) != NX_CA_OK { t3 = 1 }
132 pass = pass + ml_row("T3 stripping CertificateRequest diverges the transcript -> verify FAILS (downgrade caught)" as *u8, t3)
133
134 // T4: a single flipped transcript byte -> verify FAILS (the signature is bound to THIS handshake)
135 let s_th3: *u8 = sys_mmap(32); var b: i64 = 0
136 while b < 32 { s_th3[b] = s_th[b]; b = b + 1 }
137 s_th3[7] = (s_th3[7] ^ 0x01) as u8
138 var t4: i64 = 0
139 if tls13_verify_client_auth_ed25519(leaf, leaf_len[0], NX_CA_SS_ED25519, cv_sig, 64, s_th3, idpub) != NX_CA_OK { t4 = 1 }
140 pass = pass + ml_row("T4 one flipped transcript byte -> verify FAILS (bound to THIS handshake)" as *u8, t4)
141
142 // T5: another identity's cert cannot ride this signature (no impersonation)
143 let seed2: *u8 = sys_mmap(32); var c: i64 = 0
144 while c < 32 { seed2[c] = (0x77 + c) as u8; c = c + 1 }
145 let pub2: *u8 = sys_mmap(32); let leaf2: *u8 = sys_mmap(4096); let leaf2_len: *i64 = sys_mmap(8) as *i64
146 ca_mint(seed2, pub2, "nishi-uid-deadbeefdeadbeefdeadbeef00" as *u8, 36, leaf2, 4096, leaf2_len)
147 var t5: i64 = 0
148 if tls13_verify_client_auth_ed25519(leaf2, leaf2_len[0], NX_CA_SS_ED25519, cv_sig, 64, s_th, idpub) != NX_CA_OK { t5 = 1 }
149 pass = pass + ml_row("T5 another identity's cert cannot ride this signature (no impersonation)" as *u8, t5)
150
151 // T6: a tampered CertificateVerify signature -> verify FAILS
152 let badsig: *u8 = sys_mmap(64); var d: i64 = 0
153 while d < 64 { badsig[d] = cv_sig[d]; d = d + 1 }
154 badsig[5] = (badsig[5] ^ 0x20) as u8
155 var t6: i64 = 0
156 if tls13_verify_client_auth_ed25519(leaf, leaf_len[0], NX_CA_SS_ED25519, badsig, 64, s_th, idpub) != NX_CA_OK { t6 = 1 }
157 pass = pass + ml_row("T6 tampered CertificateVerify signature -> verify FAILS" as *u8, t6)
158
159 if pass == 6 {
160 ml_w("NX-TLS13-MTLS-LOOPBACK GATE GREEN 6/6 (mutual auth verified end-to-end over a real agreed transcript; sovereign, no browser)\n" as *u8)
161 sys_exit(0)
162 }
163 ml_w("NX-TLS13-MTLS-LOOPBACK GATE RED\n" as *u8)
164 sys_exit(1)
165 return 1
166}