code wiki / _hdl_build / nx_cms_pw.nx
nx_cms_pw.nx source
↩ module page · 138 lines · 5797 B
1// nx_cms_pw.nx -- CMS C10: memory-hard admin-password verification on the team's own argon2id
2// (RFC 9106, the vault KDF v2 primitive -- its 2026-06-10 "integration segfault" was the lane
3// compiler's >6-arg tail-call drop, killed + blessed same day; argon2id itself was never the bug).
4// The credential file <site>/admin_pw.ar2 is SELF-DESCRIBING -- params are DATA in the file, not
5// constants in code (rule 11):
6// ar2 <m_kib> <t> <salt 32 hex chars> <tag 64 hex chars>\n
7// The salt's 32 hex chars are used VERBATIM as the 32-byte salt input (128 bits urandom entropy via
8// st_hex128 at set time; deterministic fixtures may pin it). Production params = the vault's measured
9// m=32768 t=3 (m=1MiB:58ms 8MiB:412ms 64MiB:3644ms on this box, _ar2_perf_probe 2026-06-10); gates
10// may use smaller m -- the file says so, the code never assumes. license_tier: ORIGINAL
11import "nx_argon2id.nx"
12import "nx_syscalls.nx"
13const K_MAGIC_1024: i64 = 1024
14const K_MAGIC_2048: i64 = 2048
15
16// argon2id ctx bundle (caller-owns-memory pattern; same alloc shape as nx_machine_key/nx_argon2id_test).
17// Allocate ONCE per daemon (m_kib KiB of block memory) and reuse across logins.
18func cpw_ctx_new(m_kib: i64) -> *NxArgon2idCtx {
19 let raw: *u8 = sys_mmap(NX_ARGON2ID_CTX_BYTES)
20 let ctx: *NxArgon2idCtx = raw as *NxArgon2idCtx
21 ctx.memory_blocks = sys_mmap(m_kib * K_MAGIC_1024)
22 ctx.h0_buf = sys_mmap(64)
23 ctx.prepend_buf = sys_mmap(K_MAGIC_2048)
24 ctx.prev_buf = sys_mmap(64)
25 ctx.curr_buf = sys_mmap(64)
26 ctx.zero_block = sys_mmap(K_MAGIC_1024)
27 ctx.z_buf = sys_mmap(K_MAGIC_1024)
28 ctx.tmp_block = sys_mmap(K_MAGIC_1024)
29 ctx.addr_block = sys_mmap(K_MAGIC_1024)
30 ctx.final_block = sys_mmap(K_MAGIC_1024)
31 ctx.h0_input = sys_mmap(K_MAGIC_2048)
32 ctx.b2b_ctx = sys_mmap(NX_BLAKE2B_CTX_BYTES) as *NxBlake2b
33 ctx.b2b_buf = sys_mmap(128)
34 ctx.b2b_sv = sys_mmap(128) as *i64
35 ctx.b2b_sm = sys_mmap(128) as *i64
36 ctx.g_r = sys_mmap(K_MAGIC_1024) as *i64
37 ctx.g_rs = sys_mmap(K_MAGIC_1024) as *i64
38 ctx.g_col = sys_mmap(128) as *i64
39 return ctx
40}
41
42// derive the 32-byte tag for (pw, salt32, m_kib, t) and emit 64 hex chars + NUL into taghex. 1/0.
43func cpw_derive(ctx: *NxArgon2idCtx, pw: *u8, plen: i64, salt32: *u8, m_kib: i64, t: i64, taghex: *u8) -> i64 {
44 let tag: *u8 = sys_mmap(64)
45 let rc: i64 = nx_argon2id_hash(ctx, pw, plen, salt32, 32, 1, 32, m_kib, t, tag)
46 if rc != NX_AR2_OK { return 0 }
47 let hx: *u8 = "0123456789abcdef" as *u8
48 var i: i64 = 0
49 while i < 32 {
50 taghex[i*2] = hx[((tag[i] as i64) >> 4) & 15]
51 taghex[i*2+1] = hx[(tag[i] as i64) & 15]
52 i = i + 1
53 }
54 taghex[64] = 0 as u8
55 return 1
56}
57
58// parse an admin_pw.ar2 buffer. box[0]=m_kib box[1]=t; salt_out gets 32 bytes + NUL; taghex_out 64 + NUL.
59// Returns 1 iff the line is well-formed with sane params (m>=8, multiple of 4, t>=1).
60func cpw_parse(buf: *u8, n: i64, box: *i64, salt_out: *u8, taghex_out: *u8) -> i64 {
61 if n < 4 + 1 + 1 + 1 + 1 + 1 + 32 + 1 + 64 { return 0 }
62 if (buf[0] as i64) != 97 { return 0 } // 'a'
63 if (buf[1] as i64) != 114 { return 0 } // 'r'
64 if (buf[2] as i64) != 50 { return 0 } // '2'
65 if (buf[3] as i64) != 32 { return 0 }
66 var i: i64 = 4
67 var m: i64 = 0
68 var seen: i64 = 0
69 while i < n { let c: i64 = buf[i] as i64; if c >= 48 { if c <= 57 { m = m * 10 + (c - 48); seen = seen + 1; i = i + 1 } else { break } } else { break } }
70 if seen == 0 { return 0 }
71 if i >= n { return 0 }
72 if (buf[i] as i64) != 32 { return 0 }
73 i = i + 1
74 var t: i64 = 0
75 seen = 0
76 while i < n { let c2: i64 = buf[i] as i64; if c2 >= 48 { if c2 <= 57 { t = t * 10 + (c2 - 48); seen = seen + 1; i = i + 1 } else { break } } else { break } }
77 if seen == 0 { return 0 }
78 if i >= n { return 0 }
79 if (buf[i] as i64) != 32 { return 0 }
80 i = i + 1
81 if i + 32 + 1 + 64 > n { return 0 }
82 var q: i64 = 0
83 while q < 32 { salt_out[q] = buf[i+q]; q = q + 1 }
84 salt_out[32] = 0 as u8
85 i = i + 32
86 if (buf[i] as i64) != 32 { return 0 }
87 i = i + 1
88 q = 0
89 while q < 64 { taghex_out[q] = buf[i+q]; q = q + 1 }
90 taghex_out[64] = 0 as u8
91 if m < 8 { return 0 }
92 if (m % 4) != 0 { return 0 }
93 if t < 1 { return 0 }
94 box[0] = m
95 box[1] = t
96 return 1
97}
98
99func cpw_catnum(dst: *u8, off: i64, v: i64) -> i64 {
100 let t: *u8 = sys_mmap(24)
101 var m: i64 = v
102 var k: i64 = 0
103 if m == 0 { t[0] = 48 as u8; k = 1 }
104 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
105 var o: i64 = off
106 var i: i64 = 0
107 while i < k { dst[o] = t[k-1-i]; o = o + 1; i = i + 1 }
108 return o
109}
110
111// derive + write <path> as a self-describing ar2 credential file. salt32 = caller's 32 bytes
112// (st_hex128 for production, pinned for deterministic gates). Returns 1/0.
113func cpw_write(path: *u8, pw: *u8, plen: i64, salt32: *u8, m_kib: i64, t: i64) -> i64 {
114 let ctx: *NxArgon2idCtx = cpw_ctx_new(m_kib)
115 let taghex: *u8 = sys_mmap(80)
116 if cpw_derive(ctx, pw, plen, salt32, m_kib, t, taghex) != 1 { return 0 }
117 let line: *u8 = sys_mmap(256)
118 var o: i64 = 0
119 line[o] = 97 as u8; o = o + 1
120 line[o] = 114 as u8; o = o + 1
121 line[o] = 50 as u8; o = o + 1
122 line[o] = 32 as u8; o = o + 1
123 o = cpw_catnum(line, o, m_kib)
124 line[o] = 32 as u8; o = o + 1
125 o = cpw_catnum(line, o, t)
126 line[o] = 32 as u8; o = o + 1
127 var q: i64 = 0
128 while q < 32 { line[o] = salt32[q]; o = o + 1; q = q + 1 }
129 line[o] = 32 as u8; o = o + 1
130 q = 0
131 while q < 64 { line[o] = taghex[q]; o = o + 1; q = q + 1 }
132 line[o] = 10 as u8; o = o + 1
133 let fd: i64 = sys_openat_wr(path, 0x180) // 0600: credential file
134 if fd < 0 { return 0 }
135 sys_write(fd, line, o)
136 sys_close(fd)
137 return 1
138}