nx_tls13_mtls_recv_cert.nx source
↩ module page · 137 lines · 8564 B
1// nx_tls13_mtls_recv_cert.nx -- R7: receive the client's Certificate record, EMPTY-cert aware.
2//
3// When the server requests a client cert (CertReq) but the browser has none suitable, RFC 8446 ยง4.4.2 says it
4// MUST still send a Certificate message with an EMPTY certificate_list. The server MUST fold that empty
5// message into the transcript (or the client's Finished MAC won't verify) and continue -- this is exactly the
6// NEVER-LOCKOUT path that lets a non-cert browser complete the handshake and fall back to the header session.
7// On a real cert: decrypt + extract the leaf DER + present=1. Extracted from the run loop so the empty-cert
8// branch is GATED, not just compile-checked. license_tier: ORIGINAL expect_exit: 0
9import "nx_syscalls.nx"
10import "nx_tls13.nx"
11import "nx_tls13_record.nx"
12import "nx_tls13_transcript.nx"
13import "nx_tls13_server_session.nx"
14import "nx_tls13_auth.nx"
15
16const NX_MRC_REC_HEADER: i64 = 5
17const NX_MRC_REC_TAG: i64 = 16
18
19// out_present: 1 = a real client cert (out_cert/out_len set), 0 = the browser sent an empty cert (no auth).
20// Either way the Certificate message is folded into the transcript + client_seq advances. OK / negative.
21func nx_tls13_mtls_recv_client_cert(
22 s: *Tls13ServerSession, record: *u8, record_len: i64,
23 out_cert: *u8, out_cap: i64, out_len: *i64, out_present: *i64
24) -> i64 {
25 out_present[0] = 0; out_len[0] = 0
26 if record[0] != 0x17 { return 0 - NX_TLS13_SSESSION_PROTOCOL_ERR }
27 let body_len: i64 = ((record[3] as i64) << 8) | (record[4] as i64)
28 let ct_len: i64 = body_len - NX_MRC_REC_TAG
29 if ct_len < 1 { return 0 - NX_TLS13_SSESSION_PROTOCOL_ERR }
30 let ct: *u8 = (record as i64 + NX_MRC_REC_HEADER) as *u8
31 let tag: *u8 = (record as i64 + NX_MRC_REC_HEADER + ct_len) as *u8
32 let pt: *u8 = sys_mmap(ct_len + 16); let rct: *i64 = sys_mmap(8) as *i64; let rlen: *i64 = sys_mmap(8) as *i64
33 if nx_tls13_record_decrypt_v2(s.cipher_suite, s.client_hs_traffic_key, s.client_hs_iv, s.client_seq,
34 record, ct, ct_len, tag, pt, rct, rlen) != NX_TLS13_REC_VERDICT_OK { return 0 - NX_TLS13_SSESSION_PROTOCOL_ERR }
35 if rct[0] != CT_HANDSHAKE { return 0 - NX_TLS13_SSESSION_PROTOCOL_ERR }
36 let pt_len: i64 = rlen[0]
37 if (pt[0] & 0xff) != (HT_CERTIFICATE & 0xff) { return 0 - NX_TLS13_SSESSION_PROTOCOL_ERR }
38
39 let cof: *i64 = sys_mmap(8) as *i64; let cln: *i64 = sys_mmap(8) as *i64
40 let crof: *i64 = sys_mmap(8) as *i64; let crln: *i64 = sys_mmap(8) as *i64
41 let exof: *i64 = sys_mmap(8) as *i64; let exln: *i64 = sys_mmap(8) as *i64; let chl: *i64 = sys_mmap(8) as *i64
42 let pr: i64 = tls13_parse_certificate_chain_first(pt, pt_len, cof, cln, crof, crln, exof, exln, chl)
43 if pr == NX_TLS13_AUTH_VERDICT_OK {
44 if crln[0] > out_cap { return 0 - NX_TLS13_SSESSION_BUF_OVERFLOW }
45 var i: i64 = 0
46 while i < crln[0] { out_cert[i] = pt[crof[0] + i]; i = i + 1 }
47 out_len[0] = crln[0]
48 out_present[0] = 1
49 }
50 // fold the Certificate (real OR empty) into the transcript so the client Finished MAC verifies; advance seq.
51 nx_tls13_transcript_update(s.transcript, pt, pt_len)
52 s.client_seq = s.client_seq + 1
53 return NX_TLS13_SSESSION_OK
54}
55
56// ===================== gate: real cert (present=1) + EMPTY cert (present=0, still folded) =====================
57func mrc_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
58func mrc_row(name: *u8, ok: i64) -> i64 { if ok == 1 { mrc_w(" PASS " as *u8) } else { mrc_w(" FAIL " as *u8) } mrc_w(name); mrc_w("\n" as *u8); return ok }
59func mrc_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 }
60
61func mrc_seal(msg: *u8, msg_len: i64, key: *u8, iv: *u8, rec: *u8) -> i64 {
62 let header: *u8 = sys_mmap(5); let ctb: *u8 = sys_mmap(msg_len + 17); let tagb: *u8 = sys_mmap(16)
63 if nx_tls13_record_encrypt_v2(0x1303, key, iv, 0, msg, msg_len, CT_HANDSHAKE, 0, header, ctb, tagb) != NX_TLS13_REC_VERDICT_OK { return 0 - 1 }
64 let ct_len: i64 = msg_len + 1
65 var w: i64 = 0
66 var hi: i64 = 0; while hi < 5 { rec[w + hi] = header[hi]; hi = hi + 1 } w = w + 5
67 var ci: i64 = 0; while ci < ct_len { rec[w + ci] = ctb[ci]; ci = ci + 1 } w = w + ct_len
68 var ti: i64 = 0; while ti < 16 { rec[w + ti] = tagb[ti]; ti = ti + 1 } w = w + 16
69 return w
70}
71func mrc_mksession(key: *u8, iv: *u8) -> *Tls13ServerSession {
72 let s: *Tls13ServerSession = sys_mmap(264) as *Tls13ServerSession
73 s.cipher_suite = 0x1303; s.client_hs_traffic_key = key; s.client_hs_iv = iv; s.client_seq = 0
74 s.transcript = nx_tls13_transcript_new(); s.state = NX_TLS13_SSTATE_SF_SENT
75 return s
76}
77
78func main() -> i64 {
79 mrc_w("nx_tls13 mTLS recv_client_cert gate (real cert present=1; EMPTY cert present=0 + still folded; R7)\n" as *u8)
80 let key: *u8 = sys_mmap(32); var a: i64 = 0; while a < 32 { key[a] = (0x44 + a) as u8; a = a + 1 }
81 let iv: *u8 = sys_mmap(12); a = 0; while a < 12 { iv[a] = (0x55 + a) as u8; a = a + 1 }
82 let got: *u8 = sys_mmap(256); let glen: *i64 = sys_mmap(8) as *i64; let present: *i64 = sys_mmap(8) as *i64
83 var pass: i64 = 0
84
85 // --- real cert ---
86 let dlen: i64 = 40; let dcert: *u8 = sys_mmap(dlen); var d: i64 = 0; while d < dlen { dcert[d] = (0x30 + d) as u8; d = d + 1 }
87 let entry: i64 = 3 + dlen + 2; let body: i64 = 1 + 3 + entry
88 let msg: *u8 = sys_mmap(128); msg[0] = HT_CERTIFICATE & 0xff
89 var o: i64 = mrc_u24(msg, 1, body); msg[o] = 0 as u8; o = o + 1
90 o = mrc_u24(msg, o, entry); o = mrc_u24(msg, o, dlen)
91 var c: i64 = 0; while c < dlen { msg[o + c] = dcert[c]; c = c + 1 } o = o + dlen
92 msg[o] = 0 as u8; msg[o+1] = 0 as u8; o = o + 2
93 let rec: *u8 = sys_mmap(160); let rlen: i64 = mrc_seal(msg, o, key, iv, rec)
94 let s1: *Tls13ServerSession = mrc_mksession(key, iv)
95 let r1: i64 = nx_tls13_mtls_recv_client_cert(s1, rec, rlen, got, 256, glen, present)
96 var t1: i64 = 0
97 if r1 == NX_TLS13_SSESSION_OK { if present[0] == 1 { if glen[0] == dlen { t1 = 1 } } }
98 pass = pass + mrc_row("T1 real client cert: present=1 + extracted" as *u8, t1)
99 var t2: i64 = 1
100 if t1 == 1 { var j: i64 = 0; while j < dlen { if got[j] != dcert[j] { t2 = 0; j = dlen } else { j = j + 1 } } } else { t2 = 0 }
101 pass = pass + mrc_row("T2 extracted cert is byte-identical" as *u8, t2)
102
103 // --- EMPTY cert (browser declined): present=0, but STILL folded into the transcript (never-lockout) ---
104 let emsg: *u8 = sys_mmap(16)
105 emsg[0] = HT_CERTIFICATE & 0xff; emsg[1] = 0 as u8; emsg[2] = 0 as u8; emsg[3] = 4 as u8 // body_len = 4
106 emsg[4] = 0 as u8 // ctx = empty
107 emsg[5] = 0 as u8; emsg[6] = 0 as u8; emsg[7] = 0 as u8 // certificate_list len = 0
108 let erec: *u8 = sys_mmap(64); let erlen: i64 = mrc_seal(emsg, 8, key, iv, erec)
109 let s2: *Tls13ServerSession = mrc_mksession(key, iv)
110 let r3: i64 = nx_tls13_mtls_recv_client_cert(s2, erec, erlen, got, 256, glen, present)
111 var t3: i64 = 0
112 if r3 == NX_TLS13_SSESSION_OK { if present[0] == 0 { t3 = 1 } }
113 pass = pass + mrc_row("T3 EMPTY client cert: handshake continues, present=0 (no-auth, never-lockout)" as *u8, t3)
114 var t4: i64 = 0
115 if s2.client_seq == 1 { t4 = 1 } // seq advanced even for empty
116 pass = pass + mrc_row("T4 empty cert still consumes the record (client_seq advanced)" as *u8, t4)
117 let th: *u8 = sys_mmap(32); nx_tls13_transcript_snapshot(s2.transcript, th)
118 var t5: i64 = 0; var z: i64 = 0
119 while z < 32 { if (th[z] & 0xff) != 0 { t5 = 1; z = 32 } else { z = z + 1 } }
120 pass = pass + mrc_row("T5 empty cert STILL folded into transcript (so client Finished MAC verifies)" as *u8, t5)
121
122 // --- tampered record rejected ---
123 let rec2: *u8 = sys_mmap(160); var q: i64 = 0; while q < rlen { rec2[q] = rec[q]; q = q + 1 }
124 rec2[7] = (rec2[7] ^ 0x80) as u8
125 let s3: *Tls13ServerSession = mrc_mksession(key, iv)
126 var t6: i64 = 0
127 if nx_tls13_mtls_recv_client_cert(s3, rec2, rlen, got, 256, glen, present) != NX_TLS13_SSESSION_OK { t6 = 1 }
128 pass = pass + mrc_row("T6 tampered Certificate record rejected (AEAD)" as *u8, t6)
129
130 if pass == 6 {
131 mrc_w("NX-TLS13-MTLS-RECV-CERT GATE GREEN 6/6 (real + EMPTY cert handled; never-lockout proven)\n" as *u8)
132 sys_exit(0)
133 }
134 mrc_w("NX-TLS13-MTLS-RECV-CERT GATE RED\n" as *u8)
135 sys_exit(1)
136 return 1
137}