code wiki / _hdl_build / nx_reg_sign.nx
nx_reg_sign.nx source
↩ module page · 133 lines · 6139 B
1// nx_reg_sign.nx -- SOVEREIGN registry integrity tool (operator directive 2026-07-16, the security debt
2// the maturity census SURFACED: "daemons.reg is owner-writable plain file -> local write grants
3// revive-exec"). Signs / verifies a registry file with HMAC-SHA256 keyed by the machine-bound secret, so
4// the daemon supervisor can REFUSE to apply a registry that was changed without going through the signer.
5//
6// HONEST SCOPE (stated, not oversold -- this session's whole ethic): this is TAMPER-EVIDENCE + FAIL-SAFE
7// integrity, NOT prevention against a same-uid attacker who can read the 0600 key. What it genuinely gives:
8// - a corrupt/truncated/partial-written registry FAILS verify -> supervisor holds last-good (never-brick)
9// - a different-uid or remote-dropped registry can't be validly signed (they can't read the 0600 key)
10// - ANY change to daemons.reg without re-signing is CAUGHT + surfaced (SECURITY event on /status) within
11// one supervisor cycle -> silent revive-exec injection becomes impossible; it is seen + held.
12// The L3 rung (root-held ed25519 asymmetric key so even the same uid as the supervisor can't forge) is the
13// stated next step; signing is a DELIBERATE act (not auto-run), so an un-re-signed change genuinely means
14// "changed outside the signer" = the threat.
15//
16// usage: nx_reg_sign sign <reg-path> <key-path> -> writes <reg-path>.sig (64 hex chars), exit 0
17// nx_reg_sign verify <reg-path> <key-path> -> exit 0 match · 3 mismatch · 4 sig-absent · 5 reg-unreadable · 2 usage
18// license_tier: ORIGINAL module: nishi-core.ops.reg_sign
19import "nx_syscalls.nx"
20import "nx_hmac.nx" // was hmac.nx -- CODE-IDENTICAL twin (49/49 stmts) on the LEGACY syscalls.nx+sha256.nx family.
21// Two files defining hmac_sha256 + main, with the expander deduping BY PATH NOT BY SYMBOL, made
22// every legacy importer a duplicate-symbol landmine for the nx_ family (debt 1785524913). // hmac_sha256(key,keylen,msg,msglen,out32) -- import chain dedups to nx_syscalls/nx_sha256
23
24const RS_CAP: i64 = 262144
25const RS_KEYCAP: i64 = 4096
26
27func rs_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
28func rs_p(s: *u8) -> i64 { sys_write(1, s, rs_slen(s)); return 0 }
29func rs_streq(a: *u8, b: *u8) -> i64 {
30 var i: i64 = 0
31 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
32 if b[i] != (0 as u8) { return 0 }
33 return 1
34}
35func rs_read(path: *u8, buf: *u8, cap: i64) -> i64 {
36 let fd: i64 = sys_openat_rd(path)
37 if fd < 0 { return 0 - 1 }
38 var t: i64 = 0
39 var r: i64 = 1
40 while r > 0 { r = sys_read(fd, (buf as i64 + t) as *u8, cap - 1 - t); if r > 0 { t = t + r } if t >= cap - 1 { r = 0 } }
41 sys_close(fd)
42 return t
43}
44// hex-encode n bytes of src into dst (2*n chars), lowercase
45func rs_hex(src: *u8, n: i64, dst: *u8) -> i64 {
46 let H: *u8 = "0123456789abcdef" as *u8
47 var i: i64 = 0
48 while i < n {
49 let b: i64 = src[i] as i64
50 dst[i * 2] = H[(b >> 4) & 15]
51 dst[i * 2 + 1] = H[b & 15]
52 i = i + 1
53 }
54 return n * 2
55}
56func rs_write_atomic(path: *u8, buf: *u8, n: i64) -> i64 {
57 let np: *u8 = sys_mmap(600)
58 var o: i64 = 0
59 while path[o] != (0 as u8) { np[o] = path[o]; o = o + 1 }
60 let sfx: *u8 = ".nxnew" as *u8
61 var si: i64 = 0
62 while sfx[si] != (0 as u8) { np[o] = sfx[si]; o = o + 1; si = si + 1 }
63 np[o] = 0 as u8
64 let fd: i64 = sys_openat_wr(np, 384) // 0600: the sig is not secret, but match the key's tightness
65 if fd < 0 { return 0 - 1 }
66 var off: i64 = 0
67 while off < n {
68 let w: i64 = sys_write(fd, (buf as i64 + off) as *u8, n - off)
69 if w <= 0 { sys_close(fd); return 0 - 2 }
70 off = off + w
71 }
72 sys_close(fd)
73 return sys_renameat(np, path)
74}
75// compute HMAC-SHA256(key, reg) -> hex64 in out (must be >=64). returns 0 ok, negative on read fail.
76func rs_compute(reg: *u8, key: *u8, out: *u8) -> i64 {
77 let rb: *u8 = sys_mmap(RS_CAP)
78 let rn: i64 = rs_read(reg, rb, RS_CAP)
79 if rn < 0 { return 0 - 5 }
80 let kb: *u8 = sys_mmap(RS_KEYCAP)
81 let kn: i64 = rs_read(key, kb, RS_KEYCAP)
82 if kn <= 0 { return 0 - 6 }
83 let tag: *u8 = sys_mmap(32)
84 hmac_sha256(kb, kn, rb, rn, tag)
85 rs_hex(tag, 32, out)
86 return 0
87}
88
89func main(argc: i64, argv: *i64) -> i64 {
90 if argc < 4 { rs_p("usage: nx_reg_sign sign|verify <reg-path> <key-path>\n" as *u8); return 2 }
91 let verb: *u8 = argv[1] as *u8
92 let reg: *u8 = argv[2] as *u8
93 let key: *u8 = argv[3] as *u8
94 let hex: *u8 = sys_mmap(80)
95 let rc: i64 = rs_compute(reg, key, hex)
96 if rc == 0 - 5 { rs_p("reg-sign: registry unreadable\n" as *u8); return 5 }
97 if rc == 0 - 6 { rs_p("reg-sign: key unreadable\n" as *u8); return 5 }
98
99 if rs_streq(verb, "sign" as *u8) == 1 {
100 // sig-path = <reg>.sig
101 let sp: *u8 = sys_mmap(600)
102 var o: i64 = 0
103 while reg[o] != (0 as u8) { sp[o] = reg[o]; o = o + 1 }
104 let sfx: *u8 = ".sig" as *u8
105 var si: i64 = 0
106 while sfx[si] != (0 as u8) { sp[o] = sfx[si]; o = o + 1; si = si + 1 }
107 sp[o] = 0 as u8
108 if rs_write_atomic(sp, hex, 64) != 0 { rs_p("reg-sign: WRITE-FAIL .sig\n" as *u8); return 5 }
109 rs_p("reg-sign: SIGNED " as *u8); rs_p(reg); rs_p(".sig (hmac-sha256)\n" as *u8)
110 return 0
111 }
112
113 if rs_streq(verb, "verify" as *u8) == 1 {
114 let sp: *u8 = sys_mmap(600)
115 var o: i64 = 0
116 while reg[o] != (0 as u8) { sp[o] = reg[o]; o = o + 1 }
117 let sfx: *u8 = ".sig" as *u8
118 var si: i64 = 0
119 while sfx[si] != (0 as u8) { sp[o] = sfx[si]; o = o + 1; si = si + 1 }
120 sp[o] = 0 as u8
121 let want: *u8 = sys_mmap(128)
122 let wn: i64 = rs_read(sp, want, 128)
123 if wn < 0 { return 4 } // sig absent
124 // compare exactly 64 hex chars (ignore any trailing newline in the file)
125 if wn < 64 { return 3 }
126 var i: i64 = 0
127 while i < 64 { if want[i] != hex[i] { return 3 } i = i + 1 }
128 return 0
129 }
130
131 rs_p("usage: nx_reg_sign sign|verify <reg-path> <key-path>\n" as *u8)
132 return 2
133}