nx_drbg_hmac.nx source
↩ module page · 235 lines · 8232 B
1// nx_drbg_hmac.nx -- NIST SP 800-90A HMAC-DRBG with SHA-256.
2//
3// license_tier: INDEPENDENT_REDERIVE
4// genealogy_id: international-research-sources/nist/sp_800_90a
5//
6// Deterministic Random Bit Generator. Given a seed (entropy +
7// optional personalization), produces an arbitrary-length pseudorandom
8// stream that any party with the same seed can reproduce. Built on
9// HMAC-SHA-256 per SP 800-90A §10.1.2.
10//
11// Why this exists alongside nx_csprng:
12// - nx_csprng reads /dev/urandom (getrandom syscall). Online-only,
13// no determinism, useless on MCU/sensor targets without an OS
14// entropy source.
15// - nx_drbg_hmac takes a seed and produces a deterministic stream.
16// Replayable for testing; usable in air-gapped + embedded contexts;
17// foundation for NIST post-quantum signing (ML-DSA, SLH-DSA).
18//
19// State layout (72 bytes, caller-allocated):
20// bytes 0..31 : K (HMAC key, output_len = 32 bytes)
21// bytes 32..63 : V (current value, output_len = 32 bytes)
22// bytes 64..71 : reseed_counter (i64)
23//
24// SP 800-90A §10.1.2.2 Update operation:
25// K = HMAC(K, V || 0x00 || data)
26// V = HMAC(K, V)
27// if len(data) > 0:
28// K = HMAC(K, V || 0x01 || data)
29// V = HMAC(K, V)
30//
31// §10.1.2.3 Instantiate:
32// K = 0x00 * 32 ; V = 0x01 * 32
33// Update(seed_material)
34// reseed_counter = 1
35//
36// §10.1.2.5 Generate:
37// temp = empty
38// while len(temp) < requested_bytes:
39// V = HMAC(K, V)
40// temp ||= V
41// Update(empty) -- even with no additional input
42// reseed_counter += 1
43//
44// Invariants:
45// D1 State buffer is exactly 72 bytes. Caller allocates; this
46// module never frees -- sys_mmap pages live until process exit.
47// D2 Output length per call <= 65536 (2^19 / 8) per SP 800-90A.
48// Caller wanting more must loop with reseed in between.
49// D3 Reseed_counter increments on every Generate call; never
50// reaches the 2^48 SP 800-90A maximum in practice.
51// D4 HMAC-SHA-256 is constant-time on its inputs; DRBG state
52// transitions are similarly constant-time on V and K.
53//
54// nx_safety_envelope:
55// intended_use: "HMAC-based deterministic random bit generator
56// (NIST SP 800-90A §10.1.2) -- CSPRNG output
57// for key gen / nonce derivation / signature
58// scalars"
59// sil_target: SIL3 (RNG primitive; weak output =
60// broken keys = arbitrary system
61// compromise)
62// asil_target: QM
63// dal_target: DAL B
64// iec_62304_class: B
65// evidence: [NIST_SP_800-90A_canonical_basis, no_FP,
66// constant_time_V_K_transitions,
67// backtracking_resistance_via_K_reseed,
68// CAVS_test_vectors_target]
69// hazard_register: [bug-tape-DRBG-low-entropy-init,
70// bug-tape-reseed-counter-overflow,
71// bug-tape-state-not-cleared-after-use]
72// residual_risk: "Initial seed entropy is CALLER
73// RESPONSIBILITY. Substrate provides the
74// DRBG transform but cannot enforce that
75// the seed has ≥256 bits of entropy."
76// verdict: NOT_YET_EVALUATED
77
78import "nx_syscalls.nx"
79import "nx_sha256.nx"
80import "nx_hmac.nx"
81
82const DRBG_OUTLEN: i64 = 32 // HMAC-SHA-256 output bytes
83const DRBG_STATE_LEN: i64 = 72 // K(32) + V(32) + reseed_counter(8)
84const DRBG_K_OFF: i64 = 0
85const DRBG_V_OFF: i64 = 32
86const DRBG_RC_OFF: i64 = 64
87const DRBG_MAX_BYTES: i64 = 65536 // per SP 800-90A §10.1 table
88
89// ---- internal helpers ----------------------------------------------
90
91func _drbg_k(state: *u8) -> *u8 {
92 return (state as i64 + DRBG_K_OFF) as *u8
93}
94
95func _drbg_v(state: *u8) -> *u8 {
96 return (state as i64 + DRBG_V_OFF) as *u8
97}
98
99// Copy n bytes from src to dst (no overlap assumed; DRBG callers
100// always allocate fresh buffers).
101func _drbg_copy(dst: *u8, src: *u8, n: i64) -> i64 {
102 var i: i64 = 0
103 while i < n { dst[i] = src[i]; i = i + 1 }
104 return 0
105}
106
107func _drbg_fill(dst: *u8, val: i64, n: i64) -> i64 {
108 var i: i64 = 0
109 while i < n { dst[i] = val; i = i + 1 }
110 return 0
111}
112
113// ---- Update (SP 800-90A §10.1.2.2) ---------------------------------
114//
115// K = HMAC(K, V || sep || data)
116// V = HMAC(K, V)
117// if data_len > 0:
118// K = HMAC(K, V || 0x01 || data)
119// V = HMAC(K, V)
120//
121// We build the HMAC message in a scratch buffer of length
122// 32 + 1 + data_len.
123
124func drbg_hmac_update(state: *u8, data: *u8, data_len: i64) -> i64 {
125 let k: *u8 = _drbg_k(state)
126 let v: *u8 = _drbg_v(state)
127
128 // First pass: separator byte 0x00.
129 let msg_len: i64 = DRBG_OUTLEN + 1 + data_len
130 let msg: *u8 = sys_mmap(msg_len + 32)
131 _drbg_copy(msg, v, DRBG_OUTLEN)
132 msg[DRBG_OUTLEN] = 0x00
133 if data_len > 0 {
134 let dst: *u8 = (msg as i64 + DRBG_OUTLEN + 1) as *u8
135 _drbg_copy(dst, data, data_len)
136 }
137 let new_k: *u8 = sys_mmap(DRBG_OUTLEN + 16)
138 hmac_sha256(k, DRBG_OUTLEN, msg, msg_len, new_k)
139 _drbg_copy(k, new_k, DRBG_OUTLEN)
140
141 // V = HMAC(K, V)
142 let new_v: *u8 = sys_mmap(DRBG_OUTLEN + 16)
143 hmac_sha256(k, DRBG_OUTLEN, v, DRBG_OUTLEN, new_v)
144 _drbg_copy(v, new_v, DRBG_OUTLEN)
145
146 if data_len == 0 { return 0 }
147
148 // Second pass: separator byte 0x01.
149 let msg2_len: i64 = DRBG_OUTLEN + 1 + data_len
150 let msg2: *u8 = sys_mmap(msg2_len + 32)
151 _drbg_copy(msg2, v, DRBG_OUTLEN)
152 msg2[DRBG_OUTLEN] = 0x01
153 let dst2: *u8 = (msg2 as i64 + DRBG_OUTLEN + 1) as *u8
154 _drbg_copy(dst2, data, data_len)
155
156 let new_k2: *u8 = sys_mmap(DRBG_OUTLEN + 16)
157 hmac_sha256(k, DRBG_OUTLEN, msg2, msg2_len, new_k2)
158 _drbg_copy(k, new_k2, DRBG_OUTLEN)
159
160 let new_v2: *u8 = sys_mmap(DRBG_OUTLEN + 16)
161 hmac_sha256(k, DRBG_OUTLEN, v, DRBG_OUTLEN, new_v2)
162 _drbg_copy(v, new_v2, DRBG_OUTLEN)
163 return 0
164}
165
166// ---- Instantiate (SP 800-90A §10.1.2.3) ---------------------------
167//
168// K = 0x00 * outlen
169// V = 0x01 * outlen
170// Update(seed_material)
171// reseed_counter = 1
172//
173// `state` must point to >= 72 bytes of writable memory.
174
175func drbg_hmac_init(state: *u8, seed: *u8, seed_len: i64) -> i64 {
176 let k: *u8 = _drbg_k(state)
177 let v: *u8 = _drbg_v(state)
178 _drbg_fill(k, 0x00, DRBG_OUTLEN)
179 _drbg_fill(v, 0x01, DRBG_OUTLEN)
180 drbg_hmac_update(state, seed, seed_len)
181 let rc_p: *i64 = (state as i64 + DRBG_RC_OFF) as *i64
182 rc_p[0] = 1
183 return 0
184}
185
186// ---- Generate (SP 800-90A §10.1.2.5) -------------------------------
187//
188// temp = empty
189// while len(temp) < n_bytes:
190// V = HMAC(K, V)
191// temp ||= V
192// Update(additional_input) -- substrate variant: no additional input
193// reseed_counter += 1
194
195func drbg_hmac_generate(state: *u8, out: *u8, n_bytes: i64) -> i64 {
196 if n_bytes <= 0 { return 0 }
197 if n_bytes > DRBG_MAX_BYTES { return -1 }
198 let k: *u8 = _drbg_k(state)
199 let v: *u8 = _drbg_v(state)
200 let new_v: *u8 = sys_mmap(DRBG_OUTLEN + 16)
201 var produced: i64 = 0
202 while produced < n_bytes {
203 hmac_sha256(k, DRBG_OUTLEN, v, DRBG_OUTLEN, new_v)
204 _drbg_copy(v, new_v, DRBG_OUTLEN)
205 var i: i64 = 0
206 let remaining: i64 = n_bytes - produced
207 let take: i64 = DRBG_OUTLEN
208 if remaining < take {
209 let take2: i64 = remaining
210 while i < take2 {
211 let dst_ptr: *u8 = (out as i64 + produced + i) as *u8
212 dst_ptr[0] = v[i]
213 i = i + 1
214 }
215 produced = produced + take2
216 } else {
217 while i < take {
218 let dst_ptr: *u8 = (out as i64 + produced + i) as *u8
219 dst_ptr[0] = v[i]
220 i = i + 1
221 }
222 produced = produced + take
223 }
224 }
225 // SP 800-90A: even with no additional input, finalize state.
226 drbg_hmac_update(state, 0 as *u8, 0)
227 let rc_p: *i64 = (state as i64 + DRBG_RC_OFF) as *i64
228 rc_p[0] = rc_p[0] + 1
229 return n_bytes
230}
231
232func drbg_hmac_reseed_counter(state: *u8) -> i64 {
233 let rc_p: *i64 = (state as i64 + DRBG_RC_OFF) as *i64
234 return rc_p[0]
235}