nx_x509_leaf_emit.nx source
↩ module page · 197 lines · 10579 B
1// nx_x509_leaf_emit.nx -- self-signed Ed25519 X.509 leaf-certificate emitter (RFC 5280 + RFC 8410).
2//
3// mTLS rung R4 of the NO-COOKIE session carrier. Mints the CLIENT IDENTITY certificate the browser
4// presents at the TLS layer: a self-signed Ed25519 leaf whose Subject CN carries the sovereign identity
5// (e.g. hex(user_id_hash)). R5 derives the keypair from the OPAQUE export_key + packages this cert as
6// PKCS#12; R3 verifies a presented cert's CertificateVerify against the pubkey parsed out of it; R6 reads
7// the Subject CN -> handle -> HR entitlements.
8//
9// Certificate ::= SEQUENCE { tbsCertificate, signatureAlgorithm, signatureValue BIT STRING }
10// TBSCertificate ::= SEQUENCE { [0]{INTEGER v3(2)}, serial INTEGER, sigAlg, issuer Name, validity,
11// subject Name, subjectPublicKeyInfo } (extensions omitted -- OPTIONAL)
12// Self-signed: issuer == subject; signatureValue = Ed25519 over DER(tbsCertificate).
13//
14// SYSCALL-VARIANT NOTE (Rule 23 -- comment the WHY): the x509 VERIFY stack (nx_x509 / nx_x509_validate /
15// nx_ed25519_signature) imports nx_syscalls.nx, but nx_x509_csr_emit imports nx_syscalls_x86_64.nx --
16// importing both = duplicate sys_* link error. So this file stays on nx_syscalls.nx and reimplements the
17// few small ASN.1 Name/SPKI helpers inline (pure nxae_ compositions) rather than import the CSR emitter.
18// Composes nx_asn1_emit (DER) + nx_ed25519_signature (sign). license_tier: ORIGINAL expect_exit: 0
19import "nx_syscalls.nx"
20import "nx_asn1_emit.nx"
21import "nx_ed25519_signature.nx"
22import "nx_x509.nx"
23import "nx_x509_validate.nx"
24const K_MAGIC_2048: i64 = 2048
25const K_MAGIC_4096: i64 = 4096
26
27func lf_oid_ed25519(out: *u8) -> i64 { out[0] = 0x2b as u8; out[1] = 0x65 as u8; out[2] = 0x70 as u8; return 3 }
28func lf_oid_cn(out: *u8) -> i64 { out[0] = 0x55 as u8; out[1] = 0x04 as u8; out[2] = 0x03 as u8; return 3 }
29
30// SubjectPublicKeyInfo for Ed25519: SEQUENCE { SEQUENCE{ OID id-Ed25519 }, BIT STRING pubkey }. 0 ok / 1 err.
31func lf_emit_spki_ed25519(out: *u8, off: *i64, cap: i64, pubkey_32: *u8) -> i64 {
32 let oid: *u8 = sys_mmap(8); let oid_n: i64 = lf_oid_ed25519(oid)
33 let alg: *u8 = sys_mmap(32); let alg_off: *i64 = sys_mmap(8) as *i64; alg_off[0] = 0
34 if nxae_put_oid(alg, alg_off, 32, oid, oid_n) != NXAE_OK { return 1 }
35 let spki: *u8 = sys_mmap(128); let spki_off: *i64 = sys_mmap(8) as *i64; spki_off[0] = 0
36 if nxae_put_tlv(spki, spki_off, 128, 0x30, alg, alg_off[0]) != NXAE_OK { return 1 }
37 if nxae_put_bit_string(spki, spki_off, 128, 0, pubkey_32, 32) != NXAE_OK { return 1 }
38 if nxae_put_tlv(out, off, cap, 0x30, spki, spki_off[0]) != NXAE_OK { return 1 }
39 return 0
40}
41
42// Name with a single commonName: SEQUENCE { SET { SEQUENCE { OID 2.5.4.3, UTF8String cn } } }. 0 ok / 1 err.
43func lf_emit_name_cn(out: *u8, off: *i64, cap: i64, cn: *u8, cn_n: i64) -> i64 {
44 let oid: *u8 = sys_mmap(8); let oid_n: i64 = lf_oid_cn(oid)
45 let atv: *u8 = sys_mmap(256); let atv_off: *i64 = sys_mmap(8) as *i64; atv_off[0] = 0
46 if nxae_put_oid(atv, atv_off, 256, oid, oid_n) != NXAE_OK { return 1 }
47 if nxae_put_utf8(atv, atv_off, 256, cn, cn_n) != NXAE_OK { return 1 }
48 let rdn: *u8 = sys_mmap(384); let rdn_off: *i64 = sys_mmap(8) as *i64; rdn_off[0] = 0
49 if nxae_put_tlv(rdn, rdn_off, 384, 0x30, atv, atv_off[0]) != NXAE_OK { return 1 }
50 let set: *u8 = sys_mmap(512); let set_off: *i64 = sys_mmap(8) as *i64; set_off[0] = 0
51 if nxae_put_tlv(set, set_off, 512, 0x31, rdn, rdn_off[0]) != NXAE_OK { return 1 }
52 if nxae_put_tlv(out, off, cap, 0x30, set, set_off[0]) != NXAE_OK { return 1 }
53 return 0
54}
55
56// signature AlgorithmIdentifier: SEQUENCE { OID id-Ed25519 } (no parameters per RFC 8410). 0 ok / 1 err.
57func lf_emit_sigalg(out: *u8, off: *i64, cap: i64) -> i64 {
58 let oid: *u8 = sys_mmap(8); let oid_n: i64 = lf_oid_ed25519(oid)
59 let s: *u8 = sys_mmap(32); let so: *i64 = sys_mmap(8) as *i64; so[0] = 0
60 if nxae_put_oid(s, so, 32, oid, oid_n) != NXAE_OK { return 1 }
61 if nxae_put_tlv(out, off, cap, 0x30, s, so[0]) != NXAE_OK { return 1 }
62 return 0
63}
64
65// validity SEQUENCE { UTCTime notBefore, UTCTime notAfter }; each a 13-char "YYMMDDHHMMSSZ". 0 ok / 1 err.
66func lf_emit_validity(out: *u8, off: *i64, cap: i64, nb_13: *u8, na_13: *u8) -> i64 {
67 let v: *u8 = sys_mmap(64); let vo: *i64 = sys_mmap(8) as *i64; vo[0] = 0
68 if nxae_put_tlv(v, vo, 64, 0x17, nb_13, 13) != NXAE_OK { return 1 }
69 if nxae_put_tlv(v, vo, 64, 0x17, na_13, 13) != NXAE_OK { return 1 }
70 if nxae_put_tlv(out, off, cap, 0x30, v, vo[0]) != NXAE_OK { return 1 }
71 return 0
72}
73
74// Emit a self-signed Ed25519 leaf certificate. serial must be a positive INTEGER (first byte < 0x80).
75// not_before / not_after are 13-byte UTCTime strings. Returns 0 on success, negative on error; out_n set.
76func nx_x509_leaf_emit_ed25519(
77 priv_32: *u8, pubkey_32: *u8,
78 cn: *u8, cn_n: i64,
79 serial: *u8, serial_n: i64,
80 not_before: *u8, not_after: *u8,
81 out: *u8, cap: i64, out_n: *i64
82) -> i64 {
83 if (priv_32 as i64) == 0 { return 0 - 1 }
84 if (pubkey_32 as i64) == 0 { return 0 - 1 }
85 if cn_n <= 0 { return 0 - 1 }
86 if serial_n <= 0 { return 0 - 1 }
87
88 // ---- TBSCertificate body ----
89 let tbs: *u8 = sys_mmap(K_MAGIC_2048); let to: *i64 = sys_mmap(8) as *i64; to[0] = 0
90 // version [0] EXPLICIT { INTEGER 2 } (v3)
91 let ver: *u8 = sys_mmap(16); let vo: *i64 = sys_mmap(8) as *i64; vo[0] = 0
92 if nxae_put_int_u8(ver, vo, 16, 2) != NXAE_OK { return 0 - 1 }
93 if nxae_put_context_explicit(tbs, to, K_MAGIC_2048, 0, ver, vo[0]) != NXAE_OK { return 0 - 1 }
94 // serialNumber INTEGER
95 if nxae_put_tlv(tbs, to, K_MAGIC_2048, 0x02, serial, serial_n) != NXAE_OK { return 0 - 1 }
96 // signature AlgorithmIdentifier
97 if lf_emit_sigalg(tbs, to, K_MAGIC_2048) != 0 { return 0 - 1 }
98 // issuer (self-signed: == subject)
99 if lf_emit_name_cn(tbs, to, K_MAGIC_2048, cn, cn_n) != 0 { return 0 - 1 }
100 // validity
101 if lf_emit_validity(tbs, to, K_MAGIC_2048, not_before, not_after) != 0 { return 0 - 1 }
102 // subject
103 if lf_emit_name_cn(tbs, to, K_MAGIC_2048, cn, cn_n) != 0 { return 0 - 1 }
104 // subjectPublicKeyInfo
105 if lf_emit_spki_ed25519(tbs, to, K_MAGIC_2048, pubkey_32) != 0 { return 0 - 1 }
106
107 // ---- wrap TBS in SEQUENCE (this DER is what gets signed + hashed) ----
108 let tbsw: *u8 = sys_mmap(K_MAGIC_2048); let tw: *i64 = sys_mmap(8) as *i64; tw[0] = 0
109 if nxae_put_tlv(tbsw, tw, K_MAGIC_2048, 0x30, tbs, to[0]) != NXAE_OK { return 0 - 1 }
110
111 // ---- sign DER(TBSCertificate) ----
112 let sig: *u8 = sys_mmap(64)
113 if ed25519_sign_full(priv_32, tbsw, tw[0], sig) != 0 { return 0 - 2 }
114
115 // ---- Certificate = SEQUENCE { TBS, sigAlg, BIT STRING(sig) } ----
116 let cc: *u8 = sys_mmap(K_MAGIC_4096); let co: *i64 = sys_mmap(8) as *i64; co[0] = 0
117 if nxae_put_bytes(cc, co, K_MAGIC_4096, tbsw, tw[0]) != NXAE_OK { return 0 - 1 } // TBS (already DER)
118 if lf_emit_sigalg(cc, co, K_MAGIC_4096) != 0 { return 0 - 1 }
119 if nxae_put_bit_string(cc, co, K_MAGIC_4096, 0, sig, 64) != NXAE_OK { return 0 - 1 }
120 var fo: i64 = 0
121 if nxae_put_tlv(out, &fo, cap, 0x30, cc, co[0]) != NXAE_OK { return 0 - 1 }
122 out_n[0] = fo
123 return 0
124}
125
126// ===== in-process gate: emit -> parse -> self-verify -> embedded-pubkey -> negative cases =====
127func lf_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
128func lf_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
129func lf_row(name: *u8, ok: i64) -> i64 { if ok == 1 { lf_w(" PASS " as *u8) } else { lf_w(" FAIL " as *u8) } lf_w(name); lf_w("\n" as *u8); return ok }
130
131func main() -> i64 {
132 lf_w("nx_x509 leaf-cert emit/verify gate (RFC 5280; self-signed Ed25519 identity; mTLS rung R4)\n" as *u8)
133 // deterministic test seed -> derive the matching Ed25519 public key
134 let seed: *u8 = sys_mmap(32)
135 var i: i64 = 0
136 while i < 32 { seed[i] = (0x11 + i) as u8; i = i + 1 }
137 let pub: *u8 = sys_mmap(32)
138 ed25519_pub_from_priv(seed, pub)
139
140 let cn: *u8 = "nishi-uid-2f9c4b7e10a8d6c3e5f10293" as *u8 // stand-in identity (R5 uses hex(user_id_hash))
141 let cn_n: i64 = lf_len(cn)
142 let serial: *u8 = sys_mmap(8)
143 serial[0] = 0x42 as u8; serial[1] = 0x13 as u8; serial[2] = 0x37 as u8; serial[3] = 0x01 as u8
144 serial[4] = 0x02 as u8; serial[5] = 0x03 as u8; serial[6] = 0x04 as u8; serial[7] = 0x05 as u8
145 let nb: *u8 = "260101000000Z" as *u8
146 let na: *u8 = "360101000000Z" as *u8
147
148 let cert: *u8 = sys_mmap(K_MAGIC_4096)
149 let clen: *i64 = sys_mmap(8) as *i64
150 let rc: i64 = nx_x509_leaf_emit_ed25519(seed, pub, cn, cn_n, serial, 8, nb, na, cert, K_MAGIC_4096, clen)
151 var pass: i64 = 0
152
153 var t1: i64 = 0
154 if rc == 0 { if clen[0] > 64 { t1 = 1 } }
155 pass = pass + lf_row("T1 leaf cert emitted (rc==0, plausible DER length)" as *u8, t1)
156
157 let xc: *X509Cert = sys_mmap(256) as *X509Cert
158 let pr: i64 = x509_parse(cert, clen[0], xc)
159 var t2: i64 = 0
160 if pr == 0 { t2 = 1 }
161 pass = pass + lf_row("T2 our own X.509 parser accepts the emitted cert" as *u8, t2)
162
163 var t3: i64 = 0
164 if x509_verify_signature_ed25519(cert, xc, pub) == NX_X509_VAL_OK { t3 = 1 }
165 pass = pass + lf_row("T3 self-signature verifies under the cert's own Ed25519 key" as *u8, t3)
166
167 var t4: i64 = 0
168 if xc.pubkey_len == 32 {
169 t4 = 1
170 var j: i64 = 0
171 while j < 32 { if cert[xc.pubkey_off + j] != pub[j] { t4 = 0; j = 32 } else { j = j + 1 } }
172 }
173 pass = pass + lf_row("T4 SubjectPublicKeyInfo embeds the exact identity pubkey (extractable for authz)" as *u8, t4)
174
175 // T5: a different signer pubkey must NOT verify
176 let pub2: *u8 = sys_mmap(32)
177 var k: i64 = 0
178 while k < 32 { pub2[k] = pub[k]; k = k + 1 }
179 pub2[0] = (pub2[0] ^ 1) as u8
180 var t5: i64 = 0
181 if x509_verify_signature_ed25519(cert, xc, pub2) != NX_X509_VAL_OK { t5 = 1 }
182 pass = pass + lf_row("T5 wrong signer pubkey rejected" as *u8, t5)
183
184 // T6: tampering a TBS byte must break the signature
185 cert[xc.tbs_off + 6] = (cert[xc.tbs_off + 6] ^ 0x40) as u8
186 var t6: i64 = 0
187 if x509_verify_signature_ed25519(cert, xc, pub) != NX_X509_VAL_OK { t6 = 1 }
188 pass = pass + lf_row("T6 tampered tbsCertificate byte rejected" as *u8, t6)
189
190 if pass == 6 {
191 lf_w("NX-X509-LEAF-EMIT GATE GREEN 6/6 (sovereign Ed25519 identity cert; verifies on our own X.509 stack)\n" as *u8)
192 sys_exit(0)
193 }
194 lf_w("NX-X509-LEAF-EMIT GATE RED\n" as *u8)
195 sys_exit(1)
196 return 1
197}