code wiki / _hdl_build / nx_fw_signed_robust.nx
nx_fw_signed_robust.nx source
↩ module page · 115 lines · 6072 B
1// nx_fw_signed_robust.nx -- the fusion: S-class never-brick (A/B + immutable factory + atomic
2// commit) WHERE BOOTABILITY = ed25519 SIGNATURE, not just a checksum.
3//
4// module: nishi-core.genealogy.fw_signed_robust
5// capability: CORE_COMPUTE (forgery-resistant AND fault-resistant firmware selection)
6//
7// Rung 6 (nx_fw_robust_flash) survives power loss + double faults but would boot any image with a
8// valid checksum. Rung 8 (nx_fw_capsule) rejects forgery but had no fallback. FUSED here: the banks
9// + factory hold SIGNED capsules, and sr_select boots a bank ONLY if cap_verify passes (signature
10// valid against the trusted platform key). So a FORGED image planted in the active bank is rejected
11// at select time and the system falls back to the other bank or the signed immutable factory.
12// Sandboxed (/tmp). Sovereign: nx_fw_capsule (-> nx_ed25519) + nx_syscalls. license_tier: ORIGINAL
13import "nx_fw_capsule.nx"
14import "nx_syscalls.nx"
15
16const SR_BANK_A: i64 = 65
17const SR_BANK_B: i64 = 66
18const SR_OK: i64 = 0
19const SR_REJECTED: i64 = 1 // unsigned/forged/corrupt source -> not written
20const SR_NO_ANCHOR: i64 = 3 // no signature-valid fallback (active bank nor factory) -> refuse
21const SR_VIOLATION: i64 = 4 // a path not under /tmp/
22const SR_HELD: i64 = 6 // inactive write failed verify -> not committed, active retained
23
24func sr_is_sandbox(p: *u8) -> i64 {
25 let pre: *u8 = "/tmp/"
26 var i: i64 = 0
27 while pre[i] != 0 as u8 { if p[i] != pre[i] { return 0 } i = i + 1 }
28 return 1
29}
30func sr_tmp(sel: *u8, out: *u8) -> i64 { var o: i64 = cp_cat(out, 0, sel); o = cp_cat(out, o, ".tmp\x00" as *u8); out[o] = 0 as u8; return o }
31func sr_read_sel(sel: *u8) -> i64 {
32 let lb: *i64 = sys_mmap(16) as *i64; lb[0] = 0
33 let b: *u8 = cp_read(sel, lb)
34 if (b as i64) == 0 { return 0 }
35 if lb[0] < 1 { return 0 }
36 let c: i64 = b[0] as i64
37 if c == SR_BANK_A { return SR_BANK_A }
38 if c == SR_BANK_B { return SR_BANK_B }
39 return 0
40}
41func sr_write_sel_atomic(sel: *u8, bankc: i64) -> i64 {
42 if sr_is_sandbox(sel) == 0 { return 0 - 1 }
43 let tmp: *u8 = sys_mmap(512); sr_tmp(sel, tmp)
44 let buf: *u8 = sys_mmap(2); buf[0] = bankc as u8; buf[1] = 0 as u8
45 cp_write(tmp, buf, 1)
46 sys_renameat(tmp, sel)
47 return 0
48}
49func sr_other(bankc: i64) -> i64 { if bankc == SR_BANK_B { return SR_BANK_A } return SR_BANK_B }
50func sr_copy(src: *u8, dst: *u8) -> i64 {
51 let lb: *i64 = sys_mmap(16) as *i64; lb[0] = 0
52 let b: *u8 = cp_read(src, lb)
53 if (b as i64) == 0 { return 0 - 1 }
54 cp_write(dst, b, lb[0])
55 return 0
56}
57
58// SELECT a SIGNATURE-VERIFIED image: preferred bank -> other bank -> signed immutable factory.
59// Returns a path whose capsule verifies against the trusted platform key, or null if none does.
60func sr_select(sel: *u8, ba: *u8, bb: *u8, fac: *u8) -> *u8 {
61 let pref: i64 = sr_read_sel(sel)
62 if pref == SR_BANK_B { if cap_verify(bb) == 1 { return bb } } else { if cap_verify(ba) == 1 { return ba } }
63 if pref == SR_BANK_B { if cap_verify(ba) == 1 { return ba } } else { if cap_verify(bb) == 1 { return bb } }
64 if cap_verify(fac) == 1 { return fac }
65 return 0 as *u8
66}
67func sr_selectable(sel: *u8, ba: *u8, bb: *u8, fac: *u8) -> i64 {
68 if (sr_select(sel, ba, bb, fac) as i64) != 0 { return 1 }
69 return 0
70}
71
72// flash a SIGNED capsule into the inactive bank, verify its signature, atomic-commit. A forged or
73// unsigned source is rejected; a fallback (active bank OR signed factory) must exist.
74func sr_safe_flash(sel: *u8, ba: *u8, bb: *u8, fac: *u8, newcap: *u8, corrupt: i64) -> i64 {
75 if sr_is_sandbox(sel) == 0 { return SR_VIOLATION }
76 if sr_is_sandbox(ba) == 0 { return SR_VIOLATION }
77 if sr_is_sandbox(bb) == 0 { return SR_VIOLATION }
78 if sr_is_sandbox(newcap) == 0 { return SR_VIOLATION }
79 if cap_verify(newcap) == 0 { return SR_REJECTED } // unsigned/forged/corrupt -> never write
80 var act: i64 = sr_read_sel(sel)
81 if act == 0 { act = SR_BANK_A }
82 var actpath: *u8 = ba
83 if act == SR_BANK_B { actpath = bb }
84 if cap_verify(actpath) == 0 { if cap_verify(fac) == 0 { return SR_NO_ANCHOR } }
85 let tgtc: i64 = sr_other(act)
86 var tgt: *u8 = ba
87 if tgtc == SR_BANK_B { tgt = bb }
88 sr_copy(newcap, tgt)
89 if corrupt == 1 { cap_corrupt(tgt) }
90 if cap_verify(tgt) == 0 { return SR_HELD }
91 sr_write_sel_atomic(sel, tgtc)
92 return SR_OK
93}
94
95func main() -> i64 {
96 cp_puts("=== NISHI SIGNED ROBUST FLASH (A/B + immutable factory + atomic commit, SIGNATURE-gated) ===\n")
97 let epoch: i64 = sys_now_realtime_sec()
98 let pid: i64 = __syscall(39, 0, 0, 0, 0, 0, 0)
99 let plat: *u8 = sys_mmap(32); cap_plat_seed(plat)
100 let forged: *u8 = sys_mmap(32); var i: i64 = 0; while i < 32 { forged[i] = ((i * 3 + 99) & 0xff) as u8; i = i + 1 }
101 let a: *u8 = sys_mmap(256); let b: *u8 = sys_mmap(256); let fac: *u8 = sys_mmap(256); let sel: *u8 = sys_mmap(256)
102 cap_path(a, "/tmp/sr_a." as *u8, epoch, pid); cap_path(b, "/tmp/sr_b." as *u8, epoch, pid)
103 cap_path(fac, "/tmp/sr_fac." as *u8, epoch, pid); cap_path(sel, "/tmp/sr_sel." as *u8, epoch, pid)
104
105 cap_make(fac, "NISHI-fw-signed-image" as *u8, 21, plat) // signed factory
106 cap_make(a, "NISHI-fw-signed-image" as *u8, 21, plat) // signed bank A
107 cap_make(b, "NISHI-fw-signed-image" as *u8, 21, plat) // signed bank B
108 sr_write_sel_atomic(sel, SR_BANK_A)
109 cp_puts(" start: selectable="); cp_putn(sr_selectable(sel, a, b, fac)); cp_puts(" active="); cp_putn(sr_read_sel(sel)); cp_puts("\n")
110 // plant a FORGED image into the ACTIVE bank A
111 cap_make(a, "NISHI-fw-signed-image" as *u8, 21, forged)
112 cp_puts(" FORGED image planted in active bank A -> cap_verify(A)="); cp_putn(cap_verify(a)); cp_puts(" selectable="); cp_putn(sr_selectable(sel, a, b, fac)); cp_puts(" (falls back to signed B/factory)\n")
113 cp_puts(" INVARIANT: sr_select boots ONLY a signature-verified image -> forgery can't be selected even if planted in a bank; A/B+factory keeps it never-brick\n")
114 sys_exit(0); return 0
115}