code wiki / (root) / nx_tls13_clientauth_ecdsa.nx

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}