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}