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}