nx_tls12_client_session.nx source
↩ module page · 603 lines · 29472 B
1// nx_tls12_client_session.nx -- PHASE 2 of the sovereign TLS 1.2 client:
2// the HANDSHAKE STATE MACHINE that completes a full TLS 1.2 handshake
3// (ECDHE-RSA-AES128-GCM-SHA256) and establishes an encrypted session.
4//
5// WHY: api.porkbun.com (and every Porkbun endpoint) negotiates TLS 1.2
6// MAXIMUM; our nx_tls13_* client is 1.3-only and gets a protocol_version
7// alert. This organ drives the 1.2 handshake to CONNECTED so the Porkbun
8// API (nx_porkbun_set_txt -> ACME wildcard renew) works. Phase 3 wires
9// this into nx_acme_http + the live Porkbun e2e.
10//
11// This is ORCHESTRATION of already-shipped, already-gated primitives
12// (verify-don't-rebuild) -- no new crypto:
13// tls12_client_hello_emit (nx_tls12_hello.nx) flight 1
14// tls12_parse_server_hello (nx_tls12_parse.nx) SH
15// tls12_parse_server_key_exchange(nx_tls12_parse.nx) SKE
16// nx_tls13_client_validate_certificate (the SAME cert-chain cert
17// bridge the 1.3 client uses) -- fed a 1.2->1.3 cert-msg
18// transform built here so the whole RFC-5280 path-validation
19// pipeline + Mozilla trust store is reused unchanged.
20// nx_x509_pubkey_extract_rsa (nx_x509_pubkey_rsa.nx) leaf key
21// nx_tls12_ske_verify (nx_tls12_ske_verify.nx) SKE sig
22// p256_ecdh_derive_priv/pub/shared (nx_p256_ecdh.nx) ECDHE
23// tls12_derive_master_secret / _key_block / _split_keys /
24// _finished_verify_data (nx_tls12_keys.nx) schedule
25// tls12_record_seal / _open (nx_tls12_record.nx) AEAD
26// sha256_digest (nx_sha256.nx) transcript
27// nx_tls13_read_record_from_fd (read framer, reused) IO
28//
29// Pipeline (RFC 5246 §7.3, full ECDHE-RSA handshake):
30// 1. Send ClientHello; start a running SHA-256 transcript over every
31// handshake message (4-byte type+len header + body, NO 5-byte
32// record header), in send/recv order.
33// 2. Recv+reassemble ServerHello / Certificate / ServerKeyExchange /
34// ServerHelloDone; append each to the transcript.
35// 3. VALIDATE the certificate chain to the trust store + SNI host
36// (reused bridge). FAIL CLOSED.
37// 4. VERIFY the ServerKeyExchange RSA signature under the leaf key
38// over client_random||server_random||ECDHE_params. FAIL CLOSED.
39// 5. ECDHE: derive client ephemeral P-256 key; pre_master = ECDH x.
40// 6. master = derive_master_secret; key_block; split to
41// c_key/s_key/c_salt/s_salt.
42// 7. Send ClientKeyExchange (msg type 16, 1-byte-len client point);
43// append to transcript. Send ChangeCipherSpec (record type 20 --
44// NOT a handshake msg, NOT in the transcript).
45// 8. client Finished = finished_verify_data(master,"client finished",
46// SHA256(transcript)); msg type 20 (0x14)+12-byte body; ENCRYPT
47// via tls12_record_seal (client key/salt, seq 0, ctype 22); send.
48// Then append the PLAINTEXT Finished to the transcript.
49// 9. Recv server ChangeCipherSpec (type 20) then the server's
50// encrypted Finished (type 22): tls12_record_open (server key/salt,
51// seq 0), constant-time-VERIFY verify_data == finished_verify_data(
52// master,"server finished",SHA256(transcript incl client Finished)).
53// FAIL CLOSED on mismatch.
54// 10. CONNECTED: app-data send/recv with independent client/server
55// sequence counters (start at 1 -- Finished consumed seq 0).
56//
57// SECURITY (this is a real handshake -- forged-accept = MITM):
58// Cert chain validation, SKE signature verify, and BOTH Finished MACs
59// are MANDATORY and FAIL CLOSED -- no session is established if any
60// check fails. The Finished verify_data and the GCM tag (the record
61// layer) are compared in constant time.
62//
63// license_tier: INDEPENDENT_REDERIVE
64// genealogy_id: international-research-sources/ietf/rfc_5246 + rfc_5280 + rfc_8422
65// lineage_id: nishi_tls12_client_session_q2
66//
67// nx_safety_envelope:
68// intended_use: "sovereign TLS 1.2 client handshake to CONNECTED +
69// encrypted app-data session (server authentication)"
70// sil_target: SIL3 (authentication; a forged-accept = MITM)
71// evidence: [composed_from_gated_phase1_primitives,
72// live_handshake_api_porkbun_com_gate,
73// neg_control_empty_trust_store_REJECTED,
74// neg_control_tampered_ske_sig_REJECTED,
75// neg_control_tampered_server_finished_REJECTED]
76// verdict: NOT_YET_EVALUATED
77
78import "nx_syscalls.nx"
79import "nx_sha256.nx"
80import "nx_u2048.nx"
81import "nx_u256.nx" // u256_alloc -> EC P-256 pubkey limbs for the ECDHE-ECDSA branch
82import "nx_x509.nx"
83import "nx_x509_pubkey_rsa.nx"
84import "nx_x509_pubkey_ec.nx" // nx_x509_pubkey_extract_ec_p256 (leaf EC key for ECDHE-ECDSA)
85import "nx_tls12_hello.nx"
86import "nx_tls12_parse.nx"
87import "nx_tls12_keys.nx"
88import "nx_tls12_record.nx"
89import "nx_tls12_ske_verify.nx"
90import "nx_p256_ecdh.nx"
91import "nx_tls13_client_validate_certificate.nx"
92import "nx_tls13_read_record_from_fd.nx"
93const NX_MAGIC_65536: i64 = 65536
94const NX_MAGIC_2048: i64 = 2048
95const NX_MAGIC_131072: i64 = 131072
96const NX_MAGIC_32768: i64 = 32768
97const NX_MAGIC_1024: i64 = 1024
98
99// ---- Sealed verdict enum (the NEGATIVE codes returned by run) --------
100const NX_TLS12_SESS_OK: i64 = 1
101const NX_TLS12_SESS_EMIT_CH: i64 = 2 // ClientHello emit failed
102const NX_TLS12_SESS_WRITE_FAIL: i64 = 3 // sys_write failed
103const NX_TLS12_SESS_READ_FLIGHT: i64 = 4 // could not read server flight 1
104const NX_TLS12_SESS_ALERT: i64 = 5 // server sent a TLS alert
105const NX_TLS12_SESS_UNEXPECTED: i64 = 6 // unexpected record content-type
106const NX_TLS12_SESS_INCOMPLETE_FLIGHT: i64 = 7 // missing SH/Cert/SKE/SHD
107const NX_TLS12_SESS_UNSUPPORTED_CIPHER: i64 = 8 // server picked a suite we don't complete
108const NX_TLS12_SESS_CERT_FAIL: i64 = 9 // chain/host/trust validation failed
109const NX_TLS12_SESS_LEAF_PARSE: i64 = 10 // leaf DER parse failed
110const NX_TLS12_SESS_LEAF_PUBKEY: i64 = 11 // leaf RSA pubkey extract failed
111const NX_TLS12_SESS_SKE_PARSE: i64 = 12 // SKE wire parse failed
112const NX_TLS12_SESS_SKE_VERIFY: i64 = 13 // SKE RSA signature verify failed
113const NX_TLS12_SESS_ECDH: i64 = 14 // ECDHE key-agreement failed
114const NX_TLS12_SESS_READ_SF: i64 = 15 // could not read server flight 2
115const NX_TLS12_SESS_SF_OPEN: i64 = 16 // server Finished record GCM open failed
116const NX_TLS12_SESS_SF_FORMAT: i64 = 17 // server Finished plaintext malformed
117const NX_TLS12_SESS_SF_VERIFY: i64 = 18 // server Finished verify_data mismatch
118const NX_TLS12_SESS_CERT_MSG_BAD: i64 = 19 // 1.2 Certificate body malformed
119// app-data verdicts
120const NX_TLS12_SESS_BAD_STATE: i64 = 20 // session not CONNECTED
121const NX_TLS12_SESS_EOF: i64 = 21 // peer closed the TCP connection
122const NX_TLS12_SESS_READ_FAIL: i64 = 22 // record read error
123const NX_TLS12_SESS_DECRYPT_FAIL: i64 = 23 // record AEAD open failed
124const NX_TLS12_SESS_OVERFLOW: i64 = 24 // record exceeds caller buffer
125const NX_TLS12_SESS_VERDICT_N: i64 = 25
126
127func nx_tls12_sess_verdict_is_valid(v: i64) -> i64 {
128 if v < NX_TLS12_SESS_OK { return 0 }
129 if v >= NX_TLS12_SESS_VERDICT_N { return 0 }
130 return 1
131}
132
133// The only session state we expose: a fully-established connection.
134const NX_TLS12_SESS_STATE_CONNECTED: i64 = 1
135
136// The cipher suites this handshake completes end-to-end (both = P-256 ECDHE + AES-128-GCM + SHA-256 PRF;
137// they differ ONLY in the leaf-cert key type used to verify the ServerKeyExchange signature: RSA vs ECDSA).
138const NX_TLS12_SUITE_ECDHE_RSA_AES128_GCM_SHA256: i64 = 0xC02F
139const NX_TLS12_SUITE_ECDHE_ECDSA_AES128_GCM_SHA256: i64 = 0xC02B
140
141// Read budgets (defensive: an evil server can't feed us forever).
142const NX_TLS12_SESS_MAX_FLIGHT_RECORDS: i64 = 24
143const NX_TLS12_SESS_MAX_SF_RECORDS: i64 = 8
144const NX_TLS12_SESS_RECORD_BUF: i64 = 20000
145
146// A connected TLS 1.2 client session (mirrors Tls13ClientSession shape).
147struct Tls12ClientSession {
148 fd: i64,
149 cipher_suite: i64,
150 state: i64,
151 client_key: *u8, // 16-byte AES-128 client write key
152 server_key: *u8, // 16-byte AES-128 server write key
153 client_salt: *u8, // 4-byte client write IV (GCM salt)
154 server_salt: *u8, // 4-byte server write IV (GCM salt)
155 client_seq: i64, // next record sequence we will SEND (app data starts at 1)
156 server_seq: i64, // next record sequence we will RECEIVE
157 master: *u8, // 48-byte master secret (kept for renegotiation/debug)
158}
159
160// ---- small helpers ---------------------------------------------------
161
162static _T12S_PN_BUF: i64
163func _t12s_pn2(c0: i64, c1: i64, v: i64) -> i64 {
164 let lab: *u8 = sys_mmap(8)
165 lab[0] = c0 as u8; lab[1] = c1 as u8; lab[2] = 0x3D as u8
166 sys_write(2, lab, 3)
167 if _T12S_PN_BUF == 0 { _T12S_PN_BUF = sys_mmap(32) as i64 }
168 let b: *u8 = _T12S_PN_BUF as *u8
169 var m: i64 = v
170 if m < 0 { sys_write(2, "-" as *u8, 1); m = 0 - m }
171 var i: i64 = 22
172 if m == 0 { b[i] = 0x30 as u8; i = i - 1 }
173 else { while m > 0 { b[i] = (0x30 + (m - (m/10)*10)) as u8; m = m / 10; i = i - 1 } }
174 sys_write(2, ((b as i64) + i + 1) as *u8, 22 - i)
175 sys_write(2, "\n" as *u8, 1)
176 return 0
177}
178
179// Write exactly n bytes to fd; 0 on success, -1 on any sys_write error.
180func _t12s_write_n(fd: i64, buf: *u8, n: i64) -> i64 {
181 var off: i64 = 0
182 while off < n {
183 let w: i64 = sys_write(fd, (buf as i64 + off) as *u8, n - off)
184 if w <= 0 { return 0 - 1 }
185 off = off + w
186 }
187 return 0
188}
189
190// Frame [ctype][0x03 0x03][len BE 2][payload] and write it. 0 ok / -1 fail.
191func _t12s_send_record(fd: i64, ctype: i64, payload: *u8, plen: i64) -> i64 {
192 let rec: *u8 = sys_mmap(plen + 16)
193 rec[0] = ctype as u8
194 rec[1] = 0x03 as u8
195 rec[2] = 0x03 as u8
196 rec[3] = ((plen >> 8) & 0xff) as u8
197 rec[4] = (plen & 0xff) as u8
198 var i: i64 = 0
199 while i < plen { rec[5 + i] = payload[i]; i = i + 1 }
200 return _t12s_write_n(fd, rec, 5 + plen)
201}
202
203// 64-bit sequence number -> 8-byte big-endian explicit nonce / AAD field.
204func _t12s_seq8(seq: i64, out8: *u8) -> i64 {
205 var i: i64 = 0
206 while i < 8 {
207 out8[7 - i] = ((seq >> (i * 8)) & 0xff) as u8
208 i = i + 1
209 }
210 return 0
211}
212
213// Constant-time equality over n bytes: 1 iff equal, 0 otherwise.
214// (No early exit -- timing must not leak how many bytes matched.)
215func _t12s_ct_eq(a: *u8, b: *u8, n: i64) -> i64 {
216 var diff: i64 = 0
217 var i: i64 = 0
218 while i < n {
219 diff = diff | ((a[i] as i64) ^ (b[i] as i64))
220 i = i + 1
221 }
222 if diff == 0 { return 1 }
223 return 0
224}
225
226// Transform a TLS 1.2 Certificate-message BODY (RFC 5246 §7.4.2:
227// cert_list_len(3) || repeated[ cert_len(3) || DER ] )
228// into a TLS 1.3 Certificate handshake MESSAGE (RFC 8446 §4.4.2:
229// type(1)=11 || body_len(3) || ctx_len(1)=0 || list_len(3) ||
230// repeated[ cert_len(3) || DER || ext_len(2)=0 ] )
231// so the SHIPPED 1.3 cert pipeline (tls13_parse_certificate_chain_all
232// + nx_https_cert_pipeline_verify_with_store) validates it unchanged.
233// Returns the 1.3 message length, or negative on malformed input.
234func _t12s_certmsg_to_tls13(cl: *u8, cl_len: i64, out: *u8, out_cap: i64) -> i64 {
235 if cl_len < 3 { return 0 - 1 }
236 let list_len: i64 = ((cl[0] as i64) << 16) | ((cl[1] as i64) << 8) | (cl[2] as i64)
237 if 3 + list_len > cl_len { return 0 - 1 }
238
239 var o: i64 = 8 // entries start after hdr(4)+ctx(1)+listlen(3)
240 var p: i64 = 3 // read cursor (past the 1.2 list length)
241 let end: i64 = 3 + list_len
242 while p < end {
243 if p + 3 > cl_len { return 0 - 1 }
244 let clen: i64 = ((cl[p] as i64) << 16) | ((cl[p+1] as i64) << 8) | (cl[p+2] as i64)
245 if clen < 1 { return 0 - 1 }
246 if p + 3 + clen > cl_len { return 0 - 1 }
247 if o + 3 + clen + 2 > out_cap { return 0 - 1 }
248 out[o] = cl[p]
249 out[o+1] = cl[p+1]
250 out[o+2] = cl[p+2]
251 o = o + 3
252 var i: i64 = 0
253 while i < clen { out[o + i] = cl[p + 3 + i]; i = i + 1 }
254 o = o + clen
255 out[o] = 0 as u8 // CertificateEntry extensions length = 0
256 out[o+1] = 0 as u8
257 o = o + 2
258 p = p + 3 + clen
259 }
260
261 let entries_len: i64 = o - 8
262 out[4] = 0 as u8 // certificate_request_context length
263 out[5] = ((entries_len >> 16) & 0xff) as u8 // 1.3 certificate_list length
264 out[6] = ((entries_len >> 8) & 0xff) as u8
265 out[7] = (entries_len & 0xff) as u8
266 let body_len: i64 = o - 4
267 out[0] = 11 as u8 // HandshakeType certificate(11)
268 out[1] = ((body_len >> 16) & 0xff) as u8
269 out[2] = ((body_len >> 8) & 0xff) as u8
270 out[3] = (body_len & 0xff) as u8
271 return o
272}
273
274// ---------------------------------------------------------------------
275// THE handshake orchestrator core.
276//
277// Returns POSITIVE pointer-as-i64 to the connected *Tls12ClientSession
278// on success; NEGATIVE -NX_TLS12_SESS_* verdict on any failure (every
279// security check FAILS CLOSED).
280//
281// fault_inject is TEST-ONLY (production callers pass 0 via the public
282// wrapper below). Non-zero deliberately corrupts ONE security-critical
283// value mid-handshake so a gate can prove the live path FAILS CLOSED:
284// 1 = flip a byte of the RECEIVED server Finished verify_data
285// 2 = flip a byte of the SKE signature before verification
286// ---------------------------------------------------------------------
287func _nx_tls12_run_core(
288 fd: i64,
289 sni: *u8, sni_len: i64,
290 client_random: *u8, // 32 caller-supplied random bytes
291 ephem_seed: *u8, // 32 bytes -> domain-separated P-256 ephemeral
292 val_ctx: *TlsValidationContext,
293 fault_inject: i64
294) -> i64 {
295 // Running handshake transcript (message headers + bodies, in order).
296 let tr: *u8 = sys_mmap(NX_MAGIC_65536)
297 var tr_len: i64 = 0
298
299 // ---- Step 1: emit + send ClientHello ----
300 let ch: *u8 = sys_mmap(NX_MAGIC_2048)
301 let ch_n: i64 = tls12_client_hello_emit(client_random, sni, sni_len, ch, NX_MAGIC_2048)
302 if ch_n < 0 { return 0 - NX_TLS12_SESS_EMIT_CH }
303 var i: i64 = 0
304 while i < ch_n { tr[tr_len + i] = ch[i]; i = i + 1 }
305 tr_len = tr_len + ch_n
306 if _t12s_send_record(fd, 22, ch, ch_n) < 0 { return 0 - NX_TLS12_SESS_WRITE_FAIL }
307
308 // ---- Step 2: read + reassemble server flight 1 to ServerHelloDone ----
309 let resp: *u8 = sys_mmap(NX_TLS12_SESS_RECORD_BUF)
310 let hs: *u8 = sys_mmap(NX_MAGIC_131072)
311 var hlen: i64 = 0
312 var hpos: i64 = 0
313 let server_random: *u8 = sys_mmap(64)
314 let cipher_p: *i64 = sys_mmap(16) as *i64
315 var got_sh: i64 = 0
316 var got_cert: i64 = 0
317 var got_skx: i64 = 0
318 var got_done: i64 = 0
319 var cert_body: *u8 = 0 as *u8
320 var cert_body_len: i64 = 0
321 var skx_body: *u8 = 0 as *u8
322 var skx_len: i64 = 0
323 var recs: i64 = 0
324 while got_done == 0 {
325 if recs >= NX_TLS12_SESS_MAX_FLIGHT_RECORDS { return 0 - NX_TLS12_SESS_READ_FLIGHT }
326 let rt: i64 = nx_tls13_read_record_from_fd(fd, resp, NX_TLS12_SESS_RECORD_BUF)
327 if rt < 5 { return 0 - NX_TLS12_SESS_READ_FLIGHT }
328 recs = recs + 1
329 let ct: i64 = resp[0] as i64
330 if ct == 21 { _t12s_pn2(0x41, 0x4C, resp[6] as i64); return 0 - NX_TLS12_SESS_ALERT }
331 if ct != 22 { return 0 - NX_TLS12_SESS_UNEXPECTED }
332 // accumulate handshake bytes
333 var bi: i64 = 5
334 while bi < rt { hs[hlen] = resp[bi]; hlen = hlen + 1; bi = bi + 1 }
335 // parse every complete handshake message currently buffered
336 var more: i64 = 1
337 while more == 1 {
338 if hpos + 4 > hlen { more = 0 }
339 else {
340 let mtype: i64 = hs[hpos] as i64
341 let mlen: i64 = ((hs[hpos+1] as i64) << 16) | ((hs[hpos+2] as i64) << 8) | (hs[hpos+3] as i64)
342 if hpos + 4 + mlen > hlen { more = 0 }
343 else {
344 let body: *u8 = (hs as i64 + hpos + 4) as *u8
345 // append the WHOLE message (header+body) to the transcript
346 var ci: i64 = 0
347 while ci < 4 + mlen { tr[tr_len] = hs[hpos + ci]; tr_len = tr_len + 1; ci = ci + 1 }
348 if mtype == 2 { if tls12_parse_server_hello(body, mlen, server_random, cipher_p) == 1 { got_sh = 1 } }
349 if mtype == 11 { cert_body = body; cert_body_len = mlen; got_cert = 1 }
350 if mtype == 12 { skx_body = body; skx_len = mlen; got_skx = 1 }
351 if mtype == 14 { got_done = 1 }
352 hpos = hpos + 4 + mlen
353 }
354 }
355 }
356 }
357 if got_sh == 0 { return 0 - NX_TLS12_SESS_INCOMPLETE_FLIGHT }
358 if got_cert == 0 { return 0 - NX_TLS12_SESS_INCOMPLETE_FLIGHT }
359 if got_skx == 0 { return 0 - NX_TLS12_SESS_INCOMPLETE_FLIGHT }
360 _t12s_pn2(0x43, 0x49, cipher_p[0]) // CI= negotiated cipher
361 var is_ecdsa: i64 = 0
362 if cipher_p[0] == NX_TLS12_SUITE_ECDHE_ECDSA_AES128_GCM_SHA256 { is_ecdsa = 1 }
363 if cipher_p[0] != NX_TLS12_SUITE_ECDHE_RSA_AES128_GCM_SHA256 {
364 if is_ecdsa == 0 { return 0 - NX_TLS12_SESS_UNSUPPORTED_CIPHER }
365 }
366
367 // ---- Step 3: VALIDATE the certificate chain (reused 1.3 bridge) ----
368 // Transform the 1.2 cert-list into a 1.3 Certificate message and feed
369 // it to the SAME validator the 1.3 client uses (full RFC-5280 path
370 // validation + SNI host match + Mozilla trust anchor resolution).
371 let synth: *u8 = sys_mmap(NX_MAGIC_32768)
372 let synth_n: i64 = _t12s_certmsg_to_tls13(cert_body, cert_body_len, synth, NX_MAGIC_32768)
373 if synth_n < 0 { return 0 - NX_TLS12_SESS_CERT_MSG_BAD }
374 let cv: i64 = nx_tls13_client_validate_certificate(synth, synth_n, val_ctx)
375 _t12s_pn2(0x43, 0x56, cv) // CV= cert-validation verdict
376 if cv != NX_TLS13_CLIENT_CV_OK { return 0 - NX_TLS12_SESS_CERT_FAIL }
377
378 // ---- Step 4: VERIFY the ServerKeyExchange signature (leaf key: RSA or ECDSA) ----
379 // Extract the leaf pubkey straight from the 1.2 cert-list (leaf is the first
380 // cert: skip list_len(3) + cert0_len(3)). The key TYPE follows the negotiated
381 // cipher (ECDHE-RSA -> RSA leaf; ECDHE-ECDSA -> EC P-256 leaf).
382 if cert_body_len < 6 { return 0 - NX_TLS12_SESS_CERT_MSG_BAD }
383 let leaf_len: i64 = ((cert_body[3] as i64) << 16) | ((cert_body[4] as i64) << 8) | (cert_body[5] as i64)
384 if 6 + leaf_len > cert_body_len { return 0 - NX_TLS12_SESS_CERT_MSG_BAD }
385 let leaf_der: *u8 = (cert_body as i64 + 6) as *u8
386 let leaf_cert: *X509Cert = sys_mmap(256) as *X509Cert
387 if x509_parse(leaf_der, leaf_len, leaf_cert) < 0 { return 0 - NX_TLS12_SESS_LEAF_PARSE }
388
389 // SKE wire parse (common to both key types): curve + server ephemeral point + sig_alg + signature.
390 let curve_p: *i64 = sys_mmap(16) as *i64
391 let spub: *u8 = sys_mmap(256)
392 let spublen_p: *i64 = sys_mmap(16) as *i64
393 let sigalg_p: *i64 = sys_mmap(16) as *i64
394 let sig: *u8 = sys_mmap(NX_MAGIC_1024)
395 let siglen_p: *i64 = sys_mmap(16) as *i64
396 if tls12_parse_server_key_exchange(skx_body, skx_len, curve_p, spub, spublen_p, sigalg_p, sig, siglen_p) != 1 {
397 return 0 - NX_TLS12_SESS_SKE_PARSE
398 }
399 // TEST-ONLY fault: corrupt the signature so verify MUST reject.
400 if fault_inject == 2 { sig[10] = (sig[10] ^ 0x01) as u8 }
401 // ECDHE_params = curve_type(1)+named_curve(2)+point_len(1)+point.
402 let params_len: i64 = 4 + spublen_p[0]
403 var skev: i64 = 0
404 if is_ecdsa == 1 {
405 // ECDHE-ECDSA: extract the leaf EC P-256 key, verify the DER ECDSA(SHA-256) SKE signature.
406 let ecx: *i64 = u256_alloc()
407 let ecy: *i64 = u256_alloc()
408 if nx_x509_pubkey_extract_ec_p256(leaf_der, leaf_cert, ecx, ecy) != NX_X509_PUBKEY_EC_OK {
409 return 0 - NX_TLS12_SESS_LEAF_PUBKEY
410 }
411 skev = nx_tls12_ske_verify_ecdsa(client_random, server_random, skx_body, params_len, sig, siglen_p[0], ecx, ecy)
412 } else {
413 // ECDHE-RSA: extract the leaf RSA key, verify the PKCS1-v1.5(SHA-256) SKE signature.
414 let pub_n: *i64 = u2048_alloc()
415 let pub_e_p: *i64 = sys_mmap(16) as *i64
416 if nx_x509_pubkey_extract_rsa(leaf_der, leaf_cert, pub_n, pub_e_p) != NX_X509_PUBKEY_RSA_OK {
417 return 0 - NX_TLS12_SESS_LEAF_PUBKEY
418 }
419 skev = nx_tls12_ske_verify(client_random, server_random, skx_body, params_len, sig, siglen_p[0], pub_n, pub_e_p[0])
420 }
421 _t12s_pn2(0x53, 0x4B, skev) // SK= SKE verify verdict
422 if skev != NX_TLS12_SKE_OK { return 0 - NX_TLS12_SESS_SKE_VERIFY }
423
424 // ---- Step 5: ECDHE -- client ephemeral key + shared pre_master ----
425 let cpriv: *u8 = sys_mmap(32)
426 if p256_ecdh_derive_priv(ephem_seed, cpriv) != NX_P256_ECDH_OK { return 0 - NX_TLS12_SESS_ECDH }
427 let pre_master: *u8 = sys_mmap(32)
428 if p256_ecdh_shared(cpriv, spub, spublen_p[0], pre_master) != NX_P256_ECDH_OK { return 0 - NX_TLS12_SESS_ECDH }
429 let cpub: *u8 = sys_mmap(65)
430 if p256_ecdh_pub(cpriv, cpub) != NX_P256_ECDH_OK { return 0 - NX_TLS12_SESS_ECDH }
431
432 // ---- Step 6: key schedule ----
433 let master: *u8 = sys_mmap(48)
434 tls12_derive_master_secret(pre_master, 32, client_random, server_random, master)
435 let kb: *u8 = sys_mmap(64)
436 tls12_derive_key_block(master, server_random, client_random, kb, 40)
437 let c_key: *u8 = sys_mmap(16)
438 let s_key: *u8 = sys_mmap(16)
439 let c_salt: *u8 = sys_mmap(4)
440 let s_salt: *u8 = sys_mmap(4)
441 tls12_split_keys_aes128gcm(kb, c_key, s_key, c_salt, s_salt)
442
443 // ---- Step 7: ClientKeyExchange (msg 16) + ChangeCipherSpec (rec 20) ----
444 let cke: *u8 = sys_mmap(80)
445 cke[0] = 0x10 as u8 // HandshakeType client_key_exchange
446 cke[1] = 0 as u8; cke[2] = 0 as u8; cke[3] = 66 as u8 // body length = 1 + 65
447 cke[4] = 65 as u8 // ECDH point length
448 i = 0
449 while i < 65 { cke[5 + i] = cpub[i]; i = i + 1 }
450 let cke_msg_len: i64 = 70 // 4 hdr + 1 + 65
451 i = 0
452 while i < cke_msg_len { tr[tr_len] = cke[i]; tr_len = tr_len + 1; i = i + 1 }
453 if _t12s_send_record(fd, 22, cke, cke_msg_len) < 0 { return 0 - NX_TLS12_SESS_WRITE_FAIL }
454
455 let ccs: *u8 = sys_mmap(8)
456 ccs[0] = 1 as u8 // ChangeCipherSpec message
457 if _t12s_send_record(fd, 20, ccs, 1) < 0 { return 0 - NX_TLS12_SESS_WRITE_FAIL }
458
459 // ---- Step 8: client Finished (encrypted, seq 0) ----
460 let th: *u8 = sys_mmap(32)
461 sha256_digest(tr, tr_len, th) // SHA256(CH..CKE)
462 let cvd: *u8 = sys_mmap(16)
463 tls12_finished_verify_data(master, "client finished" as *u8, 15, th, cvd)
464 let fin: *u8 = sys_mmap(32)
465 fin[0] = 0x14 as u8 // HandshakeType finished
466 fin[1] = 0 as u8; fin[2] = 0 as u8; fin[3] = 12 as u8
467 i = 0
468 while i < 12 { fin[4 + i] = cvd[i]; i = i + 1 }
469 let fin_msg_len: i64 = 16
470 let seq0: *u8 = sys_mmap(8); _t12s_seq8(0, seq0)
471 let sealed: *u8 = sys_mmap(64)
472 let sealed_n: i64 = tls12_record_seal(c_key, c_salt, seq0, 22, fin, fin_msg_len, sealed)
473 if _t12s_send_record(fd, 22, sealed, sealed_n) < 0 { return 0 - NX_TLS12_SESS_WRITE_FAIL }
474 // add the PLAINTEXT Finished to the transcript (for the server Finished hash)
475 i = 0
476 while i < fin_msg_len { tr[tr_len] = fin[i]; tr_len = tr_len + 1; i = i + 1 }
477
478 // ---- Step 9: server ChangeCipherSpec + encrypted Finished ----
479 var sf_done: i64 = 0
480 var rc2: i64 = 0
481 while sf_done == 0 {
482 if rc2 >= NX_TLS12_SESS_MAX_SF_RECORDS { return 0 - NX_TLS12_SESS_READ_SF }
483 let rt: i64 = nx_tls13_read_record_from_fd(fd, resp, NX_TLS12_SESS_RECORD_BUF)
484 if rt < 5 { return 0 - NX_TLS12_SESS_READ_SF }
485 rc2 = rc2 + 1
486 let ct: i64 = resp[0] as i64
487 if ct == 20 {
488 // ChangeCipherSpec -- not a handshake message, just advance.
489 } else {
490 if ct == 22 {
491 let payload: *u8 = (resp as i64 + 5) as *u8
492 let plen: i64 = rt - 5
493 let sfpt: *u8 = sys_mmap(64)
494 let sseq0: *u8 = sys_mmap(8); _t12s_seq8(0, sseq0)
495 let ptlen: i64 = tls12_record_open(s_key, s_salt, sseq0, 22, payload, plen, sfpt)
496 if ptlen < 0 { return 0 - NX_TLS12_SESS_SF_OPEN }
497 if ptlen != 16 { return 0 - NX_TLS12_SESS_SF_FORMAT }
498 if sfpt[0] != (0x14 as u8) { return 0 - NX_TLS12_SESS_SF_FORMAT }
499 let recv_vd: *u8 = (sfpt as i64 + 4) as *u8
500 // TEST-ONLY fault: corrupt the received verify_data.
501 if fault_inject == 1 { recv_vd[0] = (recv_vd[0] ^ 0x01) as u8 }
502 let th2: *u8 = sys_mmap(32)
503 sha256_digest(tr, tr_len, th2) // SHA256(CH..client Finished)
504 let svd: *u8 = sys_mmap(16)
505 tls12_finished_verify_data(master, "server finished" as *u8, 15, th2, svd)
506 if _t12s_ct_eq(recv_vd, svd, 12) != 1 { return 0 - NX_TLS12_SESS_SF_VERIFY }
507 sf_done = 1
508 } else {
509 if ct == 21 { return 0 - NX_TLS12_SESS_ALERT }
510 return 0 - NX_TLS12_SESS_UNEXPECTED
511 }
512 }
513 }
514 _t12s_pn2(0x46, 0x48, 1) // FH= both Finished verified
515
516 // ---- Step 10: session established ----
517 let s: *Tls12ClientSession = sys_mmap(128) as *Tls12ClientSession
518 s.fd = fd
519 s.cipher_suite = cipher_p[0]
520 s.state = NX_TLS12_SESS_STATE_CONNECTED
521 s.client_key = c_key
522 s.server_key = s_key
523 s.client_salt = c_salt
524 s.server_salt = s_salt
525 s.client_seq = 1 // Finished used seq 0
526 s.server_seq = 1
527 s.master = master
528 return s as i64
529}
530
531// PUBLIC API (production): drive the handshake with NO fault injection.
532// Returns POSITIVE *Tls12ClientSession-as-i64 | NEGATIVE -NX_TLS12_SESS_*.
533func nx_tls12_client_session_run(
534 fd: i64,
535 sni: *u8, sni_len: i64,
536 client_random: *u8,
537 ephem_seed: *u8,
538 val_ctx: *TlsValidationContext
539) -> i64 {
540 return _nx_tls12_run_core(fd, sni, sni_len, client_random, ephem_seed, val_ctx, 0)
541}
542
543// TEST-ONLY variant: identical, but `fault` deliberately corrupts a
544// security-critical value so gates can prove the handshake FAILS CLOSED.
545// Production code MUST use nx_tls12_client_session_run (fault = 0).
546func nx_tls12_client_session_run_faulted(
547 fd: i64,
548 sni: *u8, sni_len: i64,
549 client_random: *u8,
550 ephem_seed: *u8,
551 val_ctx: *TlsValidationContext,
552 fault: i64
553) -> i64 {
554 return _nx_tls12_run_core(fd, sni, sni_len, client_random, ephem_seed, val_ctx, fault)
555}
556
557// ---- application-data over the established session --------------------
558
559// Encrypt + send `data[0..len)` as ONE application_data record (type 23)
560// under the client write key, incrementing the client sequence. 0 ok /
561// NEGATIVE -NX_TLS12_SESS_* on failure.
562func nx_tls12_session_send(s: *Tls12ClientSession, fd: i64, data: *u8, len: i64) -> i64 {
563 if s.state != NX_TLS12_SESS_STATE_CONNECTED { return 0 - NX_TLS12_SESS_BAD_STATE }
564 let seq: *u8 = sys_mmap(8); _t12s_seq8(s.client_seq, seq)
565 let out: *u8 = sys_mmap(len + 64)
566 let n: i64 = tls12_record_seal(s.client_key, s.client_salt, seq, 23, data, len, out)
567 s.client_seq = s.client_seq + 1
568 if _t12s_send_record(fd, 23, out, n) < 0 { return 0 - NX_TLS12_SESS_WRITE_FAIL }
569 return 0
570}
571
572// Read + decrypt ONE record under the server write key, incrementing the
573// server sequence. On success returns the plaintext length into `out`
574// and writes the record content-type to out_ctype[0] (23 app-data, 21
575// alert, 22 handshake). NEGATIVE -NX_TLS12_SESS_EOF on TCP close, or
576// another -NX_TLS12_SESS_* verdict on error.
577func nx_tls12_session_recv(s: *Tls12ClientSession, fd: i64, out: *u8, out_cap: i64, out_ctype: *i64) -> i64 {
578 if s.state != NX_TLS12_SESS_STATE_CONNECTED { return 0 - NX_TLS12_SESS_BAD_STATE }
579 let resp: *u8 = sys_mmap(NX_TLS12_SESS_RECORD_BUF)
580 let rt: i64 = nx_tls13_read_record_from_fd(fd, resp, NX_TLS12_SESS_RECORD_BUF)
581 if rt < 0 {
582 let nv: i64 = 0 - rt
583 if nv == NX_TLS13_READ_REC_EOF { return 0 - NX_TLS12_SESS_EOF }
584 if nv == NX_TLS13_READ_REC_PAYLOAD_EOF { return 0 - NX_TLS12_SESS_EOF }
585 return 0 - NX_TLS12_SESS_READ_FAIL
586 }
587 if rt < 5 { return 0 - NX_TLS12_SESS_EOF }
588 let ct: i64 = resp[0] as i64
589 let payload: *u8 = (resp as i64 + 5) as *u8
590 let plen: i64 = rt - 5
591 if plen > out_cap { return 0 - NX_TLS12_SESS_OVERFLOW }
592 let seq: *u8 = sys_mmap(8); _t12s_seq8(s.server_seq, seq)
593 let ptlen: i64 = tls12_record_open(s.server_key, s.server_salt, seq, ct, payload, plen, out)
594 s.server_seq = s.server_seq + 1
595 if ptlen < 0 { return 0 - NX_TLS12_SESS_DECRYPT_FAIL }
596 out_ctype[0] = ct
597 return ptlen
598}
599
600// Compile-only smoke. Real proof in nx_tls12_handshake_gate.nx (LIVE).
601func main() -> i64 {
602 return 0
603}