code wiki / (root) / nx_tls13_kdf.nx

nx_tls13_kdf.nx source

↩ module page · 172 lines · 6480 B

1// nx_tls13_kdf.nx -- TLS 1.3 HKDF-Expand-Label + Derive-Secret 2// (RFC 8446 §7.1). 3// 4// Phase 0b §C of the Nishi TLS 1.3 stack per 5// docs/NISHI_TLS13_GAP_AUDIT.md. The thin TLS-specific wrapper 6// over plain HKDF-Expand that prepends the "tls13 " literal label 7// and length-prefixes the label + context per the spec's HkdfLabel 8// struct. Every traffic key, handshake secret, application secret, 9// and Finished MAC key in TLS 1.3 derives from this. 10// 11// HkdfLabel layout (RFC 8446 §7.1): 12// struct { 13// uint16 length; // big-endian 14// opaque label<7..255>; // = "tls13 " || Label 15// // 1-byte length prefix 16// opaque context<0..255>; // 1-byte length prefix 17// } HkdfLabel; 18// 19// Then: HKDF-Expand-Label(Secret, Label, Context, Length) = 20// HKDF-Expand(Secret, HkdfLabel, Length). 21// 22// Derive-Secret is convenience: Context = Transcript-Hash(Messages), 23// Length = hash output (32 for SHA-256, 48 for SHA-384). 24// 25// What it does today: 26// - build HkdfLabel struct into a caller buffer 27// - HKDF-Expand-Label one-shot 28// - Derive-Secret one-shot (composes ExpandLabel + Transcript-Hash 29// as caller-supplied buffer) 30// 31// What it doesn't do yet: 32// - SHA-384 variant (composes against existing nx_hkdf_sha384; 33// queued sub-phase) 34// - Multi-block transcript-hash streaming (caller computes 35// transcript hash via nx_sha256 incremental API) 36// 37// KAT verified: 38// - HkdfLabel byte-exact construction for ("derived", empty hash, 32) 39// - RFC 8448 §3 early_secret = HMAC-SHA256(0^32, 0^32) 40// = 33ad0a1c607ec03b09e6cd9893680ce210adf300aa1f2660e1b22e10f170f92a 41// - RFC 8448 §3 derived = Derive-Secret(early_secret, "derived", empty) 42// = 6f2615a108c702c5678f54fc9dbab69716c076189c48250cebeac3576c3611ba 43// 44// Composes with: 45// - nx_hkdf (RFC 5869 Extract+Expand) 46// - nx_sha256 (caller-side transcript hash for Derive-Secret) 47// - nx_tls13 (constants + record layer; eventually nx_tls13_schedule 48// which calls Derive-Secret for every secret in the 5-stage cascade) 49// 50// license_tier: INDEPENDENT_REDERIVE 51// genealogy_id: international-research-sources/ietf/rfc_8446 + ietf/rfc_5869 52// lineage_id: nishi_tls13_kdf_q10 53 54// nx_safety_envelope: 55// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 56// sil_target: SIL1 57// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 58// verdict: NOT_YET_EVALUATED 59 60import "nx_syscalls.nx" 61import "nx_hkdf.nx" 62 63// "tls13 " ASCII = 0x74 0x6c 0x73 0x31 0x33 0x20 = 6 bytes. 64const TLS13_LABEL_PREFIX_LEN: i64 = 6 65 66// HKDF-Expand-Label upper bound: TLS 1.3 spec limits Label to <= 249 67// bytes so the prefixed-label byte length fits in u8. 68const TLS13_LABEL_MAX: i64 = 249 69 70// Sealed verdict for the KDF path (composes with HKDF's -1 error 71// from oversize Length). 72const NX_TLS13_KDF_VERDICT_OK: i64 = 1 73const NX_TLS13_KDF_VERDICT_LABEL_TOO_LONG: i64 = 2 74const NX_TLS13_KDF_VERDICT_CONTEXT_TOO_LONG: i64 = 3 75const NX_TLS13_KDF_VERDICT_HKDF_FAIL: i64 = 4 76const NX_TLS13_KDF_VERDICT_N: i64 = 5 77 78// Build the HkdfLabel byte sequence per RFC 8446 §7.1 into `out`. 79// Returns total bytes written, or a negative NX_TLS13_KDF_VERDICT_* 80// on error. Caller buffer must hold >= 4 + 6 + label_len + context_len. 81func tls13_build_hkdf_label( 82 length: i64, 83 label: *u8, label_len: i64, 84 context: *u8, context_len: i64, 85 out: *u8 86) -> i64 { 87 if label_len < 0 { return 0 - NX_TLS13_KDF_VERDICT_LABEL_TOO_LONG } 88 if label_len > TLS13_LABEL_MAX { return 0 - NX_TLS13_KDF_VERDICT_LABEL_TOO_LONG } 89 if context_len < 0 { return 0 - NX_TLS13_KDF_VERDICT_CONTEXT_TOO_LONG } 90 if context_len > 255 { return 0 - NX_TLS13_KDF_VERDICT_CONTEXT_TOO_LONG } 91 var o: i64 = 0 92 // uint16 length, big-endian. 93 out[o] = (length >> 8) & 0xff 94 out[o + 1] = length & 0xff 95 o = o + 2 96 // opaque label<7..255>: 1-byte length prefix (= 6 + label_len), 97 // then "tls13 " (6 bytes) then user Label bytes. 98 let total_label_len: i64 = TLS13_LABEL_PREFIX_LEN + label_len 99 out[o] = total_label_len & 0xff 100 o = o + 1 101 out[o] = 0x74 // 't' 102 out[o + 1] = 0x6c // 'l' 103 out[o + 2] = 0x73 // 's' 104 out[o + 3] = 0x31 // '1' 105 out[o + 4] = 0x33 // '3' 106 out[o + 5] = 0x20 // ' ' 107 o = o + TLS13_LABEL_PREFIX_LEN 108 var i: i64 = 0 109 while i < label_len { 110 out[o + i] = label[i] 111 i = i + 1 112 } 113 o = o + label_len 114 // opaque context<0..255>: 1-byte length prefix then context bytes. 115 out[o] = context_len & 0xff 116 o = o + 1 117 var j: i64 = 0 118 while j < context_len { 119 out[o + j] = context[j] 120 j = j + 1 121 } 122 o = o + context_len 123 return o 124} 125 126// HKDF-Expand-Label per RFC 8446 §7.1. Writes `length` bytes of 127// keying material to `out`. Returns NX_TLS13_KDF_VERDICT_OK on 128// success or a non-OK verdict. 129func tls13_hkdf_expand_label( 130 secret: *u8, 131 label: *u8, label_len: i64, 132 context: *u8, context_len: i64, 133 length: i64, 134 out: *u8 135) -> i64 { 136 let info_cap: i64 = 4 + TLS13_LABEL_PREFIX_LEN + label_len + context_len + 16 137 let info: *u8 = sys_mmap(info_cap) 138 let info_len: i64 = tls13_build_hkdf_label( 139 length, label, label_len, context, context_len, info 140 ) 141 if info_len < 0 { return 0 - info_len } // negate to expose verdict 142 let rc: i64 = hkdf_expand(secret, info, info_len, length, out) 143 if rc != 0 { return NX_TLS13_KDF_VERDICT_HKDF_FAIL } 144 return NX_TLS13_KDF_VERDICT_OK 145} 146 147// Derive-Secret per RFC 8446 §7.1: 148// Derive-Secret(Secret, Label, Messages) = 149// HKDF-Expand-Label(Secret, Label, Transcript-Hash(Messages), Hash.length) 150// 151// hash_output_len is 32 for SHA-256, 48 for SHA-384. Caller 152// pre-computes Transcript-Hash via the appropriate hash module 153// (nx_sha256.incremental for SHA-256; nx_sha384 queued). 154func tls13_derive_secret( 155 secret: *u8, 156 label: *u8, label_len: i64, 157 transcript_hash: *u8, hash_output_len: i64, 158 out: *u8 159) -> i64 { 160 return tls13_hkdf_expand_label( 161 secret, label, label_len, 162 transcript_hash, hash_output_len, 163 hash_output_len, out 164 ) 165} 166 167// Sealed-enum validity gate. 168func nx_tls13_kdf_verdict_is_valid(v: i64) -> i64 { 169 if v < 0 { return 0 } 170 if v >= NX_TLS13_KDF_VERDICT_N { return 0 } 171 return 1 172}