code wiki / (root) / nx_tls13_client_verify_cv.nx

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}