code wiki / (root) / nx_hkdf_sha1.nx

nx_hkdf_sha1.nx source

↩ module page · 155 lines · 4839 B

1// hkdf_sha1.nx -- HMAC-based Key Derivation Function with SHA-1. 2// 3// RFC 5869 HKDF, SHA-1 variant. Less common today than the 4// SHA-256 variant (hkdf.nx) but still on the wire in: 5// - Signal Protocol interop with older clients 6// - Some TLS 1.2 PRF variants 7// - Legacy WPA3 Dragonfly (SAE) key schedule 8// 9// HKDF in two phases: 10// PRK = HMAC(salt, IKM) (extract) 11// T(0) = \"\" 12// T(i) = HMAC(PRK, T(i-1) || info || i) (expand) 13// OKM = T(1) || T(2) || ... truncated to L 14// 15// The expand phase counter is a single byte (range 1..255), so 16// max output is 255 * 20 = 5100 bytes. For larger keys (rare) 17// composers must re-run with a different info string. 18// 19// Composes hmac_sha1.nx. 20// 21// Invariants: 22// H1 If salt is empty, RFC 5869 says use a zero-filled HashLen 23// (20 zeros). We implement that default. 24// H2 Output length capped at 255 * 20 = 5100 bytes; beyond 25// that we truncate silently. Callers requesting more 26// should raise it in info/DOM-specific mode. 27// 28// license_tier: INDEPENDENT_REDERIVE 29// genealogy_id: international-research-sources/ietf/rfc_5869 30// 31 32// nx_safety_envelope: 33// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 34// sil_target: SIL1 35// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 36// verdict: NOT_YET_EVALUATED 37 38import "nx_syscalls.nx" 39import "nx_hmac_sha1.nx" 40 41const HK_HLEN: i64 = 20 42const HK_MAX_OKM: i64 = 5100 // 255 * 20 43 44// Extract phase: PRK = HMAC(salt, IKM). salt_len == 0 substitutes 45// a zero-filled 20-byte salt per RFC 5869 ยง2.2. 46func hkdf_sha1_extract(salt: *u8, salt_len: i64, 47 ikm: *u8, ikm_len: i64, 48 prk_out: *u8) -> i64 { 49 if salt_len == 0 { 50 let zero_salt: *u8 = sys_mmap(HK_HLEN + 16) 51 var i: i64 = 0 52 while i < HK_HLEN { zero_salt[i] = 0; i = i + 1 } 53 hmac_sha1(zero_salt, HK_HLEN, ikm, ikm_len, prk_out) 54 } else { 55 hmac_sha1(salt, salt_len, ikm, ikm_len, prk_out) 56 } 57 return 0 58} 59 60// Expand phase. prk must be HK_HLEN bytes. Writes `okm_len` 61// bytes into out. 62func hkdf_sha1_expand(prk: *u8, 63 info: *u8, info_len: i64, 64 okm: *u8, okm_len: i64) -> i64 { 65 var produced: i64 = 0 66 var i: i64 = 1 67 let t_prev: *u8 = sys_mmap(32) 68 var t_prev_len: i64 = 0 69 let t_cur: *u8 = sys_mmap(32) 70 let msg_buf: *u8 = sys_mmap(HK_HLEN + info_len + 32) 71 72 while produced < okm_len { 73 if produced >= HK_MAX_OKM { return produced } 74 if i > 255 { return produced } 75 76 // Build HMAC input: T(i-1) || info || i. 77 var pos: i64 = 0 78 var k: i64 = 0 79 while k < t_prev_len { 80 msg_buf[pos] = t_prev[k] 81 pos = pos + 1 82 k = k + 1 83 } 84 k = 0 85 while k < info_len { 86 msg_buf[pos] = info[k] 87 pos = pos + 1 88 k = k + 1 89 } 90 msg_buf[pos] = i 91 pos = pos + 1 92 93 hmac_sha1(prk, HK_HLEN, msg_buf, pos, t_cur) 94 95 // Copy as much of t_cur as we need into okm. 96 let take_max: i64 = okm_len - produced 97 var take: i64 = HK_HLEN 98 if take_max < take { take = take_max } 99 k = 0 100 while k < take { 101 okm[produced + k] = t_cur[k] 102 k = k + 1 103 } 104 produced = produced + take 105 106 // Rotate t_cur -> t_prev for next iteration. 107 k = 0 108 while k < HK_HLEN { 109 t_prev[k] = t_cur[k] 110 k = k + 1 111 } 112 t_prev_len = HK_HLEN 113 i = i + 1 114 } 115 return produced 116} 117 118// Convenience: Extract + Expand in one call. 119func hkdf_sha1(salt: *u8, salt_len: i64, 120 ikm: *u8, ikm_len: i64, 121 info: *u8, info_len: i64, 122 okm: *u8, okm_len: i64) -> i64 { 123 let prk: *u8 = sys_mmap(32) 124 hkdf_sha1_extract(salt, salt_len, ikm, ikm_len, prk) 125 return hkdf_sha1_expand(prk, info, info_len, okm, okm_len) 126} 127 128// Compile-only smoke -- RFC 5869 Test Vector 1: 129// IKM = 0x0b * 22, salt = 0x00..0x0c (13 bytes), 130// info = 0xf0..0xf9 (10 bytes), L = 42 131// OKM begins 3cb25f25faacd57a90434f64d0362f2a... 132func main() -> i64 { 133 let ikm: *u8 = sys_mmap(32) 134 var i: i64 = 0 135 while i < 22 { ikm[i] = 0x0B; i = i + 1 } 136 137 let salt: *u8 = sys_mmap(32) 138 i = 0 139 while i < 13 { salt[i] = i; i = i + 1 } 140 141 let info: *u8 = sys_mmap(32) 142 i = 0 143 while i < 10 { info[i] = 0xF0 + i; i = i + 1 } 144 145 let okm: *u8 = sys_mmap(64) 146 let n: i64 = hkdf_sha1(salt, 13, ikm, 22, info, 10, okm, 42) 147 if n != 42 { return 1 } 148 149 // Prefix 3c b2 5f 25 150 if okm[0] != 0x3C { return 2 } 151 if okm[1] != 0xB2 { return 3 } 152 if okm[2] != 0x5F { return 4 } 153 if okm[3] != 0x25 { return 5 } 154 return 0 155}