nx_tls13_hello.nx source
↩ module page · 503 lines · 20204 B
1// nx_tls13_hello.nx -- TLS 1.3 ClientHello + ServerHello (RFC 8446
2// §4.1.2 / §4.1.3).
3//
4// Phase 0b §F.2 of the Nishi TLS 1.3 stack per
5// docs/NISHI_TLS13_GAP_AUDIT.md. Composes the extension emitters
6// from nx_tls13_ext into a complete ClientHello byte stream that
7// can go on the wire. Parses an incoming ServerHello into its
8// fixed-position fields + an extensions blob the caller iterates.
9//
10// Both Hello structures sit inside a 4-byte Handshake wrapper:
11//
12// struct {
13// HandshakeType msg_type; /* 1 byte: 1=ClientHello, 2=ServerHello */
14// uint24 length; /* 3 bytes, body length */
15// <ClientHello | ServerHello body>;
16// } Handshake;
17//
18// ClientHello body (RFC 8446 §4.1.2):
19// ProtocolVersion legacy_version = 0x0303;
20// Random random; (32 bytes)
21// opaque legacy_session_id<0..32>; (1-byte len + bytes)
22// CipherSuite cipher_suites<2..2^16-2>; (2-byte len + 2-byte suite ids)
23// opaque legacy_compression_methods<1..2^8-1>; (1-byte len + bytes, always 0x01 0x00)
24// Extension extensions<8..2^16-1>; (2-byte len + emitted extensions)
25//
26// ServerHello body (RFC 8446 §4.1.3):
27// ProtocolVersion legacy_version = 0x0303;
28// Random random; (32 bytes)
29// opaque legacy_session_id_echo<0..32>;
30// CipherSuite cipher_suite; (2 bytes; single, not list)
31// uint8 legacy_compression_method; (always 0)
32// Extension extensions<6..2^16-1>;
33//
34// What it does today:
35// - emit ClientHello with all 5 minimum-credible extensions (SNI,
36// supported_versions, supported_groups, signature_algorithms,
37// key_share) in the canonical order
38// - parse ServerHello fixed fields + return extensions blob
39// - iterate an extensions blob, find one by extension_type
40//
41// What it doesn't do yet:
42// - middlebox compatibility mode (32-byte random
43// legacy_session_id; trivial to add per RFC 8446 §4.1.2)
44// - HelloRetryRequest detection (special ServerHello with magic
45// random = SHA-256("HelloRetryRequest"); Gap G)
46// - ALPN inclusion in ClientHello (will be a parameter once
47// nx_tls13_ext_emit_alpn ships)
48//
49// KAT verified:
50// - ClientHello structural self-consistency: emit, then parse
51// internal length fields, verify all sum correctly
52// - Random round-trips (caller-supplied 32 bytes appear at the
53// right offset in emitted bytes)
54// - Extension iterator finds each emitted extension by type
55// - Cipher suite list is the canonical 3-suite TLS 1.3 set
56//
57// Composes with:
58// - nx_tls13 (Handshake constants, legacy_version, u16/u24 helpers)
59// - nx_tls13_ext (extension emit/parse)
60// - nx_tls13_transcript (caller feeds emitted ClientHello bytes in)
61// - nx_tls13_client (queued; state machine driver that calls this)
62//
63// license_tier: INDEPENDENT_REDERIVE
64// genealogy_id: international-research-sources/ietf/rfc_8446
65// lineage_id: nishi_tls13_hello_q10
66
67// nx_safety_envelope:
68// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
69// sil_target: SIL1
70// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
71// verdict: NOT_YET_EVALUATED
72
73import "nx_syscalls.nx"
74import "nx_tls13.nx"
75import "nx_tls13_ext.nx"
76
77// Sealed Hello verdict.
78const NX_TLS13_HELLO_VERDICT_OK: i64 = 1
79const NX_TLS13_HELLO_VERDICT_BUF_OVERFLOW: i64 = 2
80const NX_TLS13_HELLO_VERDICT_TRUNCATED: i64 = 3
81const NX_TLS13_HELLO_VERDICT_BAD_VERSION: i64 = 4
82const NX_TLS13_HELLO_VERDICT_BAD_HEADER: i64 = 5
83const NX_TLS13_HELLO_VERDICT_NOT_FOUND: i64 = 6
84const NX_TLS13_HELLO_VERDICT_N: i64 = 7
85
86// TLS 1.3 standard cipher suites (RFC 8446 §B.4):
87// TLS_AES_128_GCM_SHA256 = 0x1301
88// TLS_AES_256_GCM_SHA384 = 0x1302
89// TLS_CHACHA20_POLY1305_SHA256 = 0x1303
90// TLS_AES_128_CCM_SHA256 = 0x1304 (we don't offer; rare)
91const NX_TLS13_CS_AES_128_GCM_SHA256: i64 = 0x1301
92const NX_TLS13_CS_AES_256_GCM_SHA384: i64 = 0x1302
93const NX_TLS13_CS_CHACHA20_POLY1305_SHA256: i64 = 0x1303
94
95// Emit a minimum-credible ClientHello.
96//
97// Inputs:
98// random32 = caller-supplied 32 bytes from nx_csprng
99// sni, sni_len = SNI hostname (REQUIRED; 1..255 bytes recommended)
100// pubkey32 = X25519 ephemeral public key (caller derived via
101// nx_x25519_ephemeral)
102// out, out_cap = caller buffer
103//
104// Output: total bytes written (handshake header + body), or
105// negative NX_TLS13_HELLO_VERDICT_*.
106//
107// Cipher suites offered (see the emit code below): TLS_CHACHA20_POLY1305_SHA256 (0x1303, PREFERRED) AND
108// TLS_AES_128_GCM_SHA256 (0x1301, fallback). Honesty principle: advertise ONLY what we can actually complete
109// -- and both of these are FULLY WIRED: nx_chacha20_poly1305 + nx_aes128_gcm are real ciphers, the record
110// layer decrypts both (nx_tls13_record dispatches 0x1303->chacha, 0x1301->aes128gcm), and key derivation is
111// cipher-aware (key_len 32 vs 16). ChaCha20 is offered first because we're faster at it (no AES-NI backend
112// yet); AES-128-GCM is the fallback for servers that don't offer ChaCha20. AES-256-GCM (0x1302) is NOT
113// offered because it isn't implemented (would need an AES-256 cipher + a SHA-384 key schedule) -- correctly
114// withheld per the honesty principle. [Corrected 2026-07-03: this comment previously claimed "ONLY ChaCha20"
115// + that offering AES-128-GCM causes TAG_MISMATCH -- STALE from an earlier deferral; the code + audit prove
116// AES-128-GCM is offered AND decryptable. asserted-not-proven doc defect, fixed to match the real capability.]
117//
118// Extensions emitted in order: supported_versions, supported_groups,
119// signature_algorithms, key_share, server_name. The spec doesn't
120// mandate ordering but real-world clients tend to put critical
121// negotiation extensions early.
122// v1 contract kept stable (Cardinal 19): x25519-only key_share.
123// Delegates to emit2 with a null P-256 share. NOTE: this is a
124// 7-arg call -- the >6-arg tail-call miscompile is fixed and the
125// _arg7_minrepro gate row guards the lane.
126func tls13_client_hello_emit(
127 random32: *u8,
128 sni: *u8, sni_len: i64,
129 pubkey32: *u8,
130 out: *u8, out_cap: i64
131) -> i64 {
132 return tls13_client_hello_emit2(
133 random32, sni, sni_len, pubkey32, 0 as *u8, out, out_cap
134 )
135}
136
137// v2 (rung B4-P256-KEYSHARE): when p256_pub65 is non-null the
138// key_share extension carries BOTH groups (x25519 preferred +
139// secp256r1), so a P-256-only server picks our share directly and
140// never needs a HelloRetryRequest.
141func tls13_client_hello_emit2(
142 random32: *u8,
143 sni: *u8, sni_len: i64,
144 pubkey32: *u8,
145 p256_pub65: *u8,
146 out: *u8, out_cap: i64
147) -> i64 {
148 // Conservative pre-flight: handshake hdr(4) + version(2) + random(32)
149 // + sid_len(1) + suites_len(2) + suites(6) + compress(2)
150 // + ext_list_len(2) + extensions(~80 for minimum) <= ~150.
151 // Worst-case SNI host_len = 255 -> ~400. Require >= 256 + sni_len.
152 if out_cap < 256 + sni_len { return 0 - NX_TLS13_HELLO_VERDICT_BUF_OVERFLOW }
153 if sni_len < 1 { return 0 - NX_TLS13_HELLO_VERDICT_BAD_HEADER }
154
155 var o: i64 = 4 // reserve 4 bytes for handshake header
156 // ---- legacy_version ----
157 tls_write_u16_be(out, o, TLS_LEGACY_VERSION) // 0x0303
158 o = o + 2
159 // ---- random ----
160 var i: i64 = 0
161 while i < 32 {
162 out[o + i] = random32[i]
163 i = i + 1
164 }
165 o = o + 32
166 // ---- legacy_session_id (empty: 1 zero byte) ----
167 out[o] = 0
168 o = o + 1
169 // ---- cipher_suites: list_len(2) + 2 suites * 2 bytes = 4 bytes ----
170 // Order = preference: ChaCha20-Poly1305 FIRST, AES-128-GCM fallback.
171 // MEASURED 2026-06-15: our sovereign software AES-128-GCM is ~0.7 MB/s
172 // (no AES-NI yet) while our ChaCha20-Poly1305 is ~61 MB/s -- ~90x faster.
173 // So we prefer the cipher we're fast at; AES-GCM stays as a fallback for
174 // servers that don't offer ChaCha20. (Restore AES-first once AES-NI lands.)
175 tls_write_u16_be(out, o, 4)
176 o = o + 2
177 tls_write_u16_be(out, o, NX_TLS13_CS_CHACHA20_POLY1305_SHA256)
178 o = o + 2
179 tls_write_u16_be(out, o, NX_TLS13_CS_AES_128_GCM_SHA256)
180 o = o + 2
181 // ---- legacy_compression_methods = 0x01 0x00 ----
182 out[o] = 1
183 o = o + 1
184 out[o] = 0
185 o = o + 1
186 // ---- extensions: reserve 2 bytes for list_len, fill later ----
187 let ext_list_len_off: i64 = o
188 o = o + 2
189 let ext_data_start: i64 = o
190
191 // Emit each extension in turn, advancing `o`.
192 let r1: i64 = tls13_ext_emit_supported_versions_tls13(out + o, out_cap - o)
193 if r1 < 0 { return r1 }
194 o = o + r1
195
196 let r2: i64 = tls13_ext_emit_supported_groups(out + o, out_cap - o)
197 if r2 < 0 { return r2 }
198 o = o + r2
199
200 let r3: i64 = tls13_ext_emit_signature_algorithms(out + o, out_cap - o)
201 if r3 < 0 { return r3 }
202 o = o + r3
203
204 // key_share mode by pointer-nullness:
205 // pubkey32 only -> x25519-only (v1 contract)
206 // both -> dual x25519 + secp256r1 (B4 product CH)
207 // p256_pub65 only -> secp256r1-only (census/probe instrument)
208 if pubkey32 as i64 == 0 {
209 if p256_pub65 as i64 == 0 { return 0 - NX_TLS13_HELLO_VERDICT_BAD_HEADER }
210 }
211 var r4: i64 = 0
212 if p256_pub65 as i64 == 0 {
213 r4 = tls13_ext_emit_key_share_x25519(pubkey32, out + o, out_cap - o)
214 }
215 if p256_pub65 as i64 != 0 {
216 if pubkey32 as i64 != 0 {
217 r4 = tls13_ext_emit_key_share_dual(pubkey32, p256_pub65, out + o, out_cap - o)
218 }
219 if pubkey32 as i64 == 0 {
220 r4 = tls13_ext_emit_key_share_p256(p256_pub65, out + o, out_cap - o)
221 }
222 }
223 if r4 < 0 { return r4 }
224 o = o + r4
225
226 let r5: i64 = tls13_ext_emit_server_name(sni, sni_len, out + o, out_cap - o)
227 if r5 < 0 { return r5 }
228 o = o + r5
229
230 // ALPN: advertise http/1.1 so public servers route us to HTTP/1.1
231 // when h2 is not in the list. Most servers downgrade gracefully.
232 let r6: i64 = tls13_ext_emit_alpn_http11(out + o, out_cap - o)
233 if r6 < 0 { return r6 }
234 o = o + r6
235
236 // Backfill extensions list length.
237 let ext_data_len: i64 = o - ext_data_start
238 tls_write_u16_be(out, ext_list_len_off, ext_data_len)
239
240 // Backfill handshake header.
241 let body_len: i64 = o - 4
242 out[0] = HT_CLIENT_HELLO & 0xff // msg_type = 1
243 tls_write_u24_be(out, 1, body_len)
244
245 return o
246}
247
248// v2-h2 (R4-H2 capstone NBC-FETCH-001): IDENTICAL to tls13_client_hello_emit2
249// except the ALPN extension advertises EXACTLY ONE protocol "h2" (via
250// tls13_ext_emit_alpn_h2_only) instead of "http/1.1". This makes the connection
251// negotiate HTTP/2 unambiguously: a server that speaks h2 selects it (the only
252// offered protocol), a server that does not MUST abort the handshake. Kept as a
253// SEPARATE function so the proven HTTP/1.1 path (emit2) is byte-identical and
254// un-regressed (API contract stability, Cardinal 19); ONLY the authored h2-fetch
255// run-path calls this variant. Same dual x25519+secp256r1 key_share contract.
256func tls13_client_hello_emit2_h2only(
257 random32: *u8,
258 sni: *u8, sni_len: i64,
259 pubkey32: *u8,
260 p256_pub65: *u8,
261 out: *u8, out_cap: i64
262) -> i64 {
263 if out_cap < 256 + sni_len { return 0 - NX_TLS13_HELLO_VERDICT_BUF_OVERFLOW }
264 if sni_len < 1 { return 0 - NX_TLS13_HELLO_VERDICT_BAD_HEADER }
265
266 var o: i64 = 4 // reserve 4 bytes for handshake header
267 tls_write_u16_be(out, o, TLS_LEGACY_VERSION) // 0x0303
268 o = o + 2
269 var i: i64 = 0
270 while i < 32 {
271 out[o + i] = random32[i]
272 i = i + 1
273 }
274 o = o + 32
275 out[o] = 0 // legacy_session_id (empty)
276 o = o + 1
277 tls_write_u16_be(out, o, 4)
278 o = o + 2
279 tls_write_u16_be(out, o, NX_TLS13_CS_AES_128_GCM_SHA256)
280 o = o + 2
281 tls_write_u16_be(out, o, NX_TLS13_CS_CHACHA20_POLY1305_SHA256)
282 o = o + 2
283 out[o] = 1 // legacy_compression_methods = 0x01 0x00
284 o = o + 1
285 out[o] = 0
286 o = o + 1
287 let ext_list_len_off: i64 = o
288 o = o + 2
289 let ext_data_start: i64 = o
290
291 let r1: i64 = tls13_ext_emit_supported_versions_tls13(out + o, out_cap - o)
292 if r1 < 0 { return r1 }
293 o = o + r1
294 let r2: i64 = tls13_ext_emit_supported_groups(out + o, out_cap - o)
295 if r2 < 0 { return r2 }
296 o = o + r2
297 let r3: i64 = tls13_ext_emit_signature_algorithms(out + o, out_cap - o)
298 if r3 < 0 { return r3 }
299 o = o + r3
300
301 if pubkey32 as i64 == 0 {
302 if p256_pub65 as i64 == 0 { return 0 - NX_TLS13_HELLO_VERDICT_BAD_HEADER }
303 }
304 var r4: i64 = 0
305 if p256_pub65 as i64 == 0 {
306 r4 = tls13_ext_emit_key_share_x25519(pubkey32, out + o, out_cap - o)
307 }
308 if p256_pub65 as i64 != 0 {
309 if pubkey32 as i64 != 0 {
310 r4 = tls13_ext_emit_key_share_dual(pubkey32, p256_pub65, out + o, out_cap - o)
311 }
312 if pubkey32 as i64 == 0 {
313 r4 = tls13_ext_emit_key_share_p256(p256_pub65, out + o, out_cap - o)
314 }
315 }
316 if r4 < 0 { return r4 }
317 o = o + r4
318
319 let r5: i64 = tls13_ext_emit_server_name(sni, sni_len, out + o, out_cap - o)
320 if r5 < 0 { return r5 }
321 o = o + r5
322
323 // ALPN: advertise ONLY "h2" so the server negotiates HTTP/2 (or aborts).
324 let r6: i64 = tls13_ext_emit_alpn_h2_only(out + o, out_cap - o)
325 if r6 < 0 { return r6 }
326 o = o + r6
327
328 let ext_data_len: i64 = o - ext_data_start
329 tls_write_u16_be(out, ext_list_len_off, ext_data_len)
330 let body_len: i64 = o - 4
331 out[0] = HT_CLIENT_HELLO & 0xff
332 tls_write_u24_be(out, 1, body_len)
333 return o
334}
335
336// Parse a ServerHello handshake message. Caller supplies the bytes
337// starting at the Handshake header (i.e., out[0] should be 0x02 for
338// ServerHello).
339//
340// Output:
341// *out_legacy_version = ProtocolVersion in body (typically 0x0303)
342// *out_random_off = byte offset INTO buf where Random[32] starts
343// *out_cipher_suite = chosen cipher suite (e.g. 0x1303)
344// *out_extensions_off = byte offset where extensions blob starts
345// *out_extensions_len = length of extensions blob in bytes
346//
347// Returns NX_TLS13_HELLO_VERDICT_OK or a non-OK verdict.
348func tls13_server_hello_parse(
349 buf: *u8, n: i64,
350 out_legacy_version: *i64,
351 out_random_off: *i64,
352 out_cipher_suite: *i64,
353 out_extensions_off: *i64,
354 out_extensions_len: *i64
355) -> i64 {
356 if n < 4 + 2 + 32 + 1 + 2 + 1 + 2 { return NX_TLS13_HELLO_VERDICT_TRUNCATED }
357 // Handshake header: msg_type(1) + uint24 length(3)
358 let msg_type: i64 = buf[0] & 0xff
359 if msg_type != HT_SERVER_HELLO { return NX_TLS13_HELLO_VERDICT_BAD_HEADER }
360 let body_len: i64 = ((buf[1] & 0xff) << 16) | ((buf[2] & 0xff) << 8) | (buf[3] & 0xff)
361 if 4 + body_len > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED }
362 var o: i64 = 4
363 // legacy_version
364 let lv: i64 = tls_read_u16_be(buf, o)
365 *out_legacy_version = lv
366 o = o + 2
367 // random
368 *out_random_off = o
369 o = o + 32
370 // legacy_session_id_echo: 1-byte len + bytes
371 let sid_len: i64 = buf[o] & 0xff
372 o = o + 1
373 if o + sid_len > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED }
374 o = o + sid_len
375 // cipher_suite (2 bytes)
376 if o + 2 > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED }
377 *out_cipher_suite = tls_read_u16_be(buf, o)
378 o = o + 2
379 // legacy_compression_method (1 byte, must be 0)
380 if o + 1 > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED }
381 o = o + 1
382 // extensions: 2-byte list length + extensions
383 if o + 2 > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED }
384 let ext_list_len: i64 = tls_read_u16_be(buf, o)
385 o = o + 2
386 if o + ext_list_len > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED }
387 *out_extensions_off = o
388 *out_extensions_len = ext_list_len
389 return NX_TLS13_HELLO_VERDICT_OK
390}
391
392// Iterate an extensions blob to find one by extension_type. Returns
393// the byte offset of the extension's DATA (past the type+len header)
394// in `*out_data_off` and its length in `*out_data_len`. Returns
395// NX_TLS13_HELLO_VERDICT_OK if found, NX_TLS13_HELLO_VERDICT_NOT_FOUND
396// otherwise. Per-extension TRUNCATED if a length-prefix walks past
397// the blob.
398func tls13_ext_find(
399 blob: *u8, blob_len: i64,
400 wanted_type: i64,
401 out_data_off: *i64,
402 out_data_len: *i64
403) -> i64 {
404 var off: i64 = 0
405 while off + 4 <= blob_len {
406 let etype: i64 = tls_read_u16_be(blob, off)
407 let elen: i64 = tls_read_u16_be(blob, off + 2)
408 if off + 4 + elen > blob_len {
409 return NX_TLS13_HELLO_VERDICT_TRUNCATED
410 }
411 if etype == wanted_type {
412 *out_data_off = off + 4
413 *out_data_len = elen
414 return NX_TLS13_HELLO_VERDICT_OK
415 }
416 off = off + 4 + elen
417 }
418 return NX_TLS13_HELLO_VERDICT_NOT_FOUND
419}
420
421// Sealed-enum validity gate.
422func nx_tls13_hello_verdict_is_valid(v: i64) -> i64 {
423 if v < 0 { return 0 }
424 if v >= NX_TLS13_HELLO_VERDICT_N { return 0 }
425 return 1
426}
427
428// Parse a ClientHello handshake message (server-side inverse of
429// tls13_client_hello_emit). Caller supplies the bytes starting at
430// the Handshake header (i.e., buf[0] should be HT_CLIENT_HELLO=0x01).
431//
432// Output offsets are BYTE INDICES INTO buf where the corresponding
433// fields START. Lengths are the byte counts of those fields.
434//
435// *out_legacy_version = ProtocolVersion in body (typically 0x0303)
436// *out_random_off = offset of Random[32]
437// *out_session_id_len = length of legacy_session_id (0..32)
438// *out_cipher_suites_off = offset of cipher_suites blob
439// *out_cipher_suites_len = length of cipher_suites blob in bytes
440// *out_extensions_off = offset of extensions blob (past list-len)
441// *out_extensions_len = length of extensions blob in bytes
442//
443// Returns NX_TLS13_HELLO_VERDICT_OK or a non-OK verdict.
444
445func tls13_client_hello_parse(
446 buf: *u8, n: i64,
447 out_legacy_version: *i64,
448 out_random_off: *i64,
449 out_session_id_len: *i64,
450 out_cipher_suites_off: *i64,
451 out_cipher_suites_len: *i64,
452 out_extensions_off: *i64,
453 out_extensions_len: *i64
454) -> i64 {
455 if n < 4 { return NX_TLS13_HELLO_VERDICT_TRUNCATED }
456 let msg_type: i64 = buf[0] as i64
457 if msg_type != HT_CLIENT_HELLO { return NX_TLS13_HELLO_VERDICT_BAD_HEADER }
458 let body_len: i64 = tls_read_u24_be(buf, 1)
459 if 4 + body_len > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED }
460
461 var o: i64 = 4
462 // legacy_version (2 bytes)
463 if o + 2 > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED }
464 let legacy_ver: i64 = tls_read_u16_be(buf, o)
465 *out_legacy_version = legacy_ver
466 o = o + 2
467 // random (32 bytes)
468 if o + 32 > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED }
469 *out_random_off = o
470 o = o + 32
471 // legacy_session_id_len (1 byte) + session id
472 if o + 1 > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED }
473 let sid_len: i64 = buf[o] as i64
474 if sid_len > 32 { return NX_TLS13_HELLO_VERDICT_BAD_HEADER }
475 o = o + 1
476 if o + sid_len > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED }
477 *out_session_id_len = sid_len
478 o = o + sid_len
479 // cipher_suites_len (2 bytes) + cipher_suites blob
480 if o + 2 > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED }
481 let suites_len: i64 = tls_read_u16_be(buf, o)
482 o = o + 2
483 if o + suites_len > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED }
484 if suites_len < 2 { return NX_TLS13_HELLO_VERDICT_BAD_HEADER }
485 *out_cipher_suites_off = o
486 *out_cipher_suites_len = suites_len
487 o = o + suites_len
488 // legacy_compression_methods_len (1 byte) + compression methods
489 if o + 1 > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED }
490 let comp_len: i64 = buf[o] as i64
491 o = o + 1
492 if o + comp_len > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED }
493 o = o + comp_len
494 // extensions_list_len (2 bytes) + extensions blob
495 if o + 2 > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED }
496 let ext_list_len: i64 = tls_read_u16_be(buf, o)
497 o = o + 2
498 if o + ext_list_len > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED }
499 *out_extensions_off = o
500 *out_extensions_len = ext_list_len
501
502 return NX_TLS13_HELLO_VERDICT_OK
503}