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}