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}