code wiki / _hdl_build / nx_pw_rotate.nx

nx_pw_rotate.nx source

↩ module page · 172 lines · 8381 B

1// nx_pw_rotate.nx -- vault SECRET ROTATION (the true closure of the plaintext-pw debt: the 2// vault migration protects the value going forward, but a value that ever sat in plaintext or 3// in a transcript must be CHANGED). Rotates the stored secret VALUE; the vault passphrase stays 4// machine-bound (nx_machine_key, unattended). Rollback-safe and self-verifying: 5// rotate <name> [len] (len default 24; the secret lives at ~/.nishi/secrets/<name>.nv) 6// 1. machine-key derive -> /tmp/nxpass 7// 2. OPEN current vault (PROVES we can read it before we touch it; abort if not) 8// 3. BACKUP <name>.nv -> <name>.nv.rotatebak (byte copy) 9// 4. CSPRNG new password (nx_entropy, 64-char uniform alphabet) -> /tmp/nxsecret.in 10// 5. SEAL (v3 random-iv) -> overwrites <name>.nv with the new value 11// 6. VERIFY: open the new vault, compare byte-exact to the generated value 12// MISMATCH -> RESTORE the backup, fail closed (the old secret is never lost) 13// 7. success -> new value to /tmp/nxnewpw (0600) for the operator to push to the service; 14// audit-log the rotation (value never logged); shred /tmp/nxpass + /tmp/nxsecret.in 15// This tool ROTATES THE STORE. Pushing the new value to the live service (e.g. the NAS account) 16// is the operator's deploy step -- a tool must never silently change a credential a human also 17// uses to log in. license_tier: ORIGINAL 18import "nx_syscalls.nx" 19import "nx_entropy.nx" 20const AT_MAGIC_65536: i64 = 65536 21 22const AT_FDCWD: i64 = 0 - 100 23const PWR_MAXLEN: i64 = 256 24 25func _p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 26func _pn(v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m;sys_write(1,"-" as *u8,1)}; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48;k=1}; while m>0{t[k]=48+(m%10);m=m/10;k=k+1}; var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1}; sys_write(1,bb,k); return 0 } 27func pwr_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 28func pwr_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { dst[off+i] = s[i]; i = i + 1 } return off + i } 29func pwr_unlink(path: *u8) -> i64 { __syscall(263, AT_FDCWD, path, 0, 0, 0, 0) return 0 } 30 31func pwr_read(path: *u8, buf: *u8, cap: i64) -> i64 { 32 let fd: i64 = sys_openat_rd(path) 33 if fd < 0 { return 0 - 1 } 34 var n: i64 = 0 35 var go: i64 = 1 36 while go == 1 { 37 let base: i64 = buf as i64 38 let r: i64 = sys_read(fd, (base + n) as *u8, cap - n) 39 if r <= 0 { go = 0 } else { n = n + r } 40 if n >= cap { go = 0 } 41 } 42 sys_close(fd) 43 return n 44} 45 46func pwr_write(path: *u8, buf: *u8, n: i64, mode: i64) -> i64 { 47 let fd: i64 = sys_openat_wr(path, mode) 48 if fd < 0 { return 0 - 1 } 49 sys_write(fd, buf, n) 50 sys_close(fd) 51 return 0 52} 53 54// byte-copy src -> dst (preserves the sealed-vault bytes exactly) 55func pwr_copy(src: *u8, dst: *u8) -> i64 { 56 let buf: *u8 = sys_mmap(AT_MAGIC_65536) 57 let n: i64 = pwr_read(src, buf, AT_MAGIC_65536) 58 if n < 0 { return 0 - 1 } 59 return pwr_write(dst, buf, n, 0x180) 60} 61 62// run an _offc tool with up to 2 args, muted; RAW wait4 exit code 63func pwr_run(path: *u8, a1: *u8, a2: *u8) -> i64 { 64 let pid: i64 = sys_fork() 65 if pid == 0 { 66 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 67 if dn >= 0 { sys_dup3(dn, 1, 0); sys_dup3(dn, 2, 0) } 68 let argv: *i64 = sys_mmap(64) as *i64 69 argv[0] = path as i64 70 var ai: i64 = 1 71 if (a1 as i64) != 0 { argv[ai] = a1 as i64; ai = ai + 1 } 72 if (a2 as i64) != 0 { argv[ai] = a2 as i64; ai = ai + 1 } 73 argv[ai] = 0 74 let envp: *i64 = sys_mmap(16) as *i64 75 envp[0] = 0 76 sys_execve(path, argv, envp) 77 sys_exit(127) 78 } 79 let st: *i64 = sys_mmap(16) as *i64 80 sys_wait4(pid, st, 0) 81 let raw: i64 = st[0] 82 if (raw & 0x7f) != 0 { return 128 + (raw & 0x7f) } 83 return (raw >> 8) & 0xff 84} 85 86func pwr_eq(a: *u8, b: *u8, n: i64) -> i64 { 87 var i: i64 = 0 88 while i < n { if a[i] != b[i] { return 0 } i = i + 1 } 89 return 1 90} 91 92// audit (best-effort; never blocks the rotation outcome) -- logs the EVENT, never the value 93func pwr_audit(name: *u8, outcome: *u8) -> i64 { 94 let fd: i64 = sys_openat_append("knowledge/status/secret_scan.log" as *u8, 0x1a4) 95 if fd < 0 { return 0 } 96 sys_write(fd, "ROTATE name=" as *u8, 12) 97 sys_write(fd, name, pwr_slen(name)) 98 sys_write(fd, " outcome=" as *u8, 9) 99 sys_write(fd, outcome, pwr_slen(outcome)) 100 sys_write(fd, " (value never logged)\n" as *u8, 21) 101 sys_close(fd) 102 return 0 103} 104 105func main(argc: i64, argv: *i64) -> i64 { 106 if argc < 2 { _p("usage: nx_pw_rotate <name> [len]\n" as *u8); sys_exit(2); return 2 } 107 let name: *u8 = argv[1] as *u8 108 var plen: i64 = 24 109 if argc >= 3 { 110 let ls: *u8 = argv[2] as *u8 111 var v: i64 = 0; var i: i64 = 0 112 while ls[i] != (0 as u8) { let c: i64 = ls[i] as i64; if c >= 48 { if c <= 57 { v = v*10 + (c-48) } } i = i + 1 } 113 if v >= 8 { if v <= 128 { plen = v } } 114 } 115 _p("=== PW ROTATE: rollback-safe vault secret rotation (CSPRNG, self-verifying) ===\n" as *u8) 116 117 // vault path ~/.nishi/secrets/<name>.nv (HOME hardcoded per Warden custody, as secret_cli) 118 let vpath: *u8 = sys_mmap(512) 119 var o: i64 = 0 120 o = pwr_cat(vpath, o, "/home/elderwesto/.nishi/secrets/" as *u8) 121 o = pwr_cat(vpath, o, name); o = pwr_cat(vpath, o, ".nv" as *u8) 122 vpath[o] = 0 as u8 123 let bpath: *u8 = sys_mmap(512) 124 o = 0; o = pwr_cat(bpath, o, vpath); o = pwr_cat(bpath, o, ".rotatebak" as *u8); bpath[o] = 0 as u8 125 126 // 1. machine-key derive 127 if pwr_run("_offc/nx_machine_key.elf" as *u8, 0 as *u8, 0 as *u8) != 0 { 128 _p(" machine-key derive failed\n" as *u8); pwr_unlink("/tmp/nxpass" as *u8); sys_exit(1); return 1 129 } 130 // 2. PROVE current access 131 pwr_unlink("/tmp/nxsecret.out" as *u8) 132 if pwr_run("_offc/nx_vault.elf" as *u8, "open" as *u8, vpath) != 0 { 133 _p(" cannot open current vault (no such secret / wrong machine / tampered) -- ABORT, nothing changed\n" as *u8) 134 pwr_unlink("/tmp/nxpass" as *u8); sys_exit(1); return 1 135 } 136 pwr_unlink("/tmp/nxsecret.out" as *u8) 137 // 3. backup the sealed vault file 138 if pwr_copy(vpath, bpath) != 0 { _p(" backup failed -- ABORT\n" as *u8); pwr_unlink("/tmp/nxpass" as *u8); sys_exit(1); return 1 } 139 // 4. CSPRNG new value 140 let newpw: *u8 = sys_mmap(PWR_MAXLEN) 141 if ent_pw(newpw, plen) != 0 { _p(" ENTROPY UNAVAILABLE -- ABORT (fails closed)\n" as *u8); pwr_unlink("/tmp/nxpass" as *u8); sys_exit(1); return 1 } 142 pwr_write("/tmp/nxsecret.in" as *u8, newpw, plen, 0x180) 143 // 5. seal the new value (v3 random-iv) 144 if pwr_run("_offc/nx_vault.elf" as *u8, "seal" as *u8, vpath) != 0 { 145 _p(" seal failed -- RESTORING backup\n" as *u8) 146 pwr_copy(bpath, vpath) 147 pwr_unlink("/tmp/nxpass" as *u8); pwr_unlink("/tmp/nxsecret.in" as *u8) 148 pwr_audit(name, "seal-fail-restored" as *u8); sys_exit(1); return 1 149 } 150 // 6. verify byte-exact 151 pwr_unlink("/tmp/nxsecret.out" as *u8) 152 var verified: i64 = 0 153 if pwr_run("_offc/nx_vault.elf" as *u8, "open" as *u8, vpath) == 0 { 154 let chk: *u8 = sys_mmap(PWR_MAXLEN) 155 let cn: i64 = pwr_read("/tmp/nxsecret.out" as *u8, chk, PWR_MAXLEN) 156 if cn == plen { if pwr_eq(chk, newpw, plen) == 1 { verified = 1 } } 157 } 158 if verified == 0 { 159 _p(" VERIFY FAILED -- RESTORING backup (old secret intact)\n" as *u8) 160 pwr_copy(bpath, vpath) 161 pwr_unlink("/tmp/nxpass" as *u8); pwr_unlink("/tmp/nxsecret.in" as *u8); pwr_unlink("/tmp/nxsecret.out" as *u8) 162 pwr_audit(name, "verify-fail-restored" as *u8); sys_exit(1); return 1 163 } 164 // 7. success: hand the new value to the operator (0600) for the live-service push 165 pwr_write("/tmp/nxnewpw" as *u8, newpw, plen, 0x180) 166 pwr_unlink("/tmp/nxpass" as *u8); pwr_unlink("/tmp/nxsecret.in" as *u8); pwr_unlink("/tmp/nxsecret.out" as *u8) 167 pwr_audit(name, "rotated-verified" as *u8) 168 _p(" ROTATED + VERIFIED: " as *u8); _p(name); _p(" now holds a fresh " as *u8); _pn(plen) 169 _p("-char CSPRNG value (vault re-sealed v3). New value -> /tmp/nxnewpw (0600); push it to the live service, then shred. Backup at " as *u8); _p(bpath); _p("\n" as *u8) 170 sys_exit(0) 171 return 0 172}