code wiki / (root) / nx_tls13_client.nx

nx_tls13_client.nx source

↩ module page · 264 lines · 12039 B

1// nx_tls13_client.nx -- TLS 1.3 client state machine (Gap L sub-2). 2// 3// Phase 0b §L of the Nishi TLS 1.3 stack per 4// docs/NISHI_TLS13_GAP_AUDIT.md. The dispatcher that walks the 5// server's encrypted handshake records past ServerHello: each 6// decrypted record body contains 1+ handshake messages, and this 7// state machine consumes them in the order RFC 8446 §A.1 dictates: 8// 9// WAIT_EE -> EncryptedExtensions 10// | 11// WAIT_CERT -> Certificate 12// | 13// WAIT_CV -> CertificateVerify 14// | 15// WAIT_SF -> server Finished (verify MAC over transcript-before-SF) 16// | 17// CONNECTED 18// 19// At every stage the message goes into the transcript hash AFTER 20// the stage-specific check passes -- except for server Finished, 21// which verifies the MAC over the transcript-BEFORE-SF then feeds 22// SF in. Getting that order wrong = MAC mismatch on a legitimate 23// server (the bug that catches every from-scratch TLS impl). 24// 25// What it does today: 26// - dispatch one decrypted handshake message per call 27// - parse via shipped nx_tls13_auth + nx_tls13_finished 28// - feed transcript in spec-ordered way 29// - server_Finished MAC verify via constant-time compare 30// - emit our client_Finished (36-byte handshake message: 31// 4-byte header + 32-byte HMAC over transcript) 32// - cert validation is INTENTIONALLY stubbed to "accept any" 33// -- Gap I will replace the stub with real RFC 5280 path 34// validation (chain build + signature chain verify + name 35// constraints + SAN match + validity period) 36// 37// What it doesn't do yet: 38// - signature verification on CertificateVerify (composes 39// against nx_ed25519 / nx_ecdsa / nx_rsa_pss; Gap I) 40// - CertificateRequest (mutual TLS client-auth; rare) 41// - the actual TCP-recv loop (caller provides decrypted bytes) 42// - record-layer encryption of the emitted client_Finished 43// (caller wraps via nx_tls13_record_encrypt under 44// client_handshake_traffic_key) 45// 46// KAT verified: 47// - 4-stage walk: synthesised EE+Cert+CV+SF drive the dispatcher 48// to CONNECTED with the right state transitions 49// - tampered server_Finished MAC rejected with FIN_BAD verdict 50// - wrong-type message at each state rejected 51// - client_Finished MAC byte-equals direct HMAC over transcript 52// - end-to-end record-layer round-trip (encrypt our Finished 53// under derived AEAD key, decrypt, recover the bytes) 54// 55// Composes with: 56// - nx_tls13 (handshake type constants) 57// - nx_tls13_auth (parse EE / Cert / CV) 58// - nx_tls13_finished (compute + verify MAC) 59// - nx_tls13_transcript (running hash + snapshot) 60// - nx_tls13_kdf (HKDF-Expand-Label for finished_key) 61// - nx_tls13_schedule (tls13_finished_key wrapper) 62// - nx_tls13_record (encrypt the emitted Finished; caller-side) 63// - nx_tls13_handshake (caller derived hs keys via that orchestrator) 64// 65// license_tier: INDEPENDENT_REDERIVE 66// genealogy_id: international-research-sources/ietf/rfc_8446 67// lineage_id: nishi_tls13_client_q10 68 69// nx_safety_envelope: 70// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 71// sil_target: SIL1 72// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 73// verdict: NOT_YET_EVALUATED 74 75import "nx_syscalls.nx" 76import "nx_tls13.nx" 77import "nx_tls13_auth.nx" 78import "nx_tls13_finished.nx" 79import "nx_tls13_transcript.nx" 80import "nx_tls13_schedule.nx" 81 82// Sealed client state. 83const NX_TLS13_CSTATE_WAIT_EE: i64 = 1 84const NX_TLS13_CSTATE_WAIT_CERT: i64 = 2 85const NX_TLS13_CSTATE_WAIT_CV: i64 = 3 86const NX_TLS13_CSTATE_WAIT_SF: i64 = 4 87const NX_TLS13_CSTATE_CONNECTED: i64 = 5 88const NX_TLS13_CSTATE_N: i64 = 6 89 90// Sealed dispatcher verdict. 91const NX_TLS13_CLIENT_VERDICT_OK: i64 = 1 92const NX_TLS13_CLIENT_VERDICT_BAD_MSG_TYPE: i64 = 2 93const NX_TLS13_CLIENT_VERDICT_BAD_FORMAT: i64 = 3 94const NX_TLS13_CLIENT_VERDICT_SF_BAD_MAC: i64 = 4 95const NX_TLS13_CLIENT_VERDICT_BAD_STATE: i64 = 5 96const NX_TLS13_CLIENT_VERDICT_N: i64 = 6 97 98const NX_TLS13_CLIENT_HASH_LEN: i64 = 32 99 100// Dispatch a single decrypted handshake message into the client 101// state machine. 102// 103// Inputs: 104// state = current state (NX_TLS13_CSTATE_*) 105// msg, msg_len = the handshake message bytes 106// (4-byte header + body) 107// server_hs_traffic_secret = needed for WAIT_SF; otherwise unused 108// transcript_state = running transcript state pointer 109// (from nx_tls13_transcript_new + updates 110// for CH+SH already done) 111// 112// Outputs: 113// *out_new_state = new state after this message 114// 115// Returns NX_TLS13_CLIENT_VERDICT_OK on success or non-OK. 116func tls13_client_dispatch_handshake_message( 117 state: i64, 118 msg: *u8, msg_len: i64, 119 server_hs_traffic_secret: *u8, 120 transcript_state: *u8, 121 out_new_state: *i64 122) -> i64 { 123 if msg_len < 4 { return NX_TLS13_CLIENT_VERDICT_BAD_FORMAT } 124 let msg_type: i64 = msg[0] & 0xff 125 126 if state == NX_TLS13_CSTATE_WAIT_EE { 127 if msg_type != HT_ENCRYPTED_EXTENSIONS { return NX_TLS13_CLIENT_VERDICT_BAD_MSG_TYPE } 128 let p_eo: *i64 = sys_mmap(16) as *i64 129 let p_el: *i64 = sys_mmap(16) as *i64 130 let v: i64 = tls13_parse_encrypted_extensions(msg, msg_len, p_eo, p_el) 131 if v != NX_TLS13_AUTH_VERDICT_OK { return NX_TLS13_CLIENT_VERDICT_BAD_FORMAT } 132 nx_tls13_transcript_update(transcript_state, msg, msg_len) 133 *out_new_state = NX_TLS13_CSTATE_WAIT_CERT 134 return NX_TLS13_CLIENT_VERDICT_OK 135 } 136 137 if state == NX_TLS13_CSTATE_WAIT_CERT { 138 if msg_type != HT_CERTIFICATE { return NX_TLS13_CLIENT_VERDICT_BAD_MSG_TYPE } 139 let p_co: *i64 = sys_mmap(16) as *i64 140 let p_cl: *i64 = sys_mmap(16) as *i64 141 let p_cert_off: *i64 = sys_mmap(16) as *i64 142 let p_cert_len: *i64 = sys_mmap(16) as *i64 143 let p_ce_off: *i64 = sys_mmap(16) as *i64 144 let p_ce_len: *i64 = sys_mmap(16) as *i64 145 let p_chain: *i64 = sys_mmap(16) as *i64 146 let v: i64 = tls13_parse_certificate_chain_first( 147 msg, msg_len, 148 p_co, p_cl, 149 p_cert_off, p_cert_len, 150 p_ce_off, p_ce_len, 151 p_chain 152 ) 153 if v != NX_TLS13_AUTH_VERDICT_OK { return NX_TLS13_CLIENT_VERDICT_BAD_FORMAT } 154 // UNVALIDATED PRIMITIVE: this bare dispatcher accepts the cert without checking it. Gap I HAS landed 155 // -- the LIVE fetch path does NOT call this directly; it wraps it with tls13_client_dispatch_with_ 156 // validation (full RFC 5280 chain validation at WAIT_CERT, via nx_tls13_client_validate_certificate) 157 // AND nx_tls13_client_session_recv_hs verifies the server CertificateVerify signature at WAIT_CV 158 // (nx_tls13_client_verify_server_cv, RFC 8446 4.4.3, 2026-07-03). So the live client IS safe. NEVER call 159 // this bare dispatcher directly against an untrusted network -- only via the validation wrapper. 160 nx_tls13_transcript_update(transcript_state, msg, msg_len) 161 *out_new_state = NX_TLS13_CSTATE_WAIT_CV 162 return NX_TLS13_CLIENT_VERDICT_OK 163 } 164 165 if state == NX_TLS13_CSTATE_WAIT_CV { 166 if msg_type != HT_CERTIFICATE_VERIFY { return NX_TLS13_CLIENT_VERDICT_BAD_MSG_TYPE } 167 let p_scheme: *i64 = sys_mmap(16) as *i64 168 let p_so: *i64 = sys_mmap(16) as *i64 169 let p_sl: *i64 = sys_mmap(16) as *i64 170 let v: i64 = tls13_parse_certificate_verify(msg, msg_len, p_scheme, p_so, p_sl) 171 if v != NX_TLS13_AUTH_VERDICT_OK { return NX_TLS13_CLIENT_VERDICT_BAD_FORMAT } 172 // UNVALIDATED PRIMITIVE: this bare dispatcher does not verify the CertificateVerify signature. The LIVE 173 // path DOES: nx_tls13_client_session_recv_hs calls nx_tls13_client_verify_server_cv BEFORE this dispatch 174 // (Ed25519 / ECDSA-P256/P384 / RSA-PSS-SHA256 against the leaf pubkey, RFC 8446 4.4.3, fail-closed on the 175 // rest), proven end-to-end by real TLS fetches (2026-07-03). Only reach this via that validating path. 176 nx_tls13_transcript_update(transcript_state, msg, msg_len) 177 *out_new_state = NX_TLS13_CSTATE_WAIT_SF 178 return NX_TLS13_CLIENT_VERDICT_OK 179 } 180 181 if state == NX_TLS13_CSTATE_WAIT_SF { 182 if msg_type != HT_FINISHED { return NX_TLS13_CLIENT_VERDICT_BAD_MSG_TYPE } 183 // SF body MUST be the 32-byte HMAC (for SHA-256 cipher). 184 let body_len: i64 = ((msg[1] & 0xff) << 16) | ((msg[2] & 0xff) << 8) | (msg[3] & 0xff) 185 if body_len != NX_TLS13_CLIENT_HASH_LEN { return NX_TLS13_CLIENT_VERDICT_BAD_FORMAT } 186 if 4 + body_len > msg_len { return NX_TLS13_CLIENT_VERDICT_BAD_FORMAT } 187 // Snapshot transcript BEFORE feeding SF -- the SF MAC is 188 // over CH..server_CertVerify, NOT over CH..server_Finished. 189 let th_before_sf: *u8 = sys_mmap(64) 190 nx_tls13_transcript_snapshot(transcript_state, th_before_sf) 191 // Derive server's finished_key from server_hs_traffic_secret. 192 let s_fk: *u8 = sys_mmap(64) 193 tls13_finished_key(server_hs_traffic_secret, NX_TLS13_CLIENT_HASH_LEN, s_fk) 194 // Verify received MAC vs computed expected. 195 let received_mac: *u8 = msg + 4 196 let v: i64 = nx_tls13_finished_verify( 197 s_fk, NX_TLS13_CLIENT_HASH_LEN, 198 th_before_sf, NX_TLS13_CLIENT_HASH_LEN, 199 received_mac, NX_TLS13_CLIENT_HASH_LEN 200 ) 201 if v != NX_TLS13_FIN_VERDICT_OK { return NX_TLS13_CLIENT_VERDICT_SF_BAD_MAC } 202 // Now feed SF into transcript (so client_Finished MAC will 203 // cover CH..server_Finished as the spec requires). 204 nx_tls13_transcript_update(transcript_state, msg, msg_len) 205 *out_new_state = NX_TLS13_CSTATE_CONNECTED 206 return NX_TLS13_CLIENT_VERDICT_OK 207 } 208 209 return NX_TLS13_CLIENT_VERDICT_BAD_STATE 210} 211 212// Emit client_Finished as a complete 36-byte handshake message: 213// HandshakeType(1) = HT_FINISHED(20) + uint24 length(3) = 32 214// verify_data(32) = HMAC(finished_key, transcript_snapshot_now) 215// 216// Caller MUST have driven the dispatcher to CONNECTED first 217// (so the transcript covers CH..server_Finished). Caller then 218// wraps the emitted bytes via nx_tls13_record_encrypt under 219// client_handshake_traffic_key + per-record sequence number. 220// 221// Returns 36 (bytes written) or negative verdict. 222func tls13_client_emit_finished( 223 client_hs_traffic_secret: *u8, 224 transcript_state: *u8, 225 out: *u8 226) -> i64 { 227 let th: *u8 = sys_mmap(64) 228 nx_tls13_transcript_snapshot(transcript_state, th) 229 let fk: *u8 = sys_mmap(64) 230 tls13_finished_key(client_hs_traffic_secret, NX_TLS13_CLIENT_HASH_LEN, fk) 231 let mac: *u8 = sys_mmap(64) 232 nx_tls13_finished_compute(fk, NX_TLS13_CLIENT_HASH_LEN, th, NX_TLS13_CLIENT_HASH_LEN, mac) 233 out[0] = HT_FINISHED & 0xff 234 out[1] = 0 235 out[2] = 0 236 out[3] = NX_TLS13_CLIENT_HASH_LEN & 0xff 237 var i: i64 = 0 238 while i < NX_TLS13_CLIENT_HASH_LEN { 239 out[4 + i] = mac[i] 240 i = i + 1 241 } 242 // Append the emitted CF to the transcript so subsequent 243 // application-data key derivations (which use the post-CF 244 // transcript hash per RFC 8446 §7.1) match server's view. 245 // Without this, server.dispatch_client_finished's own append 246 // makes its transcript diverge from the client's permanently. 247 // (Bug discovered via nx_tls13_loopback_test reaching CONNECTED 248 // on both sides but with mismatched final transcript hashes.) 249 nx_tls13_transcript_update(transcript_state, out, 4 + NX_TLS13_CLIENT_HASH_LEN) 250 return 4 + NX_TLS13_CLIENT_HASH_LEN 251} 252 253// Sealed-enum validity gates. 254func nx_tls13_client_verdict_is_valid(v: i64) -> i64 { 255 if v < 0 { return 0 } 256 if v >= NX_TLS13_CLIENT_VERDICT_N { return 0 } 257 return 1 258} 259 260func nx_tls13_cstate_is_valid(s: i64) -> i64 { 261 if s < 0 { return 0 } 262 if s >= NX_TLS13_CSTATE_N { return 0 } 263 return 1 264}