nx_tls12_hello.nx source
↩ module page · 93 lines · 4671 B
1// nx_tls12_hello.nx -- TLS 1.2 ClientHello emitter (rung 1 of the sovereign TLS 1.2 client).
2//
3// ROOT CAUSE (probed 2026-06-23, news.ycombinator.com): some endpoints are TLS 1.2-ONLY and reject our
4// 1.3-only ClientHello with a fatal protocol_version(70) alert; after we offered 1.2 they want a 1.2 cipher
5// we didn't send. This module emits a real 1.2 ClientHello so we can negotiate 1.2 with those servers --
6// the "access everywhere" unlock (the crypto primitives + nx_tls12_prf already exist; this wires the protocol).
7//
8// Offered cipher suites = the ones we can actually COMPLETE (AEAD + SHA256 PRF = nx_tls12_prf_sha256):
9// 0xC02B ECDHE-ECDSA-AES128-GCM-SHA256
10// 0xC02F ECDHE-RSA-AES128-GCM-SHA256
11// 0xCCA9 ECDHE-ECDSA-CHACHA20-POLY1305-SHA256
12// 0xCCA8 ECDHE-RSA-CHACHA20-POLY1305-SHA256
13// (AES-256-GCM-SHA384 omitted: needs the P_SHA384 PRF/key-schedule, a later rung.)
14//
15// Extensions reuse the version-agnostic emitters from nx_tls13_ext.nx (server_name, supported_groups,
16// signature_algorithms) + a 1.2-needed ec_point_formats. license_tier: ORIGINAL
17import "nx_syscalls.nx"
18import "nx_tls13_ext.nx"
19
20// supported_groups (ext 0x000A) offering secp256r1 (0x0017) ONLY. The shared 1.3 emitter offers X25519
21// first, but this 1.2 client completes ECDHE over P-256 ONLY (nx_p256_ecdh) -- if a modern server picks
22// X25519 for the ECDHE the handshake fails at key-agreement (verdict 14). Offering just P-256 FORCES the
23// server onto the curve we can complete. All TLS servers support secp256r1, so reach is unaffected.
24func tls12_ext_emit_supported_groups_p256(out: *u8, out_cap: i64) -> i64 {
25 if out_cap < 8 { return 0 - 1 }
26 tls_write_u16_be(out, 0, 0x000A) // supported_groups
27 tls_write_u16_be(out, 2, 4) // ext_data_len = 2 (list_len) + 2 (one group)
28 tls_write_u16_be(out, 4, 2) // list_len = 2 bytes (1 entry)
29 tls_write_u16_be(out, 6, 0x0017) // secp256r1
30 return 8
31}
32// ec_point_formats (ext 0x000B): advertise "uncompressed" (0) -- required by many TLS 1.2 ECDHE servers.
33func tls12_ext_emit_ec_point_formats(out: *u8, out_cap: i64) -> i64 {
34 if out_cap < 6 { return 0 - 1 }
35 tls_write_u16_be(out, 0, 0x000B) // ec_point_formats
36 tls_write_u16_be(out, 2, 2) // ext_data_len = 1 (list len) + 1 (format)
37 out[4] = 1 as u8 // formats list length = 1
38 out[5] = 0 as u8 // format 0 = uncompressed
39 return 6
40}
41
42// Emit a TLS 1.2 ClientHello body (handshake message: type 1 + 3-byte len + body) into `out`.
43// Returns total bytes written, or negative on buffer overflow.
44func tls12_client_hello_emit(random32: *u8, sni: *u8, sni_len: i64, out: *u8, out_cap: i64) -> i64 {
45 if out_cap < 300 + sni_len { return 0 - 1 }
46 if sni_len < 1 { return 0 - 2 }
47
48 var o: i64 = 4 // reserve 4 bytes for the handshake header (type + 24-bit len)
49 tls_write_u16_be(out, o, 0x0303) // client_version = TLS 1.2
50 o = o + 2
51 var i: i64 = 0
52 while i < 32 { out[o + i] = random32[i]; i = i + 1 }
53 o = o + 32
54 out[o] = 0 as u8 // legacy_session_id: empty
55 o = o + 1
56
57 // cipher_suites: list_len(2) + 4 suites * 2 bytes
58 tls_write_u16_be(out, o, 8); o = o + 2
59 tls_write_u16_be(out, o, 0xC02B); o = o + 2
60 tls_write_u16_be(out, o, 0xC02F); o = o + 2
61 tls_write_u16_be(out, o, 0xCCA9); o = o + 2
62 tls_write_u16_be(out, o, 0xCCA8); o = o + 2
63
64 // legacy_compression_methods = { null }
65 out[o] = 1 as u8; o = o + 1
66 out[o] = 0 as u8; o = o + 1
67
68 // extensions: reserve 2 bytes for the list length, fill after
69 let ext_len_off: i64 = o
70 o = o + 2
71 let ext_start: i64 = o
72 let r1: i64 = tls13_ext_emit_server_name(sni, sni_len, (out as i64 + o) as *u8, out_cap - o)
73 if r1 < 0 { return 0 - 3 }
74 o = o + r1
75 let r2: i64 = tls12_ext_emit_supported_groups_p256((out as i64 + o) as *u8, out_cap - o)
76 if r2 < 0 { return 0 - 4 }
77 o = o + r2
78 let r3: i64 = tls12_ext_emit_ec_point_formats((out as i64 + o) as *u8, out_cap - o)
79 if r3 < 0 { return 0 - 5 }
80 o = o + r3
81 let r4: i64 = tls13_ext_emit_signature_algorithms((out as i64 + o) as *u8, out_cap - o)
82 if r4 < 0 { return 0 - 6 }
83 o = o + r4
84 tls_write_u16_be(out, ext_len_off, o - ext_start)
85
86 // handshake header: msg_type(1) = ClientHello(1) + length(3) = body bytes after the header
87 out[0] = 1 as u8
88 let body: i64 = o - 4
89 out[1] = ((body >> 16) & 0xff) as u8
90 out[2] = ((body >> 8) & 0xff) as u8
91 out[3] = (body & 0xff) as u8
92 return o
93}