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}