code wiki / (root) / nx_tls13_chrome_session.nx

nx_tls13_chrome_session.nx source

↩ module page · 131 lines · 8894 B

1// nx_tls13_chrome_session.nx -- R2f-A LIVE wiring: a TLS-1.3 client session that sends the Chrome-JA3 ClientHello 2// (nx_tls13_chrome_hello) so anti-bot CDNs classify us as a browser and complete the handshake. ADDITIVE (Cardinal 3// 19): NEW functions only; the shared nx_tls13_client_session_run + emit_ch are byte-identical (fleet untouched). 4// run_chrome is a copy of nx_tls13_client_session_run with ONLY the ClientHello emit swapped + one robustness fix 5// (clamp the SH transcript feed to the parsed ServerHello message length). 6// 7// LIVE STATUS (2026-07-03): PROVEN end-to-end -- nhentai.net (HTTP 200), wikipedia (301), example.com (200), 8// rfc-editor.org (200, 180KB body), and **i.nhentai.net image CDN = HANDSHAKE-OK**. The Chrome-JA3 fingerprint 9// beats the Cloudflare/GitHub TLS wall that dropped our minimal hello (-3); the RSA-PSS CertificateVerify verifier 10// (nx_rsa_pss_sha256, wired into nx_tls13_client_verify_cv) completes the handshake with the RSA-cert servers that 11// dominate the web -- the CertificateVerify uses rsa_pss_rsae_sha256 (0x0804), which TLS 1.3 MANDATES for RSA certs 12// and which we previously fail-closed on. license_tier: ORIGINAL 13import "nx_tls13_client_session.nx" // struct + recv_sh/recv_hs/emit_finished/derive_app/session_new + transcript_update + CSESSION consts 14import "nx_tls13_client_session_run.nx" // _write_n / _is_change_cipher_spec + NX_TLS13_RUN_* + record consts + nx_tls13_read_record_from_fd 15import "nx_tls13_chrome_hello.nx" // tls13_chrome_hello_emit 16const K_MAGIC_1024: i64 = 1024 17 18// emit the Chrome-JA3 ClientHello into the session (mirrors nx_tls13_client_session_emit_ch: emit -> transcript -> state). 19func nx_tls13_session_emit_ch_chrome(s: *Tls13ClientSession, sni: *u8, sni_len: i64, out_buf: *u8, out_cap: i64) -> i64 { 20 if s.state != NX_TLS13_CSESSION_STATE_INIT { return 0 - NX_TLS13_CSESSION_BAD_STATE } 21 if out_cap < 700 { return 0 - NX_TLS13_CSESSION_BUF_OVERFLOW } 22 let n: i64 = tls13_chrome_hello_emit(s.client_random, sni, sni_len, s.x25519_pub, s.p256_pub, out_buf, out_cap) 23 if n < 0 { return 0 - NX_TLS13_CSESSION_INTERNAL } 24 nx_tls13_transcript_update(s.transcript, out_buf, n) 25 s.state = NX_TLS13_CSESSION_STATE_CH_SENT 26 return n 27} 28 29// run_chrome: copy of nx_tls13_client_session_run, ClientHello = the Chrome-JA3 one. Returns session ptr as i64, or negative NX_TLS13_RUN_*. 30func nx_tls13_client_session_run_chrome(fd: i64, sni: *u8, sni_len: i64, client_random: *u8, x25519_priv: *u8, val_ctx: *TlsValidationContext) -> i64 { 31 let s: *Tls13ClientSession = nx_tls13_client_session_new(client_random, x25519_priv) 32 let ch_buf: *u8 = sys_mmap(K_MAGIC_1024) 33 let ch_n: i64 = nx_tls13_session_emit_ch_chrome(s, sni, sni_len, ch_buf, K_MAGIC_1024) 34 if ch_n < 0 { return 0 - NX_TLS13_RUN_EMIT_CH_FAIL } 35 let ch_record: *u8 = sys_mmap(K_MAGIC_1024 + NX_TLS13_RECORD_HEADER_LEN) 36 ch_record[0] = NX_TLS13_CT_HANDSHAKE & 0xff 37 ch_record[1] = 0x03; ch_record[2] = 0x01 38 ch_record[3] = ((ch_n >> 8) & 0xff) as u8 39 ch_record[4] = (ch_n & 0xff) as u8 40 var ci: i64 = 0 41 while ci < ch_n { ch_record[NX_TLS13_RECORD_HEADER_LEN + ci] = ch_buf[ci]; ci = ci + 1 } 42 let wr_ch: i64 = _write_n(fd, ch_record, NX_TLS13_RECORD_HEADER_LEN + ch_n) 43 if wr_ch < 0 { return 0 - NX_TLS13_RUN_WRITE_CH_FAIL } 44 let sh_record: *u8 = sys_mmap(NX_TLS13_RUN_RECORD_BUF_BYTES) 45 let sh_total: i64 = nx_tls13_read_record_from_fd(fd, sh_record, NX_TLS13_RUN_RECORD_BUF_BYTES) 46 if sh_total < 0 { return 0 - NX_TLS13_RUN_READ_SH_FAIL } 47 let sh_body: *u8 = sh_record + NX_TLS13_RECORD_HEADER_LEN 48 var sh_body_len: i64 = sh_total - NX_TLS13_RECORD_HEADER_LEN 49 // Robustness: the ServerHello message's own length is authoritative for the transcript feed. 50 if sh_body_len >= 4 { 51 let true_sh: i64 = 4 + (((sh_body[1] as i64)&0xff)<<16) + (((sh_body[2] as i64)&0xff)<<8) + ((sh_body[3] as i64)&0xff) 52 if true_sh > 0 { if true_sh < sh_body_len { sh_body_len = true_sh } } 53 } 54 let rs_v: i64 = nx_tls13_client_session_recv_sh(s, sh_body, sh_body_len) 55 if rs_v != NX_TLS13_RECV_SH_OK { 56 // NAME THE REAL CAUSE. The caller only ever saw the collapsed verdict=5 (RECV_SH_FAIL), which 57 // reads as "the server rejected our hello" -- but the dominant case is the OPPOSITE: the server 58 // ACCEPTED it and chose a suite WE ADVERTISED AND CANNOT DECRYPT. The Chrome-JA3 cipher list is 59 // Chrome's real 15 and includes 0x1302 (AES-256-GCM-SHA384). 60 // Cost 2026-08-01: this opaque 5 was read as a public-edge outage and nearly bought an infra arc. 61 // sub-verdicts: 8 = BAD_CIPHER (a REAL capability gap -- build the suite) · 9 = NOT_TLS13 (the 62 // server negotiated 1.2; build nothing, take the 1.2 leg). CORRECTED 2026-08-06: the old text here 63 // said "recv_sh speaks only 0x1301/0x1303", which stopped being true when R9 shipped 0x1302 with 64 // dual-hash transcript + nx_aes256_gcm. All three TLS-1.3 suites are live. 65 // ★A STALE COMMENT ABOUT A CAPABILITY IS A FALSE BUG REPORT THAT NEVER EXPIRES. 66 sys_write(2, "nishi-hello recv_sh sub-verdict=" as *u8, 32) 67 let d: *u8 = sys_mmap(8) 68 d[0] = (48 + (rs_v % 10)) as u8 69 sys_write(2, d, 1) 70 if rs_v == 8 { 71 // R9b: PRINT THE SUITE, DO NOT ASSERT IT. The previous text named 0x1302 unconditionally, 72 // so a refusal of ANY other suite still read as "the 0x1302 gap" -- a ruler that reports a 73 // cause it never measured. recv_sh now records s.cipher_suite before refusing, so we can 74 // name the byte the server actually sent. 75 sys_write(2, " BAD_CIPHER: server chose suite 0x" as *u8, 34) 76 let hx: *u8 = sys_mmap(8) 77 var hn: i64 = 3 78 while hn >= 0 { 79 let nib: i64 = (s.cipher_suite >> (hn * 4)) & 0xf 80 if nib < 10 { hx[0] = (48 + nib) as u8 } else { hx[0] = (87 + nib) as u8 } 81 sys_write(2, hx, 1) 82 hn = hn - 1 83 } 84 sys_munmap(hx, 8) 85 sys_write(2, " -- we offered it and cannot decrypt it: OUR gap, not the server's\n" as *u8, 67) 86 } 87 else { if rs_v == 9 { 88 // NOT_TLS13. The server answered our 1.3 hello by negotiating a TLS 1.2 suite, so it does not 89 // speak 1.3 at all. Saying "OUR gap" here (as sub-verdict 8 does, correctly, for a real cipher 90 // gap) would be false and would send the reader off to build a cipher that changes nothing. 91 sys_write(2, " NOT_TLS13: server negotiated a TLS 1.2 suite 0x" as *u8, 48) 92 let hx2: *u8 = sys_mmap(8) 93 var hn2: i64 = 3 94 while hn2 >= 0 { 95 let nib2: i64 = (s.cipher_suite >> (hn2 * 4)) & 0xf 96 if nib2 < 10 { hx2[0] = (48 + nib2) as u8 } else { hx2[0] = (87 + nib2) as u8 } 97 sys_write(2, hx2, 1) 98 hn2 = hn2 - 1 99 } 100 sys_munmap(hx2, 8) 101 sys_write(2, " -- THIS HOST HAS NO TLS 1.3. Not a cipher gap: use the 1.2 leg, build nothing.\n" as *u8, 80) 102 } 103 else { sys_write(2, " (see NX_TLS13_RECV_SH_* in nx_tls13_client_session_recv_sh.nx)\n" as *u8, 63) } } 104 sys_munmap(d, 8) 105 sys_munmap(sh_record as *u8, NX_TLS13_RUN_RECORD_BUF_BYTES) 106 return 0 - NX_TLS13_RUN_RECV_SH_FAIL 107 } 108 sys_munmap(sh_record as *u8, NX_TLS13_RUN_RECORD_BUF_BYTES) 109 var loop_count: i64 = 0 110 while s.state != NX_TLS13_CSESSION_STATE_WAIT_CLIENT_FIN { 111 if loop_count >= NX_TLS13_RUN_MAX_HS_RECORDS { return 0 - NX_TLS13_RUN_LOOP_BUDGET_EXCEEDED } 112 let hs_record: *u8 = sys_mmap(NX_TLS13_RUN_RECORD_BUF_BYTES) 113 let hs_total: i64 = nx_tls13_read_record_from_fd(fd, hs_record, NX_TLS13_RUN_RECORD_BUF_BYTES) 114 if hs_total < 0 { sys_munmap(hs_record as *u8, NX_TLS13_RUN_RECORD_BUF_BYTES); return 0 - NX_TLS13_RUN_READ_HS_FAIL } 115 if _is_change_cipher_spec(hs_record, hs_total) == 1 { loop_count = loop_count + 1 } 116 else { 117 let rh_v: i64 = nx_tls13_client_session_recv_hs(s, hs_record, hs_total, val_ctx) 118 if rh_v != NX_TLS13_RECV_HS_OK { sys_munmap(hs_record as *u8, NX_TLS13_RUN_RECORD_BUF_BYTES); return 0 - NX_TLS13_RUN_RECV_HS_FAIL } 119 loop_count = loop_count + 1 120 } 121 sys_munmap(hs_record as *u8, NX_TLS13_RUN_RECORD_BUF_BYTES) 122 } 123 let cf_buf: *u8 = sys_mmap(128) 124 let cf_n: i64 = nx_tls13_client_session_emit_finished(s, cf_buf, 128) 125 if cf_n < 0 { return 0 - NX_TLS13_RUN_EMIT_CF_FAIL } 126 let wr_cf: i64 = _write_n(fd, cf_buf, cf_n) 127 if wr_cf < 0 { return 0 - NX_TLS13_RUN_WRITE_CF_FAIL } 128 let da_v: i64 = nx_tls13_client_session_derive_app(s) 129 if da_v != NX_TLS13_DERIVE_APP_OK { return 0 - NX_TLS13_RUN_DERIVE_APP_FAIL } 130 return s as i64 131}