code wiki / (root) / nx_tls13_client_h2run.nx

nx_tls13_client_h2run.nx source

↩ module page · 174 lines · 7786 B

1// nx_tls13_client_h2run.nx -- TUTOR-BOOTSTRAP SCAFFOLD (Claude, authored under 2// the R4-H2 HTTP/2-transport-ladder workflow, capstone NBC-FETCH-001), NOT 3// credited as team self-authoring. 4// 5// THE h2-NEGOTIATING TLS 1.3 client handshake orchestrator. It is the 6// byte-for-byte twin of nx_tls13_client_session_run.nx (step 3c.6b) EXCEPT the 7// ClientHello it emits advertises ALPN ["h2"] ONLY (via the new 8// tls13_client_hello_emit2_h2only / tls13_ext_emit_alpn_h2_only). Consequence 9// (RFC 7301 ยง3.2): the server either selects "h2" -- so a CONNECTED session here 10// means the application-data channel IS HTTP/2, no encrypted-EE ALPN parse needed 11// -- or it MUST abort the handshake with no_application_protocol. This is how a 12// sovereign client makes "TLS ALPN selects h2" a yes/no the caller can branch on. 13// 14// FOUNDED ON (composes, does NOT reinvent -- anti-orphan law; each imported 15// EXACTLY ONCE, the RC6 double-import landmine avoided): 16// - nx_tls13_client_session.nx (the session struct + emit-CH internals + 17// transcript update; it transitively imports nx_tls13_hello.nx where the new 18// h2-only ClientHello emitter and ALPN-h2-only extension live) 19// - the recv_sh / recv_hs / emit_finished / derive_app step organs and 20// nx_tls13_read_record_from_fd -- the SAME set nx_tls13_client_session_run 21// imports, so the handshake body is identical to the proven HTTP/1.1 run. 22// This module deliberately does NOT import nx_tls13_client_session_run.nx (that 23// would double-define _write_n etc.); helper / const names carry an _H2 suffix so 24// a caller may import BOTH this and the http/1.1 run with no symbol clash. 25// 26// BACK-FILL: the team RE-AUTHORS this from the DATA spec 27// (knowledge/specs/2026-06-13-http2-transport-ladder.md) via the 28// emitter-of-emitters (X-AUT-006c/e/f); this hand scaffold is the sanctioned 29// one-time bootstrap only (meter-integrity). 30// 31// license_tier: INDEPENDENT_REDERIVE 32// genealogy_id: international-research-sources/ietf/rfc_8446 + rfc_7301 + rfc_9113 33// lineage_id: nishi_tls13_client_h2run_r4h2_capstone 34 35import "nx_syscalls.nx" 36import "nx_tls13.nx" 37import "nx_tls13_record.nx" 38import "nx_tls13_client_validate_certificate.nx" 39import "nx_tls13_client_session.nx" 40import "nx_tls13_client_session_recv_sh.nx" 41import "nx_tls13_client_session_recv_hs.nx" 42import "nx_tls13_client_session_emit_finished.nx" 43import "nx_tls13_client_session_derive_app.nx" 44import "nx_tls13_read_record_from_fd.nx" 45const NX_MAGIC_1024: i64 = 1024 46 47const NX_TLS13_H2RUN_OK: i64 = 1 48const NX_TLS13_H2RUN_EMIT_CH_FAIL: i64 = 2 49const NX_TLS13_H2RUN_WRITE_CH_FAIL: i64 = 3 50const NX_TLS13_H2RUN_READ_SH_FAIL: i64 = 4 51const NX_TLS13_H2RUN_RECV_SH_FAIL: i64 = 5 52const NX_TLS13_H2RUN_READ_HS_FAIL: i64 = 6 53const NX_TLS13_H2RUN_RECV_HS_FAIL: i64 = 7 54const NX_TLS13_H2RUN_EMIT_CF_FAIL: i64 = 8 55const NX_TLS13_H2RUN_WRITE_CF_FAIL: i64 = 9 56const NX_TLS13_H2RUN_DERIVE_APP_FAIL: i64 = 10 57const NX_TLS13_H2RUN_LOOP_BUDGET_EXCEEDED: i64 = 11 58 59const NX_TLS13_H2RUN_MAX_HS_RECORDS: i64 = 8 60const NX_TLS13_H2RUN_RECORD_BUF_BYTES: i64 = 16645 61 62// Write exactly n bytes to fd (loop sys_write). 0 ok, -1 error. 63func _h2run_write_n(fd: i64, buf: *u8, n: i64) -> i64 { 64 var off: i64 = 0 65 while off < n { 66 let w: i64 = sys_write(fd, (buf as i64 + off) as *u8, n - off) 67 if w <= 0 { return 0 - 1 } 68 off = off + w 69 } 70 return 0 71} 72 73func _h2run_is_ccs(buf: *u8, n: i64) -> i64 { 74 if n != NX_TLS13_RECORD_HEADER_LEN + 1 { return 0 } 75 if (buf[0] & 0xff) != NX_TLS13_CT_CHANGE_CIPHER_SPEC { return 0 } 76 if (buf[5] & 0xff) != 1 { return 0 } 77 return 1 78} 79 80// Emit the ClientHello advertising ALPN ["h2"] only, feed it into the transcript 81// hash, advance state to CH_SENT. Twin of nx_tls13_client_session_emit_ch but 82// calls tls13_client_hello_emit2_h2only. Returns bytes written or negative. 83func nx_tls13_client_session_emit_ch_h2( 84 s: *Tls13ClientSession, 85 sni: *u8, sni_len: i64, 86 out_buf: *u8, out_cap: i64 87) -> i64 { 88 if s.state != NX_TLS13_CSESSION_STATE_INIT { 89 return 0 - NX_TLS13_CSESSION_BAD_STATE 90 } 91 if out_cap < 160 { return 0 - NX_TLS13_CSESSION_BUF_OVERFLOW } 92 let n: i64 = tls13_client_hello_emit2_h2only( 93 s.client_random, sni, sni_len, s.x25519_pub, s.p256_pub, 94 out_buf, out_cap 95 ) 96 if n < 0 { return 0 - NX_TLS13_CSESSION_INTERNAL } 97 nx_tls13_transcript_update(s.transcript, out_buf, n) 98 s.state = NX_TLS13_CSESSION_STATE_CH_SENT 99 return n 100} 101 102// Drive the full TLS 1.3 client handshake to CONNECTED with ALPN ["h2"] only. 103// POSITIVE pointer-as-i64 to the connected session on success (the channel IS 104// h2). NEGATIVE -NX_TLS13_H2RUN_* on failure (an h2-refusing host shows up as a 105// handshake failure here -- honestly named, never a faked 200). 106func nx_tls13_client_session_run_h2( 107 fd: i64, 108 sni: *u8, sni_len: i64, 109 client_random: *u8, 110 x25519_priv: *u8, 111 val_ctx: *TlsValidationContext 112) -> i64 { 113 let s: *Tls13ClientSession = nx_tls13_client_session_new(client_random, x25519_priv) 114 115 let ch_buf: *u8 = sys_mmap(NX_MAGIC_1024) 116 let ch_n: i64 = nx_tls13_client_session_emit_ch_h2(s, sni, sni_len, ch_buf, NX_MAGIC_1024) 117 if ch_n < 0 { return 0 - NX_TLS13_H2RUN_EMIT_CH_FAIL } 118 119 let ch_record: *u8 = sys_mmap(NX_MAGIC_1024 + NX_TLS13_RECORD_HEADER_LEN) 120 ch_record[0] = NX_TLS13_CT_HANDSHAKE & 0xff 121 ch_record[1] = 0x03; ch_record[2] = 0x01 122 ch_record[3] = ((ch_n >> 8) & 0xff) as u8 123 ch_record[4] = (ch_n & 0xff) as u8 124 var ci: i64 = 0 125 while ci < ch_n { 126 ch_record[NX_TLS13_RECORD_HEADER_LEN + ci] = ch_buf[ci] 127 ci = ci + 1 128 } 129 130 let wr_ch: i64 = _h2run_write_n(fd, ch_record, NX_TLS13_RECORD_HEADER_LEN + ch_n) 131 if wr_ch < 0 { return 0 - NX_TLS13_H2RUN_WRITE_CH_FAIL } 132 133 let sh_record: *u8 = sys_mmap(NX_TLS13_H2RUN_RECORD_BUF_BYTES) 134 let sh_total: i64 = nx_tls13_read_record_from_fd(fd, sh_record, NX_TLS13_H2RUN_RECORD_BUF_BYTES) 135 if sh_total < 0 { return 0 - NX_TLS13_H2RUN_READ_SH_FAIL } 136 137 let sh_body: *u8 = sh_record + NX_TLS13_RECORD_HEADER_LEN 138 let sh_body_len: i64 = sh_total - NX_TLS13_RECORD_HEADER_LEN 139 let rs_v: i64 = nx_tls13_client_session_recv_sh(s, sh_body, sh_body_len) 140 if rs_v != NX_TLS13_RECV_SH_OK { return 0 - NX_TLS13_H2RUN_RECV_SH_FAIL } 141 142 var loop_count: i64 = 0 143 while s.state != NX_TLS13_CSESSION_STATE_WAIT_CLIENT_FIN { 144 if loop_count >= NX_TLS13_H2RUN_MAX_HS_RECORDS { 145 return 0 - NX_TLS13_H2RUN_LOOP_BUDGET_EXCEEDED 146 } 147 let hs_record: *u8 = sys_mmap(NX_TLS13_H2RUN_RECORD_BUF_BYTES) 148 let hs_total: i64 = nx_tls13_read_record_from_fd(fd, hs_record, NX_TLS13_H2RUN_RECORD_BUF_BYTES) 149 if hs_total < 0 { return 0 - NX_TLS13_H2RUN_READ_HS_FAIL } 150 if _h2run_is_ccs(hs_record, hs_total) == 1 { 151 loop_count = loop_count + 1 152 } else { 153 let rh_v: i64 = nx_tls13_client_session_recv_hs(s, hs_record, hs_total, val_ctx) 154 if rh_v != NX_TLS13_RECV_HS_OK { return 0 - NX_TLS13_H2RUN_RECV_HS_FAIL } 155 loop_count = loop_count + 1 156 } 157 } 158 159 let cf_buf: *u8 = sys_mmap(128) 160 let cf_n: i64 = nx_tls13_client_session_emit_finished(s, cf_buf, 128) 161 if cf_n < 0 { return 0 - NX_TLS13_H2RUN_EMIT_CF_FAIL } 162 let wr_cf: i64 = _h2run_write_n(fd, cf_buf, cf_n) 163 if wr_cf < 0 { return 0 - NX_TLS13_H2RUN_WRITE_CF_FAIL } 164 165 let da_v: i64 = nx_tls13_client_session_derive_app(s) 166 if da_v != NX_TLS13_DERIVE_APP_OK { return 0 - NX_TLS13_H2RUN_DERIVE_APP_FAIL } 167 168 return s as i64 169} 170 171// Compile-only smoke. The real proof is the live h2 GET gate that composes this. 172func main() -> i64 { 173 return 0 174}