nx_mtls_issue.nx source
↩ module page · 105 lines · 6618 B
1// nx_mtls_issue.nx -- R7 ISSUANCE: one call the login daemon makes to hand a browser its identity .p12.
2//
3// The enforcement side has its one-call drop-in (nx_hgw_mtls_resolve_handle). This is the issuance twin: after
4// OPAQUE login succeeds, the daemon holds the 32-byte export_key (RFC 9807) + the user_id_hash; this composes
5// R5 (derive the deterministic Ed25519 identity keypair + mint the bound cert, Subject CN = hex(user_id_hash))
6// and R5b (package key+cert into a password-protected PKCS#12) into the single browser-importable .p12. The
7// private key is NEVER stored server-side -- it is re-derived from export_key on each login, so re-issuing is
8// idempotent (same login -> byte-identical .p12 in dev). The daemon streams the result to the browser ONCE for
9// a one-time cert import; thereafter the cert rides every connection at the TLS layer (the no-cookie carrier).
10//
11// LIVE NOTE: nx_modern_auth_login / olg_login currently DISCARD export_key (R5 header); surfacing it as an
12// additive out-param is the issuance-side daemon edit. Composes nx_mtls_identity (R5) + nx_mtls_pkcs12 (R5b).
13// license_tier: ORIGINAL expect_exit: 0
14import "nx_syscalls.nx"
15import "nx_mtls_identity.nx" // nx_mtls_mint_identity_cert + NX_MID_OK + nx_mid_hex + mid_find
16import "nx_mtls_pkcs12.nx" // nx_mtls_emit_pkcs12
17const NX_MAGIC_4096: i64 = 4096
18const NX_MAGIC_8192: i64 = 8192
19
20const NX_ISSUE_OK: i64 = 0
21const NX_ISSUE_ERR: i64 = 1
22
23// OPAQUE export_key + uid_hash -> the browser-importable .p12 (identity cert + key, password-protected).
24// On OK: out_p12[0..out_n] is the PFX, out_pub_32 the identity pubkey (the stable handle R6 authorizes on).
25func nx_mtls_issue_identity_p12(
26 export_key_32: *u8, uid_hash_32: *u8,
27 pw: *u8, pw_n: i64,
28 out_p12: *u8, cap: i64, out_n: *i64,
29 out_pub_32: *u8
30) -> i64 {
31 let seed: *u8 = sys_mmap(32)
32 let cert: *u8 = sys_mmap(NX_MAGIC_4096); let clen: *i64 = sys_mmap(8) as *i64
33 if nx_mtls_mint_identity_cert(export_key_32, uid_hash_32, cert, NX_MAGIC_4096, clen, seed, out_pub_32) != NX_MID_OK { return NX_ISSUE_ERR }
34 if nx_mtls_emit_pkcs12(seed, out_pub_32, cert, clen[0], pw, pw_n, out_p12, cap, out_n) != 0 { return NX_ISSUE_ERR }
35 return NX_ISSUE_OK
36}
37
38// ===== in-process gate: export_key -> a valid, identity-bound, idempotent .p12 =====
39func iss_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
40func iss_row(name: *u8, ok: i64) -> i64 { if ok == 1 { iss_w(" PASS " as *u8) } else { iss_w(" FAIL " as *u8) } iss_w(name); iss_w("\n" as *u8); return ok }
41func iss_eq(a: *u8, an: i64, b: *u8, bn: i64) -> i64 { if an != bn { return 0 } var i: i64 = 0; while i < an { if a[i] != b[i] { return 0 } i = i + 1 } return 1 }
42
43func main() -> i64 {
44 iss_w("nx_mtls issuance gate (OPAQUE export_key -> browser-importable identity .p12; R7 issuance, idempotent)\n" as *u8)
45
46 let ek: *u8 = sys_mmap(32); let uid: *u8 = sys_mmap(32)
47 var i: i64 = 0
48 while i < 32 { ek[i] = (0x5a + i) as u8; uid[i] = (0xC3 ^ i) as u8; i = i + 1 }
49
50 let p12a: *u8 = sys_mmap(NX_MAGIC_8192); let na: *i64 = sys_mmap(8) as *i64; let pubA: *u8 = sys_mmap(32)
51 let rc: i64 = nx_mtls_issue_identity_p12(ek, uid, "nishi" as *u8, 5, p12a, NX_MAGIC_8192, na, pubA)
52 var pass: i64 = 0
53
54 // T1: issuance succeeds + emits a well-formed PFX (DER SEQUENCE) of substantial size (key+cert+mac)
55 var t1: i64 = 0
56 if rc == NX_ISSUE_OK { if na[0] > 256 { if (p12a[0] & 0xff) == 0x30 { t1 = 1 } } }
57 pass = pass + iss_row("T1 export_key -> a well-formed PFX .p12 emitted (DER SEQUENCE, key+cert+MAC)" as *u8, t1)
58
59 // T2: idempotent -- the SAME login (export_key+uid+pw) re-issues a BYTE-IDENTICAL .p12 (re-derivable, not stored)
60 let p12b: *u8 = sys_mmap(NX_MAGIC_8192); let nb: *i64 = sys_mmap(8) as *i64; let pubB: *u8 = sys_mmap(32)
61 nx_mtls_issue_identity_p12(ek, uid, "nishi" as *u8, 5, p12b, NX_MAGIC_8192, nb, pubB)
62 var t2: i64 = 0
63 if iss_eq(p12a, na[0], p12b, nb[0]) == 1 { t2 = 1 }
64 pass = pass + iss_row("T2 idempotent: same login re-issues a byte-identical .p12 (no server-stored key)" as *u8, t2)
65
66 // T3: the .p12 carries the RIGHT identity (out_pub == the R5-derived identity pubkey)
67 let seedchk: *u8 = sys_mmap(32); let pubchk: *u8 = sys_mmap(32)
68 nx_mtls_derive_identity_keypair(ek, seedchk, pubchk)
69 var t3: i64 = 0
70 var j: i64 = 0; var ok3: i64 = 1
71 while j < 32 { if pubA[j] != pubchk[j] { ok3 = 0; j = 32 } else { j = j + 1 } }
72 if ok3 == 1 { t3 = 1 }
73 pass = pass + iss_row("T3 the .p12 carries the OPAQUE-bound identity pubkey (out_pub == derived)" as *u8, t3)
74
75 // T4: the .p12 EMBEDS the bound cert (mint the reference cert + find its DER inside the PFX CertBag, plaintext)
76 let refcert: *u8 = sys_mmap(NX_MAGIC_4096); let rcl: *i64 = sys_mmap(8) as *i64
77 let rseed: *u8 = sys_mmap(32); let rpub: *u8 = sys_mmap(32)
78 nx_mtls_mint_identity_cert(ek, uid, refcert, NX_MAGIC_4096, rcl, rseed, rpub)
79 var t4: i64 = 0
80 if mid_find(p12a, na[0], refcert, rcl[0]) >= 0 { t4 = 1 }
81 pass = pass + iss_row("T4 the .p12 embeds the bound identity cert (CN = hex(user_id_hash))" as *u8, t4)
82
83 // T5: independence -- a different export_key yields a DIFFERENT .p12 (distinct identity, not constant output)
84 let ek2: *u8 = sys_mmap(32); var a: i64 = 0; while a < 32 { ek2[a] = ek[a]; a = a + 1 } ek2[0] = (ek2[0] ^ 1) as u8
85 let p12c: *u8 = sys_mmap(NX_MAGIC_8192); let nc: *i64 = sys_mmap(8) as *i64; let pubC: *u8 = sys_mmap(32)
86 nx_mtls_issue_identity_p12(ek2, uid, "nishi" as *u8, 5, p12c, NX_MAGIC_8192, nc, pubC)
87 var t5: i64 = 0
88 if iss_eq(p12a, na[0], p12c, nc[0]) == 0 { t5 = 1 }
89 pass = pass + iss_row("T5 independence: a different export_key -> a different .p12 (distinct identity)" as *u8, t5)
90
91 // T6: the password protects the key -- a different password yields a different .p12 (PBES2 over the key)
92 let p12d: *u8 = sys_mmap(NX_MAGIC_8192); let nd: *i64 = sys_mmap(8) as *i64; let pubD: *u8 = sys_mmap(32)
93 nx_mtls_issue_identity_p12(ek, uid, "other" as *u8, 5, p12d, NX_MAGIC_8192, nd, pubD)
94 var t6: i64 = 0
95 if iss_eq(p12a, na[0], p12d, nd[0]) == 0 { t6 = 1 }
96 pass = pass + iss_row("T6 the password protects the key (different pw -> different .p12; PBES2 binding)" as *u8, t6)
97
98 if pass == 6 {
99 iss_w("NX-MTLS-ISSUE GATE GREEN 6/6 (one call: export_key -> identity-bound, idempotent, password-protected .p12; drops into the login daemon)\n" as *u8)
100 sys_exit(0)
101 }
102 iss_w("NX-MTLS-ISSUE GATE RED\n" as *u8)
103 sys_exit(1)
104 return 1
105}