nx_tls13_ext.nx source
↩ module page · 485 lines · 22198 B
1// nx_tls13_ext.nx -- TLS 1.3 extension emitters + ServerHello parsers.
2//
3// Phase 0b §F.1 of the Nishi TLS 1.3 stack per
4// docs/NISHI_TLS13_GAP_AUDIT.md. The byte-level emit functions
5// for the 5 extensions a minimum-credible TLS 1.3 ClientHello
6// MUST carry, plus parse functions for the two extensions a
7// ServerHello echoes back (supported_versions + key_share).
8//
9// Per RFC 8446 §9.2: any TLS 1.3 ClientHello MUST contain
10// supported_versions, supported_groups, key_share, and
11// signature_algorithms. We add server_name (RFC 6066 §3) so
12// we can talk to TLS-vhosted servers, and we make ALPN
13// trivially extendable.
14//
15// Wire format per extension is:
16// uint16 extension_type
17// uint16 extension_data_length
18// opaque extension_data[extension_data_length]
19//
20// What it does today:
21// Client emit:
22// - server_name (SNI, RFC 6066 §3, host_name only)
23// - supported_versions client-variant TLS 1.3 only (§4.2.1)
24// - supported_groups X25519 + secp256r1 (§4.2.7)
25// - signature_algorithms ed25519 + rsa_pss_rsae_sha256 +
26// rsa_pss_rsae_sha384 + ecdsa_secp256r1_sha256 (§4.2.3)
27// - key_share client X25519 only (§4.2.8)
28// Server parse:
29// - supported_versions server-variant (single u16) (§4.2.1)
30// - key_share server-variant (single KeyShareEntry) (§4.2.8)
31//
32// What it doesn't do yet:
33// - ALPN emit/parse (queued; single-protocol case is trivial,
34// multi-protocol needs a more general list helper)
35// - psk_key_exchange_modes (queued with Gap M resumption)
36// - pre_shared_key (queued with Gap M)
37// - cookie (HRR path; queued with Gap G)
38// - additional cert chain extensions (status_request, SCT)
39// - additional named groups (P-384, P-521, Kyber, X25519+Kyber768)
40// - additional signature schemes (ecdsa_secp384r1_sha384,
41// rsa_pkcs1_sha256 for legacy, ed448)
42//
43// KAT verified:
44// - each emit produces byte-exact bytes per the wire format
45// - parse round-trip: emit then parse recovers the input
46//
47// Composes with:
48// - nx_tls13 (extension type / named group / signature scheme constants)
49// - nx_tls13_hello (queued; composes these into ClientHello)
50//
51// license_tier: INDEPENDENT_REDERIVE
52// genealogy_id: international-research-sources/ietf/rfc_8446 + ietf/rfc_6066 + ietf/rfc_7301
53// lineage_id: nishi_tls13_extensions_q10
54
55// nx_safety_envelope:
56// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
57// sil_target: SIL1
58// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
59// verdict: NOT_YET_EVALUATED
60
61import "nx_syscalls.nx"
62import "nx_tls13.nx"
63const NX_MAGIC_65535: i64 = 65535
64
65// Sealed verdict for emit/parse operations.
66const NX_TLS13_EXT_VERDICT_OK: i64 = 1
67const NX_TLS13_EXT_VERDICT_BUF_OVERFLOW: i64 = 2
68const NX_TLS13_EXT_VERDICT_BAD_FORMAT: i64 = 3
69const NX_TLS13_EXT_VERDICT_TRUNCATED: i64 = 4
70const NX_TLS13_EXT_VERDICT_UNSUPPORTED: i64 = 5
71const NX_TLS13_EXT_VERDICT_N: i64 = 6
72
73// SNI NameType per RFC 6066 §3 (only host_name = 0 is registered).
74const NX_TLS13_SNI_NAME_TYPE_HOST: i64 = 0
75
76// Emit a complete extension into `out`. Caller provides the body via
77// a callback-less pattern: the body emitter is inlined here per
78// extension since NishiLang has no closures yet.
79//
80// Returns total bytes written (4 header + body), or a negative
81// NX_TLS13_EXT_VERDICT_* on error.
82
83// ---- server_name (extension 0) ----
84//
85// Wire:
86// ext_type(2) + ext_len(2)
87// + ServerNameList_len(2)
88// + name_type(1) + host_name_len(2) + host_name(host_len bytes)
89//
90// Total = 4 + 2 + 1 + 2 + host_len = 9 + host_len bytes.
91func tls13_ext_emit_server_name(
92 host: *u8, host_len: i64,
93 out: *u8, out_cap: i64
94) -> i64 {
95 if host_len < 1 { return 0 - NX_TLS13_EXT_VERDICT_BAD_FORMAT }
96 if host_len > NX_MAGIC_65535 { return 0 - NX_TLS13_EXT_VERDICT_BAD_FORMAT }
97 let total: i64 = 9 + host_len
98 if out_cap < total { return 0 - NX_TLS13_EXT_VERDICT_BUF_OVERFLOW }
99 let inner_data_len: i64 = 3 + 2 + host_len // list_len(2) + type(1) + name_len(2) + name
100 let server_name_list_len: i64 = 1 + 2 + host_len
101 tls_write_u16_be(out, 0, EXT_SERVER_NAME)
102 tls_write_u16_be(out, 2, inner_data_len)
103 tls_write_u16_be(out, 4, server_name_list_len)
104 out[6] = NX_TLS13_SNI_NAME_TYPE_HOST & 0xff
105 tls_write_u16_be(out, 7, host_len)
106 var i: i64 = 0
107 while i < host_len {
108 out[9 + i] = host[i]
109 i = i + 1
110 }
111 return total
112}
113
114// ---- supported_versions client (extension 43, TLS 1.3 only) ----
115//
116// Wire:
117// ext_type(2)=0x002b + ext_len(2)=0x0003
118// + versions_len(1)=0x02 + version(2)=0x0304
119//
120// Always exactly 9 bytes. No caller-supplied data.
121func tls13_ext_emit_supported_versions_tls13(out: *u8, out_cap: i64) -> i64 {
122 // Offer TLS 1.3 AND TLS 1.2 (browser-like). ROOT CAUSE (probed 2026-06-23 vs news.ycombinator.com): a
123 // 1.3-ONLY offer drew a FATAL protocol_version(70) alert from servers whose policy wants 1.2 in the list.
124 // A real 1.3 server still negotiates 1.3 (which we speak) when both are offered; Wikipedia keeps working.
125 // Return value EXACTLY matches the 9 bytes written (the old off-by-2 warning was a return!=written mismatch;
126 // here they agree at 9).
127 if out_cap < 9 { return 0 - NX_TLS13_EXT_VERDICT_BUF_OVERFLOW }
128 tls_write_u16_be(out, 0, EXT_SUPPORTED_VERSIONS)
129 tls_write_u16_be(out, 2, 5) // ext_data_len = 1 (list len) + 4 (two versions)
130 out[4] = 4 // versions list len = 4 (two versions * 2 bytes)
131 tls_write_u16_be(out, 5, TLS_13_VERSION) // 0x0304 (preferred)
132 tls_write_u16_be(out, 7, 0x0303) // 0x0303 = TLS 1.2 (server still picks 1.3 if it supports it)
133 return 9
134}
135
136// ---- ALPN (extension 16, RFC 7301 + RFC 8446 §4.2) ----
137//
138// Wire format:
139// ext_type(2)=0x0010 + ext_len(2)
140// + protocol_name_list_len(2)
141// + (protocol_name_len(1) + protocol_name(bytes)) repeated
142//
143// This emit ships the single most common case: advertise "http/1.1"
144// alone. The server selects ONE protocol from the client's list and
145// echoes it back. Single-protocol clients still MUST send the list
146// framing; the protocol_name_list_len wraps the (len + name) pair.
147//
148// Bytes for "http/1.1":
149// protocol_name_len(1) = 8
150// protocol_name(8) = "http/1.1"
151// protocol_name_list_len(2) = 9 (the 1 + 8 above)
152// ext_data_len(2) = 11 (the 2 + 9 above)
153// ext_type(2) + ext_data_len(2) + payload(11) = 15 wire bytes total.
154//
155// Multi-protocol ALPN (e.g. advertising both "h2" and "http/1.1") is
156// queued -- needs a more general list helper that takes a caller-
157// supplied slice of (name, len) pairs. This single-protocol primitive
158// is enough to unblock fetching from public HTTPS servers that don't
159// require HTTP/2 (most servers will fall back to http/1.1 when h2 is
160// not in the ALPN list).
161func tls13_ext_emit_alpn_http11(out: *u8, out_cap: i64) -> i64 {
162 if out_cap < 15 { return 0 - NX_TLS13_EXT_VERDICT_BUF_OVERFLOW }
163 tls_write_u16_be(out, 0, EXT_APPLICATION_LAYER_PROTOCOL)
164 tls_write_u16_be(out, 2, 11) // ext_data_len = list_len(2) + entry(9)
165 tls_write_u16_be(out, 4, 9) // protocol_name_list_len = name_len(1) + name(8)
166 out[6] = 8 as u8 // protocol_name_len
167 out[7] = 104 as u8 // 'h'
168 out[8] = 116 as u8 // 't'
169 out[9] = 116 as u8 // 't'
170 out[10] = 112 as u8 // 'p'
171 out[11] = 47 as u8 // '/'
172 out[12] = 49 as u8 // '1'
173 out[13] = 46 as u8 // '.'
174 out[14] = 49 as u8 // '1'
175 return 15
176}
177
178// ---- ALPN advertise ["h2","http/1.1"] (extension 16, RFC 7301 + RFC 9113 §3.1) ----
179//
180// R4-H2-006 (HTTP/2 transport ladder). The multi-protocol sibling of
181// tls13_ext_emit_alpn_http11 (:157): a 2-entry ProtocolNameList in the
182// browser-default order h2-first, http/1.1-fallback. h2c is DEPRECATED and is
183// never advertised (RFC 9113 §3.1). This is the ALPN list a sovereign HTTP/2
184// client sends so the server can negotiate "h2" (and gracefully fall back to
185// "http/1.1" when it cannot).
186//
187// Wire (RFC 7301 §3.1 / RFC 8446 §4.2):
188// ext_type(2)=0x0010 + ext_data_len(2)
189// + protocol_name_list_len(2)
190// + name_len(1)=2 + "h2" (0x68 0x32)
191// + name_len(1)=8 + "http/1.1" (0x68 0x74 0x74 0x70 0x2f 0x31 0x2e 0x31)
192// list_len = (1+2) + (1+8) = 12
193// ext_data_len = list_len(2) + 12 = 14
194// total wire = type(2) + ext_data_len(2) + 14 = 18 bytes.
195// KAT (spec Part A): 00 10 00 0e 00 0c 02 68 32 08 68 74 74 70 2f 31 2e 31
196func tls13_ext_emit_alpn_h2_http11(out: *u8, out_cap: i64) -> i64 {
197 if out_cap < 18 { return 0 - NX_TLS13_EXT_VERDICT_BUF_OVERFLOW }
198 let h2_len: i64 = 2
199 let http11_len: i64 = 8
200 let list_len: i64 = (1 + h2_len) + (1 + http11_len) // 12
201 let ext_data_len: i64 = 2 + list_len // 14
202 tls_write_u16_be(out, 0, EXT_APPLICATION_LAYER_PROTOCOL)
203 tls_write_u16_be(out, 2, ext_data_len) // 0x000e
204 tls_write_u16_be(out, 4, list_len) // 0x000c
205 // entry 1: "h2"
206 out[6] = h2_len as u8 // 0x02
207 out[7] = 104 as u8 // 'h'
208 out[8] = 50 as u8 // '2'
209 // entry 2: "http/1.1"
210 out[9] = http11_len as u8 // 0x08
211 out[10] = 104 as u8 // 'h'
212 out[11] = 116 as u8 // 't'
213 out[12] = 116 as u8 // 't'
214 out[13] = 112 as u8 // 'p'
215 out[14] = 47 as u8 // '/'
216 out[15] = 49 as u8 // '1'
217 out[16] = 46 as u8 // '.'
218 out[17] = 49 as u8 // '1'
219 return 18
220}
221
222// ---- ALPN advertise ["h2"] ONLY (extension 16, RFC 7301 + RFC 9113 §3.1) ----
223//
224// R4-H2 capstone (NBC-FETCH-001): the UNAMBIGUOUS-negotiation sibling of
225// tls13_ext_emit_alpn_h2_http11 (:192). Advertising EXACTLY ONE protocol "h2"
226// makes the negotiation a yes/no: a server that supports HTTP/2 selects "h2"
227// (and the connection IS h2 after CONNECTED -- no need to parse the encrypted EE
228// ALPN echo to learn the protocol); a server that does NOT support h2 MUST abort
229// the handshake with a no_application_protocol alert (RFC 7301 §3.2). This is
230// what an h2-ONLY sovereign client sends so "TLS ALPN selects h2" == "handshake
231// succeeded". http/1.1 fallback is deliberately omitted here (use
232// tls13_ext_emit_alpn_h2_http11 when graceful downgrade is wanted).
233//
234// Wire (RFC 7301 §3.1 / RFC 8446 §4.2):
235// ext_type(2)=0x0010 + ext_data_len(2)
236// + protocol_name_list_len(2)
237// + name_len(1)=2 + "h2" (0x68 0x32)
238// list_len = 1 + 2 = 3 ; ext_data_len = 2 + 3 = 5 ; total wire = 4 + 5 = 9.
239// KAT: 00 10 00 05 00 03 02 68 32
240func tls13_ext_emit_alpn_h2_only(out: *u8, out_cap: i64) -> i64 {
241 if out_cap < 9 { return 0 - NX_TLS13_EXT_VERDICT_BUF_OVERFLOW }
242 let h2_len: i64 = 2
243 let list_len: i64 = 1 + h2_len // 3
244 let ext_data_len: i64 = 2 + list_len // 5
245 tls_write_u16_be(out, 0, EXT_APPLICATION_LAYER_PROTOCOL)
246 tls_write_u16_be(out, 2, ext_data_len) // 0x0005
247 tls_write_u16_be(out, 4, list_len) // 0x0003
248 out[6] = h2_len as u8 // 0x02
249 out[7] = 104 as u8 // 'h'
250 out[8] = 50 as u8 // '2'
251 return 9
252}
253
254// ---- Parse the server-selected ALPN protocol from EncryptedExtensions ----
255//
256// Per RFC 7301 §3.1 the server echoes the chosen protocol in a
257// ProtocolNameList that MUST contain EXACTLY ONE ProtocolName (TLS 1.3 carries
258// it in EncryptedExtensions, RFC 8446 §4.3.1 -- parse AFTER decrypting EE).
259//
260// Inputs: `ext_data` points PAST the ext_type + ext_data_len pair (i.e. at the
261// 2-byte protocol_name_list_len); `ext_data_len` is that ext_data length.
262// Outputs: *out_ptr = offset (relative to ext_data) of the protocol-name bytes,
263// *out_len = its length. Returns OK or a negative NX_TLS13_EXT_VERDICT_*.
264//
265// Rejections (tamper discipline):
266// - ext_data shorter than the list header -> TRUNCATED
267// - list_len != ext_data_len - 2 (framing mismatch) -> BAD_FORMAT
268// - the single entry does not consume the whole list
269// (i.e. a SECOND ProtocolName follows -> trailing bytes) -> BAD_FORMAT
270// KAT (spec Part A): EE ALPN selecting h2, ext_data = 00 03 02 68 32 (len 5) ->
271// out_ptr -> the "68 32" bytes (offset 3), out_len = 2 ("h2").
272func tls13_ext_parse_alpn_selected(
273 ext_data: *u8, ext_data_len: i64,
274 out_ptr: *i64, out_len: *i64
275) -> i64 {
276 if ext_data_len < 3 { return 0 - NX_TLS13_EXT_VERDICT_TRUNCATED }
277 let list_len: i64 = tls_read_u16_be(ext_data, 0)
278 // list_len MUST equal everything after the 2-byte list-length field.
279 if list_len != ext_data_len - 2 { return 0 - NX_TLS13_EXT_VERDICT_BAD_FORMAT }
280 if list_len < 1 { return 0 - NX_TLS13_EXT_VERDICT_BAD_FORMAT }
281 let name_len: i64 = ext_data[2] & 0xff
282 // the SINGLE entry (name_len(1) + name) MUST exactly fill the list -- a
283 // second ProtocolName would leave (name_len(1)+name) < list_len, rejected.
284 if 1 + name_len != list_len { return 0 - NX_TLS13_EXT_VERDICT_BAD_FORMAT }
285 if name_len < 1 { return 0 - NX_TLS13_EXT_VERDICT_BAD_FORMAT }
286 *out_ptr = 3 // offset of the name bytes within ext_data
287 *out_len = name_len
288 return NX_TLS13_EXT_VERDICT_OK
289}
290
291// ---- supported_groups (extension 10) ----
292//
293// Wire:
294// ext_type(2)=0x000a + ext_len(2)
295// + named_group_list_len(2)
296// + named_group(2) repeated
297//
298// We ship X25519 + secp256r1 = 2 entries = 4 bytes of groups.
299// Total: 4 + 2 + 4 = 10 bytes.
300func tls13_ext_emit_supported_groups(out: *u8, out_cap: i64) -> i64 {
301 if out_cap < 10 { return 0 - NX_TLS13_EXT_VERDICT_BUF_OVERFLOW }
302 tls_write_u16_be(out, 0, EXT_SUPPORTED_GROUPS)
303 tls_write_u16_be(out, 2, 6) // ext_data_len = 2 (list_len) + 4 (groups)
304 tls_write_u16_be(out, 4, 4) // list_len = 4 bytes (2 entries * 2)
305 tls_write_u16_be(out, 6, NG_X25519) // 0x001d
306 tls_write_u16_be(out, 8, NG_SECP256R1) // 0x0017
307 // Wire bytes: type(2) + ext_data_len(2) + list_len(2) + 2 groups * 2 = 10.
308 // Earlier this function returned 12 (off-by-2) which threw off forward
309 // iteration in tls13_ext_find; see nx_tls13_hello_test assertion #82.
310 return 10
311}
312
313// ---- signature_algorithms (extension 13) ----
314//
315// Wire:
316// ext_type(2)=0x000d + ext_len(2)
317// + schemes_list_len(2)
318// + scheme(2) repeated
319//
320// We ship 4 schemes: ed25519, ecdsa_secp256r1_sha256, rsa_pss_rsae_sha256,
321// rsa_pss_rsae_sha384 = 8 bytes of schemes.
322// Total: 4 + 2 + 8 = 14 bytes.
323func tls13_ext_emit_signature_algorithms(out: *u8, out_cap: i64) -> i64 {
324 // The 4 we can verify for the handshake CertificateVerify (ECDSA P-256, RSA-PSS 256/384, ed25519) PLUS the
325 // legacy rsa_pkcs1_* algs MANY cert chains/intermediates are signed with. TLS 1.3 forbids rsa_pkcs1 for the
326 // handshake signature, so a compliant server only uses them for the CERT CHAIN (which our X.509 pipeline
327 // validates with standard RSA) -- additive, can't be mis-selected for CertVerify, can't regress the working
328 // Wikipedia handshake. Fixes -4 failures where the server couldn't present a chain our narrow list accepted,
329 // and broadens our ClientHello toward a real browser's set.
330 if out_cap < 20 { return 0 - NX_TLS13_EXT_VERDICT_BUF_OVERFLOW }
331 tls_write_u16_be(out, 0, EXT_SIGNATURE_ALGORITHMS)
332 tls_write_u16_be(out, 2, 16) // ext_data_len = 2 (list_len) + 14 (7 entries)
333 tls_write_u16_be(out, 4, 14) // list_len = 7 entries * 2
334 tls_write_u16_be(out, 6, SS_ECDSA_SECP256R1_SHA256) // 0x0403 (handshake + cert)
335 tls_write_u16_be(out, 8, SS_RSA_PSS_RSAE_SHA256) // 0x0804 (handshake + cert)
336 tls_write_u16_be(out, 10, SS_RSA_PSS_RSAE_SHA384) // 0x0805 (handshake + cert)
337 tls_write_u16_be(out, 12, SS_ED25519) // 0x0807 (handshake + cert)
338 tls_write_u16_be(out, 14, 0x0401) // rsa_pkcs1_sha256 (CERT CHAIN only)
339 tls_write_u16_be(out, 16, 0x0501) // rsa_pkcs1_sha384 (CERT CHAIN only)
340 tls_write_u16_be(out, 18, 0x0601) // rsa_pkcs1_sha512 (CERT CHAIN only)
341 return 20
342}
343
344// ---- key_share client (extension 51, X25519 only) ----
345//
346// Wire:
347// ext_type(2)=0x0033 + ext_len(2)
348// + client_shares_len(2)
349// + group(2)=0x001d + key_exchange_len(2)=0x0020 + pubkey(32)
350//
351// Total: 4 + 2 + 4 + 32 = 42 bytes... actually let me recount.
352// ext_type(2) + ext_len(2) + client_shares_len(2) + group(2) + ke_len(2) + ke(32) = 42 bytes.
353// ext_data = client_shares_len(2) + group(2) + ke_len(2) + ke(32) = 38 bytes.
354// client_shares = group(2) + ke_len(2) + ke(32) = 36 bytes.
355func tls13_ext_emit_key_share_x25519(
356 pubkey32: *u8,
357 out: *u8, out_cap: i64
358) -> i64 {
359 if out_cap < 42 { return 0 - NX_TLS13_EXT_VERDICT_BUF_OVERFLOW }
360 tls_write_u16_be(out, 0, EXT_KEY_SHARE)
361 tls_write_u16_be(out, 2, 38) // ext_data_len
362 tls_write_u16_be(out, 4, 36) // client_shares_len
363 tls_write_u16_be(out, 6, NG_X25519) // group = 0x001d
364 tls_write_u16_be(out, 8, 32) // key_exchange length
365 var i: i64 = 0
366 while i < 32 {
367 out[10 + i] = pubkey32[i]
368 i = i + 1
369 }
370 return 42
371}
372
373// ---- key_share client DUAL (extension 51, X25519 + secp256r1) ----
374//
375// What real browsers send: one KeyShareEntry per group the client
376// can actually complete, so a server preferring EITHER group picks
377// its share directly and no HelloRetryRequest round-trip is needed
378// (rung B4-P256-KEYSHARE; unblocks the B2 httpbin CH-alert class).
379//
380// Wire:
381// ext_type(2)=0x0033 + ext_len(2)=107
382// + client_shares_len(2)=105
383// + group(2)=0x001d + ke_len(2)=32 + pubkey(32) (X25519, preferred)
384// + group(2)=0x0017 + ke_len(2)=65 + pubkey(65) (secp256r1, 0x04||X||Y)
385//
386// Total: 4 + 2 + 36 + 69 = 111 bytes.
387func tls13_ext_emit_key_share_dual(
388 x25519_pub32: *u8,
389 p256_pub65: *u8,
390 out: *u8, out_cap: i64
391) -> i64 {
392 if out_cap < 111 { return 0 - NX_TLS13_EXT_VERDICT_BUF_OVERFLOW }
393 tls_write_u16_be(out, 0, EXT_KEY_SHARE)
394 tls_write_u16_be(out, 2, 107) // ext_data_len
395 tls_write_u16_be(out, 4, 105) // client_shares_len
396 tls_write_u16_be(out, 6, NG_X25519) // entry 1: group 0x001d
397 tls_write_u16_be(out, 8, 32)
398 var i: i64 = 0
399 while i < 32 {
400 out[10 + i] = x25519_pub32[i]
401 i = i + 1
402 }
403 tls_write_u16_be(out, 42, NG_SECP256R1) // entry 2: group 0x0017
404 tls_write_u16_be(out, 44, 65)
405 i = 0
406 while i < 65 {
407 out[46 + i] = p256_pub65[i]
408 i = i + 1
409 }
410 return 111
411}
412
413// ---- key_share client P-256 ONLY (extension 51, secp256r1) ----
414//
415// Census/probe instrument (and future HRR building block): forces
416// the server to complete on secp256r1 or refuse, which is how the
417// B2 row live-proves the P-256 path against an independent peer
418// (servers offered x25519 always prefer it, so the dual share never
419// exercises P-256 live).
420//
421// Wire: ext_type(2)=0x0033 + ext_len(2)=71 + shares_len(2)=69
422// + group(2)=0x0017 + ke_len(2)=65 + pubkey(65) = 75 bytes.
423func tls13_ext_emit_key_share_p256(
424 p256_pub65: *u8,
425 out: *u8, out_cap: i64
426) -> i64 {
427 if out_cap < 75 { return 0 - NX_TLS13_EXT_VERDICT_BUF_OVERFLOW }
428 tls_write_u16_be(out, 0, EXT_KEY_SHARE)
429 tls_write_u16_be(out, 2, 71) // ext_data_len
430 tls_write_u16_be(out, 4, 69) // client_shares_len
431 tls_write_u16_be(out, 6, NG_SECP256R1) // group = 0x0017
432 tls_write_u16_be(out, 8, 65)
433 var i: i64 = 0
434 while i < 65 {
435 out[10 + i] = p256_pub65[i]
436 i = i + 1
437 }
438 return 75
439}
440
441// ---- Parse supported_versions in ServerHello (server-variant). ----
442//
443// Per RFC 8446 §4.2.1: in ServerHello the supported_versions
444// extension data is just a single ProtocolVersion (no list wrapper).
445//
446// Inputs: pointer to the ext_data bytes (i.e. PAST the ext_type +
447// ext_len pair) and its length. Returns the selected_version, or
448// negative NX_TLS13_EXT_VERDICT_* on error.
449func tls13_ext_parse_supported_versions_server(
450 ext_data: *u8, ext_data_len: i64
451) -> i64 {
452 if ext_data_len != 2 { return 0 - NX_TLS13_EXT_VERDICT_BAD_FORMAT }
453 return tls_read_u16_be(ext_data, 0)
454}
455
456// ---- Parse key_share in ServerHello (single KeyShareEntry). ----
457//
458// Per RFC 8446 §4.2.8: in ServerHello the key_share extension data
459// is a single KeyShareEntry (group + key_exchange).
460//
461// Inputs: pointer to ext_data + length. Outputs: selected group +
462// pointer to key_exchange bytes within ext_data + length of those
463// bytes. Returns OK or negative verdict.
464func tls13_ext_parse_key_share_server(
465 ext_data: *u8, ext_data_len: i64,
466 out_group: *i64,
467 out_ke_off: *i64,
468 out_ke_len: *i64
469) -> i64 {
470 if ext_data_len < 4 { return 0 - NX_TLS13_EXT_VERDICT_TRUNCATED }
471 let group: i64 = tls_read_u16_be(ext_data, 0)
472 let ke_len: i64 = tls_read_u16_be(ext_data, 2)
473 if 4 + ke_len > ext_data_len { return 0 - NX_TLS13_EXT_VERDICT_TRUNCATED }
474 *out_group = group
475 *out_ke_off = 4
476 *out_ke_len = ke_len
477 return NX_TLS13_EXT_VERDICT_OK
478}
479
480// Sealed-enum validity gate.
481func nx_tls13_ext_verdict_is_valid(v: i64) -> i64 {
482 if v < 0 { return 0 }
483 if v >= NX_TLS13_EXT_VERDICT_N { return 0 }
484 return 1
485}