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}