nx_mtls_ecdsa.nx source
↩ module page · 78 lines · 4579 B
1// nx_mtls_ecdsa.nx -- ECDSA-P256 OPAQUE-bound client identity cert (the guaranteed-browser-interop branch).
2//
3// mTLS rung R5c: Chrome/Edge + the Windows cert store reliably present ECDSA-P256 client certs (Ed25519
4// client-cert support is spotty), so this is the interop-safe identity. Same shape as the Ed25519 path
5// (R4/R5) but P-256: derive a deterministic P-256 keypair from the OPAQUE export_key (private key never
6// stored server-side), then mint a self-signed ECDSA-with-SHA256 leaf cert (Subject CN = hex(user_id_hash)).
7//
8// Imports ONLY hub/nx_x509_build (the proven pattern from nx_acme_csr) -- nx_x509_build_self_signed dispatches
9// the ECDSA path (SHA-256(TBS) -> nx_ecdsa_p256_sign -> DER). Cert correctness is proven by openssl + python
10// (dev-time oracle). The in-process ECDSA CertificateVerify path (R3-ecdsa) lives in nx_tls13_clientauth_ecdsa.
11// license_tier: ORIGINAL expect_exit: 0
12import "nx_syscalls.nx"
13import "hub/nx_x509_build.nx"
14import "nx_p256_ecdh.nx" // p256_ecdh_derive_priv (NOT pulled by the build hub)
15const NX_MAGIC_4096: i64 = 4096
16
17const NX_ME_OK: i64 = 0
18const NX_ME_ERR: i64 = 1
19
20// hex-encode n bytes -> out[0..2n]. Returns 2n.
21func nx_me_hex(src: *u8, n: i64, out: *u8) -> i64 {
22 let hx: *u8 = "0123456789abcdef" as *u8
23 var i: i64 = 0
24 while i < n { let c: i64 = src[i] as i64; out[i*2] = hx[(c >> 4) & 15]; out[i*2+1] = hx[c & 15]; i = i + 1 }
25 return n * 2
26}
27
28// Mint the OPAQUE-bound ECDSA-P256 identity cert (Subject CN = hex(uid_hash)). out_n = DER length.
29// export_key is a uniform 32-byte secret; p256_ecdh_derive_priv reduces it to a valid [1,n-1] scalar, so the
30// identity is deterministic + re-derivable from the password proof, never stored server-side.
31func nx_mtls_ecdsa_mint_cert(
32 export_key_32: *u8, uid_hash_32: *u8, serial: i64,
33 out: *u8, cap: i64, out_n: *i64
34) -> i64 {
35 let priv32: *u8 = sys_mmap(32)
36 p256_ecdh_derive_priv(export_key_32, priv32)
37 let priv_limbs: *i64 = u256_alloc()
38 u256_load_be(priv_limbs, priv32)
39 let g: *P256Point = p256_point_alloc()
40 p256_point_load_g(g)
41 let pub: *P256Point = p256_point_alloc()
42 p256_scalar_mul(pub, priv_limbs, g)
43 p256_point_to_affine(pub)
44
45 let cn: *u8 = sys_mmap(72); let cn_n: i64 = nx_me_hex(uid_hash_32, 32, cn)
46 let sanp: *i64 = sys_mmap(8) as *i64
47 let sanl: *i64 = sys_mmap(8) as *i64
48 let inp: *NxX509BuildInputs = sys_mmap(256) as *NxX509BuildInputs
49 if nx_x509_inputs_init_ecdsa_p256(inp, cn, cn_n, sanp, sanl, 0, priv_limbs, pub.x, pub.y,
50 "20260101000000Z" as *u8, "20360101000000Z" as *u8, serial) != NX_X509_OK { return NX_ME_ERR }
51 if nx_x509_build_self_signed(inp, out, cap, out_n) != NX_X509_OK { return NX_ME_ERR }
52 return NX_ME_OK
53}
54
55// ===================== GATE: mint + write DER for openssl/python =====================
56func ec_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
57func ec_putn(v: i64) -> i64 { let t: *u8 = sys_mmap(24); var m: i64 = v; var k: i64 = 0; if m==0 { t[0]=48 as u8; k=1 } while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } let o: *u8 = sys_mmap(24); var i: i64=0; while i<k { o[i]=t[k-1-i]; i=i+1 } sys_write(1,o,k); return 0 }
58
59func main() -> i64 {
60 ec_puts("nx_mtls_ecdsa gate: OPAQUE export_key -> P-256 identity cert -> /tmp/nishi_ec_cert.der (rung R5c)\n" as *u8)
61 let ek: *u8 = sys_mmap(32); let uid: *u8 = sys_mmap(32)
62 var i: i64 = 0
63 while i < 32 { ek[i] = (0x5a + i) as u8; uid[i] = (0xC3 ^ i) as u8; i = i + 1 }
64
65 let cert: *u8 = sys_mmap(NX_MAGIC_4096); let cl: *i64 = sys_mmap(8) as *i64
66 if nx_mtls_ecdsa_mint_cert(ek, uid, 0x4a102030, cert, NX_MAGIC_4096, cl) != NX_ME_OK { ec_puts("MINT FAIL\n" as *u8); sys_exit(1) }
67 let fd: i64 = sys_openat_wr("/tmp/nishi_ec_cert.der" as *u8, 0x1a4)
68 if fd >= 0 { sys_write(fd, cert, cl[0]); sys_close(fd) } else { ec_puts("WRITE FAIL\n" as *u8); sys_exit(1) }
69 // also write the (deterministic) EC private scalar so the .p12 driver can package it (decoupled to dodge
70 // the build-hub vs pkcs12-container syscall/sha256 closure clash).
71 let priv32: *u8 = sys_mmap(32); p256_ecdh_derive_priv(ek, priv32)
72 let pfd: i64 = sys_openat_wr("/tmp/nishi_ec_priv.bin" as *u8, 0x1a4)
73 if pfd >= 0 { sys_write(pfd, priv32, 32); sys_close(pfd) }
74 ec_puts("wrote /tmp/nishi_ec_cert.der bytes="); ec_putn(cl[0]); ec_puts("\n" as *u8)
75 ec_puts("NX-MTLS-ECDSA EMITTED -- validate: openssl x509 -inform DER -in /tmp/nishi_ec_cert.der -noout -text\n" as *u8)
76 sys_exit(0)
77 return 0
78}