code wiki / _hdl_build / nx_pbkdf2sha1_extvec_gate.nx
nx_pbkdf2sha1_extvec_gate.nx source
↩ module page · 330 lines · 14745 B
1// nx_pbkdf2sha1_extvec_gate.nx -- PBKDF2-HMAC-SHA1 vs RFC 6070, ALL SIX published vectors.
2//
3// A NEW AUTHORITY, ACQUIRED THROUGH THE REPAIRED PIPELINE. rfc6070.txt was fetched by nx_vecfetch v2:
4// HTTP status verified, 720 bytes of response envelope stripped, de-chunked, hashed as a DOCUMENT, and the
5// digest CORROBORATED against .NET WebClient before a byte was written (CORROBORATION.tsv). Under v1 this
6// file would have been a wire transcript with Cloudflare headers pinned as "the authority".
7//
8// ★WHY THIS GATE EXISTS: pbkdf2_sha1 is one of the FIVE modules riding the defective sha1.nx core, and it
9// is one of the TWO I originally missed when I filed the defect (I listed hmac/hkdf/hotp and overlooked
10// PBKDF2 and TOTP -- password-based key derivation and two-factor codes, the two with the worst blast
11// radius). ★COUNT THE IMPORT CLOSURE, NOT THE FILES YOU HAPPENED TO GREP.
12// This gate binds `nx_pbkdf2_sha1.nx` -> nx_hmac_sha1.nx -> nx_sha1.nx (the CORRECT family). Both families
13// export the identical symbol `pbkdf2_sha1` with an identical signature, so ★THE ONLY THING THAT
14// DISTINGUISHES THEM IS THE FILE BOUND -- NAME THE FILE, NEVER THE FUNCTION.
15//
16// ⚠THREE READER HAZARDS IN THIS DIALECT, ALL HANDLED EXPLICITLY:
17// 1. P and S are QUOTED ASCII, not hex: P = "password" (8 octets)
18// 2. VECTOR 6 EMBEDS A LITERAL NUL as the two-character sequence \0 :
19// P = "pass\0word" (9 octets) S = "sa\0lt" (5 octets)
20// A reader that copies bytes literally yields 10 and 6 octets and silently tests the wrong input. The
21// declared octet count in the document is what catches this, so it is CROSS-CHECKED on every vector.
22// 3. DK is space-separated hex wrapped across lines and terminated by "(20 octets)". Continuation lines
23// carry no '=' -- the same structural discriminator used elsewhere in this workstream, because "da",
24// "Da" and friends are all valid hex and a lexical test cannot separate a value from a label.
25//
26// ⚠VECTOR 5 COSTS 16,777,216 ITERATIONS (~33.5M SHA-1 compressions). It is RUN, not skipped: the authority
27// published six vectors and grading five while printing GREEN is the coverage-gaming this lane exists to
28// stop. If it must ever be dropped, it must be dropped LOUDLY with its parameters printed -- never silently.
29// license_tier: ORIGINAL expect_exit: 0
30import "nx_syscalls.nx"
31import "nx_sha256_wasm.nx"
32import "nx_pbkdf2_sha1.nx"
33import "nx_gate_verdict.nx"
34
35func w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
36func wb(b: *u8, n: i64) -> i64 { sys_write(1, b, n); return 0 }
37
38func nn(v: i64) -> i64 {
39 var m: i64 = v
40 if m < 0 { w("-" as *u8); m = 0 - m }
41 let t: *u8 = sys_mmap(32)
42 var k: i64 = 0
43 if m == 0 { t[0] = 48 as u8; k = 1 }
44 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
45 let b: *u8 = sys_mmap(32)
46 var j: i64 = 0
47 while j < k { b[j] = t[k - 1 - j]; j = j + 1 }
48 sys_write(1, b, k)
49 return 0
50}
51
52func hexnib(v: i64) -> i64 { if v < 10 { return 48 + v } return 87 + v }
53func ph(v: i64) -> i64 {
54 let t: *u8 = sys_mmap(8)
55 t[0] = hexnib((v / 16) & 15) as u8
56 t[1] = hexnib(v & 15) as u8
57 sys_write(1, t, 2)
58 return 0
59}
60func hexval(c: i64) -> i64 {
61 if c >= 48 { if c <= 57 { return c - 48 } }
62 if c >= 97 { if c <= 102 { return c - 87 } }
63 if c >= 65 { if c <= 70 { return c - 55 } }
64 return 0 - 1
65}
66func isws(c: i64) -> i64 {
67 if c == 32 { return 1 }
68 if c == 10 { return 1 }
69 if c == 13 { return 1 }
70 if c == 9 { return 1 }
71 return 0
72}
73func starts(b: *u8, n: i64, at: i64, s: *u8) -> i64 {
74 var i: i64 = 0
75 while s[i] != (0 as u8) {
76 if at + i >= n { return 0 }
77 if b[at + i] != s[i] { return 0 }
78 i = i + 1
79 }
80 return 1
81}
82func findfrom(b: *u8, n: i64, s: *u8, from: i64) -> i64 {
83 var p: i64 = from
84 while p < n { if starts(b, n, p, s) == 1 { return p } p = p + 1 }
85 return 0 - 1
86}
87
88// Copy a quoted ASCII value, translating the TWO-CHARACTER sequence \0 into ONE NUL byte.
89// Returns the byte count, or -1 if unterminated.
90func readq(b: *u8, n: i64, from: i64, out: *u8, cap: i64) -> i64 {
91 var p: i64 = from
92 var done: i64 = 0
93 while done == 0 { if p >= n { return 0 - 1 } if b[p] == (34 as u8) { done = 1 } else { p = p + 1 } }
94 p = p + 1
95 var k: i64 = 0
96 while p < n {
97 if b[p] == (34 as u8) { return k }
98 if k >= cap { return 0 - 1 }
99 if b[p] == (92 as u8) {
100 if p + 1 < n {
101 if b[p + 1] == (48 as u8) { out[k] = 0 as u8; k = k + 1; p = p + 2 }
102 else { out[k] = b[p]; k = k + 1; p = p + 1 }
103 } else { out[k] = b[p]; k = k + 1; p = p + 1 }
104 } else { out[k] = b[p]; k = k + 1; p = p + 1 }
105 }
106 return 0 - 1
107}
108
109// Decimal after the next '=' following `from`.
110func readdec_eq(b: *u8, n: i64, from: i64, lim: i64) -> i64 {
111 var p: i64 = from
112 var d: i64 = 0
113 while d == 0 { if p >= lim { return 0 - 1 } if b[p] == (61 as u8) { d = 1 } p = p + 1 }
114 while p < lim { if isws(b[p] as i64) == 1 { p = p + 1 } else { p = lim + 1 } }
115 p = from
116 d = 0
117 while d == 0 { if p >= lim { return 0 - 1 } if b[p] == (61 as u8) { d = 1 } p = p + 1 }
118 var d2: i64 = 0
119 while d2 == 0 { if p >= lim { return 0 - 1 } if isws(b[p] as i64) == 1 { p = p + 1 } else { d2 = 1 } }
120 var v: i64 = 0
121 var any: i64 = 0
122 var done: i64 = 0
123 while done == 0 {
124 if p >= lim { done = 1 }
125 else {
126 let c: i64 = b[p] as i64
127 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); any = 1; p = p + 1 } else { done = 1 } }
128 else { done = 1 }
129 }
130 }
131 if any == 0 { return 0 - 1 }
132 return v
133}
134
135// Space-separated hex pairs after '=', wrapping across indented lines, terminated by '(' .
136// A wrap continues ONLY onto a line with no '=' (a continuation), never onto a new field/label line.
137func readhexrun(b: *u8, n: i64, from: i64, lim: i64, out: *u8, cap: i64) -> i64 {
138 var p: i64 = from
139 var d: i64 = 0
140 while d == 0 { if p >= lim { return 0 - 1 } if b[p] == (61 as u8) { d = 1 } p = p + 1 }
141 var got: i64 = 0
142 var done: i64 = 0
143 while done == 0 {
144 if got >= cap { done = 1 }
145 else {
146 if p >= lim { done = 1 }
147 else {
148 if b[p] == (40 as u8) { done = 1 }
149 else {
150 if isws(b[p] as i64) == 1 {
151 var crossed: i64 = 0
152 var dw: i64 = 0
153 while dw == 0 {
154 if p >= lim { dw = 1 }
155 else {
156 if isws(b[p] as i64) == 1 { if b[p] == (10 as u8) { crossed = 1 } p = p + 1 }
157 else { dw = 1 }
158 }
159 }
160 if crossed == 1 {
161 var eq: i64 = 0
162 var sc: i64 = p
163 var de: i64 = 0
164 while de == 0 {
165 if sc >= n { de = 1 }
166 else {
167 if b[sc] == (10 as u8) { de = 1 }
168 else { if b[sc] == (61 as u8) { eq = 1; de = 1 } else { sc = sc + 1 } }
169 }
170 }
171 if eq == 1 { done = 1 }
172 }
173 }
174 else {
175 if p + 1 >= lim { done = 1 }
176 else {
177 let h1: i64 = hexval(b[p] as i64)
178 let h2: i64 = hexval(b[p + 1] as i64)
179 if h1 < 0 { done = 1 }
180 else {
181 if h2 < 0 { done = 1 }
182 else { out[got] = ((h1 * 16) + h2) as u8; got = got + 1; p = p + 2 }
183 }
184 }
185 }
186 }
187 }
188 }
189 }
190 return got
191}
192
193func main() -> i64 {
194 w("nx_pbkdf2sha1_extvec_gate -- PBKDF2-HMAC-SHA1 vs RFC 6070, READ FROM THE FETCHED DOCUMENT\n" as *u8)
195
196 let lp: *i64 = sys_mmap(16) as *i64
197 lp[0] = 0
198 let b: *u8 = sys_read_file("knowledge/extvec/rfc6070.txt\x00" as *u8, lp)
199 if lp[0] <= 0 { w("RED: fetched vector file absent -- run nx_vecfetch.\n" as *u8); return 1 }
200
201 let ctx: *u8 = sys_mmap(1024)
202 let dg: *u8 = sys_mmap(64)
203 nx_sha256_one_shot(b, lp[0], ctx, dg)
204 let hx: *u8 = sys_mmap(80)
205 var i: i64 = 0
206 while i < 32 { hx[i*2] = hexnib(((dg[i] as i64)/16)&15) as u8; hx[i*2+1] = hexnib((dg[i] as i64)&15) as u8; i = i + 1 }
207 let wnt: *u8 = "e8cb0810032ad90f0fbd2001e7a42a079b6551064225e12b2a1ad4a7cb9105ea\x00" as *u8
208 var pin: i64 = 1
209 i = 0
210 while i < 64 { if hx[i] != wnt[i] { pin = 0 } i = i + 1 }
211 w(" acquisition digest: " as *u8); wb(hx, 64); w("\n" as *u8)
212 if pin == 0 { w("RED: PIN FAILED -- not the corroborated document.\n" as *u8); return 1 }
213 w(" PIN OK -- CORROBORATED (sovereign fetch and .NET WebClient agree)\n\n" as *u8)
214
215 // Anchor past the table of contents: take the LAST heading occurrence.
216 var sec: i64 = 0 - 1
217 var sp2: i64 = 0
218 var dsec: i64 = 0
219 while dsec == 0 {
220 let h: i64 = findfrom(b, lp[0], "PBKDF2 HMAC-SHA1 Test Vectors" as *u8, sp2)
221 if h < 0 { dsec = 1 } else { sec = h; sp2 = h + 10 }
222 }
223 if sec < 0 { w("RED: no vector section\n" as *u8); return 1 }
224
225 let pw: *u8 = sys_mmap(128)
226 let sa: *u8 = sys_mmap(128)
227 let dk: *u8 = sys_mmap(128)
228 let got: *u8 = sys_mmap(128)
229
230 var pass: i64 = 0
231 var fail: i64 = 0
232 var seen: i64 = 0
233 var cur: i64 = sec
234 var done: i64 = 0
235 while done == 0 {
236 let inp: i64 = findfrom(b, lp[0], "Input:" as *u8, cur)
237 if inp < 0 { done = 1 }
238 else {
239 let outp: i64 = findfrom(b, lp[0], "Output:" as *u8, inp)
240 if outp < 0 { done = 1 }
241 else {
242 // bound the OUTPUT block at the next "Input:" (or EOF)
243 var oend: i64 = lp[0]
244 let nxt: i64 = findfrom(b, lp[0], "Input:" as *u8, outp)
245 if nxt > outp { oend = nxt }
246
247 let fp: i64 = findfrom(b, outp, "P = " as *u8, inp)
248 let fs: i64 = findfrom(b, outp, "S = " as *u8, inp)
249 let fc: i64 = findfrom(b, outp, "c = " as *u8, inp)
250 let fd: i64 = findfrom(b, outp, "dkLen = " as *u8, inp)
251 let fk: i64 = findfrom(b, oend, "DK = " as *u8, outp)
252
253 var bad: i64 = 0
254 if fp < 0 { bad = 1 }
255 if fs < 0 { bad = 1 }
256 if fc < 0 { bad = 1 }
257 if fd < 0 { bad = 1 }
258 if fk < 0 { bad = 1 }
259 if bad == 1 {
260 w(" RED: vector " as *u8); nn(seen + 1); w(" missing a field -- the READER.\n" as *u8)
261 fail = fail + 1
262 done = 1
263 } else {
264 let pn: i64 = readq(b, lp[0], fp, pw, 120)
265 let sn: i64 = readq(b, lp[0], fs, sa, 120)
266 let cc: i64 = readdec_eq(b, lp[0], fc, outp)
267 let dl: i64 = readdec_eq(b, lp[0], fd, outp)
268 let dn: i64 = readhexrun(b, lp[0], fk, oend, dk, 120)
269
270 if pn < 0 { bad = 1 }
271 if sn < 0 { bad = 1 }
272 if cc <= 0 { bad = 1 }
273 if dl <= 0 { bad = 1 }
274 if dn != dl { bad = 1 }
275
276 if bad == 1 {
277 w(" RED: vector " as *u8); nn(seen + 1); w(" inconsistent (P=" as *u8); nn(pn)
278 w(" S=" as *u8); nn(sn); w(" c=" as *u8); nn(cc); w(" dkLen=" as *u8); nn(dl)
279 w(" DK=" as *u8); nn(dn); w(") -- the READER.\n" as *u8)
280 fail = fail + 1
281 done = 1
282 } else {
283 seen = seen + 1
284 w(" running vector " as *u8); nn(seen); w(" P=" as *u8); nn(pn)
285 w(" S=" as *u8); nn(sn); w(" c=" as *u8); nn(cc); w(" dkLen=" as *u8); nn(dl)
286 if cc > 1000000 { w(" <-- LONG RUN, " as *u8); nn(cc); w(" iterations, NOT skipped\n" as *u8) }
287 else { w("\n" as *u8) }
288 pbkdf2_sha1(pw, pn, sa, sn, cc, got, dl)
289 var same: i64 = 1
290 var z: i64 = 0
291 while z < dl { if got[z] != dk[z] { same = 0 } z = z + 1 }
292 if same == 1 {
293 pass = pass + 1
294 w(" PASS DK matches published value\n" as *u8)
295 } else {
296 fail = fail + 1
297 w(" FAIL expected " as *u8); z = 0; while z < 8 { ph(dk[z] as i64); z = z + 1 }
298 w("... computed " as *u8); z = 0; while z < 8 { ph(got[z] as i64); z = z + 1 }
299 w("...\n" as *u8)
300 }
301 cur = outp + 7
302 }
303 }
304 }
305 }
306 }
307
308 // RFC 6070 publishes SIX vectors.
309 if seen < 6 {
310 w("\n RED: only " as *u8); nn(seen); w(" of 6 published vectors graded -- refusing GREEN on a partial read.\n" as *u8)
311 fail = fail + 1
312 }
313
314 w("\n refsrc=https://www.rfc-editor.org/rfc/rfc6070.txt\n" as *u8)
315 w(" refsrcdig=" as *u8); wb(hx, 64); w("\n" as *u8)
316 w(" ref=RFC6070-all gate=nx_pbkdf2sha1_extvec_gate\n" as *u8)
317 w(" BOUND: nx_pbkdf2_sha1.nx -> nx_hmac_sha1.nx -> nx_sha1.nx (NOT pbkdf2_sha1.nx, which rides the\n" as *u8)
318 w(" defective sha1.nx -- both export an identical `pbkdf2_sha1` symbol and signature)\n" as *u8)
319 w("nx_pbkdf2sha1_extvec_gate: vectors=" as *u8); nn(seen)
320 w(" pass=" as *u8); nn(pass); w(" fail=" as *u8); nn(fail)
321 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check
322 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled
323 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify.
324 let ctr__dry: *i64 = gv_ctr()
325 ctr__dry[0] = pass
326 ctr__dry[1] = pass + fail
327 let rc__dry: i64 = gv_verdict("PBKDF2SHA1-EXTVEC-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8)
328 sys_exit(rc__dry)
329 return rc__dry
330}