code wiki / (root) / nx_tls13_client_session_emit_finished.nx

nx_tls13_client_session_emit_finished.nx source

↩ module page · 158 lines · 6236 B

1// nx_tls13_client_session_emit_finished.nx -- step 3c.4 of the 2// nx_https_client wiring arc. 3// 4// Builds the client_Finished handshake message + wraps it in an 5// AEAD-encrypted TLS record under the client handshake traffic 6// key. Caller writes the resulting bytes to fd. After this 7// primitive returns OK, the session is ready for step 3c.5 8// (derive application traffic keys; state -> CONNECTED). 9// 10// Composes 2 shipped primitives: 11// tls13_client_emit_finished -- 36-byte Finished + transcript 12// update (CRITICAL: appends CF to 13// transcript so app-key derivation 14// matches server's view) 15// nx_tls13_record_encrypt -- ChaCha20-Poly1305 record wrap 16// 17// Pipeline: 18// transcript_state at WAIT_CLIENT_FIN (covers CH..server_Finished) 19// ↓ snapshot 20// transcript_hash_CH..SF 21// ↓ tls13_finished_key(client_hs_traffic_secret) -> finished_key 22// ↓ HMAC(finished_key, transcript_hash) 23// verify_data (32 bytes) 24// ↓ wrap in handshake message: [HT_FINISHED][3-byte len=32][verify_data] 25// client_Finished_msg (36 bytes) 26// ↓ append to transcript (for app-key derivation hash) 27// ↓ AEAD encrypt under client_hs_traffic_key + client_hs_iv + 28// client_seq 29// encrypted_record (5-byte header + 36+1 inner + 16-byte tag = 58 bytes) 30// 31// Caller calls sys_write(fd, record_bytes, record_len) to send. 32// 33// Public API: 34// nx_tls13_client_session_emit_finished( 35// session, out_buf, out_cap 36// ) -> positive bytes-written | negative -NX_TLS13_EMIT_CF_* code 37// nx_tls13_emit_cf_verdict_is_valid(v) -> 0|1 38// 39// Sealed verdict: 40// NX_TLS13_EMIT_CF_OK positive rc = bytes written 41// NX_TLS13_EMIT_CF_BAD_STATE session not at WAIT_CLIENT_FIN 42// NX_TLS13_EMIT_CF_BUF_OVERFLOW out_buf too small (need ~58 bytes) 43// NX_TLS13_EMIT_CF_INTERNAL emit or encrypt returned non-OK 44// 45// Per Cardinals 9 (single-responsibility -- ONE handshake step), 46// 12 (defensive at boundaries -- state guard + buf cap check + 47// client_seq increment post-use to maintain nonce uniqueness), 48// 19 (composes shipped primitives unchanged), 22 (composition -- 49// 2 shipped primitives stack into one step), 23 (preamble names 50// the transcript-update criticality from the loopback-test bug 51// history). 52// 53// license_tier: INDEPENDENT_REDERIVE 54// genealogy_id: international-research-sources/ietf/rfc_8446 55// lineage_id: nishi_tls13_client_session_emit_finished_q10 56 57// nx_safety_envelope: 58// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 59// sil_target: SIL1 60// evidence: [bulk_applied_2026-05-19, tls13-client-emit-cf-step-3c4] 61// verdict: NOT_YET_EVALUATED 62 63import "nx_syscalls.nx" 64import "nx_tls13.nx" 65import "nx_tls13_record.nx" 66import "nx_tls13_client.nx" 67import "nx_tls13_client_session.nx" 68import "nx_tls13_transcript.nx" 69 70const NX_TLS13_EMIT_CF_OK: i64 = 1 71const NX_TLS13_EMIT_CF_BAD_STATE: i64 = 2 72const NX_TLS13_EMIT_CF_BUF_OVERFLOW: i64 = 3 73const NX_TLS13_EMIT_CF_INTERNAL: i64 = 4 74const NX_TLS13_EMIT_CF_VERDICT_N: i64 = 5 75 76// client_Finished encrypted record byte budget: 77// header(5) + inner(36 + 1 type-byte) + tag(16) = 58 bytes 78const NX_TLS13_CF_RECORD_BYTES: i64 = 58 79 80func nx_tls13_emit_cf_verdict_is_valid(v: i64) -> i64 { 81 if v < NX_TLS13_EMIT_CF_OK { return 0 } 82 if v >= NX_TLS13_EMIT_CF_VERDICT_N { return 0 } 83 return 1 84} 85 86// Step 3c.4: build encrypted client_Finished record into out_buf. 87// 88// Returns POSITIVE bytes-written (= 58) on success. Returns 89// NEGATIVE (-NX_TLS13_EMIT_CF_*) on failure. 90func nx_tls13_client_session_emit_finished( 91 s: *Tls13ClientSession, 92 out_buf: *u8, out_cap: i64 93) -> i64 { 94 if s.state != NX_TLS13_CSESSION_STATE_WAIT_CLIENT_FIN { 95 return 0 - NX_TLS13_EMIT_CF_BAD_STATE 96 } 97 if out_cap < NX_TLS13_CF_RECORD_BYTES { 98 return 0 - NX_TLS13_EMIT_CF_BUF_OVERFLOW 99 } 100 101 // ---- Snapshot transcript at hash(CH..SF) BEFORE building CF ---- 102 // RFC 8446 §7.1: application-traffic-secrets use Hash(ClientHello 103 // ...server Finished) -- the SAME prefix the CF verify_data uses, 104 // captured before the CF transcript update inside the emitter. 105 // R9: h_post_sf feeds the application-traffic derivation, so it must be the SUITE'S hash. 106 if s.cipher_suite == NX_TLS13_CS_AES_256_GCM_SHA384 { 107 nx_tls13_transcript_snapshot384(s.transcript, s.h_post_sf) 108 } else { 109 nx_tls13_transcript_snapshot(s.transcript, s.h_post_sf) 110 } 111 112 // ---- Build the 36-byte client_Finished handshake message ---- 113 let cf_msg: *u8 = sys_mmap(64) 114 let cf_len: i64 = tls13_client_emit_finished( 115 s.client_hs_traffic_secret, 116 s.transcript, 117 cf_msg 118 ) 119 if cf_len != 36 { return 0 - NX_TLS13_EMIT_CF_INTERNAL } 120 121 // ---- Encrypt as TLS record under client_hs_traffic_key ---- 122 // Inner plaintext = cf_msg || real_content_type(1) = 37 bytes 123 // Ciphertext payload = inner + tag = 37 + 16 = 53 bytes 124 // Record total = header(5) + payload(53) = 58 bytes 125 let header_out: *u8 = out_buf // bytes 0..5 126 let ct_out: *u8 = out_buf + NX_TLS13_RECORD_HEADER_LEN // bytes 5..42 127 let tag_out: *u8 = out_buf + NX_TLS13_RECORD_HEADER_LEN + cf_len + 1 // bytes 42..58 128 129 let enc_v: i64 = nx_tls13_record_encrypt_v2( 130 s.cipher_suite, 131 s.client_hs_traffic_key, 132 s.client_hs_iv, 133 s.client_seq, 134 cf_msg, cf_len, 135 NX_TLS13_CT_HANDSHAKE, 136 0, // no padding 137 header_out, 138 ct_out, 139 tag_out 140 ) 141 142 // Increment client_seq AFTER use regardless of verdict (same 143 // nonce-uniqueness discipline as recv_hs's server_seq). 144 s.client_seq = s.client_seq + 1 145 146 if enc_v != NX_TLS13_REC_VERDICT_OK { return 0 - NX_TLS13_EMIT_CF_INTERNAL } 147 148 // ---- Advance state ---- 149 s.state = NX_TLS13_CSESSION_STATE_WAIT_APP_KEYS 150 151 return NX_TLS13_CF_RECORD_BYTES 152} 153 154// Compile-only smoke. Real KAT in 155// nx_tls13_client_session_emit_finished_test.nx. 156func main() -> i64 { 157 return 0 158}