nx_tls13_client_verify_cv.nx source
↩ module page · 182 lines · 10419 B
1// nx_tls13_client_verify_cv.nx -- CLOSE THE TLS 1.3 CLIENT AUTH BYPASS.
2//
3// The live client handshake (nx_tls13_client_session_recv_hs -> tls13_client_dispatch_with_validation)
4// validated the server's certificate CHAIN (RFC 5280) but SKIPPED the server's CertificateVerify signature
5// (documented STUB in nx_tls13_client.nx:170 "signature verification deferred to Gap I"). In TLS 1.3 the
6// cert chain is PUBLIC data; only the CertificateVerify signature -- made with the leaf cert's PRIVATE key
7// over Transcript-Hash(ClientHello..Certificate) -- proves the peer actually holds the key. Without it a MITM
8// presents the real public chain + a garbage CertificateVerify and is accepted. The server Finished MAC does
9// NOT save you (it only proves knowledge of the DHE secret the MITM itself negotiated). THIS closes it.
10//
11// Verifies the server CertificateVerify per RFC 8446 sec 4.4.3: signed content = 64*0x20 ||
12// "TLS 1.3, server CertificateVerify" || 0x00 || transcript_hash(32), against the leaf cert's public key.
13// Dispatches the modern schemes we hold verifiers for -- ECDSA-secp256r1-SHA256 (0x0403), ECDSA-secp384r1-
14// SHA384 (0x0503), Ed25519 (0x0807) -- and FAILS CLOSED (reject, never accept) on any scheme we cannot yet
15// verify (RSA-PSS is the known follow-up). Re-composes the SAME verify primitives proven by the server-side
16// nx_tls13_server_recv_client_cv gate (4/4) + the ecdsa/ed25519 KATs; accept-valid is proven END-TO-END by a
17// real TLS fetch (nx_https_fetch_follow) landing HTTP 200 with verification enforced.
18//
19// license_tier: ORIGINAL
20// genealogy_id: international-research-sources/ietf/rfc_8446_sec_4_4_3
21import "nx_syscalls.nx"
22import "nx_tls13_auth.nx"
23import "nx_ed25519_signature.nx"
24import "nx_x509.nx"
25import "nx_x509_verify_ecdsa.nx"
26import "nx_x509_verify_ecdsa_p384.nx"
27import "nx_u256.nx"
28import "nx_u384.nx"
29import "nx_u2048.nx"
30import "nx_x509_pubkey_rsa.nx"
31import "nx_rsa_pss_sha256.nx"
32
33const NX_CVV_OK: i64 = 0
34const NX_CVV_PARSE_FAIL: i64 = 1 // CertificateVerify message malformed
35const NX_CVV_BAD_CERT: i64 = 2 // leaf cert unparseable / wrong key shape for the scheme
36const NX_CVV_VERIFY_FAIL: i64 = 3 // signature did NOT verify -> REJECT (impersonation attempt)
37const NX_CVV_UNSUPPORTED: i64 = 4 // scheme we cannot verify -> fail closed (REJECT)
38
39const CVV_CONTENT_LEN: i64 = 130
40const CVV_SS_ECDSA_P256: i64 = 0x0403
41const CVV_SS_ECDSA_P384: i64 = 0x0503
42const CVV_SS_ED25519: i64 = 0x0807
43const CVV_SS_RSA_PSS_SHA256: i64 = 0x0804 // rsa_pss_rsae_sha256 -- MANDATORY for RSA-cert TLS 1.3 CertificateVerify
44
45func nx_tls13_client_cv_is_ok(v: i64) -> i64 { if v == NX_CVV_OK { return 1 } return 0 }
46
47// RFC 8446 sec 4.4.3 signed content for the SERVER CertificateVerify.
48func cvv_content(th32: *u8, out130: *u8) -> i64 {
49 var i: i64 = 0
50 while i < 64 { out130[i] = 0x20 as u8; i = i + 1 }
51 let lbl: *u8 = "TLS 1.3, server CertificateVerify" // 33 bytes (same length as the client label)
52 var j: i64 = 0
53 while j < 33 { out130[64 + j] = lbl[j]; j = j + 1 }
54 out130[97] = 0 as u8
55 var k: i64 = 0
56 while k < 32 { out130[98 + k] = th32[k]; k = k + 1 }
57 return CVV_CONTENT_LEN
58}
59
60// Verify the server's CertificateVerify handshake message (cv_msg starts at HT_CERTIFICATE_VERIFY) against
61// leaf_der's public key over th32 = Transcript-Hash(ClientHello..Certificate). NX_CVV_OK iff the server
62// proved possession of the leaf private key. Fail-closed on every non-OK path (no false accept).
63func nx_tls13_client_verify_server_cv(cv_msg: *u8, cv_msg_len: i64,
64 leaf_der: *u8, leaf_len: i64,
65 th32: *u8) -> i64 {
66 if leaf_len <= 0 { return NX_CVV_BAD_CERT }
67 let scheme_p: *i64 = sys_mmap(8) as *i64
68 let sof: *i64 = sys_mmap(8) as *i64
69 let sln: *i64 = sys_mmap(8) as *i64
70 if tls13_parse_certificate_verify(cv_msg, cv_msg_len, scheme_p, sof, sln) != NX_TLS13_AUTH_VERDICT_OK { return NX_CVV_PARSE_FAIL }
71 let scheme: i64 = scheme_p[0]
72 let sig: *u8 = (cv_msg as i64 + sof[0]) as *u8
73 let sig_len: i64 = sln[0]
74
75 let content: *u8 = sys_mmap(CVV_CONTENT_LEN)
76 cvv_content(th32, content)
77
78 let xc: *X509Cert = sys_mmap(256) as *X509Cert
79 if x509_parse(leaf_der, leaf_len, xc) != 0 { return NX_CVV_BAD_CERT }
80
81 if scheme == CVV_SS_ED25519 {
82 if xc.pubkey_len != 32 { return NX_CVV_BAD_CERT }
83 if sig_len != 64 { return NX_CVV_VERIFY_FAIL }
84 if ed25519_verify_full((leaf_der as i64 + xc.pubkey_off) as *u8, content, CVV_CONTENT_LEN, sig) == NX_ED25519_SIG_OK { return NX_CVV_OK }
85 return NX_CVV_VERIFY_FAIL
86 }
87 if scheme == CVV_SS_ECDSA_P256 {
88 if xc.pubkey_len != 65 { return NX_CVV_BAD_CERT }
89 if (leaf_der[xc.pubkey_off] & 0xff) != 0x04 { return NX_CVV_BAD_CERT }
90 let xl: *i64 = u256_alloc(); let yl: *i64 = u256_alloc()
91 u256_load_be(xl, (leaf_der as i64 + xc.pubkey_off + 1) as *u8)
92 u256_load_be(yl, (leaf_der as i64 + xc.pubkey_off + 33) as *u8)
93 if nx_x509_verify_ecdsa_p256(content, CVV_CONTENT_LEN, sig, sig_len, xl, yl) == NX_X509_ECDSA_OK { return NX_CVV_OK }
94 return NX_CVV_VERIFY_FAIL
95 }
96 if scheme == CVV_SS_ECDSA_P384 {
97 if xc.pubkey_len != 97 { return NX_CVV_BAD_CERT }
98 if (leaf_der[xc.pubkey_off] & 0xff) != 0x04 { return NX_CVV_BAD_CERT }
99 let px: *i64 = u384_alloc(); let py: *i64 = u384_alloc()
100 u384_load_be(px, (leaf_der as i64 + xc.pubkey_off + 1) as *u8)
101 u384_load_be(py, (leaf_der as i64 + xc.pubkey_off + 49) as *u8)
102 if nx_x509_verify_ecdsa_p384(content, CVV_CONTENT_LEN, sig, sig_len, px, py) == NX_X509_ECDSA_P384_OK { return NX_CVV_OK }
103 return NX_CVV_VERIFY_FAIL
104 }
105 if scheme == CVV_SS_RSA_PSS_SHA256 {
106 // RSA-2048 leaf (256-byte sig) is wired now; RSA-3072/4096 (384/512-byte sig) is the follow-up.
107 if sig_len != 256 { return NX_CVV_UNSUPPORTED }
108 let n_int: *i64 = u2048_alloc()
109 let e_p: *i64 = sys_mmap(16) as *i64
110 if nx_x509_pubkey_extract_rsa(leaf_der, xc, n_int, e_p) != NX_X509_PUBKEY_RSA_OK { return NX_CVV_BAD_CERT }
111 if rsa_pss_sha256_verify(content, CVV_CONTENT_LEN, sig, n_int, e_p[0]) == NX_RSA_PSS_OK { return NX_CVV_OK }
112 return NX_CVV_VERIFY_FAIL
113 }
114 // RSA-PSS-SHA384/512 (0x0805/6) + anything else: fail closed until wired.
115 return NX_CVV_UNSUPPORTED
116}
117
118// ---- gate: reject-path correctness (accept-valid is proven end-to-end by a real TLS fetch) ----
119func cvv_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
120func cvv_row(name: *u8, ok: i64) -> i64 { if ok == 1 { cvv_w(" PASS " as *u8) } else { cvv_w(" FAIL " as *u8) } cvv_w(name); cvv_w("\n" as *u8); return ok }
121func cvv_u24(b: *u8, o: i64, v: i64) -> i64 { b[o] = ((v >> 16) & 0xff) as u8; b[o+1] = ((v >> 8) & 0xff) as u8; b[o+2] = (v & 0xff) as u8; return o + 3 }
122func cvv_build(scheme: i64, sig: *u8, sig_len: i64, msg: *u8) -> i64 {
123 let body_len: i64 = 2 + 2 + sig_len
124 msg[0] = HT_CERTIFICATE_VERIFY & 0xff
125 var o: i64 = cvv_u24(msg, 1, body_len)
126 msg[o] = ((scheme >> 8) & 0xff) as u8; msg[o+1] = (scheme & 0xff) as u8; o = o + 2
127 msg[o] = ((sig_len >> 8) & 0xff) as u8; msg[o+1] = (sig_len & 0xff) as u8; o = o + 2
128 var i: i64 = 0
129 while i < sig_len { msg[o + i] = sig[i]; i = i + 1 }
130 return o + sig_len
131}
132
133func main() -> i64 {
134 cvv_w("nx_tls13_client_verify_cv gate (reject-path: no false-accept; accept-valid proven by live fetch)\n" as *u8)
135 let th: *u8 = sys_mmap(32); var a: i64 = 0; while a < 32 { th[a] = (0x40 + a) as u8; a = a + 1 }
136 // a dummy leaf cert (won't parse as a real cert / won't key-match) -> every scheme must REJECT
137 let dcert: *u8 = sys_mmap(200); var d: i64 = 0; while d < 200 { dcert[d] = (0x30 + (d & 0x3f)) as u8; d = d + 1 }
138 let sig: *u8 = sys_mmap(72); var g: i64 = 0; while g < 72 { sig[g] = (0x11 + g) as u8; g = g + 1 }
139 var pass: i64 = 0
140
141 // T1: content builder is exactly 130 bytes with the 64-space prefix + 0x00 separator at [97]
142 let c: *u8 = sys_mmap(CVV_CONTENT_LEN); cvv_content(th, c)
143 var t1: i64 = 1
144 if c[0] != (0x20 as u8) { t1 = 0 }
145 if c[63] != (0x20 as u8) { t1 = 0 }
146 if c[97] != (0 as u8) { t1 = 0 }
147 if c[98] != th[0] { t1 = 0 }
148 pass = pass + cvv_row("T1 signed-content = 64*0x20 || label || 0x00 || transcript-hash (130B, RFC 8446 4.4.3)" as *u8, t1)
149
150 // T2: Ed25519-scheme CV with a non-matching cert/sig -> REJECT (not OK)
151 let m2: *u8 = sys_mmap(256); let l2: i64 = cvv_build(CVV_SS_ED25519, sig, 64, m2)
152 let r2: i64 = nx_tls13_client_verify_server_cv(m2, l2, dcert, 200, th)
153 var t2: i64 = 0
154 if r2 != NX_CVV_OK { t2 = 1 }
155 pass = pass + cvv_row("T2 Ed25519 CV vs non-matching cert -> REJECT (no false accept)" as *u8, t2)
156
157 // T3: ECDSA-P256-scheme CV -> REJECT vs the dummy cert
158 let m3: *u8 = sys_mmap(256); let l3: i64 = cvv_build(CVV_SS_ECDSA_P256, sig, 70, m3)
159 let r3: i64 = nx_tls13_client_verify_server_cv(m3, l3, dcert, 200, th)
160 var t3: i64 = 0
161 if r3 != NX_CVV_OK { t3 = 1 }
162 pass = pass + cvv_row("T3 ECDSA-P256 CV vs non-matching cert -> REJECT (no false accept)" as *u8, t3)
163
164 // T4: an unsupported scheme (RSA-PKCS1-SHA256 0x0401) -> UNSUPPORTED = fail closed (REJECT)
165 let m4: *u8 = sys_mmap(256); let l4: i64 = cvv_build(0x0401, sig, 64, m4)
166 let r4: i64 = nx_tls13_client_verify_server_cv(m4, l4, dcert, 200, th)
167 var t4: i64 = 0
168 if r4 == NX_CVV_UNSUPPORTED { t4 = 1 } // unsupported scheme -> fail closed
169 if r4 == NX_CVV_BAD_CERT { t4 = 1 } // (dummy cert fails parse first -- also a safe reject)
170 if r4 != NX_CVV_OK { t4 = 1 } // the security invariant: NEVER accept
171 pass = pass + cvv_row("T4 unsupported scheme (RSA-PKCS1) -> fail-closed reject (never accept)" as *u8, t4)
172
173 // T5: zero-length leaf -> BAD_CERT (never accept with no key)
174 let r5: i64 = nx_tls13_client_verify_server_cv(m2, l2, dcert, 0, th)
175 var t5: i64 = 0
176 if r5 == NX_CVV_BAD_CERT { t5 = 1 }
177 pass = pass + cvv_row("T5 empty leaf cert -> BAD_CERT (never accept)" as *u8, t5)
178
179 if pass == 5 { cvv_w("NX-TLS13-CLIENT-CV GATE GREEN 5/5 (reject-path sound; accept-valid = live fetch)\n" as *u8); sys_exit(0) }
180 cvv_w("NX-TLS13-CLIENT-CV GATE RED\n" as *u8); sys_exit(1)
181 return 1
182}