code wiki / (root) / nx_tls13_server.nx

nx_tls13_server.nx source

↩ module page · 268 lines · 10570 B

1// nx_tls13_server.nx -- TLS 1.3 server-side handshake (sovereign loopback). 2// 3// Phase 0b sovereign-loopback brick per 4// docs/NISHI_TLS13_GAP_AUDIT.md. Mirrors nx_tls13_client.nx so the 5// substrate can be proven end-to-end without depending on any 6// external TLS server (Cloudflare, openssl s_server, etc). The 7// loopback test in nx_tls13_loopback_test.nx wires this module 8// against nx_tls13_client.nx and verifies both sides reach 9// CONNECTED with byte-identical handshake keys. 10// 11// Per user (2026-05-16): "i dont want anything but sovereign". 12// External-server interop is a SEPARATE optional check, never the 13// substrate's correctness proof. 14// 15// What it does today: 16// - parse ClientHello: extract random / session_id / extensions 17// offsets so caller can walk to find key_share + supported_groups 18// - emit minimal ServerHello with TLS 1.3 supported_versions + 19// X25519 key_share + chosen cipher (ChaCha20-Poly1305-SHA256) 20// - server state machine: WAIT_CH -> WAIT_CF -> CONNECTED 21// (caller drives WAIT_CH by calling parse + emit; this module's 22// dispatcher handles WAIT_CF when the client_Finished arrives) 23// - sealed verdict + validity gate 24// 25// What it doesn't do yet: 26// - signature over CertificateVerify (currently the loopback 27// test uses a placeholder; Gap I when wired will sign with 28// ed25519 over the transcript) 29// - HelloRetryRequest emission (the server here ALWAYS accepts 30// the client's first key_share if X25519; if the client offered 31// a group we don't support, today we just fail; queued: emit 32// HRR with our preferred group) 33// - ALPN negotiation (CH offers a list; server picks one; queued) 34// - PSK acceptance / 0-RTT (Gap M) 35// 36// KAT verified: 37// - end-to-end via nx_tls13_loopback_test.nx: client + server both 38// using shipped substrate reach CONNECTED, derive identical 39// handshake_secret + traffic secrets, verify each other's MACs 40// 41// Composes with: 42// - nx_tls13 (handshake type constants + u16/u24 helpers) 43// - nx_tls13_hello (NX_TLS13_CS_* cipher suite constants) 44// - nx_tls13_ext (parser for ServerHello's key_share echo not 45// needed here -- server EMITS them) 46// - nx_tls13_finished + nx_tls13_schedule (CF MAC verify) 47// - nx_tls13_transcript (running hash) 48// 49// license_tier: INDEPENDENT_REDERIVE 50// genealogy_id: international-research-sources/ietf/rfc_8446 51// lineage_id: nishi_tls13_server_q10 52 53// nx_safety_envelope: 54// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 55// sil_target: SIL1 56// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 57// verdict: NOT_YET_EVALUATED 58 59import "nx_syscalls.nx" 60import "nx_tls13.nx" 61import "nx_tls13_hello.nx" 62import "nx_tls13_finished.nx" 63import "nx_tls13_transcript.nx" 64import "nx_tls13_schedule.nx" 65 66const NX_TLS13_SSTATE_WAIT_CH: i64 = 1 67const NX_TLS13_SSTATE_WAIT_CF: i64 = 2 68const NX_TLS13_SSTATE_CONNECTED: i64 = 3 69const NX_TLS13_SSTATE_N: i64 = 4 70 71const NX_TLS13_SERVER_VERDICT_OK: i64 = 1 72const NX_TLS13_SERVER_VERDICT_BAD_MSG_TYPE: i64 = 2 73const NX_TLS13_SERVER_VERDICT_BAD_CH: i64 = 3 74const NX_TLS13_SERVER_VERDICT_BAD_FORMAT: i64 = 4 75const NX_TLS13_SERVER_VERDICT_CF_BAD_MAC: i64 = 5 76const NX_TLS13_SERVER_VERDICT_BUF_OVERFLOW: i64 = 6 77const NX_TLS13_SERVER_VERDICT_N: i64 = 7 78 79const NX_TLS13_SERVER_HASH_LEN: i64 = 32 80 81// Parse ClientHello, expose key field offsets within `buf`. Caller 82// uses tls13_ext_find (shipped) on the returned extensions blob to 83// locate key_share, supported_versions, server_name, etc. 84// 85// On success: *out_random_off points to byte 0 of the 32-byte 86// client random; *out_sid_off/len describe the legacy_session_id 87// (may be empty or 32-byte middlebox-compat random); 88// *out_ext_off/len describe the extensions blob. 89func tls13_server_parse_client_hello( 90 buf: *u8, n: i64, 91 out_random_off: *i64, 92 out_sid_off: *i64, out_sid_len: *i64, 93 out_ext_off: *i64, out_ext_len: *i64 94) -> i64 { 95 if n < 4 { return NX_TLS13_SERVER_VERDICT_BAD_CH } 96 if (buf[0] & 0xff) != HT_CLIENT_HELLO { return NX_TLS13_SERVER_VERDICT_BAD_MSG_TYPE } 97 let body_len: i64 = ((buf[1] & 0xff) << 16) | ((buf[2] & 0xff) << 8) | (buf[3] & 0xff) 98 if 4 + body_len > n { return NX_TLS13_SERVER_VERDICT_BAD_CH } 99 var o: i64 = 4 100 // legacy_version (2) 101 if o + 2 > n { return NX_TLS13_SERVER_VERDICT_BAD_CH } 102 o = o + 2 103 // random (32) 104 if o + 32 > n { return NX_TLS13_SERVER_VERDICT_BAD_CH } 105 *out_random_off = o 106 o = o + 32 107 // session_id (1 byte length + bytes) 108 if o + 1 > n { return NX_TLS13_SERVER_VERDICT_BAD_CH } 109 let sid_len: i64 = buf[o] & 0xff 110 o = o + 1 111 if o + sid_len > n { return NX_TLS13_SERVER_VERDICT_BAD_CH } 112 *out_sid_off = o 113 *out_sid_len = sid_len 114 o = o + sid_len 115 // cipher_suites (2-byte length + suite ids) 116 if o + 2 > n { return NX_TLS13_SERVER_VERDICT_BAD_CH } 117 let cs_list_len: i64 = tls_read_u16_be(buf, o) 118 o = o + 2 + cs_list_len 119 if o > n { return NX_TLS13_SERVER_VERDICT_BAD_CH } 120 // compression methods (1 byte length + bytes) 121 if o + 1 > n { return NX_TLS13_SERVER_VERDICT_BAD_CH } 122 let cm_len: i64 = buf[o] & 0xff 123 o = o + 1 + cm_len 124 if o > n { return NX_TLS13_SERVER_VERDICT_BAD_CH } 125 // extensions (2-byte length + extensions blob) 126 if o + 2 > n { return NX_TLS13_SERVER_VERDICT_BAD_CH } 127 let ext_list_len: i64 = tls_read_u16_be(buf, o) 128 o = o + 2 129 if o + ext_list_len > n { return NX_TLS13_SERVER_VERDICT_BAD_CH } 130 *out_ext_off = o 131 *out_ext_len = ext_list_len 132 return NX_TLS13_SERVER_VERDICT_OK 133} 134 135// Emit a minimal ServerHello. Cipher chosen: ChaCha20-Poly1305-SHA256 136// (matches our AEAD). Extensions emitted: supported_versions 137// (server variant, just 0x0304) + key_share (server variant, single 138// KeyShareEntry with X25519 + our pubkey). session_id_echo is 139// copied verbatim from the client (middlebox compatibility). 140// 141// Output: bytes written including 4-byte handshake header. 142func tls13_server_emit_server_hello( 143 server_random32: *u8, 144 sid_echo: *u8, sid_echo_len: i64, 145 server_pubkey32: *u8, 146 out: *u8, out_cap: i64 147) -> i64 { 148 // header(4) + version(2) + random(32) + sid_len(1) + sid(<=32) 149 // + cipher(2) + compress(1) + ext_list_len(2) + sv_ext(6) + ks_ext(40) 150 // worst case = 4 + 2 + 32 + 1 + 32 + 2 + 1 + 2 + 6 + 40 = 122 151 let need: i64 = 122 + sid_echo_len 152 if out_cap < need { return 0 - NX_TLS13_SERVER_VERDICT_BUF_OVERFLOW } 153 var o: i64 = 4 // reserve 4 for handshake header 154 // legacy_version 0x0303 155 tls_write_u16_be(out, o, TLS_LEGACY_VERSION) 156 o = o + 2 157 // random 158 var i: i64 = 0 159 while i < 32 { 160 out[o + i] = server_random32[i] 161 i = i + 1 162 } 163 o = o + 32 164 // session_id_echo 165 out[o] = sid_echo_len & 0xff 166 o = o + 1 167 var j: i64 = 0 168 while j < sid_echo_len { 169 out[o + j] = sid_echo[j] 170 j = j + 1 171 } 172 o = o + sid_echo_len 173 // cipher_suite (single) 174 tls_write_u16_be(out, o, NX_TLS13_CS_CHACHA20_POLY1305_SHA256) 175 o = o + 2 176 // compression method = 0 177 out[o] = 0 178 o = o + 1 179 // extensions list_len placeholder 180 let ext_list_off: i64 = o 181 o = o + 2 182 let ext_data_start: i64 = o 183 // supported_versions: 0x002b + len(2)=2 + version(2)=0x0304 = 6 bytes 184 tls_write_u16_be(out, o, EXT_SUPPORTED_VERSIONS) 185 o = o + 2 186 tls_write_u16_be(out, o, 2) 187 o = o + 2 188 tls_write_u16_be(out, o, TLS_13_VERSION) 189 o = o + 2 190 // key_share server-variant: 0x0033 + len(2)=36 + group(2) + ke_len(2)=32 + ke(32) = 40 bytes 191 tls_write_u16_be(out, o, EXT_KEY_SHARE) 192 o = o + 2 193 tls_write_u16_be(out, o, 36) 194 o = o + 2 195 tls_write_u16_be(out, o, NG_X25519) 196 o = o + 2 197 tls_write_u16_be(out, o, 32) 198 o = o + 2 199 var k: i64 = 0 200 while k < 32 { 201 out[o + k] = server_pubkey32[k] 202 k = k + 1 203 } 204 o = o + 32 205 // Backfill ext list length 206 let ext_data_len: i64 = o - ext_data_start 207 tls_write_u16_be(out, ext_list_off, ext_data_len) 208 // Backfill handshake header 209 let body_len: i64 = o - 4 210 out[0] = HT_SERVER_HELLO & 0xff 211 tls_write_u24_be(out, 1, body_len) 212 return o 213} 214 215// Server state-machine dispatcher: handle a client_Finished message. 216// 217// Inputs: 218// msg, msg_len = the decrypted handshake message bytes 219// (HT_FINISHED + uint24=32 + 32-byte MAC) 220// client_hs_traffic_secret = chts; the same secret the client 221// derived; server uses it to verify CF 222// transcript_state = running transcript over CH+SH+EE+ 223// Cert+CV+SF (server has fed all of 224// those in already) 225// Outputs: 226// *out_new_state = CONNECTED on success 227func tls13_server_dispatch_client_finished( 228 msg: *u8, msg_len: i64, 229 client_hs_traffic_secret: *u8, 230 transcript_state: *u8, 231 out_new_state: *i64 232) -> i64 { 233 if msg_len < 4 { return NX_TLS13_SERVER_VERDICT_BAD_FORMAT } 234 if (msg[0] & 0xff) != HT_FINISHED { return NX_TLS13_SERVER_VERDICT_BAD_MSG_TYPE } 235 let body_len: i64 = ((msg[1] & 0xff) << 16) | ((msg[2] & 0xff) << 8) | (msg[3] & 0xff) 236 if body_len != NX_TLS13_SERVER_HASH_LEN { return NX_TLS13_SERVER_VERDICT_BAD_FORMAT } 237 if 4 + body_len > msg_len { return NX_TLS13_SERVER_VERDICT_BAD_FORMAT } 238 // Snapshot transcript BEFORE feeding CF -- the CF MAC is over 239 // CH..server_Finished (which we've already fed in). 240 let th: *u8 = sys_mmap(64) 241 nx_tls13_transcript_snapshot(transcript_state, th) 242 // finished_key from chts 243 let c_fk: *u8 = sys_mmap(64) 244 tls13_finished_key(client_hs_traffic_secret, NX_TLS13_SERVER_HASH_LEN, c_fk) 245 let received_mac: *u8 = msg + 4 246 let v: i64 = nx_tls13_finished_verify( 247 c_fk, NX_TLS13_SERVER_HASH_LEN, 248 th, NX_TLS13_SERVER_HASH_LEN, 249 received_mac, NX_TLS13_SERVER_HASH_LEN 250 ) 251 if v != NX_TLS13_FIN_VERDICT_OK { return NX_TLS13_SERVER_VERDICT_CF_BAD_MAC } 252 nx_tls13_transcript_update(transcript_state, msg, msg_len) 253 *out_new_state = NX_TLS13_SSTATE_CONNECTED 254 return NX_TLS13_SERVER_VERDICT_OK 255} 256 257// Sealed-enum validity gates. 258func nx_tls13_server_verdict_is_valid(v: i64) -> i64 { 259 if v < 0 { return 0 } 260 if v >= NX_TLS13_SERVER_VERDICT_N { return 0 } 261 return 1 262} 263 264func nx_tls13_sstate_is_valid(s: i64) -> i64 { 265 if s < 0 { return 0 } 266 if s >= NX_TLS13_SSTATE_N { return 0 } 267 return 1 268}