code wiki / (root) / nx_tls13_client_session_derive_app.nx

nx_tls13_client_session_derive_app.nx source

↩ module page · 133 lines · 5757 B

1// nx_tls13_client_session_derive_app.nx -- step 3c.5 of the 2// nx_https_client wiring arc. 3// 4// Runs the SECOND HALF of the RFC 8446 §7.1 key schedule cascade 5// to derive the application-traffic keys, advancing the session 6// from WAIT_APP_KEYS to CONNECTED. After this primitive returns 7// OK, the session is fully usable for application-data send/recv. 8// 9// Cascade (continuing from where 3c.2 left off): 10// 11// handshake_secret (from 3c.2) 12// | 13// v 14// Derive-Secret(., "derived", H_empty) = derived_2 15// | 16// v IKM = 0^HashLen 17// HKDF-Extract(salt=derived_2, IKM=0) = master_secret 18// | 19// v salt = master_secret; context = H2 = hash(CH..CF) 20// +--> Derive-Secret(., "c ap traffic", H2) = c_app_traffic_secret_0 21// +--> Derive-Secret(., "s ap traffic", H2) = s_app_traffic_secret_0 22// then per-secret expand: 23// key = HKDF-Expand-Label(secret, "key", "", 32) 24// iv = HKDF-Expand-Label(secret, "iv", "", 12) 25// 26// The transcript at this point covers CH..client_Finished 27// (CF was appended in 3c.4's emit). H2 = transcript snapshot. 28// 29// Once derived, app_seq counters are reset to 0 (per-direction 30// AEAD nonce sequence starts fresh for application data). State 31// advances to CONNECTED -- application send/recv is now safe. 32// 33// Public API: 34// nx_tls13_client_session_derive_app(session) -> verdict 35// nx_tls13_derive_app_verdict_is_valid(v) -> 0|1 36// 37// Sealed verdict: 38// NX_TLS13_DERIVE_APP_OK keys derived, state CONNECTED 39// NX_TLS13_DERIVE_APP_BAD_STATE session not at WAIT_APP_KEYS 40// 41// Per Cardinals 9 (single-responsibility -- ONE step: the final 42// derivation), 12 (defensive at boundaries -- state guard), 19 43// (composes shipped key-schedule primitives unchanged), 22 44// (composition -- 5 shipped primitives compose into one step), 45// 23 (preamble shows the cascade diagram). 46// 47// license_tier: INDEPENDENT_REDERIVE 48// genealogy_id: international-research-sources/ietf/rfc_8446 49// lineage_id: nishi_tls13_client_session_derive_app_q10 50 51// nx_safety_envelope: 52// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 53// sil_target: SIL1 54// evidence: [bulk_applied_2026-05-19, tls13-client-derive-app-step-3c5] 55// verdict: NOT_YET_EVALUATED 56 57import "nx_syscalls.nx" 58import "nx_sha256.nx" 59import "nx_tls13_transcript.nx" 60import "nx_tls13_schedule.nx" 61import "nx_tls13_client_session.nx" 62 63const NX_TLS13_DERIVE_APP_OK: i64 = 1 64const NX_TLS13_DERIVE_APP_BAD_STATE: i64 = 2 65const NX_TLS13_DERIVE_APP_VERDICT_N: i64 = 3 66 67func nx_tls13_derive_app_verdict_is_valid(v: i64) -> i64 { 68 if v < NX_TLS13_DERIVE_APP_OK { return 0 } 69 if v >= NX_TLS13_DERIVE_APP_VERDICT_N { return 0 } 70 return 1 71} 72 73// Step 3c.5: derive application-traffic keys + advance to CONNECTED. 74func nx_tls13_client_session_derive_app(s: *Tls13ClientSession) -> i64 { 75 if s.state != NX_TLS13_CSESSION_STATE_WAIT_APP_KEYS { 76 return NX_TLS13_DERIVE_APP_BAD_STATE 77 } 78 79 // R9 (2026-08-05): the application key schedule runs on the SUITE'S hash too. This was the 80 // last hardcoded SHA-256 in the client path -- missing it would have derived correct 81 // handshake keys and then WRONG application keys, i.e. a handshake that completes and then 82 // garbles the first byte of real data. Buffers 64 so the 48-byte SHA-384 secrets fit. 83 var hash_len: i64 = TLS13_HASH_LEN_SHA256 84 if s.cipher_suite == NX_TLS13_CS_AES_256_GCM_SHA384 { hash_len = 48 } 85 let zeros: *u8 = sys_mmap(64) 86 87 // ---- derived_2 = Derive-Secret(handshake_secret, "derived", H_empty) ---- 88 let empty_hash: *u8 = sys_mmap(64) 89 if hash_len == 48 { sha384_digest(zeros, 0, empty_hash) } else { sha256_digest(zeros, 0, empty_hash) } 90 let derived_2: *u8 = sys_mmap(64) // R9: 48-byte secret under SHA-384 91 tls13_derived(s.handshake_secret, empty_hash, hash_len, derived_2) 92 93 // ---- master_secret = HKDF-Extract(derived_2, 0^HashLen) ---- 94 tls13_master_secret(derived_2, hash_len, s.master_secret) 95 96 // ---- H2 = transcript snapshot covering CH..SF ---- 97 // RFC 8446 §7.1: application-traffic secrets are derived from 98 // Hash(ClientHello...server Finished) -- the prefix BEFORE the 99 // client Finished is appended. emit_finished (step 3c.4) 100 // captured this hash into s.h_post_sf RIGHT BEFORE appending CF. 101 // Previously this code snapshotted transcript HERE, which was 102 // post-CF and produced TAG_MISMATCH on every real-server 103 // app-data record (2026-05-20 root cause). 104 let h2: *u8 = s.h_post_sf 105 106 // ---- Derive c/s app traffic SECRETS ---- 107 tls13_traffic_secret(s.master_secret, NX_TLS13_LABEL_C_AP_TRAFFIC, 108 h2, hash_len, s.client_app_traffic_secret) 109 tls13_traffic_secret(s.master_secret, NX_TLS13_LABEL_S_AP_TRAFFIC, 110 h2, hash_len, s.server_app_traffic_secret) 111 112 // ---- Expand into key + IV (width tracks s.cipher_suite) ---- 113 var key_len: i64 = 32 114 if s.cipher_suite == 0x1301 { key_len = 16 } 115 tls13_traffic_key(s.client_app_traffic_secret, key_len, s.client_app_traffic_key) 116 tls13_traffic_key(s.server_app_traffic_secret, key_len, s.server_app_traffic_key) 117 tls13_traffic_iv(s.client_app_traffic_secret, 12, s.client_app_iv) 118 tls13_traffic_iv(s.server_app_traffic_secret, 12, s.server_app_iv) 119 120 // ---- Reset per-direction app-record sequence numbers ---- 121 s.client_app_seq = 0 122 s.server_app_seq = 0 123 124 // ---- Advance state ---- 125 s.state = NX_TLS13_CSESSION_STATE_CONNECTED 126 return NX_TLS13_DERIVE_APP_OK 127} 128 129// Compile-only smoke. Real KAT in 130// nx_tls13_client_session_derive_app_test.nx. 131func main() -> i64 { 132 return 0 133}