code wiki / (root) / hkdf_sha1.nx

hkdf_sha1.nx source

↩ module page · 145 lines · 4621 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 28import "syscalls.nx" 29import "hmac_sha1.nx" 30 31const HK_HLEN: i64 = 20 32const HK_MAX_OKM: i64 = 5100 // 255 * 20 33 34// Extract phase: PRK = HMAC(salt, IKM). salt_len == 0 substitutes 35// a zero-filled 20-byte salt per RFC 5869 ยง2.2. 36func hkdf_sha1_extract(salt: *u8, salt_len: i64, 37 ikm: *u8, ikm_len: i64, 38 prk_out: *u8) -> i64 { 39 if salt_len == 0 { 40 let zero_salt: *u8 = sys_mmap(HK_HLEN + 16) 41 var i: i64 = 0 42 while i < HK_HLEN { zero_salt[i] = 0; i = i + 1 } 43 hmac_sha1(zero_salt, HK_HLEN, ikm, ikm_len, prk_out) 44 } else { 45 hmac_sha1(salt, salt_len, ikm, ikm_len, prk_out) 46 } 47 return 0 48} 49 50// Expand phase. prk must be HK_HLEN bytes. Writes `okm_len` 51// bytes into out. 52func hkdf_sha1_expand(prk: *u8, 53 info: *u8, info_len: i64, 54 okm: *u8, okm_len: i64) -> i64 { 55 var produced: i64 = 0 56 var i: i64 = 1 57 let t_prev: *u8 = sys_mmap(32) 58 var t_prev_len: i64 = 0 59 let t_cur: *u8 = sys_mmap(32) 60 let msg_buf: *u8 = sys_mmap(HK_HLEN + info_len + 32) 61 62 while produced < okm_len { 63 if produced >= HK_MAX_OKM { return produced } 64 if i > 255 { return produced } 65 66 // Build HMAC input: T(i-1) || info || i. 67 var pos: i64 = 0 68 var k: i64 = 0 69 while k < t_prev_len { 70 msg_buf[pos] = t_prev[k] 71 pos = pos + 1 72 k = k + 1 73 } 74 k = 0 75 while k < info_len { 76 msg_buf[pos] = info[k] 77 pos = pos + 1 78 k = k + 1 79 } 80 msg_buf[pos] = i 81 pos = pos + 1 82 83 hmac_sha1(prk, HK_HLEN, msg_buf, pos, t_cur) 84 85 // Copy as much of t_cur as we need into okm. 86 let take_max: i64 = okm_len - produced 87 var take: i64 = HK_HLEN 88 if take_max < take { take = take_max } 89 k = 0 90 while k < take { 91 okm[produced + k] = t_cur[k] 92 k = k + 1 93 } 94 produced = produced + take 95 96 // Rotate t_cur -> t_prev for next iteration. 97 k = 0 98 while k < HK_HLEN { 99 t_prev[k] = t_cur[k] 100 k = k + 1 101 } 102 t_prev_len = HK_HLEN 103 i = i + 1 104 } 105 return produced 106} 107 108// Convenience: Extract + Expand in one call. 109func hkdf_sha1(salt: *u8, salt_len: i64, 110 ikm: *u8, ikm_len: i64, 111 info: *u8, info_len: i64, 112 okm: *u8, okm_len: i64) -> i64 { 113 let prk: *u8 = sys_mmap(32) 114 hkdf_sha1_extract(salt, salt_len, ikm, ikm_len, prk) 115 return hkdf_sha1_expand(prk, info, info_len, okm, okm_len) 116} 117 118// Compile-only smoke -- RFC 5869 Test Vector 1: 119// IKM = 0x0b * 22, salt = 0x00..0x0c (13 bytes), 120// info = 0xf0..0xf9 (10 bytes), L = 42 121// OKM begins 3cb25f25faacd57a90434f64d0362f2a... 122func main() -> i64 { 123 let ikm: *u8 = sys_mmap(32) 124 var i: i64 = 0 125 while i < 22 { ikm[i] = 0x0B; i = i + 1 } 126 127 let salt: *u8 = sys_mmap(32) 128 i = 0 129 while i < 13 { salt[i] = i; i = i + 1 } 130 131 let info: *u8 = sys_mmap(32) 132 i = 0 133 while i < 10 { info[i] = 0xF0 + i; i = i + 1 } 134 135 let okm: *u8 = sys_mmap(64) 136 let n: i64 = hkdf_sha1(salt, 13, ikm, 22, info, 10, okm, 42) 137 if n != 42 { return 1 } 138 139 // Prefix 3c b2 5f 25 140 if okm[0] != 0x3C { return 2 } 141 if okm[1] != 0xB2 { return 3 } 142 if okm[2] != 0x5F { return 4 } 143 if okm[3] != 0x25 { return 5 } 144 return 0 145}