nx_tls12_req.nx source
↩ module page · 454 lines · 27438 B
1// nx_tls12_req.nx -- REUSABLE sovereign TLS-1.2 HTTPS request (ECDHE-RSA-AES128-GCM-SHA256). LIBRARY (no main):
2// one call t12_request(full_url, method, token, body, ...) does connect -> ECDHE-RSA handshake -> encrypted request
3// -> incremental-decrypt response, returning the plaintext HTTP response (headers+body). This is what gets nx_gpu_ctl
4// (and Porkbun) off curl: console.vast.ai + Porkbun are TLS-1.2-only while the shipped stack is 1.3-only. Composes
5// the shipped crypto primitives (p256_ecdh_*, hmac_sha256 PRF, nx_aes128_gcm_seal/open, sha256_digest). SILENT: no
6// stdout chatter, so a caller gets clean response bytes. Proven end-to-end (authenticated account GET) 2026-07-14.
7// NOTE(security): cert-chain + ServerKeyExchange-signature validation NOT yet wired -- confidential but not yet
8// MITM-authenticated; harden before trusting for adversarial networks. license_tier: ORIGINAL
9import "nx_syscalls.nx"
10import "nx_csprng.nx"
11import "nx_https_url_for_fetch.nx"
12import "nx_https_url_connect.nx"
13import "nx_sha256.nx"
14import "nx_p256_ecdh.nx"
15import "nx_hmac.nx" // was hmac.nx: SAME hmac_sha256 signature but the LEGACY family (syscalls.nx + sha256.nx).
16// Importing it alongside the nx_ family gave DUPLICATE hmac_sha256 the moment nx_https_fetch_follow
17// started importing this file, breaking every organ whose closure held both. This organ already
18// imports nx_sha256.nx, so the legacy pull-in was redundant as well as harmful.
19import "nx_aes128_gcm.nx"
20import "nx_trust_store_load_from_certdata.nx" // trust store loader
21import "nx_https_cert_pipeline.nx" // chain + hostname + validity verify (+ NX_HTTPS_PIPELINE_OK, x509_parse, X509Cert)
22import "nx_x509_pubkey_rsa.nx" // leaf RSA pubkey extract (2048)
23import "nx_x509_pubkey_rsa_4096.nx" // leaf RSA pubkey extract (4096/3072)
24import "nx_x509_verify_rsa_pkcs1_sha256.nx" // generic RSA-PKCS1-SHA256 verify -- used on the SKE signature
25import "nx_x509_verify_rsa_pkcs1_sha256_4096.nx"
26import "nx_u2048.nx"
27import "nx_u4096.nx"
28const TR_MAGIC_2048: i64 = 2048
29const TR_MAGIC_4096: i64 = 4096
30const TR_MAGIC_3072: i64 = 3072
31const TR_MAGIC_1024: i64 = 1024
32const TR_MAGIC_16384: i64 = 16384
33const TR_MAGIC_65536: i64 = 65536
34const TR_MAGIC_32768: i64 = 32768
35const TR_MAGIC_4194304: i64 = 4194304
36const TR_MAGIC_40960: i64 = 40960
37const TR_MAGIC_8192: i64 = 8192
38const TR_MAGIC_8300: i64 = 8300
39const TR_MAGIC_262144: i64 = 262144
40
41const TR_HS: i64 = 22
42const TR_CCS: i64 = 20
43const TR_ALERT: i64 = 21
44
45func tr_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
46func tr_b1(d: *u8, o: i64, v: i64) -> i64 { d[o]=(v&0xff) as u8; return o+1 }
47func tr_b2(d: *u8, o: i64, v: i64) -> i64 { d[o]=((v>>8)&0xff) as u8; d[o+1]=(v&0xff) as u8; return o+2 }
48func tr_b3(d: *u8, o: i64, v: i64) -> i64 { d[o]=((v>>16)&0xff) as u8; d[o+1]=((v>>8)&0xff) as u8; d[o+2]=(v&0xff) as u8; return o+3 }
49func tr_bytes(d: *u8, o: i64, s: *u8, n: i64) -> i64 { var i: i64=0; while i<n { d[o+i]=s[i]; i=i+1 } return o+n }
50func tr_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){ d[o+i]=s[i]; i=i+1 } return o+i }
51func tr_catn(d: *u8, o: i64, v: i64) -> i64 {
52 let t: *u8=sys_mmap(24); var m: i64=v; var k: i64=0
53 if m==0 { t[0]=48 as u8; k=1 }
54 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 }
55 var w: i64=o; var i: i64=0; while i<k { d[w]=t[k-1-i]; w=w+1; i=i+1 } return w
56}
57func tr_u64be(d: *u8, o: i64, v: i64) -> i64 {
58 d[o]=((v>>56)&0xff) as u8; d[o+1]=((v>>48)&0xff) as u8; d[o+2]=((v>>40)&0xff) as u8; d[o+3]=((v>>32)&0xff) as u8
59 d[o+4]=((v>>24)&0xff) as u8; d[o+5]=((v>>16)&0xff) as u8; d[o+6]=((v>>8)&0xff) as u8; d[o+7]=(v&0xff) as u8
60 return o+8
61}
62func tr_write_all(fd: i64, buf: *u8, n: i64) -> i64 {
63 var off: i64=0
64 while off<n { let w: i64=sys_write(fd, ((buf as i64)+off) as *u8, n-off); if w<=0 { return 0-1 } off=off+w }
65 return 0
66}
67// TLS 1.2 PRF = P_SHA256(secret, label||seed)
68func tr_prf(secret: *u8, secret_len: i64, label: *u8, label_len: i64, seed: *u8, seed_len: i64, out: *u8, out_len: i64) -> i64 {
69 let ls_len: i64 = label_len + seed_len
70 let ls: *u8 = sys_mmap(ls_len + 8)
71 var i: i64 = 0
72 while i < label_len { ls[i] = label[i]; i = i + 1 }
73 i = 0
74 while i < seed_len { ls[label_len + i] = seed[i]; i = i + 1 }
75 let a: *u8 = sys_mmap(32); let a_next: *u8 = sys_mmap(32)
76 hmac_sha256(secret, secret_len, ls, ls_len, a)
77 let tmp: *u8 = sys_mmap(32 + ls_len + 8); let block: *u8 = sys_mmap(32)
78 var off: i64 = 0
79 while off < out_len {
80 var j: i64 = 0
81 while j < 32 { tmp[j] = a[j]; j = j + 1 }
82 j = 0
83 while j < ls_len { tmp[32 + j] = ls[j]; j = j + 1 }
84 hmac_sha256(secret, secret_len, tmp, 32 + ls_len, block)
85 var c: i64 = 32
86 if out_len - off < 32 { c = out_len - off }
87 j = 0
88 while j < c { out[off + j] = block[j]; j = j + 1 }
89 off = off + c
90 hmac_sha256(secret, secret_len, a, 32, a_next)
91 j = 0
92 while j < 32 { a[j] = a_next[j]; j = j + 1 }
93 }
94 return 0
95}
96func tr_client_hello(host: *u8, hlen: i64, cr_out: *u8, out: *u8) -> i64 {
97 let body: *u8 = sys_mmap(TR_MAGIC_1024); var o: i64 = 0
98 o = tr_b2(body, o, 0x0303)
99 nx_csprng_fill(cr_out, 32)
100 o = tr_bytes(body, o, cr_out, 32)
101 o = tr_b1(body, o, 0)
102 o = tr_b2(body, o, 2); o = tr_b2(body, o, 0xC02F)
103 o = tr_b1(body, o, 1); o = tr_b1(body, o, 0)
104 let ext: *u8 = sys_mmap(512); var e: i64 = 0
105 e = tr_b2(ext, e, 0x0000); e = tr_b2(ext, e, hlen + 5); e = tr_b2(ext, e, hlen + 3); e = tr_b1(ext, e, 0); e = tr_b2(ext, e, hlen); e = tr_bytes(ext, e, host, hlen)
106 e = tr_b2(ext, e, 0x000A); e = tr_b2(ext, e, 4); e = tr_b2(ext, e, 2); e = tr_b2(ext, e, 0x0017)
107 e = tr_b2(ext, e, 0x000B); e = tr_b2(ext, e, 2); e = tr_b1(ext, e, 1); e = tr_b1(ext, e, 0)
108 e = tr_b2(ext, e, 0x000D); e = tr_b2(ext, e, 8); e = tr_b2(ext, e, 6); e = tr_b2(ext, e, 0x0401); e = tr_b2(ext, e, 0x0501); e = tr_b2(ext, e, 0x0601)
109 o = tr_b2(body, o, e); o = tr_bytes(body, o, ext, e)
110 let body_len: i64 = o
111 var r: i64 = 0
112 r = tr_b1(out, r, TR_HS); r = tr_b2(out, r, 0x0303); r = tr_b2(out, r, body_len + 4)
113 r = tr_b1(out, r, 1); r = tr_b3(out, r, body_len); r = tr_bytes(out, r, body, body_len)
114 return r
115}
116func tr_hdr_end(b: *u8, n: i64) -> i64 {
117 var i: i64 = 0
118 while i + 3 < n { if b[i]==(13 as u8) { if b[i+1]==(10 as u8) { if b[i+2]==(13 as u8) { if b[i+3]==(10 as u8) { return i+4 } } } } i = i + 1 }
119 return 0 - 1
120}
121func tr_lc(c: i64) -> i64 { if c >= 65 { if c <= 90 { return c + 32 } } return c }
122func tr_match_ci(b: *u8, i: i64, n: i64, targ: *u8, tlen: i64) -> i64 {
123 if i + tlen > n { return 0 }
124 var j: i64 = 0
125 while j < tlen { if tr_lc(b[i+j]&0xff) != (targ[j] as i64) { return 0 } j = j + 1 }
126 return 1
127}
128func tr_content_length(b: *u8, n: i64) -> i64 {
129 var i: i64 = 0
130 while i + 15 < n {
131 if tr_match_ci(b, i, n, "content-length:" as *u8, 15) == 1 {
132 var p: i64 = i + 15; var sk: i64 = 1
133 while sk == 1 { if p < n { if b[p]==(32 as u8) { p = p + 1 } else { sk = 0 } } else { sk = 0 } }
134 var val: i64 = 0; var any: i64 = 0; var go: i64 = 1
135 while go == 1 { if p < n { let c: i64 = b[p]&0xff; if c >= 48 { if c <= 57 { val = val*10 + (c-48); any = 1; p = p + 1 } else { go = 0 } } else { go = 0 } } else { go = 0 } }
136 if any == 1 { return val }
137 return 0 - 1
138 }
139 i = i + 1
140 }
141 return 0 - 1
142}
143// Does the header block declare the chunked transfer coding? Bounded to the headers so a body that merely
144// contains the word cannot flip it.
145func tr_is_chunked(b: *u8, hdr_end: i64) -> i64 {
146 var i: i64 = 0
147 while i + 7 <= hdr_end {
148 if tr_match_ci(b, i, hdr_end, "chunked" as *u8, 7) == 1 { return 1 }
149 i = i + 1
150 }
151 return 0
152}
153// Has the TERMINAL zero-length chunk arrived? Looks for CRLF "0" CRLF -- that byte run can only be the
154// last-chunk marker, because every other chunk-size line is hex with a non-CR byte following the digit
155// (a size of 0x0a reads "0a\r\n", so b[i+3] is 'a', not CR).
156func tr_chunk_done(b: *u8, from: i64, n: i64) -> i64 {
157 var i: i64 = from
158 while i + 5 <= n {
159 if b[i]==(13 as u8) { if b[i+1]==(10 as u8) { if b[i+2]==(48 as u8) { if b[i+3]==(13 as u8) { if b[i+4]==(10 as u8) { return 1 } } } } }
160 i = i + 1
161 }
162 return 0
163}
164// build request-line + headers (+ body) into out; returns length
165func tr_http_req(method: *u8, mlen: i64, target: *u8, tlen: i64, host: *u8, hlen: i64, token: *u8, tn: i64, body: *u8, blen: i64, out: *u8) -> i64 {
166 var o: i64 = 0
167 o = tr_bytes(out, o, method, mlen); out[o]=0x20 as u8; o=o+1
168 o = tr_bytes(out, o, target, tlen)
169 o = tr_cat(out, o, " HTTP/1.1\r\nHost: " as *u8)
170 o = tr_bytes(out, o, host, hlen)
171 if tn > 0 { o = tr_cat(out, o, "\r\nAuthorization: Bearer " as *u8); o = tr_bytes(out, o, token, tn) }
172 if blen > 0 { o = tr_cat(out, o, "\r\nContent-Type: application/json\r\nContent-Length: " as *u8); o = tr_catn(out, o, blen) }
173 o = tr_cat(out, o, "\r\nUser-Agent: nishi-tls12\r\nConnection: close\r\n\r\n" as *u8)
174 if blen > 0 { o = tr_bytes(out, o, body, blen) }
175 return o
176}
177
178// Reformat a TLS-1.2 Certificate message body ([3B list_len][ {3B certlen}{DER} ...]) into a TLS-1.3 Certificate
179// message ([1B ctx=0][3B list_len][ {3B certlen}{DER}{2B ext_len=0} ...]) so the shipped 1.3 chain+hostname+validity
180// pipeline (nx_https_cert_pipeline_verify_with_store) validates it unchanged. Returns the 1.3 message length.
181func tr_certs12_to_13(body12: *u8, len12: i64, out13: *u8) -> i64 {
182 var o: i64 = 0
183 out13[o]=11 as u8; o=o+1 // HT_CERTIFICATE handshake type (the parser checks this)
184 let hdrpos: i64 = o
185 out13[o]=0 as u8; out13[o+1]=0 as u8; out13[o+2]=0 as u8; o=o+3 // reserve 3-byte handshake-message length
186 out13[o]=0 as u8; o=o+1 // certificate_request_context length = 0
187 let listpos: i64 = o
188 out13[o]=0 as u8; out13[o+1]=0 as u8; out13[o+2]=0 as u8; o=o+3 // reserve 3-byte certificate_list length
189 var i: i64 = 3 // skip the 1.2 cert-list's own 3-byte length
190 while i + 3 <= len12 {
191 let cl: i64 = ((body12[i]&0xff)<<16)|((body12[i+1]&0xff)<<8)|(body12[i+2]&0xff)
192 if cl <= 0 { i = len12 } else {
193 out13[o]=body12[i]; out13[o+1]=body12[i+1]; out13[o+2]=body12[i+2]; o=o+3
194 var z: i64=0; while z<cl { out13[o+z]=body12[i+3+z]; z=z+1 } o=o+cl
195 out13[o]=0 as u8; out13[o+1]=0 as u8; o=o+2 // per-cert extensions length = 0
196 i = i + 3 + cl
197 }
198 }
199 let ll: i64 = o - (listpos+3) // certificate_list content length
200 out13[listpos]=((ll>>16)&0xff) as u8; out13[listpos+1]=((ll>>8)&0xff) as u8; out13[listpos+2]=(ll&0xff) as u8
201 let total: i64 = o - (hdrpos+3) // handshake-message length = everything after the 4B header
202 out13[hdrpos]=((total>>16)&0xff) as u8; out13[hdrpos+1]=((total>>8)&0xff) as u8; out13[hdrpos+2]=(total&0xff) as u8
203 return o
204}
205
206// Verify the ServerKeyExchange signature: the server signs SHA256(client_random || server_random || ecdhe_params)
207// with its certificate's private key; we verify with the leaf cert's PUBLIC key. This BINDS the ECDHE key we're
208// about to use to the (already chain-validated) cert -- closing the active-MITM gap (an attacker replaying a valid
209// cert with its own ECDHE key can't produce this signature). We only offer ECDHE_RSA (0xC02F) so the signature is
210// always RSA-PKCS1. Returns 1 on valid; 2=cert-parse 3=pubkey-extract 4=SIG-INVALID 5=malformed; 1000+alg=unsupported.
211func tr_verify_ske_sig(cr: *u8, sr: *u8, ske: *u8, ske_len: i64, leaf_der: *u8, leaf_len: i64) -> i64 {
212 if ske_len < 8 { return 5 }
213 let pubkey_len: i64 = ske[3] & 0xff
214 let params_len: i64 = 4 + pubkey_len // curve_type(1)+named_curve(2)+pklen(1)+pubkey
215 if params_len + 4 > ske_len { return 5 }
216 let sig_alg: i64 = ((ske[params_len]&0xff)<<8)|(ske[params_len+1]&0xff)
217 let sig_len: i64 = ((ske[params_len+2]&0xff)<<8)|(ske[params_len+3]&0xff)
218 let sig_ptr: *u8 = ((ske as i64) + params_len + 4) as *u8
219 if params_len + 4 + sig_len > ske_len { return 5 }
220 if sig_alg != 0x0401 { return 1000 + sig_alg } // only rsa_pkcs1_sha256 wired (fail-closed for others)
221 // tbs = client_random(32) || server_random(32) || ecdhe_params(params_len)
222 let tbs: *u8 = sys_mmap(512); var o: i64 = 0
223 var z: i64 = 0; while z < 32 { tbs[o]=cr[z]; o=o+1; z=z+1 }
224 z = 0; while z < 32 { tbs[o]=sr[z]; o=o+1; z=z+1 }
225 z = 0; while z < params_len { tbs[o]=ske[z]; o=o+1; z=z+1 }
226 let leaf_raw: *u8 = sys_mmap(256); let leaf: *X509Cert = leaf_raw as *X509Cert
227 if x509_parse(leaf_der, leaf_len, leaf) < 0 { return 2 }
228 if sig_len == 256 {
229 let n: *i64 = u2048_alloc(); let e_p: *i64 = sys_mmap(16) as *i64
230 if nx_x509_pubkey_extract_rsa(leaf_der, leaf, n, e_p) != NX_X509_PUBKEY_RSA_OK { return 3 }
231 if nx_x509_verify_rsa_pkcs1_sha256(tbs, o, sig_ptr, sig_len, n, *e_p) == NX_X509_RSA_PKCS1_SHA256_OK { return 1 }
232 return 4
233 }
234 if sig_len == 512 {
235 let n4: *i64 = u4096_alloc(); let e4: *i64 = sys_mmap(16) as *i64
236 if nx_x509_pubkey_extract_rsa_4096(leaf_der, leaf, n4, e4) != NX_X509_PUBKEY_RSA_4096_OK { return 3 }
237 if nx_x509_verify_rsa_pkcs1_sha256_4096(tbs, o, sig_ptr, sig_len, n4, *e4) == NX_X509_RSA_PKCS1_SHA256_4096_OK { return 1 }
238 return 4
239 }
240 return 5
241}
242
243// THE reusable request. full_url = "https://host/path?query". method/token/body optional (token/body=0 to omit).
244// Fills `out` with the plaintext HTTP response (status line + headers + body); returns its length, or <0 on error:
245// -1 bad url -2 connect -3 handshake(no server random/pubkey) -4 ECDHE -5 request write
246const TR_READ_TIMEOUT_SECS: i64 = 20 // declared: per-read ceiling, so a stalling peer fails fast
247func t12_request(full_url: *u8, method: *u8, mlen: i64, token: *u8, tn: i64, body: *u8, blen: i64, out: *u8, outcap: i64) -> i64 {
248 let target_raw: *u8 = sys_mmap(64); let target: *NxHttpsTarget = target_raw as *NxHttpsTarget
249 target.url = nx_url_new(); target.port = 0
250 if nx_https_url_for_fetch(full_url, target) != NX_HTTPS_URL_OK { return 0 - 1 }
251 let host: *u8 = ((full_url as i64) + target.url.host_off) as *u8
252 let hlen: i64 = target.url.host_len
253 // request-target = everything in the URL AFTER the host (path + ?query) -- NOT the parsed path (drops query)
254 let reqtgt: *u8 = ((full_url as i64) + target.url.host_off + hlen) as *u8
255 let reqtlen: i64 = tr_slen(reqtgt)
256 let now: i64 = sys_now_realtime_sec()
257 let fd_p: *i64 = sys_mmap(16) as *i64
258 if nx_https_url_connect(target, full_url, now, fd_p) != NX_HTTPS_CONNECT_OK { return 0 - 2 }
259 let fd: i64 = fd_p[0]
260 // Bound every read. Required now that a close-delimited response legitimately reads until EOF: without
261 // this, a keep-alive peer that sends neither Content-Length nor a terminal chunk would block forever.
262 sys_set_socket_timeout(fd, TR_READ_TIMEOUT_SECS)
263
264 let tr: *u8 = sys_mmap(TR_MAGIC_16384); var trlen: i64 = 0
265 let cr: *u8 = sys_mmap(32); let ch: *u8 = sys_mmap(TR_MAGIC_2048)
266 let chlen: i64 = tr_client_hello(host, hlen, cr, ch)
267 if tr_write_all(fd, ch, chlen) < 0 { sys_close(fd); return 0 - 5 }
268 trlen = tr_bytes(tr, trlen, ((ch as i64)+5) as *u8, chlen-5)
269
270 let buf: *u8 = sys_mmap(TR_MAGIC_65536); var total: i64 = 0
271 let sr: *u8 = sys_mmap(32); let spub: *u8 = sys_mmap(65)
272 let cert12: *u8 = sys_mmap(TR_MAGIC_32768); var cert12_len: i64 = 0; var got_cert: i64 = 0
273 let ske_body: *u8 = sys_mmap(TR_MAGIC_4096); var ske_len: i64 = 0
274 var got_sr: i64 = 0; var got_spub: i64 = 0; var got_shd: i64 = 0; var reads: i64 = 0
275 while got_shd == 0 {
276 if reads > 40 { got_shd = 2 }
277 let n: i64 = sys_read(fd, ((buf as i64)+total) as *u8, TR_MAGIC_65536-total)
278 if n <= 0 { got_shd = 2 } else {
279 total = total + n; reads = reads + 1
280 var p: i64 = 0
281 while p + 5 <= total {
282 let rtype: i64 = buf[p]&0xff; let rlen: i64 = ((buf[p+3]&0xff)<<8)|(buf[p+4]&0xff)
283 if p+5+rlen > total { p = total } else {
284 if rtype == TR_HS {
285 var hp: i64 = p+5; let rend: i64 = p+5+rlen
286 while hp + 4 <= rend { let ht: i64 = buf[hp]&0xff; let hl: i64 = ((buf[hp+1]&0xff)<<16)|((buf[hp+2]&0xff)<<8)|(buf[hp+3]&0xff); if ht==14 { got_shd=1 } hp=hp+4+hl }
287 }
288 p = p + 5 + rlen
289 }
290 }
291 }
292 }
293 var p: i64 = 0
294 while p + 5 <= total {
295 let rtype: i64 = buf[p]&0xff; let rlen: i64 = ((buf[p+3]&0xff)<<8)|(buf[p+4]&0xff)
296 if rtype == TR_HS {
297 trlen = tr_bytes(tr, trlen, ((buf as i64)+p+5) as *u8, rlen)
298 var hp: i64 = p+5; let rend: i64 = p+5+rlen
299 while hp + 4 <= rend {
300 let ht: i64 = buf[hp]&0xff; let hl: i64 = ((buf[hp+1]&0xff)<<16)|((buf[hp+2]&0xff)<<8)|(buf[hp+3]&0xff); let bp: i64 = hp+4
301 if ht == 2 { var z: i64=0; while z<32 { sr[z]=buf[bp+2+z]; z=z+1 } got_sr=1 }
302 if ht == 11 { var z: i64=0; while z<hl { cert12[z]=buf[bp+z]; z=z+1 } cert12_len=hl; got_cert=1 }
303 if ht == 12 { var z: i64=0; while z<65 { spub[z]=buf[bp+4+z]; z=z+1 } var w: i64=0; while w<hl { ske_body[w]=buf[bp+w]; w=w+1 } ske_len=hl; got_spub=1 }
304 hp = hp + 4 + hl
305 }
306 }
307 p = p + 5 + rlen
308 }
309 if got_sr==0 { sys_close(fd); return 0 - 3 }
310 if got_spub==0 { sys_close(fd); return 0 - 3 }
311 if got_cert==0 { sys_close(fd); return 0 - 6 }
312 // ---- CERT VALIDATION: chain (to Mozilla roots) + hostname + validity, via the shipped 1.3 pipeline after a
313 // 1.2->1.3 reformat. FAIL-CLOSED: a cert that doesn't chain to a trusted root / doesn't cover the host / is
314 // expired ABORTS the connection (return -8) before we send the Bearer token. NOTE(residual): the
315 // ServerKeyExchange SIGNATURE (which binds the ECDHE key to this cert) is not yet verified -- so this stops
316 // self-signed / wrong-host / expired certs but not a sophisticated active MITM that replays a valid cert with
317 // its own ECDHE key. That SKE-sig check (RSA/ECDSA over client_random||server_random||params) is the final step.
318 let store_r: i64 = nx_trust_store_load_from_certdata("data/mozilla_certdata.txt" as *u8, 512, TR_MAGIC_4194304)
319 if store_r <= 0 { sys_close(fd); return 0 - 7 }
320 let store: *TrustStore = store_r as *TrustStore
321 let c13: *u8 = sys_mmap(TR_MAGIC_40960)
322 let c13len: i64 = tr_certs12_to_13(cert12, cert12_len, c13)
323 let cv: i64 = nx_https_cert_pipeline_verify_with_store(c13, c13len, host, hlen, now, store)
324 if cv != NX_HTTPS_PIPELINE_OK { sys_close(fd); return 0 - 200 - cv } // encode pipeline verdict for diagnosis
325 // ---- SKE-SIGNATURE verify: bind the ECDHE key to the validated leaf cert (closes the active-MITM gap) ----
326 let leaf_len: i64 = ((cert12[3]&0xff)<<16)|((cert12[4]&0xff)<<8)|(cert12[5]&0xff)
327 let leaf_der: *u8 = ((cert12 as i64)+6) as *u8
328 let sv: i64 = tr_verify_ske_sig(cr, sr, ske_body, ske_len, leaf_der, leaf_len)
329 if sv != 1 { sys_close(fd); return 0 - 400 - sv } // encode SKE-verify verdict for diagnosis
330
331 let seed: *u8 = sys_mmap(32); nx_csprng_fill(seed, 32)
332 let mypriv: *u8 = sys_mmap(32)
333 if p256_ecdh_derive_priv(seed, mypriv) != NX_P256_ECDH_OK { sys_close(fd); return 0 - 4 }
334 let mypub: *u8 = sys_mmap(65)
335 if p256_ecdh_pub(mypriv, mypub) != NX_P256_ECDH_OK { sys_close(fd); return 0 - 4 }
336 let premaster: *u8 = sys_mmap(32)
337 if p256_ecdh_shared(mypriv, spub, 65, premaster) != NX_P256_ECDH_OK { sys_close(fd); return 0 - 4 }
338
339 let crsr: *u8 = sys_mmap(64); tr_bytes(crsr, 0, cr, 32); tr_bytes(crsr, 32, sr, 32)
340 let srcr: *u8 = sys_mmap(64); tr_bytes(srcr, 0, sr, 32); tr_bytes(srcr, 32, cr, 32)
341 let master: *u8 = sys_mmap(48); tr_prf(premaster, 32, "master secret" as *u8, 13, crsr, 64, master, 48)
342 let kb: *u8 = sys_mmap(40); tr_prf(master, 48, "key expansion" as *u8, 13, srcr, 64, kb, 40)
343 let cwk: *u8 = ((kb as i64)+0) as *u8
344 let swk: *u8 = ((kb as i64)+16) as *u8
345 let civ: *u8 = ((kb as i64)+32) as *u8
346 let siv: *u8 = ((kb as i64)+36) as *u8
347
348 let cke: *u8 = sys_mmap(128); var ck: i64 = 0
349 ck = tr_b1(cke, ck, TR_HS); ck = tr_b2(cke, ck, 0x0303); ck = tr_b2(cke, ck, 4 + 66)
350 let cke_hs: i64 = ck
351 ck = tr_b1(cke, ck, 16); ck = tr_b3(cke, ck, 66); ck = tr_b1(cke, ck, 65); ck = tr_bytes(cke, ck, mypub, 65)
352 if tr_write_all(fd, cke, ck) < 0 { sys_close(fd); return 0 - 5 }
353 trlen = tr_bytes(tr, trlen, ((cke as i64)+cke_hs) as *u8, ck - cke_hs)
354
355 let ccs: *u8 = sys_mmap(8); var cc: i64=0
356 cc = tr_b1(ccs, cc, TR_CCS); cc = tr_b2(ccs, cc, 0x0303); cc = tr_b2(ccs, cc, 1); cc = tr_b1(ccs, cc, 1)
357 if tr_write_all(fd, ccs, cc) < 0 { sys_close(fd); return 0 - 5 }
358
359 let thash: *u8 = sys_mmap(32); sha256_digest(tr, trlen, thash)
360 let cvd: *u8 = sys_mmap(12); tr_prf(master, 48, "client finished" as *u8, 15, thash, 32, cvd, 12)
361 let fin: *u8 = sys_mmap(16); var fo: i64=0
362 fo = tr_b1(fin, fo, 20); fo = tr_b3(fin, fo, 12); fo = tr_bytes(fin, fo, cvd, 12)
363 trlen = tr_bytes(tr, trlen, fin, 16)
364 let nonce: *u8 = sys_mmap(12); var q: i64=0; while q<4 { nonce[q]=civ[q]; q=q+1 } while q<12 { nonce[q]=0; q=q+1 }
365 let aad: *u8 = sys_mmap(13); q=0; while q<8 { aad[q]=0; q=q+1 } aad[8]=22 as u8; aad[9]=3 as u8; aad[10]=3 as u8; aad[11]=0 as u8; aad[12]=16 as u8
366 let fct: *u8 = sys_mmap(16); let ftag: *u8 = sys_mmap(16)
367 nx_aes128_gcm_seal(cwk, nonce, aad, 13, fin, 16, fct, ftag)
368 let frec: *u8 = sys_mmap(64); var fr: i64=0
369 fr = tr_b1(frec, fr, TR_HS); fr = tr_b2(frec, fr, 0x0303); fr = tr_b2(frec, fr, 8+16+16)
370 q=0; while q<8 { frec[fr]=0 as u8; fr=fr+1; q=q+1 }
371 fr = tr_bytes(frec, fr, fct, 16); fr = tr_bytes(frec, fr, ftag, 16)
372 if tr_write_all(fd, frec, fr) < 0 { sys_close(fd); return 0 - 5 }
373
374 // drain server CCS + Finished (don't strictly verify here; the request+response GCM auth already proves keys)
375 let rbuf: *u8 = sys_mmap(TR_MAGIC_16384); var rtot: i64 = 0; var rr: i64 = 0; var server_done: i64 = 0
376 while server_done == 0 {
377 if rr > 30 { server_done = 2 }
378 let n: i64 = sys_read(fd, ((rbuf as i64)+rtot) as *u8, TR_MAGIC_16384-rtot)
379 if n <= 0 { server_done = 2 } else {
380 rtot = rtot + n; rr = rr + 1
381 var pp: i64 = 0
382 while pp + 5 <= rtot {
383 let rtype: i64 = rbuf[pp]&0xff; let rlen: i64 = ((rbuf[pp+3]&0xff)<<8)|(rbuf[pp+4]&0xff)
384 if pp+5+rlen > rtot { pp = rtot } else { if rtype==TR_ALERT { server_done=3 } if rtype==TR_HS { server_done=1 } pp = pp + 5 + rlen }
385 }
386 }
387 }
388
389 // send the application request (client app seq = 1: Finished was seq 0)
390 let reqb: *u8 = sys_mmap(TR_MAGIC_8192)
391 let rq: i64 = tr_http_req(method, mlen, reqtgt, reqtlen, host, hlen, token, tn, body, blen, reqb)
392 let anonce: *u8 = sys_mmap(12); var az: i64=0; while az<4 { anonce[az]=civ[az]; az=az+1 } tr_u64be(anonce, 4, 1)
393 let aaad: *u8 = sys_mmap(13); tr_u64be(aaad, 0, 1); aaad[8]=23 as u8; aaad[9]=3 as u8; aaad[10]=3 as u8; aaad[11]=((rq>>8)&0xff) as u8; aaad[12]=(rq&0xff) as u8
394 let act: *u8 = sys_mmap(TR_MAGIC_8192); let atag: *u8 = sys_mmap(16)
395 nx_aes128_gcm_seal(cwk, anonce, aaad, 13, reqb, rq, act, atag)
396 let arec: *u8 = sys_mmap(TR_MAGIC_8300); var ar: i64=0
397 ar = tr_b1(arec, ar, 23); ar = tr_b2(arec, ar, 0x0303); ar = tr_b2(arec, ar, 8+rq+16)
398 ar = tr_u64be(arec, ar, 1); ar = tr_bytes(arec, ar, act, rq); ar = tr_bytes(arec, ar, atag, 16)
399 if tr_write_all(fd, arec, ar) < 0 { sys_close(fd); return 0 - 5 }
400
401 // read + decrypt response app-data (server app seq = 1); stop on complete HTTP response (Content-Length) or close
402 let respbuf: *u8 = sys_mmap(TR_MAGIC_262144); var resptot: i64 = 0
403 var rp: i64 = 0; var rseq: i64 = 1; var plain_total: i64 = 0
404 var reading: i64 = 1; var rreads: i64 = 0
405 while reading == 1 {
406 var more: i64 = 1
407 while more == 1 {
408 if rp + 5 > resptot { more = 0 } else {
409 let rt: i64 = respbuf[rp]&0xff; let rl: i64 = ((respbuf[rp+3]&0xff)<<8)|(respbuf[rp+4]&0xff)
410 if rp+5+rl > resptot { more = 0 } else {
411 if rt == 21 { reading = 0 }
412 if rt == 23 {
413 let pl: i64 = rl - 8 - 16
414 let dn: *u8 = sys_mmap(12); var dz: i64=0; while dz<4 { dn[dz]=siv[dz]; dz=dz+1 } while dz<12 { dn[dz]=respbuf[rp+5+(dz-4)]; dz=dz+1 }
415 let dad: *u8 = sys_mmap(13); tr_u64be(dad, 0, rseq); dad[8]=23 as u8; dad[9]=3 as u8; dad[10]=3 as u8; dad[11]=((pl>>8)&0xff) as u8; dad[12]=(pl&0xff) as u8
416 let dtag: *u8 = sys_mmap(16); dz=0; while dz<16 { dtag[dz]=respbuf[rp+5+8+pl+dz]; dz=dz+1 }
417 let dpt: *u8 = sys_mmap(TR_MAGIC_65536)
418 if nx_aes128_gcm_open(swk, dn, dad, 13, ((respbuf as i64)+rp+5+8) as *u8, pl, dtag, dpt) == 0 {
419 var cz: i64=0; while cz<pl { if plain_total+cz < outcap { out[plain_total+cz]=dpt[cz] } cz=cz+1 } plain_total=plain_total+pl
420 }
421 rseq = rseq + 1
422 }
423 rp = rp + 5 + rl
424 }
425 }
426 }
427 // TERMINATION. v1 read "no Content-Length" as "the response is complete" and stopped the instant
428 // the headers landed -- so every CHUNKED reply (Apache 2.2's default for dynamic content, which is
429 // exactly what graphis.ne.jp serves) came back as headers plus a ZERO-length body. Downstream
430 // nx_http_dechunk then dechunked 0 bytes into 0, and the fetch ladder reported that as a handshake
431 // failure, sending two sessions hunting a TLS bug that was never there. debt 1785971025.
432 // ★A 200 ALONGSIDE AN EMPTY BODY IS A PARSE BUG, NOT A TRANSPORT ONE.
433 let he: i64 = tr_hdr_end(out, plain_total)
434 if he >= 0 {
435 let cl: i64 = tr_content_length(out, plain_total)
436 if cl >= 0 { if plain_total - he >= cl { reading = 0 } }
437 else {
438 var cfrom: i64 = he - 2
439 if cfrom < 0 { cfrom = 0 }
440 if tr_is_chunked(out, he) == 1 { if tr_chunk_done(out, cfrom, plain_total) == 1 { reading = 0 } }
441 // Neither Content-Length nor chunked = close-delimited (HTTP/1.0 style): keep reading until
442 // the peer closes. The socket timeout and the rreads cap bound it in both directions.
443 }
444 }
445 if reading == 1 {
446 rreads = rreads + 1
447 if rreads > 60 { reading = 0 }
448 let n: i64 = sys_read(fd, ((respbuf as i64)+resptot) as *u8, TR_MAGIC_262144-resptot)
449 if n <= 0 { reading = 0 } else { resptot = resptot + n }
450 }
451 }
452 sys_close(fd)
453 return plain_total
454}