code wiki / (root) / hkdf.nx

hkdf.nx source

↩ module page · 126 lines · 4698 B

1// hkdf.nx -- HKDF-SHA-256 (RFC 5869). 2// 3// HMAC-based Extract-and-Expand Key Derivation Function. Central 4// to TLS 1.3 (RFC 8446 §7.1) key schedule: 5// 6// Early Secret = HKDF-Extract(0, PSK) 7// Handshake Secret = HKDF-Extract(Derive-Secret(Early, ""), ECDHE) 8// Master Secret = HKDF-Extract(Derive-Secret(Handshake, ""), 0) 9// <traffic secrets> = Derive-Secret(<stage>, "<label>", transcript) 10// 11// Two-stage design: 12// Extract(salt, IKM) -> PRK 13// Treats IKM as an entropy source; produces a uniformly-random 14// pseudorandom key. Implemented as HMAC-SHA-256(salt, IKM). 15// Expand(PRK, info, L) -> OKM 16// Deterministically derives L bytes of keying material from PRK 17// and a context string `info`. Iterates HMAC over a counter. 18// 19// Properties (RFC 5869 §3.3): 20// - Deterministic: same (salt, IKM, info, L) -> same OKM. 21// - Context-binding: different `info` -> independent OKMs. 22// - Output limited to 255 * HashLen = 8160 bytes for SHA-256. 23// 24// Invariants: 25// HK1 PRK is always exactly HashLen (32 bytes for SHA-256). 26// HK2 Expand iterates exactly ceil(L / HashLen) times; each 27// iteration feeds the previous output back in, creating a 28// chain that prevents block-substitution attacks. 29// HK3 Counter bytes are 1-indexed; wraps at 255 per RFC 5869 30// §2.3. We cap L at 8160 and refuse larger requests. 31// HK4 No branches on salt/IKM/info VALUES; branches only on 32// LENGTHS. 33 34import "syscalls.nx" 35import "nx_hmac.nx" // was hmac.nx -- CODE-IDENTICAL twin (49/49 stmts) on the LEGACY syscalls.nx+sha256.nx family. 36// Two files defining hmac_sha256 + main, with the expander deduping BY PATH NOT BY SYMBOL, made 37// every legacy importer a duplicate-symbol landmine for the nx_ family (debt 1785524913). 38 39const HKDF_HASH: i64 = 32 // SHA-256 output 40const HKDF_MAX_L: i64 = 8160 // 255 * 32 41 42// HKDF-Extract. Writes 32-byte PRK to `prk`. `salt` may be NULL 43// (caller passes zero pointer + salt_len=0) in which case RFC 5869 44// mandates a zero-filled HashLen salt. 45func hkdf_extract(salt: *u8, salt_len: i64, 46 ikm: *u8, ikm_len: i64, 47 prk: *u8) -> i64 { 48 if salt_len == 0 { 49 let zero_salt: *u8 = sys_mmap(HKDF_HASH) 50 var i: i64 = 0 51 while i < HKDF_HASH { zero_salt[i] = 0; i = i + 1 } 52 hmac_sha256(zero_salt, HKDF_HASH, ikm, ikm_len, prk) 53 } else { 54 hmac_sha256(salt, salt_len, ikm, ikm_len, prk) 55 } 56 return 0 57} 58 59// HKDF-Expand. Writes `L` bytes of keying material to `out`. 60// Returns 0 on success, -1 if L exceeds the HKDF ceiling. 61func hkdf_expand(prk: *u8, 62 info: *u8, info_len: i64, 63 l: i64, out: *u8) -> i64 { 64 if l > HKDF_MAX_L { return -1 } 65 if l < 0 { return -1 } 66 67 // n = ceil(L / HashLen); each iteration appends one block. 68 // T(0) = empty; T(i) = HMAC(PRK, T(i-1) || info || i_byte). 69 let t_prev: *u8 = sys_mmap(HKDF_HASH) 70 let t_curr: *u8 = sys_mmap(HKDF_HASH) 71 let buf_cap: i64 = HKDF_HASH + info_len + 1 72 let buf: *u8 = sys_mmap(buf_cap) 73 74 var prev_len: i64 = 0 75 var produced: i64 = 0 76 var counter: i64 = 1 77 while produced < l { 78 // Build input: T(i-1) || info || counter 79 var bi: i64 = 0 80 var k: i64 = 0 81 while k < prev_len { buf[bi + k] = t_prev[k]; k = k + 1 } 82 bi = bi + prev_len 83 k = 0 84 while k < info_len { buf[bi + k] = info[k]; k = k + 1 } 85 bi = bi + info_len 86 buf[bi] = counter & 0xFF 87 bi = bi + 1 88 89 hmac_sha256(prk, HKDF_HASH, buf, bi, t_curr) 90 91 // Copy up to HashLen bytes of T(i) into output. 92 let remain: i64 = l - produced 93 var take: i64 = HKDF_HASH 94 if remain < HKDF_HASH { take = remain } 95 k = 0 96 while k < take { out[produced + k] = t_curr[k]; k = k + 1 } 97 produced = produced + take 98 99 // T(i) becomes T(i-1) for next round. 100 k = 0 101 while k < HKDF_HASH { t_prev[k] = t_curr[k]; k = k + 1 } 102 prev_len = HKDF_HASH 103 counter = counter + 1 104 } 105 return 0 106} 107 108// Compile-only smoke. Real KAT (RFC 5869 Appendix A.1 test case 1) 109// validation pending execution harness. 110func main() -> i64 { 111 let ikm: *u8 = sys_mmap(22) 112 let salt: *u8 = sys_mmap(13) 113 let info: *u8 = sys_mmap(10) 114 let prk: *u8 = sys_mmap(32) 115 let okm: *u8 = sys_mmap(42) 116 var i: i64 = 0 117 while i < 22 { ikm[i] = 0x0B; i = i + 1 } 118 i = 0 119 while i < 13 { salt[i] = i as i64; i = i + 1 } 120 i = 0 121 while i < 10 { info[i] = 0xF0 + (i as i64); i = i + 1 } 122 123 hkdf_extract(salt, 13, ikm, 22, prk) 124 hkdf_expand(prk, info, 10, 42, okm) 125 return okm[0] as i64 126}