code wiki / (root) / nx_tls13_io.nx

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}