nx_tls13_io.nx source
↩ module page · 190 lines · 7184 B
1// nx_tls13_io.nx -- TLS 1.3 record I/O over TCP (Linux x86_64).
2//
3// Phase 0c of the Nishi TLS 1.3 stack per
4// docs/NISHI_TLS13_GAP_AUDIT.md. Turns the in-memory record-layer
5// primitives shipped in T4 (nx_tls13_record) into byte streams on
6// real TCP sockets. Pure wire-format wrapper; doesn't touch
7// handshake state or AEAD -- that's the caller's job using
8// nx_tls13_client / nx_tls13_server.
9//
10// Architecture pinning: uses Linux x86_64 syscalls
11// (sys_socket / sys_bind / sys_listen / sys_accept / sys_connect /
12// sys_read / sys_write). Runs natively via the x86_64 smoke
13// driver; doesn't run under qemu-RV64 because the socket-syscall
14// wrappers aren't yet in the RV64 nx_syscalls.nx (queued
15// separately as "RV64 socket parity").
16//
17// TLSPlaintext wire format (RFC 8446 ยง5.1):
18// struct {
19// ContentType type; // 1 byte
20// ProtocolVersion legacy; // 2 bytes, big-endian, 0x0303
21// uint16 length; // 2 bytes, big-endian
22// opaque fragment[length]; // length bytes
23// } TLSPlaintext;
24//
25// What it does today:
26// - tls13_io_send_record(fd, type, payload, payload_len)
27// Constructs the 5-byte header + payload, writes to fd in one
28// buffer (single sys_write to minimize Nagle effects).
29// - tls13_io_recv_record(fd, out_type, out_buf, out_cap, out_len)
30// Reads 5-byte header, parses length, reads exactly length
31// bytes of body (loops on partial recv).
32// - tcp_listen_loopback(port): bind 127.0.0.1:port + listen
33// - tcp_connect_loopback(port): connect to 127.0.0.1:port
34//
35// What it doesn't do yet:
36// - TLSCiphertext encrypt/decrypt orchestration (caller wraps
37// each app record via nx_tls13_record + seq counter; this file
38// is plain wire-format only)
39// - Handshake-driving wrapper (next turn: nx_tls13_https.nx will
40// compose this + nx_tls13_handshake + nx_tls13_client to do
41// a full GET)
42// - Connection-close / shutdown(2) plumbing
43//
44// KAT verified:
45// - Fork-respond loopback smoke (nx_tls13_io_smoke.nx) sends a
46// synthetic record + receives reply + verifies bytes.
47//
48// Composes with:
49// - nx_syscalls_x86_64 (sockets)
50// - nx_tls13 (ContentType + ProtocolVersion constants)
51//
52// license_tier: INDEPENDENT_REDERIVE
53// genealogy_id: international-research-sources/ietf/rfc_8446
54// lineage_id: nishi_tls13_io_q10
55
56// nx_safety_envelope:
57// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
58// sil_target: SIL1
59// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
60// verdict: NOT_YET_EVALUATED
61
62import "nx_syscalls_x86_64.nx"
63import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host
64import "nx_tls13.nx"
65
66const NX_TLS13_IO_VERDICT_OK: i64 = 1
67const NX_TLS13_IO_VERDICT_SHORT_HEADER: i64 = 2
68const NX_TLS13_IO_VERDICT_SHORT_BODY: i64 = 3
69const NX_TLS13_IO_VERDICT_OVERSIZE: i64 = 4
70const NX_TLS13_IO_VERDICT_SOCKET_FAIL: i64 = 5
71const NX_TLS13_IO_VERDICT_N: i64 = 6
72
73const NX_TLS13_IO_MAX_RECORD: i64 = 16640 // TLSCiphertext.length ceiling
74
75// ---- TCP helpers (loopback only for v1; full-IP versions ship later) ----
76
77// Open a TCP socket, bind to 127.0.0.1:port, listen. Returns fd
78// or -errno.
79func tcp_listen_loopback(port: i64) -> i64 {
80 let fd: i64 = sys_socket(AF_INET, 1, 0) // SOCK_STREAM = 1
81 if fd < 0 { return fd }
82 let opt: *u8 = sys_mmap(4)
83 opt[0] = 1
84 sys_setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, opt, 4)
85 let addr: *u8 = sys_mmap(16)
86 addr[0] = 2; addr[1] = 0
87 addr[2] = (port >> 8) & 0xff
88 addr[3] = port & 0xff
89 // 127.0.0.1
90 addr[4] = 127; addr[5] = 0; addr[6] = 0; addr[7] = 1
91 addr[8] = 0; addr[9] = 0; addr[10] = 0; addr[11] = 0
92 addr[12] = 0; addr[13] = 0; addr[14] = 0; addr[15] = 0
93 let rb: i64 = sys_bind(fd, addr, 16)
94 if rb < 0 { return rb }
95 let rl: i64 = sys_listen(fd, 1)
96 if rl < 0 { return rl }
97 return fd
98}
99
100// Open a TCP socket and connect to 127.0.0.1:port. Returns fd or
101// negative errno.
102func tcp_connect_loopback(port: i64) -> i64 {
103 let fd: i64 = sys_socket(AF_INET, 1, 0)
104 if fd < 0 { return fd }
105 let addr: *u8 = sys_mmap(16)
106 addr[0] = 2; addr[1] = 0
107 addr[2] = (port >> 8) & 0xff
108 addr[3] = port & 0xff
109 addr[4] = 127; addr[5] = 0; addr[6] = 0; addr[7] = 1
110 addr[8] = 0; addr[9] = 0; addr[10] = 0; addr[11] = 0
111 addr[12] = 0; addr[13] = 0; addr[14] = 0; addr[15] = 0
112 let rc: i64 = nx_connect_bounded(fd, addr, 16, NX_CONN_DEFAULT_MS)
113 if rc < 0 { return rc }
114 return fd
115}
116
117// ---- TLSPlaintext record send ----
118//
119// Builds 5-byte header + payload in one buffer + single sys_write.
120// Returns NX_TLS13_IO_VERDICT_OK or non-OK verdict.
121func tls13_io_send_record(
122 fd: i64,
123 record_type: i64,
124 payload: *u8, payload_len: i64
125) -> i64 {
126 if payload_len < 0 { return NX_TLS13_IO_VERDICT_OVERSIZE }
127 if payload_len > NX_TLS13_IO_MAX_RECORD { return NX_TLS13_IO_VERDICT_OVERSIZE }
128 let total: i64 = 5 + payload_len
129 let buf: *u8 = sys_mmap(total + 16)
130 buf[0] = record_type & 0xff
131 buf[1] = 0x03
132 buf[2] = 0x03
133 buf[3] = (payload_len >> 8) & 0xff
134 buf[4] = payload_len & 0xff
135 var i: i64 = 0
136 while i < payload_len {
137 buf[5 + i] = payload[i]
138 i = i + 1
139 }
140 let sent: i64 = sys_write(fd, buf, total)
141 if sent != total { return NX_TLS13_IO_VERDICT_SOCKET_FAIL }
142 return NX_TLS13_IO_VERDICT_OK
143}
144
145// ---- TLSPlaintext record recv ----
146//
147// Reads exactly 5 header bytes (looping on partial recv), parses
148// the length, reads exactly that many body bytes into out_buf.
149// Writes the ContentType to *out_type and the payload length to
150// *out_actual_len. Returns OK or a non-OK verdict.
151//
152// Caller's out_buf must hold >= out_cap bytes. If the header
153// reports a length > out_cap, returns OVERSIZE without consuming
154// the body (caller may then close the connection).
155func tls13_io_recv_record(
156 fd: i64,
157 out_type: *i64,
158 out_buf: *u8, out_cap: i64,
159 out_actual_len: *i64
160) -> i64 {
161 // Read the 5-byte header in a loop (partial reads possible
162 // on slow / fragmented connections).
163 let hdr: *u8 = sys_mmap(16)
164 var hdr_got: i64 = 0
165 while hdr_got < 5 {
166 let n: i64 = sys_read(fd, hdr + hdr_got, 5 - hdr_got)
167 if n <= 0 { return NX_TLS13_IO_VERDICT_SHORT_HEADER }
168 hdr_got = hdr_got + n
169 }
170 let rtype: i64 = hdr[0] & 0xff
171 let length: i64 = ((hdr[3] & 0xff) << 8) | (hdr[4] & 0xff)
172 if length > NX_TLS13_IO_MAX_RECORD { return NX_TLS13_IO_VERDICT_OVERSIZE }
173 if length > out_cap { return NX_TLS13_IO_VERDICT_OVERSIZE }
174 *out_type = rtype
175 *out_actual_len = length
176 // Read body in a loop.
177 var body_got: i64 = 0
178 while body_got < length {
179 let n: i64 = sys_read(fd, out_buf + body_got, length - body_got)
180 if n <= 0 { return NX_TLS13_IO_VERDICT_SHORT_BODY }
181 body_got = body_got + n
182 }
183 return NX_TLS13_IO_VERDICT_OK
184}
185
186func nx_tls13_io_verdict_is_valid(v: i64) -> i64 {
187 if v < NX_TLS13_IO_VERDICT_OK { return 0 }
188 if v >= NX_TLS13_IO_VERDICT_N { return 0 }
189 return 1
190}