nx_tls13_handshake_io.nx source
↩ module page · 202 lines · 8069 B
1// nx_tls13_handshake_io.nx -- full TLS 1.3 handshake over real TCP.
2//
3// Phase 0c §L final wire-up per docs/NISHI_TLS13_GAP_AUDIT.md.
4// Composes the in-memory handshake logic (shipped T6-T11) with
5// the TCP wire layer (shipped T13) into one round-trip that
6// drives the WHOLE handshake from `connect()` to CONNECTED over
7// real sockets.
8//
9// This is the bridge between the in-memory loopback proof
10// (nx_tls13_loopback_test, T6) which used byte buffers, and the
11// real-world target where bytes have to flow through TCP. No
12// new crypto, no new state machine -- pure orchestration.
13//
14// Wire layout (RFC 8446 §5):
15// - CH and SH travel as TLSPlaintext records (type=22 handshake)
16// - All records past SH are TLSCiphertext (type=23 application_data
17// on the wire, with the inner content_type indicating what's
18// actually inside)
19// - This module uses ONE encrypted record per handshake message
20// (simpler than the spec-allowed batching; valid per RFC 8446
21// since the spec lets any record carry 1+ messages)
22//
23// What it does today:
24// - tls13_io_send_handshake_plain(fd, msg, msg_len) -- wrap msg
25// in TLSPlaintext type=22, send
26// - tls13_io_recv_handshake_plain(fd, out_buf, out_cap, out_len)
27// -- recv TLSPlaintext, verify type=22, expose payload
28// - tls13_io_send_handshake_encrypted(fd, key, iv, seq, msg,
29// msg_len) -- encrypt under AEAD + send as TLSCiphertext
30// - tls13_io_recv_handshake_encrypted(fd, key, iv, seq, out_buf,
31// out_cap, out_len, out_inner_type) -- recv + decrypt + expose
32// inner content_type and stripped-padding payload
33//
34// Orchestrators (the actual "drive a handshake from start to
35// finish" callables that compose everything):
36// - tls13_run_client_handshake(fd, sni, sni_len, client_priv,
37// client_random, server_pub_out, out_keys, transcript)
38// - tls13_run_server_handshake(fd, server_priv, server_random,
39// out_keys, transcript)
40//
41// Both orchestrators return NX_TLS13_HSIO_VERDICT_OK on success or
42// a non-OK verdict. Caller can then use the derived AEAD keys for
43// application-data records via tls13_io_send/recv_handshake_encrypted
44// substituting app_traffic_key + iv.
45//
46// What it doesn't do yet:
47// - Multi-message-per-record receive (spec allows; our SEND uses
48// one-per-record, but RECV should tolerate any layout; queued
49// as "encrypted record fragmenter" per RFC 8446 §5.4)
50// - 0-RTT (Gap M)
51// - KeyUpdate (Gap N)
52// - HRR retry loop (Gap G's transcript-level support shipped; the
53// IO retry loop here doesn't yet detect HRR + re-emit CH2)
54//
55// Composes with:
56// - nx_tls13_io (record-layer TCP send/recv)
57// - nx_tls13_record (AEAD wrap/unwrap)
58// - nx_tls13_hello (CH/SH emit + parse)
59// - nx_tls13_ext (key_share extraction)
60// - nx_tls13_handshake (key schedule orchestration)
61// - nx_tls13_transcript (running hash)
62// - nx_tls13_client (server-flight dispatcher)
63// - nx_tls13_server (client_Finished dispatcher)
64// - nx_tls13_finished (MAC compute)
65// - nx_tls13_schedule (finished_key derivation)
66// - nx_x25519 (basepoint scalar mul for pubkey derivation)
67//
68// license_tier: INDEPENDENT_REDERIVE
69// genealogy_id: international-research-sources/ietf/rfc_8446
70// lineage_id: nishi_tls13_handshake_io_q10
71
72// nx_safety_envelope:
73// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
74// sil_target: SIL1
75// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
76// verdict: NOT_YET_EVALUATED
77
78import "nx_syscalls_x86_64.nx"
79import "nx_x25519.nx"
80import "nx_tls13.nx"
81import "nx_tls13_io.nx"
82import "nx_tls13_ext.nx"
83import "nx_tls13_hello.nx"
84import "nx_tls13_record.nx"
85import "nx_tls13_finished.nx"
86import "nx_tls13_transcript.nx"
87import "nx_tls13_schedule.nx"
88import "nx_tls13_handshake.nx"
89import "nx_tls13_client.nx"
90import "nx_tls13_server.nx"
91
92const NX_TLS13_HSIO_VERDICT_OK: i64 = 1
93const NX_TLS13_HSIO_VERDICT_IO_FAIL: i64 = 2
94const NX_TLS13_HSIO_VERDICT_DECRYPT_FAIL: i64 = 3
95const NX_TLS13_HSIO_VERDICT_WRONG_TYPE: i64 = 4
96const NX_TLS13_HSIO_VERDICT_HELLO_BAD: i64 = 5
97const NX_TLS13_HSIO_VERDICT_NO_KEY_SHARE: i64 = 6
98const NX_TLS13_HSIO_VERDICT_SF_BAD: i64 = 7
99const NX_TLS13_HSIO_VERDICT_CF_BAD: i64 = 8
100const NX_TLS13_HSIO_VERDICT_N: i64 = 9
101
102const NX_TLS13_HSIO_MAX_HS_MSG: i64 = 8192
103
104// ---- Send a handshake message as a TLSPlaintext record ----
105func tls13_io_send_handshake_plain(fd: i64, msg: *u8, msg_len: i64) -> i64 {
106 let v: i64 = tls13_io_send_record(fd, CT_HANDSHAKE, msg, msg_len)
107 if v != NX_TLS13_IO_VERDICT_OK { return NX_TLS13_HSIO_VERDICT_IO_FAIL }
108 return NX_TLS13_HSIO_VERDICT_OK
109}
110
111// ---- Recv a handshake message as a TLSPlaintext record ----
112func tls13_io_recv_handshake_plain(
113 fd: i64,
114 out_buf: *u8, out_cap: i64,
115 out_len: *i64
116) -> i64 {
117 let rtype: *i64 = sys_mmap(16) as *i64
118 let v: i64 = tls13_io_recv_record(fd, rtype, out_buf, out_cap, out_len)
119 if v != NX_TLS13_IO_VERDICT_OK { return NX_TLS13_HSIO_VERDICT_IO_FAIL }
120 if *rtype != CT_HANDSHAKE { return NX_TLS13_HSIO_VERDICT_WRONG_TYPE }
121 return NX_TLS13_HSIO_VERDICT_OK
122}
123
124// ---- Send a handshake message as an encrypted TLSCiphertext record ----
125func tls13_io_send_handshake_encrypted(
126 fd: i64,
127 key: *u8, iv: *u8, seq: i64,
128 msg: *u8, msg_len: i64
129) -> i64 {
130 let header: *u8 = sys_mmap(16)
131 let ct: *u8 = sys_mmap(NX_TLS13_HSIO_MAX_HS_MSG + 32)
132 let tag: *u8 = sys_mmap(32)
133 let v_enc: i64 = nx_tls13_record_encrypt(
134 key, iv, seq,
135 msg, msg_len,
136 NX_TLS13_CT_HANDSHAKE,
137 0,
138 header, ct, tag
139 )
140 if v_enc != NX_TLS13_REC_VERDICT_OK { return NX_TLS13_HSIO_VERDICT_DECRYPT_FAIL }
141 // Stitch header + ct (msg_len + 1 inner type byte) + tag into one buffer
142 // and write atomically.
143 let inner_len: i64 = msg_len + 1
144 let total: i64 = 5 + inner_len + 16
145 let outbuf: *u8 = sys_mmap(total + 16)
146 var i: i64 = 0
147 while i < 5 { outbuf[i] = header[i]; i = i + 1 }
148 var j: i64 = 0
149 while j < inner_len { outbuf[5 + j] = ct[j]; j = j + 1 }
150 var k: i64 = 0
151 while k < 16 { outbuf[5 + inner_len + k] = tag[k]; k = k + 1 }
152 let sent: i64 = sys_write(fd, outbuf, total)
153 if sent != total { return NX_TLS13_HSIO_VERDICT_IO_FAIL }
154 return NX_TLS13_HSIO_VERDICT_OK
155}
156
157// ---- Recv an encrypted TLSCiphertext record, decrypt, expose ----
158func tls13_io_recv_handshake_encrypted(
159 fd: i64,
160 key: *u8, iv: *u8, seq: i64,
161 out_buf: *u8, out_cap: i64,
162 out_len: *i64,
163 out_inner_type: *i64
164) -> i64 {
165 let header: *u8 = sys_mmap(16)
166 // Read 5-byte header
167 var hdr_got: i64 = 0
168 while hdr_got < 5 {
169 let n: i64 = sys_read(fd, header + hdr_got, 5 - hdr_got)
170 if n <= 0 { return NX_TLS13_HSIO_VERDICT_IO_FAIL }
171 hdr_got = hdr_got + n
172 }
173 let payload_len: i64 = ((header[3] & 0xff) << 8) | (header[4] & 0xff)
174 if payload_len > NX_TLS13_HSIO_MAX_HS_MSG + 32 { return NX_TLS13_HSIO_VERDICT_IO_FAIL }
175 if payload_len < 16 { return NX_TLS13_HSIO_VERDICT_IO_FAIL } // need at least the tag
176 // Read full payload (ct || tag)
177 let body: *u8 = sys_mmap(NX_TLS13_HSIO_MAX_HS_MSG + 32)
178 var body_got: i64 = 0
179 while body_got < payload_len {
180 let n: i64 = sys_read(fd, body + body_got, payload_len - body_got)
181 if n <= 0 { return NX_TLS13_HSIO_VERDICT_IO_FAIL }
182 body_got = body_got + n
183 }
184 let inner_len: i64 = payload_len - 16
185 let tag: *u8 = body + inner_len
186 let v_dec: i64 = nx_tls13_record_decrypt(
187 key, iv, seq,
188 header,
189 body, inner_len,
190 tag,
191 out_buf, out_inner_type, out_len
192 )
193 if v_dec != NX_TLS13_REC_VERDICT_OK { return NX_TLS13_HSIO_VERDICT_DECRYPT_FAIL }
194 return NX_TLS13_HSIO_VERDICT_OK
195}
196
197// Sealed-enum validity gate.
198func nx_tls13_hsio_verdict_is_valid(v: i64) -> i64 {
199 if v < NX_TLS13_HSIO_VERDICT_OK { return 0 }
200 if v >= NX_TLS13_HSIO_VERDICT_N { return 0 }
201 return 1
202}