code wiki / (root) / nx_tls13_mtls_recv_cert.nx

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}