nx_cap_keygen.nx source
↩ module page · 76 lines · 4564 B
1// nx_cap_keygen.nx -- provision the capability-token SIGNING SECRET on the machine that will verify it (the NAS).
2// GAP 2 of the MCP grant: nx_tools_api prefers a real keyfile (TA_CAP_KEYFILE = tools_cap_secret.key, CWD-relative)
3// but FALLS BACK to a forgeable baked placeholder when it is absent -- so until a real key is provisioned, any
4// token is forgeable by anyone who can read the source/binary. This organ draws 32 bytes from the kernel CSPRNG,
5// hex-encodes them (64 ASCII chars, NO trailing newline / NO NUL -- so the file bytes are EXACTLY the secret
6// nx_tools_api reads raw and nx_cap_mint signs with), and writes them 0600.
7//
8// SINGLE RESPONSIBILITY (rule 9): this only PROVISIONS the secret; nx_cap_mint ISSUES tokens from it; nx_tools_api
9// VERIFIES them. Kept a STANDALONE organ (not a supervisor sub) so it is gate-provable off-LAN and deployable via
10// the /api chain. FAIL-SAFE: refuses to overwrite an existing keyfile unless argv[2]=="--force" (a silent rotate
11// would invalidate every outstanding capability). The secret is generated WHERE it is used and NEVER transits a
12// network, and is NEVER printed. license_tier: ORIGINAL expect_exit: 0
13import "nx_csprng.nx" // nx_csprng_fill (getrandom(2) -> /dev/urandom fallback) (+ transitive nx_syscalls, nx_random)
14
15const CK_DEFAULT: *u8 = "tools_cap_secret.key" as *u8 // must match nx_tools_api TA_CAP_KEYFILE
16const CK_BYTES: i64 = 32 // 256-bit secret
17
18// return codes
19const CK_OK: i64 = 1
20const CK_EXISTS: i64 = 0 - 2 // keyfile present + non-empty, force not given -> refused (fail-safe)
21const CK_RNG: i64 = 0 - 3 // CSPRNG draw failed
22const CK_WRITE: i64 = 0 - 4 // open/write of the keyfile failed
23
24func ck_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
25func ck_streq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } if b[i] != (0 as u8) { return 0 } return 1 }
26func ck_hexd(v: i64) -> i64 { if v < 10 { return 48 + v } return 87 + v } // 0-9 then a-f
27
28// ck_provision: PURE core. Provision the keyfile at `path` (0600) with 32 fresh CSPRNG bytes, hex-encoded, unless
29// it already exists and force==0. Also copies the hex secret (NUL-terminated) into out_key and sets out_klen[0]=64
30// so a gate can drive mint/verify against the REAL generated key in-process (no disk read-back needed). Returns CK_*.
31func ck_provision(path: *u8, force: i64, out_key: *u8, out_klen: *i64) -> i64 {
32 if force == 0 {
33 let szp: *i64 = sys_mmap(16) as *i64
34 let ex: *u8 = sys_read_file(path, szp)
35 if (ex as i64) != 0 { if szp[0] > 0 { return CK_EXISTS } }
36 }
37 let raw: *u8 = sys_mmap(64)
38 if nx_csprng_fill(raw, CK_BYTES) < 0 { return CK_RNG }
39 var i: i64 = 0
40 while i < CK_BYTES {
41 let b: i64 = raw[i] as i64
42 let hi: i64 = ck_hexd((b >> 4) & 15) // hoist the call OUT of the array store (nx_cc: call-in-array-store -> SEGV)
43 let lo: i64 = ck_hexd(b & 15)
44 out_key[i * 2] = hi as u8
45 out_key[i * 2 + 1] = lo as u8
46 i = i + 1
47 }
48 out_key[CK_BYTES * 2] = 0 as u8
49 out_klen[0] = CK_BYTES * 2
50 // O_WRONLY|O_CREAT|O_TRUNC (0x241), mode 0600 (0x180). Raw hex, NO trailing newline (byte-exact secret).
51 let fd: i64 = __syscall(257, 0 - 100, path, 0x241, 0x180, 0, 0)
52 if fd < 0 { return CK_WRITE }
53 sys_write(fd, out_key, CK_BYTES * 2)
54 sys_close(fd)
55 return CK_OK
56}
57
58// CLI: nx_cap_keygen [keyfile] [--force] (default keyfile = tools_cap_secret.key in CWD)
59func main(argc: i64, argv: *i64) -> i64 {
60 var path: *u8 = CK_DEFAULT
61 if argc >= 2 { path = argv[1] as *u8 }
62 var force: i64 = 0
63 if argc >= 3 { if ck_streq(argv[2] as *u8, "--force" as *u8) == 1 { force = 1 } }
64
65 let kb: *u8 = sys_mmap(128)
66 let klp: *i64 = sys_mmap(16) as *i64
67 let rc: i64 = ck_provision(path, force, kb, klp)
68
69 if rc == CK_EXISTS { ck_puts("REFUSED: keyfile already present. Pass --force to ROTATE (this invalidates every outstanding capability).\n" as *u8); sys_exit(3); return 3 }
70 if rc == CK_RNG { ck_puts("FAILED: CSPRNG draw failed -- no keyfile written.\n" as *u8); sys_exit(4); return 4 }
71 if rc == CK_WRITE { ck_puts("FAILED: could not open/write the keyfile.\n" as *u8); sys_exit(5); return 5 }
72
73 ck_puts("PROVISIONED capability signing keyfile (256-bit, 0600). Secret NOT printed. Restart nx_tools_api to load it, then nx_cap_mint can issue tokens.\n" as *u8)
74 sys_exit(0)
75 return 0
76}