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}