code wiki / _hdl_build / nx_vault_migrate.nx
nx_vault_migrate.nx source
↩ module page · 169 lines · 8689 B
1// nx_vault_migrate.nx -- ONE-SHOT KDF MIGRATION v1 -> v2 for every named secret in
2// ~/.nishi/secrets/*.nv (2026-06-10 machine-key upgrade: 500k SHA-256 -> + argon2id memory-hard).
3//
4// Per secret, in rollback-safe order (nx_vault derives salt+IV from the FULL PATH, so the v2
5// ciphertext MUST be sealed at the final path -- never seal-then-rename):
6// 1. derive v1 passphrase (/tmp/nx_machine_key.elf v1) -> open <name>.nv -> plaintext in memory
7// 2. rename <name>.nv -> <name>.nv.v1bak (history kept; additive-only)
8// 3. derive v2 passphrase (default) -> seal plaintext at <name>.nv
9// 4. re-open <name>.nv under v2 and BYTE-COMPARE against the v1 plaintext
10// 5. mismatch/failure -> rename the .v1bak back over <name>.nv (rollback) + exit nonzero
11// 6. SHRED every ephemeral (overwrite 4096 zeros, then unlink): nxpass nxsecret.in nxsecret.out
12//
13// Idempotent: a secret already sealed under v2 simply fails the v1 open and is SKIPPED with a
14// notice (it opens under v2 -- verified in step-0 probe), so re-running is safe.
15// license_tier: ORIGINAL
16import "nx_syscalls.nx"
17const K_MAGIC_4096: i64 = 4096
18const K_MAGIC_65536: i64 = 65536
19
20func _p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
21func _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 }
22func vg_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
23func vg_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 }
24func vg_run(path: *u8, a1: *u8, a2: *u8) -> i64 {
25 let pid: i64 = sys_fork()
26 if pid == 0 {
27 let argv: *i64 = sys_mmap(64) as *i64
28 argv[0] = path as i64
29 var ai: i64 = 1
30 if (a1 as i64) != 0 { argv[ai] = a1 as i64; ai = ai + 1 }
31 if (a2 as i64) != 0 { argv[ai] = a2 as i64; ai = ai + 1 }
32 argv[ai] = 0
33 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = 0
34 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4)
35 if dn >= 0 { sys_dup3(dn, 1, 0) }
36 sys_execve(path, argv, envp)
37 sys_exit(127)
38 }
39 let st: *i64 = sys_mmap(16) as *i64
40 sys_wait4(pid, st, 0)
41 if (st[0] % 128) != 0 { return 0 - 1 }
42 return (st[0] >> 8) & 0xff
43}
44func vg_read(path: *u8, out: *u8, cap: i64, lb: *i64) -> i64 {
45 let fd: i64 = sys_openat_rd(path)
46 if fd < 0 { lb[0] = 0; return 0 - 1 }
47 var total: i64 = 0
48 var go: i64 = 1
49 while go == 1 { let base: i64 = out as i64; let r: i64 = sys_read(fd, (base + total) as *u8, cap - total); if r <= 0 { go = 0 } else { total = total + r } if total >= cap { go = 0 } }
50 sys_close(fd)
51 lb[0] = total
52 return total
53}
54func vg_write(path: *u8, s: *u8, n: i64) -> i64 { let fd: i64 = sys_openat_wr(path, 0x180); if fd < 0 { return 0 - 1 } sys_write(fd, s, n); sys_close(fd); return 0 }
55// shred = overwrite 4096 zero bytes THEN unlink (x86 unlinkat 263; rv64 35 is NOT translated)
56func vg_shred(path: *u8) -> i64 {
57 let z: *u8 = sys_mmap(K_MAGIC_4096)
58 let fd: i64 = sys_openat_wr(path, 0x180)
59 if fd >= 0 { sys_write(fd, z, K_MAGIC_4096); sys_close(fd) }
60 __syscall(263, 0 - 100, path, 0, 0, 0, 0)
61 return 0
62}
63func vg_open_to(vpath: *u8, out: *u8, lb: *i64) -> i64 {
64 vg_shred("/tmp/nxsecret.out" as *u8)
65 let rc: i64 = vg_run("_offc/nx_vault.elf" as *u8, "open" as *u8, vpath)
66 if rc != 0 { lb[0] = 0; return rc }
67 vg_read("/tmp/nxsecret.out" as *u8, out, K_MAGIC_4096, lb)
68 return 0
69}
70func vg_bytes_eq(a: *u8, an: i64, b: *u8, bn: i64) -> i64 { if an != bn { return 0 } var k: i64 = 0; while k < an { if a[k] != b[k] { return 0 } k = k + 1 } return 1 }
71
72// migrate one secret file; returns 0 ok, 1 skipped(already-v2), 2 FAILED
73func vg_migrate_one(vpath: *u8) -> i64 {
74 _p(" secret: " as *u8); _p(vpath); _p("\n" as *u8)
75 let plain: *u8 = sys_mmap(K_MAGIC_4096)
76 let plen: *i64 = sys_mmap(16) as *i64
77 // step 0: does it already open under v2? then it is migrated -- skip.
78 if vg_run("/tmp/nx_machine_key.elf" as *u8, 0 as *u8, 0 as *u8) != 0 { _p(" machine-key v2 derive failed\n" as *u8); return 2 }
79 if vg_open_to(vpath, plain, plen) == 0 { _p(" already opens under v2 -- SKIP (migrated)\n" as *u8); return 1 }
80 // step 1: open under v1
81 if vg_run("/tmp/nx_machine_key.elf" as *u8, "v1" as *u8, 0 as *u8) != 0 { _p(" machine-key v1 derive failed\n" as *u8); return 2 }
82 if vg_open_to(vpath, plain, plen) != 0 { _p(" does NOT open under v1 either -- foreign/corrupt, leaving untouched\n" as *u8); return 2 }
83 let n: i64 = plen[0]
84 _p(" opened under v1 (" as *u8); _pn(n); _p(" bytes)\n" as *u8)
85 // step 2: keep rollback copy at .v1bak
86 let bak: *u8 = sys_mmap(512)
87 var o: i64 = vg_cat(bak, 0, vpath)
88 o = vg_cat(bak, o, ".v1bak" as *u8)
89 bak[o] = 0 as u8
90 if sys_renameat(vpath, bak) != 0 { _p(" rename to .v1bak FAILED\n" as *u8); return 2 }
91 // step 3: seal at the FINAL path under v2
92 if vg_run("/tmp/nx_machine_key.elf" as *u8, 0 as *u8, 0 as *u8) != 0 { sys_renameat(bak, vpath); _p(" v2 derive failed -- ROLLED BACK\n" as *u8); return 2 }
93 vg_write("/tmp/nxsecret.in" as *u8, plain, n)
94 if vg_run("_offc/nx_vault.elf" as *u8, "seal" as *u8, vpath) != 0 { sys_renameat(bak, vpath); _p(" v2 seal failed -- ROLLED BACK\n" as *u8); return 2 }
95 // step 4: verify v2 round-trip byte-exactly
96 let plain2: *u8 = sys_mmap(K_MAGIC_4096)
97 let plen2: *i64 = sys_mmap(16) as *i64
98 if vg_open_to(vpath, plain2, plen2) != 0 { sys_renameat(bak, vpath); _p(" v2 re-open failed -- ROLLED BACK\n" as *u8); return 2 }
99 if vg_bytes_eq(plain, n, plain2, plen2[0]) == 0 { sys_renameat(bak, vpath); _p(" v2 plaintext MISMATCH -- ROLLED BACK\n" as *u8); return 2 }
100 _p(" v2 sealed + round-trip verified byte-exact (rollback kept at .v1bak)\n" as *u8)
101 return 0
102}
103
104func main() -> i64 {
105 _p("=== VAULT KDF MIGRATION v1->v2: every named secret, rollback-safe, verified ===\n" as *u8)
106 let dirp: *u8 = "/home/elderwesto/.nishi/secrets" as *u8
107 let fd: i64 = sys_openat_rd(dirp)
108 if fd < 0 { _p(" secrets dir absent -- nothing to migrate\n" as *u8); sys_exit(1); return 1 }
109 let dirbuf: *u8 = sys_mmap(K_MAGIC_65536)
110 let names: *u8 = sys_mmap(K_MAGIC_65536) // packed nul-terminated relative names
111 var nn: i64 = 0 // count
112 var noff: i64 = 0
113 var done: i64 = 0
114 while done == 0 {
115 let nb: i64 = sys_getdents64(fd, dirbuf, K_MAGIC_65536)
116 if nb <= 0 { done = 1 }
117 else {
118 var off: i64 = 0
119 while off < nb {
120 let dbase: i64 = dirbuf as i64
121 let rec: *u8 = (dbase + off) as *u8
122 let rl: i64 = dirent_reclen(rec)
123 if rl <= 0 { off = nb }
124 else {
125 let name: *u8 = dirent_name(rec)
126 let l: i64 = vg_slen(name)
127 var is_nv: i64 = 0
128 if l > 3 { if name[l-3]==(46 as u8) { if name[l-2]==(110 as u8) { if name[l-1]==(118 as u8) { is_nv = 1 } } } }
129 if is_nv == 1 {
130 var c: i64 = 0
131 while c <= l { names[noff+c] = name[c]; c = c + 1 }
132 noff = noff + l + 1
133 nn = nn + 1
134 }
135 off = off + rl
136 }
137 }
138 }
139 }
140 sys_close(fd)
141 _p(" found " as *u8); _pn(nn); _p(" sealed secret(s)\n" as *u8)
142 var ok: i64 = 0
143 var skip: i64 = 0
144 var fail: i64 = 0
145 var p: i64 = 0
146 var i: i64 = 0
147 let full: *u8 = sys_mmap(512)
148 while i < nn {
149 let nm: *u8 = ((names as i64) + p) as *u8
150 var fo: i64 = vg_cat(full, 0, dirp)
151 fo = vg_cat(full, fo, "/" as *u8)
152 fo = vg_cat(full, fo, nm)
153 full[fo] = 0 as u8
154 let r: i64 = vg_migrate_one(full)
155 if r == 0 { ok = ok + 1 }
156 if r == 1 { skip = skip + 1 }
157 if r == 2 { fail = fail + 1 }
158 p = p + vg_slen(nm) + 1
159 i = i + 1
160 }
161 // step 6: shred every ephemeral
162 vg_shred("/tmp/nxpass" as *u8)
163 vg_shred("/tmp/nxsecret.in" as *u8)
164 vg_shred("/tmp/nxsecret.out" as *u8)
165 _p(" MIGRATION: migrated=" as *u8); _pn(ok); _p(" already-v2=" as *u8); _pn(skip); _p(" failed=" as *u8); _pn(fail); _p(" (ephemerals shredded)\n" as *u8)
166 if fail == 0 { sys_exit(0); return 0 }
167 sys_exit(1)
168 return 1
169}