code wiki / (root) / nx_profile_backup.nx

nx_profile_backup.nx source

↩ module page · 149 lines · 6367 B

1// nx_profile_backup.nx -- ENCRYPTED second-copy backup of the browser profile store (lib, no main). 2// Operator 2026-07-02: "have it also create a backup so two is one one is none, on the os encrypted 3// whether nishi os or windows or android". Blob = ChaCha20-Poly1305 AEAD under a key HKDF-derived from 4// MACHINE IDENTITY (/etc/machine-id, 128-bit random) + a fresh random 32B salt PER BACKUP (fresh salt => 5// fresh key => the (key,nonce) pair is never reused across snapshots -- the AEAD nonce-reuse hazard). 6// Threat model (same as nx_machine_key/nx_vault): defeats offline theft of the backup file without the 7// machine; NOT an attacker with local machine access. Destination = OS-level dir OUTSIDE the repo, from 8// knowledge/hosting/profile_backup.conf (line 1) -- portable across NishiOS/Windows(drvfs)/Android paths. 9// 10// .nxpb v1 layout: "NXPB1\n"(6) | salt(32) | nonce(12) | ptlen(8 LE) | tag(16) | ct(ptlen). 11// pb_backup_file WRITES then RE-READS + DECRYPTS + BYTE-COMPARES (a backup that doesn't restore is not 12// a backup) -- returns 0 only on proven roundtrip. license_tier: ORIGINAL 13import "nx_syscalls.nx" 14import "nx_chacha20_poly1305.nx" 15import "nx_hkdf.nx" 16const K_MAGIC_134217728: i64 = 134217728 17 18func pb_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 19 20// key = HKDF-Expand(HKDF-Extract(salt, ikm), "nxpb-v1", 32) 21func pb_derive_key(ikm: *u8, ilen: i64, salt: *u8, key_out: *u8) -> i64 { 22 if ilen <= 0 { return 0 - 1 } 23 let prk: *u8 = sys_mmap(32) 24 hkdf_extract(salt, 32, ikm, ilen, prk) 25 hkdf_expand(prk, "nxpb-v1" as *u8, 7, 32, key_out) 26 return 0 27} 28 29func pb_rand(buf: *u8, n: i64) -> i64 { 30 let fd: i64 = sys_openat_rd("/dev/urandom" as *u8) 31 if fd < 0 { return 0 - 1 } 32 var got: i64 = 0 33 while got < n { 34 let r: i64 = sys_read(fd, buf + got, n - got) 35 if r <= 0 { sys_close(fd); return 0 - 1 } 36 got = got + r 37 } 38 sys_close(fd) 39 return 0 40} 41 42// machine-bound key material: /etc/machine-id (hex chars, newline stripped). Returns length or -1. 43func pb_machine_ikm(out: *u8, cap: i64) -> i64 { 44 let box: *i64 = sys_mmap(16) as *i64 45 let b: *u8 = sys_read_file("/etc/machine-id" as *u8, box) 46 if (b as i64) == 0 { return 0 - 1 } 47 var n: i64 = box[0] 48 if n > cap { n = cap } 49 var m: i64 = 0 50 var i: i64 = 0 51 while i < n { if (b[i] as i64) > 32 { out[m] = b[i]; m = m + 1 } i = i + 1 } 52 if m <= 0 { return 0 - 1 } 53 return m 54} 55 56// read + authenticate + decrypt a blob into out (lenbox[0] = plaintext length). 0=ok, 1=tag-mismatch/corrupt. 57func pb_restore_buf(blobpath: *u8, ikm: *u8, ilen: i64, out: *u8, cap: i64, lenbox: *i64) -> i64 { 58 let box: *i64 = sys_mmap(16) as *i64 59 let b: *u8 = sys_read_file(blobpath, box) 60 if (b as i64) == 0 { return 1 } 61 let n: i64 = box[0] 62 if n < 74 { return 1 } 63 let magic: *u8 = "NXPB1\n" as *u8 64 var i: i64 = 0 65 while i < 6 { if b[i] != magic[i] { return 1 } i = i + 1 } 66 let salt: *u8 = b + 6 67 let nonce: *u8 = b + 38 68 var ptlen: i64 = 0 69 var mul: i64 = 1 70 i = 0 71 while i < 8 { ptlen = ptlen + ((b[50 + i] as i64) * mul); mul = mul * 256; i = i + 1 } 72 let tag: *u8 = b + 58 73 let ct: *u8 = b + 74 74 if ptlen < 0 { return 1 } 75 if ptlen > cap { return 1 } 76 if 74 + ptlen != n { return 1 } 77 let key: *u8 = sys_mmap(32) 78 if pb_derive_key(ikm, ilen, salt, key) != 0 { return 1 } 79 let aad: *u8 = sys_mmap(1) 80 if nx_chacha20_poly1305_decrypt(key, nonce, aad, 0, ct, ptlen, tag, out) != NX_AEAD_VERDICT_OK { return 1 } 81 lenbox[0] = ptlen 82 return 0 83} 84 85// encrypt src -> dst blob, then RE-READ + DECRYPT + BYTE-COMPARE. 0 = proven-good backup. 86func pb_backup_file(src: *u8, dst: *u8, ikm: *u8, ilen: i64) -> i64 { 87 let box: *i64 = sys_mmap(16) as *i64 88 let pt: *u8 = sys_read_file(src, box) 89 if (pt as i64) == 0 { return 0 - 1 } 90 let ptlen: i64 = box[0] 91 let salt: *u8 = sys_mmap(32) 92 let nonce: *u8 = sys_mmap(12) 93 if pb_rand(salt, 32) != 0 { return 0 - 2 } 94 if pb_rand(nonce, 12) != 0 { return 0 - 2 } 95 let key: *u8 = sys_mmap(32) 96 if pb_derive_key(ikm, ilen, salt, key) != 0 { return 0 - 3 } 97 let ct: *u8 = sys_mmap(ptlen + 64) 98 let tag: *u8 = sys_mmap(16) 99 let aad: *u8 = sys_mmap(1) 100 if nx_chacha20_poly1305_encrypt(key, nonce, aad, 0, pt, ptlen, ct, tag) != NX_AEAD_VERDICT_OK { return 0 - 4 } 101 // assemble the blob 102 let blob: *u8 = sys_mmap(ptlen + 128) 103 var o: i64 = 0 104 let magic: *u8 = "NXPB1\n" as *u8 105 var i: i64 = 0 106 while i < 6 { blob[o] = magic[i]; o = o + 1; i = i + 1 } 107 i = 0; while i < 32 { blob[o] = salt[i]; o = o + 1; i = i + 1 } 108 i = 0; while i < 12 { blob[o] = nonce[i]; o = o + 1; i = i + 1 } 109 var v: i64 = ptlen 110 i = 0; while i < 8 { blob[o] = (v & 0xFF) as u8; v = v / 256; o = o + 1; i = i + 1 } 111 i = 0; while i < 16 { blob[o] = tag[i]; o = o + 1; i = i + 1 } 112 i = 0; while i < ptlen { blob[o] = ct[i]; o = o + 1; i = i + 1 } 113 __syscall(263, AT_FDCWD, dst, 0, 0, 0, 0) // unlink any prior blob: stale tail bytes must never survive 114 let fd: i64 = sys_openat_wr(dst, 0x180) 115 if fd < 0 { return 0 - 5 } 116 var wr: i64 = 0 117 while wr < o { 118 let w: i64 = sys_write(fd, blob + wr, o - wr) 119 if w <= 0 { sys_close(fd); return 0 - 5 } 120 wr = wr + w 121 } 122 sys_close(fd) 123 // PROVE the backup restores: re-read the file we just wrote, decrypt, byte-compare. 124 let rt: *u8 = sys_mmap(ptlen + 64) 125 let rc: i64 = pb_restore_buf(dst, ikm, ilen, rt, ptlen + 64, box) 126 if rc != 0 { return 0 - 6 } 127 if box[0] != ptlen { return 0 - 6 } 128 i = 0 129 while i < ptlen { if rt[i] != pt[i] { return 0 - 6 } i = i + 1 } 130 return 0 131} 132 133// restore a blob to a plaintext file. 0=ok (authenticated), 1=corrupt/tampered/wrong-machine. 134func pb_restore_file(blobpath: *u8, dst: *u8, ikm: *u8, ilen: i64) -> i64 { 135 let cap: i64 = K_MAGIC_134217728 136 let out: *u8 = sys_mmap(cap) 137 let lenbox: *i64 = sys_mmap(16) as *i64 138 if pb_restore_buf(blobpath, ikm, ilen, out, cap, lenbox) != 0 { return 1 } 139 let fd: i64 = sys_openat_wr(dst, 0x1a4) 140 if fd < 0 { return 1 } 141 var wr: i64 = 0 142 while wr < lenbox[0] { 143 let w: i64 = sys_write(fd, out + wr, lenbox[0] - wr) 144 if w <= 0 { sys_close(fd); return 1 } 145 wr = wr + w 146 } 147 sys_close(fd) 148 return 0 149}