nx_tls13_clientauth_ecdsa.nx source
↩ module page · 143 lines · 7765 B
1// nx_tls13_clientauth_ecdsa.nx -- TLS 1.3 server-side CLIENT authentication, ECDSA-P256 path (RFC 8446 §4.4.3).
2//
3// mTLS rung R5c (the ECDSA half of R3): verify a presented ECDSA-P256 client cert's CertificateVerify over
4// the handshake transcript -- the path R7's live server runs when a browser presents an ECDSA client cert
5// (Chrome/Edge/Windows). Sibling of nx_tls13_server_clientauth (the Ed25519 path). Signature scheme
6// ecdsa_secp256r1_sha256 (0x0403): the client signs SHA-256(content); nx_x509_verify_ecdsa_p256 re-hashes.
7// Signed content is the same 130-byte layout (64*0x20 || "TLS 1.3, client CertificateVerify" || 0x00 || hash).
8//
9// Composes nx_x509 (parse) + nx_x509_verify_ecdsa_p256, and (gate only) hub/nx_x509_build + nx_p256_ecdh to
10// mint a real ECDSA cert + sign a real CertificateVerify. license_tier: ORIGINAL expect_exit: 0
11import "nx_syscalls.nx"
12import "hub/nx_x509_build.nx"
13import "nx_p256_ecdh.nx"
14import "nx_x509.nx"
15import "nx_x509_verify_ecdsa.nx"
16const NX_MAGIC_4096: i64 = 4096
17
18const NX_CAE_OK: i64 = 0
19const NX_CAE_BAD_CERT: i64 = 1
20const NX_CAE_BAD_SIG: i64 = 2
21const NX_CAE_SS_ECDSA_P256: i64 = 0x0403 // SignatureScheme ecdsa_secp256r1_sha256
22
23// Build the 130-byte CLIENT CertificateVerify signed content (RFC 8446 §4.4.3). Returns 130.
24func cae_cv_content(transcript_hash_32: *u8, out_130: *u8) -> i64 {
25 var i: i64 = 0
26 while i < 64 { out_130[i] = 0x20 as u8; i = i + 1 }
27 let lbl: *u8 = "TLS 1.3, client CertificateVerify"
28 var j: i64 = 0
29 while j < 33 { out_130[64 + j] = lbl[j]; j = j + 1 }
30 out_130[97] = 0 as u8
31 var k: i64 = 0
32 while k < 32 { out_130[98 + k] = transcript_hash_32[k]; k = k + 1 }
33 return 130
34}
35
36// Verify a presented ECDSA-P256 client cert's CertificateVerify (scheme 0x0403) over transcript_hash_32.
37// Proves possession of the cert's private key. Returns NX_CAE_OK on success.
38func tls13_verify_client_auth_ecdsa(
39 cert_der: *u8, cert_len: i64,
40 scheme: i64,
41 sig_der: *u8, sig_len: i64,
42 transcript_hash_32: *u8
43) -> i64 {
44 if scheme != NX_CAE_SS_ECDSA_P256 { return NX_CAE_BAD_SIG }
45 if (cert_der as i64) == 0 { return NX_CAE_BAD_CERT }
46 let xc: *X509Cert = sys_mmap(256) as *X509Cert
47 if x509_parse(cert_der, cert_len, xc) != 0 { return NX_CAE_BAD_CERT }
48 if xc.pubkey_len != 65 { return NX_CAE_BAD_CERT }
49 if (cert_der[xc.pubkey_off] & 0xff) != 0x04 { return NX_CAE_BAD_CERT } // uncompressed point
50 let xl: *i64 = u256_alloc(); let yl: *i64 = u256_alloc()
51 u256_load_be(xl, (cert_der as i64 + xc.pubkey_off + 1) as *u8)
52 u256_load_be(yl, (cert_der as i64 + xc.pubkey_off + 33) as *u8)
53 let content: *u8 = sys_mmap(130)
54 cae_cv_content(transcript_hash_32, content)
55 // nx_x509_verify_ecdsa_p256 SHA-256's its first arg, so pass the 130-byte content directly.
56 if nx_x509_verify_ecdsa_p256(content, 130, sig_der, sig_len, xl, yl) != NX_X509_ECDSA_OK { return NX_CAE_BAD_SIG }
57 return NX_CAE_OK
58}
59
60// ===================== GATE: mint an ECDSA identity, sign a real CV, verify + negatives =====================
61func cae_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
62func cae_row(name: *u8, ok: i64) -> i64 { if ok == 1 { cae_w(" PASS " as *u8) } else { cae_w(" FAIL " as *u8) } cae_w(name); cae_w("\n" as *u8); return ok }
63func cae_hex(src: *u8, n: i64, out: *u8) -> i64 { let hx: *u8 = "0123456789abcdef" as *u8; var i: i64 = 0; 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 } return n*2 }
64
65// mint an ECDSA identity cert for export_key `ek` (uid -> CN); also return the priv scalar limbs (to sign the CV).
66func cae_mint(ek: *u8, uid: *u8, out_cert: *u8, cap: i64, out_cl: *i64, out_priv_limbs: *i64) -> i64 {
67 let priv32: *u8 = sys_mmap(32)
68 p256_ecdh_derive_priv(ek, priv32)
69 u256_load_be(out_priv_limbs, priv32)
70 let g: *P256Point = p256_point_alloc(); p256_point_load_g(g)
71 let pub: *P256Point = p256_point_alloc(); p256_scalar_mul(pub, out_priv_limbs, g); p256_point_to_affine(pub)
72 let cn: *u8 = sys_mmap(72); let cn_n: i64 = cae_hex(uid, 32, cn)
73 let sanp: *i64 = sys_mmap(8) as *i64; let sanl: *i64 = sys_mmap(8) as *i64
74 let inp: *NxX509BuildInputs = sys_mmap(256) as *NxX509BuildInputs
75 if nx_x509_inputs_init_ecdsa_p256(inp, cn, cn_n, sanp, sanl, 0, out_priv_limbs, pub.x, pub.y,
76 "20260101000000Z" as *u8, "20360101000000Z" as *u8, 0x4b203040) != NX_X509_OK { return 1 }
77 return nx_x509_build_self_signed(inp, out_cert, cap, out_cl)
78}
79
80func main() -> i64 {
81 cae_w("nx_tls13 client-auth ECDSA-P256 verify gate (RFC 8446 4.4.3; mTLS rung R5c)\n" as *u8)
82 let ek: *u8 = sys_mmap(32); let uid: *u8 = sys_mmap(32)
83 var i: i64 = 0
84 while i < 32 { ek[i] = (0x21 + i) as u8; uid[i] = (0x44 ^ i) as u8; i = i + 1 }
85 let cert: *u8 = sys_mmap(NX_MAGIC_4096); let cl: *i64 = sys_mmap(8) as *i64
86 let pl: *i64 = u256_alloc()
87 if cae_mint(ek, uid, cert, NX_MAGIC_4096, cl, pl) != NX_X509_OK { cae_w("MINT FAIL\n" as *u8); sys_exit(1) }
88
89 // the client signs the CV content with its identity key = a real CertificateVerify signature
90 let th: *u8 = sys_mmap(32)
91 var t: i64 = 0
92 while t < 32 { th[t] = (0xA0 ^ t) as u8; t = t + 1 }
93 let content: *u8 = sys_mmap(130); cae_cv_content(th, content)
94 let h: *u8 = sys_mmap(32); sha256_digest(content, 130, h)
95 let hl: *i64 = u256_alloc(); u256_load_be(hl, h)
96 let r: *i64 = u256_alloc(); let s: *i64 = u256_alloc()
97 if nx_ecdsa_p256_sign(pl, hl, r, s) != NX_ECDSA_SIGN_OK { cae_w("SIGN FAIL\n" as *u8); sys_exit(1) }
98 let sig: *u8 = sys_mmap(128); let sig_n: i64 = nx_x509_ecdsa_sig_to_der(r, s, sig, 128)
99
100 var pass: i64 = 0
101
102 var t1: i64 = 0
103 if tls13_verify_client_auth_ecdsa(cert, cl[0], NX_CAE_SS_ECDSA_P256, sig, sig_n, th) == NX_CAE_OK { t1 = 1 }
104 pass = pass + cae_row("T1 genuine ECDSA client-auth verifies" as *u8, t1)
105
106 let th2: *u8 = sys_mmap(32)
107 var a: i64 = 0
108 while a < 32 { th2[a] = th[a]; a = a + 1 }
109 th2[0] = (th2[0] ^ 1) as u8
110 var t2: i64 = 0
111 if tls13_verify_client_auth_ecdsa(cert, cl[0], NX_CAE_SS_ECDSA_P256, sig, sig_n, th2) != NX_CAE_OK { t2 = 1 }
112 pass = pass + cae_row("T2 bound to THIS transcript (replay on another handshake rejected)" as *u8, t2)
113
114 let bad: *u8 = sys_mmap(128)
115 var b: i64 = 0
116 while b < sig_n { bad[b] = sig[b]; b = b + 1 }
117 bad[sig_n - 1] = (bad[sig_n - 1] ^ 0x40) as u8
118 var t3: i64 = 0
119 if tls13_verify_client_auth_ecdsa(cert, cl[0], NX_CAE_SS_ECDSA_P256, bad, sig_n, th) != NX_CAE_OK { t3 = 1 }
120 pass = pass + cae_row("T3 tampered ECDSA signature rejected" as *u8, t3)
121
122 // another identity's cert cannot ride this signature
123 let ek2: *u8 = sys_mmap(32)
124 var c: i64 = 0
125 while c < 32 { ek2[c] = (0x88 + c) as u8; c = c + 1 }
126 let cert2: *u8 = sys_mmap(NX_MAGIC_4096); let cl2: *i64 = sys_mmap(8) as *i64; let pl2: *i64 = u256_alloc()
127 cae_mint(ek2, uid, cert2, NX_MAGIC_4096, cl2, pl2)
128 var t4: i64 = 0
129 if tls13_verify_client_auth_ecdsa(cert2, cl2[0], NX_CAE_SS_ECDSA_P256, sig, sig_n, th) != NX_CAE_OK { t4 = 1 }
130 pass = pass + cae_row("T4 another identity's cert cannot ride this signature (no impersonation)" as *u8, t4)
131
132 var t5: i64 = 0
133 if tls13_verify_client_auth_ecdsa(cert, cl[0], 0x0807, sig, sig_n, th) != NX_CAE_OK { t5 = 1 }
134 pass = pass + cae_row("T5 wrong scheme (Ed25519 0x0807) rejected on the ECDSA path" as *u8, t5)
135
136 if pass == 5 {
137 cae_w("NX-TLS13-CLIENTAUTH-ECDSA GATE GREEN 5/5 (browser ECDSA-P256 client-auth verifies; no cookie/header/JS)\n" as *u8)
138 sys_exit(0)
139 }
140 cae_w("NX-TLS13-CLIENTAUTH-ECDSA GATE RED\n" as *u8)
141 sys_exit(1)
142 return 1
143}