code wiki / (root) / nx_tls13_schedule.nx

nx_tls13_schedule.nx source

↩ module page · 236 lines · 9908 B

1// nx_tls13_schedule.nx -- TLS 1.3 key schedule cascade (RFC 8446 §7.1). 2// 3// Phase 0b §D of the Nishi TLS 1.3 stack per 4// docs/NISHI_TLS13_GAP_AUDIT.md. Composes the just-landed 5// nx_hkdf + nx_tls13_kdf into the named 5-stage cascade that 6// produces every secret a TLS 1.3 session needs. 7// 8// The cascade (no-PSK case, which is the 99%-common one): 9// 10// 0 11// | 12// v 13// HKDF-Extract(salt=0^Hash, IKM=0^Hash) = early_secret 14// | 15// v 16// Derive-Secret(., "derived", "") = derived_1 (salt for next HKDF-Extract) 17// | 18// v ECDHE shared 19// HKDF-Extract(salt=derived_1, IKM=ECDHE) = handshake_secret 20// | 21// +--> Derive-Secret(., "c hs traffic", H1) = client_hs_traffic_secret 22// +--> Derive-Secret(., "s hs traffic", H1) = server_hs_traffic_secret 23// v 24// Derive-Secret(., "derived", "") = derived_2 (salt for next HKDF-Extract) 25// | 26// v 27// HKDF-Extract(salt=derived_2, IKM=0^Hash) = master_secret 28// | 29// +--> Derive-Secret(., "c ap traffic", H2) = client_app_traffic_secret_0 30// +--> Derive-Secret(., "s ap traffic", H2) = server_app_traffic_secret_0 31// +--> Derive-Secret(., "exp master", H2) = exporter_master_secret 32// +--> Derive-Secret(., "res master", H3) = resumption_master_secret 33// 34// Then per-traffic-secret AEAD key + IV derivation: 35// 36// key = HKDF-Expand-Label(traffic_secret, "key", "", key_length) 37// iv = HKDF-Expand-Label(traffic_secret, "iv", "", iv_length) 38// finished_key = HKDF-Expand-Label(traffic_secret, "finished", "", Hash.length) 39// 40// All transcript hashes (H1, H2, H3) are caller-supplied because 41// they depend on which handshake messages have been seen so far -- 42// Gap E (transcript hash) ships separately. 43// 44// What it does today: 45// - early_secret, derived_1, handshake_secret, derived_2, master_secret 46// -- the cascade BACKBONE (the secrets that flow from one 47// HKDF-Extract to the next; no transcript hash needed for these) 48// - tls13_traffic_secret -- convenience for the 6 transcript- 49// hash-dependent secrets (c/s hs/app traffic + exp/res master) 50// - tls13_traffic_key + tls13_traffic_iv + tls13_finished_key 51// 52// What it doesn't do yet: 53// - SHA-384 variant (composes against nx_hkdf_sha384; queued) 54// - PSK path early_secret = HKDF-Extract(0, PSK) where PSK != 0 55// (function signature already takes PSK so trivial; just need 56// the caller to wire it from session resumption -- Gap M) 57// - 0-RTT early secrets (queued with Gap M) 58// 59// KAT verified: 60// - RFC 8448 §3 handshake_secret w/ ECDHE = 8bd4054f...92d 61// -> 1dc826e9...eac 62// - RFC 8448 §3 master_secret -> 18df0684...919 63// 64// Composes with: 65// - nx_hkdf (extract) 66// - nx_tls13_kdf (Derive-Secret + HKDF-Expand-Label) 67// - nx_tls13_transcript (queued Gap E; produces H1/H2/H3) 68// 69// license_tier: INDEPENDENT_REDERIVE 70// genealogy_id: international-research-sources/ietf/rfc_8446 71// lineage_id: nishi_tls13_schedule_q10 72 73// nx_safety_envelope: 74// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 75// sil_target: SIL1 76// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 77// verdict: NOT_YET_EVALUATED 78 79import "nx_syscalls.nx" 80import "nx_hkdf.nx" 81import "nx_tls13_kdf.nx" 82 83const TLS13_HASH_LEN_SHA256: i64 = 32 84 85// Sealed traffic-secret kind enum -- which label gets fed to 86// Derive-Secret for the four traffic + two master-secret-derived 87// secrets in the cascade. 88const NX_TLS13_LABEL_C_HS_TRAFFIC: i64 = 1 89const NX_TLS13_LABEL_S_HS_TRAFFIC: i64 = 2 90const NX_TLS13_LABEL_C_AP_TRAFFIC: i64 = 3 91const NX_TLS13_LABEL_S_AP_TRAFFIC: i64 = 4 92const NX_TLS13_LABEL_EXP_MASTER: i64 = 5 93const NX_TLS13_LABEL_RES_MASTER: i64 = 6 94 95// ---- Stage 1: early_secret = HKDF-Extract(0^HashLen, PSK) ---- 96// 97// For the no-PSK case, caller passes psk = pointer to zeros, psk_len 98// = HashLen. Writes 32-byte output. 99func tls13_early_secret(psk: *u8, psk_len: i64, out_32: *u8) -> i64 { 100 // RFC 8446 §7.1: salt is empty (HKDF will auto-fill with HashLen zeros). 101 return hkdf_extract(psk, 0, psk, psk_len, out_32) 102} 103 104// ---- Stage 2: derived = Derive-Secret(parent, "derived", "") ---- 105// 106// The cascade "salt for next HKDF-Extract" step. Uses the 107// empty-message SHA-256 hash (constant e3b0c442...) as the 108// Transcript-Hash for the empty-context case. 109// 110// empty_hash MUST be the 32-byte SHA-256 of the empty string; 111// caller's nx_sha256 produces this. We don't hardcode here so the 112// schedule remains hash-agnostic (SHA-384 variant just passes the 113// SHA-384 empty hash and length 48). 114func tls13_derived(parent_secret: *u8, empty_hash: *u8, hash_len: i64, out: *u8) -> i64 { 115 // "derived" = 7 bytes: 64 65 72 69 76 65 64 116 let label: *u8 = sys_mmap(8) 117 label[0]=0x64; label[1]=0x65; label[2]=0x72; label[3]=0x69 118 label[4]=0x76; label[5]=0x65; label[6]=0x64 119 return tls13_derive_secret(parent_secret, label, 7, empty_hash, hash_len, out) 120} 121 122// ---- Stage 3: handshake_secret = HKDF-Extract(derived_1, ECDHE_shared) ---- 123func tls13_handshake_secret( 124 derived_1: *u8, hash_len: i64, 125 ecdhe_shared: *u8, ecdhe_len: i64, 126 out_32: *u8 127) -> i64 { 128 return hkdf_extract(derived_1, hash_len, ecdhe_shared, ecdhe_len, out_32) 129} 130 131// ---- Stage 4: master_secret = HKDF-Extract(derived_2, 0^HashLen) ---- 132// 133// IKM is hash_len bytes of zero (sovereign caller may pass a fresh 134// zero buffer or reuse the early-secret psk buffer). 135func tls13_master_secret( 136 derived_2: *u8, hash_len: i64, 137 out_32: *u8 138) -> i64 { 139 let zeros: *u8 = sys_mmap(64) 140 return hkdf_extract(derived_2, hash_len, zeros, hash_len, out_32) 141} 142 143// ---- Stage 5: per-purpose traffic secrets via Derive-Secret. ---- 144// 145// label_kind picks one of the 6 NX_TLS13_LABEL_* constants. 146// transcript_hash is the SHA-256 (or SHA-384) of the relevant 147// handshake-messages prefix; caller computes it. 148func tls13_traffic_secret( 149 parent_secret: *u8, 150 label_kind: i64, 151 transcript_hash: *u8, hash_len: i64, 152 out: *u8 153) -> i64 { 154 let lbl: *u8 = sys_mmap(16) 155 var lbl_len: i64 = 0 156 if label_kind == NX_TLS13_LABEL_C_HS_TRAFFIC { 157 // "c hs traffic" = 12 bytes 158 lbl[0]=0x63; lbl[1]=0x20; lbl[2]=0x68; lbl[3]=0x73 159 lbl[4]=0x20; lbl[5]=0x74; lbl[6]=0x72; lbl[7]=0x61 160 lbl[8]=0x66; lbl[9]=0x66; lbl[10]=0x69; lbl[11]=0x63 161 lbl_len = 12 162 } else { 163 if label_kind == NX_TLS13_LABEL_S_HS_TRAFFIC { 164 // "s hs traffic" 165 lbl[0]=0x73; lbl[1]=0x20; lbl[2]=0x68; lbl[3]=0x73 166 lbl[4]=0x20; lbl[5]=0x74; lbl[6]=0x72; lbl[7]=0x61 167 lbl[8]=0x66; lbl[9]=0x66; lbl[10]=0x69; lbl[11]=0x63 168 lbl_len = 12 169 } else { 170 if label_kind == NX_TLS13_LABEL_C_AP_TRAFFIC { 171 // "c ap traffic" 172 lbl[0]=0x63; lbl[1]=0x20; lbl[2]=0x61; lbl[3]=0x70 173 lbl[4]=0x20; lbl[5]=0x74; lbl[6]=0x72; lbl[7]=0x61 174 lbl[8]=0x66; lbl[9]=0x66; lbl[10]=0x69; lbl[11]=0x63 175 lbl_len = 12 176 } else { 177 if label_kind == NX_TLS13_LABEL_S_AP_TRAFFIC { 178 // "s ap traffic" 179 lbl[0]=0x73; lbl[1]=0x20; lbl[2]=0x61; lbl[3]=0x70 180 lbl[4]=0x20; lbl[5]=0x74; lbl[6]=0x72; lbl[7]=0x61 181 lbl[8]=0x66; lbl[9]=0x66; lbl[10]=0x69; lbl[11]=0x63 182 lbl_len = 12 183 } else { 184 if label_kind == NX_TLS13_LABEL_EXP_MASTER { 185 // "exp master" = 10 bytes 186 lbl[0]=0x65; lbl[1]=0x78; lbl[2]=0x70; lbl[3]=0x20 187 lbl[4]=0x6d; lbl[5]=0x61; lbl[6]=0x73; lbl[7]=0x74 188 lbl[8]=0x65; lbl[9]=0x72 189 lbl_len = 10 190 } else { 191 if label_kind == NX_TLS13_LABEL_RES_MASTER { 192 // "res master" 193 lbl[0]=0x72; lbl[1]=0x65; lbl[2]=0x73; lbl[3]=0x20 194 lbl[4]=0x6d; lbl[5]=0x61; lbl[6]=0x73; lbl[7]=0x74 195 lbl[8]=0x65; lbl[9]=0x72 196 lbl_len = 10 197 } else { 198 return 0 - 1 // unknown label_kind 199 } 200 } 201 } 202 } 203 } 204 } 205 return tls13_derive_secret(parent_secret, lbl, lbl_len, transcript_hash, hash_len, out) 206} 207 208// ---- Per-traffic-secret AEAD key derivation. ---- 209// 210// key = HKDF-Expand-Label(traffic_secret, "key", "", key_length). 211// For ChaCha20-Poly1305: key_length = 32. For AES-128-GCM: 16. 212func tls13_traffic_key(traffic_secret: *u8, key_len: i64, out: *u8) -> i64 { 213 let lbl: *u8 = sys_mmap(8) 214 lbl[0]=0x6b; lbl[1]=0x65; lbl[2]=0x79 // "key" 215 let empty: *u8 = sys_mmap(8) 216 return tls13_hkdf_expand_label(traffic_secret, lbl, 3, empty, 0, key_len, out) 217} 218 219// iv = HKDF-Expand-Label(traffic_secret, "iv", "", iv_length). 220// For ChaCha20-Poly1305 and AES-GCM: iv_length = 12. 221func tls13_traffic_iv(traffic_secret: *u8, iv_len: i64, out: *u8) -> i64 { 222 let lbl: *u8 = sys_mmap(8) 223 lbl[0]=0x69; lbl[1]=0x76 // "iv" 224 let empty: *u8 = sys_mmap(8) 225 return tls13_hkdf_expand_label(traffic_secret, lbl, 2, empty, 0, iv_len, out) 226} 227 228// finished_key = HKDF-Expand-Label(base_key, "finished", "", Hash.length). 229// Used to compute the Finished MAC as HMAC(finished_key, transcript_hash). 230func tls13_finished_key(base_key: *u8, hash_len: i64, out: *u8) -> i64 { 231 let lbl: *u8 = sys_mmap(16) 232 lbl[0]=0x66; lbl[1]=0x69; lbl[2]=0x6e; lbl[3]=0x69 // "fini" 233 lbl[4]=0x73; lbl[5]=0x68; lbl[6]=0x65; lbl[7]=0x64 // "shed" 234 let empty: *u8 = sys_mmap(8) 235 return tls13_hkdf_expand_label(base_key, lbl, 8, empty, 0, hash_len, out) 236}