nx_tls13_hello_test.nx source
↩ module page · 204 lines · 8734 B
1// nx_tls13_hello_test.nx -- structural + iterator KAT for the
2// ClientHello emitter + ServerHello parser + extensions iterator.
3//
4// We don't pin to RFC 8448 ยง3 ClientHello bytes because that vector
5// uses a specific extension ordering + a different cipher-suite list +
6// includes extensions we don't yet ship (padding, psk_kex_modes).
7// Instead we verify:
8//
9// A. Emit succeeds + total length matches spec formula
10// B. Handshake header is well-formed: msg_type=1, uint24 length
11// equals total - 4
12// C. Body fields land at the expected offsets: legacy_version,
13// random (round-trip), session_id (empty), cipher_suites (3
14// TLS 1.3 standard suites in canonical order), compression
15// D. Extensions list length matches actual sum of emitted extensions
16// E. Iterator finds each of the 5 extensions by type
17// F. server_name extension contents includes our SNI hostname
18// G. key_share extension contents includes our pubkey
19// H. Re-parse as ServerHello (similar structure -- cipher_suite is
20// single not list, so we use a hand-built mini-ServerHello)
21// verifies the parser's structural walk works
22//
23// expect_exit: 0
24// license_tier: ORIGINAL
25
26import "nx_syscalls.nx"
27import "nx_tls13.nx"
28import "nx_tls13_ext.nx"
29import "nx_tls13_hello.nx"
30
31func main() -> i64 {
32 // ---- Test A-D: emit ClientHello ----
33 let random32: *u8 = sys_mmap(64)
34 var i: i64 = 0
35 while i < 32 {
36 random32[i] = 0xc0 + i
37 i = i + 1
38 }
39 let sni: *u8 = sys_mmap(32)
40 // "example.com" = 11 bytes
41 sni[0]=0x65; sni[1]=0x78; sni[2]=0x61; sni[3]=0x6d
42 sni[4]=0x70; sni[5]=0x6c; sni[6]=0x65; sni[7]=0x2e
43 sni[8]=0x63; sni[9]=0x6f; sni[10]=0x6d
44 let pubkey: *u8 = sys_mmap(64)
45 i = 0
46 while i < 32 {
47 pubkey[i] = 0x10 + i
48 i = i + 1
49 }
50
51 let buf: *u8 = sys_mmap(1024)
52 let n: i64 = tls13_client_hello_emit(random32, sni, 11, pubkey, buf, 1024)
53 if n < 0 { return 1 }
54
55 // ---- Test B: handshake header sanity ----
56 if (buf[0] & 0xff) != HT_CLIENT_HELLO { return 2 }
57 let body_len: i64 = ((buf[1] & 0xff) << 16) | ((buf[2] & 0xff) << 8) | (buf[3] & 0xff)
58 if body_len != n - 4 { return 3 }
59
60 // ---- Test C: legacy_version + random + session_id + ciphers + compression ----
61 if (buf[4] & 0xff) != 0x03 { return 4 }
62 if (buf[5] & 0xff) != 0x03 { return 5 } // legacy_version
63 // random starts at offset 6
64 var ri: i64 = 0
65 while ri < 32 {
66 if (buf[6 + ri] & 0xff) != (random32[ri] & 0xff) { return 10 + ri }
67 ri = ri + 1
68 }
69 // session_id length (empty) at offset 38
70 if (buf[38] & 0xff) != 0 { return 50 }
71 // cipher_suites list_len at offset 39 (2 bytes) = 4
72 // (two suites: AES-128-GCM-SHA256 first, ChaCha20-Poly1305 second
73 // per the dual-cipher dispatch wired 2026-05-20).
74 if (buf[39] & 0xff) != 0 { return 51 }
75 if (buf[40] & 0xff) != 4 { return 52 }
76 // cipher 1: 13 01 (TLS_AES_128_GCM_SHA256)
77 if (buf[41] & 0xff) != 0x13 { return 53 }
78 if (buf[42] & 0xff) != 0x01 { return 54 }
79 // cipher 2: 13 03 (TLS_CHACHA20_POLY1305_SHA256)
80 if (buf[43] & 0xff) != 0x13 { return 55 }
81 if (buf[44] & 0xff) != 0x03 { return 56 }
82 // compression: 01 00 at offset 45
83 if (buf[45] & 0xff) != 1 { return 59 }
84 if (buf[46] & 0xff) != 0 { return 60 }
85 // extensions list_len at offset 47 (2 bytes)
86 let ext_list_len: i64 = tls_read_u16_be(buf, 47)
87 let ext_blob_off: i64 = 49
88
89 // ---- Test D: extension list length matches actual emitted total ----
90 // Sum of extension lengths: SV(7) + SG(10) + SA(14) + KS(42) + SNI(20)
91 // + ALPN(15) = 108 bytes
92 if ext_list_len != 108 { return 70 }
93 // ClientHello body bytes after the handshake header:
94 // legacy_version(2) + random(32) + sid_len(1) + suites_list_len(2)
95 // + suites(4) + compress_len(1) + compress(1) + ext_list_len(2)
96 // + ext_data(108)
97 // = 153
98 // Plus handshake header(4) = 157 total bytes
99 if n != 157 { return 71 }
100
101 // ---- Test E: iterator finds each extension by type ----
102 let blob: *u8 = buf + ext_blob_off
103 let out_off: *i64 = sys_mmap(16) as *i64
104 let out_len: *i64 = sys_mmap(16) as *i64
105
106 // supported_versions (43)
107 let f1: i64 = tls13_ext_find(blob, ext_list_len, EXT_SUPPORTED_VERSIONS, out_off, out_len)
108 if f1 != NX_TLS13_HELLO_VERDICT_OK { return 80 }
109 if *out_len != 3 { return 81 }
110
111 // supported_groups (10)
112 let f2: i64 = tls13_ext_find(blob, ext_list_len, EXT_SUPPORTED_GROUPS, out_off, out_len)
113 if f2 != NX_TLS13_HELLO_VERDICT_OK { return 82 }
114 if *out_len != 6 { return 83 }
115
116 // signature_algorithms (13)
117 let f3: i64 = tls13_ext_find(blob, ext_list_len, EXT_SIGNATURE_ALGORITHMS, out_off, out_len)
118 if f3 != NX_TLS13_HELLO_VERDICT_OK { return 84 }
119 if *out_len != 10 { return 85 }
120
121 // key_share (51)
122 let f4: i64 = tls13_ext_find(blob, ext_list_len, EXT_KEY_SHARE, out_off, out_len)
123 if f4 != NX_TLS13_HELLO_VERDICT_OK { return 86 }
124 if *out_len != 38 { return 87 }
125
126 // server_name (0)
127 let f5: i64 = tls13_ext_find(blob, ext_list_len, EXT_SERVER_NAME, out_off, out_len)
128 if f5 != NX_TLS13_HELLO_VERDICT_OK { return 88 }
129 if *out_len != 16 { return 89 }
130
131 // Non-present extension (use 0x4242 -- arbitrary unused number)
132 let fmiss: i64 = tls13_ext_find(blob, ext_list_len, 0x4242, out_off, out_len)
133 if fmiss != NX_TLS13_HELLO_VERDICT_NOT_FOUND { return 90 }
134
135 // ---- Test F: SNI hostname round-trips inside server_name ext data ----
136 // After f5 above, *out_off points to start of SNI ext_data within blob.
137 // Re-walk to recover *out_off.
138 tls13_ext_find(blob, ext_list_len, EXT_SERVER_NAME, out_off, out_len)
139 let sni_data_off: i64 = *out_off
140 // SNI ext_data layout: list_len(2) + name_type(1) + host_len(2) + host
141 let recovered_host: *u8 = blob + sni_data_off + 5
142 var hi: i64 = 0
143 while hi < 11 {
144 if (recovered_host[hi] & 0xff) != (sni[hi] & 0xff) { return 100 + hi }
145 hi = hi + 1
146 }
147
148 // ---- Test G: pubkey round-trips inside key_share ext data ----
149 tls13_ext_find(blob, ext_list_len, EXT_KEY_SHARE, out_off, out_len)
150 let ks_data_off: i64 = *out_off
151 // key_share ext_data layout: client_shares_len(2) + group(2) + ke_len(2) + pubkey
152 let recovered_pk: *u8 = blob + ks_data_off + 6
153 var pi: i64 = 0
154 while pi < 32 {
155 if (recovered_pk[pi] & 0xff) != (pubkey[pi] & 0xff) { return 120 + pi }
156 pi = pi + 1
157 }
158
159 // ---- Test H: parse a hand-built minimal ServerHello ----
160 let sh: *u8 = sys_mmap(256)
161 sh[0] = HT_SERVER_HELLO & 0xff
162 // Body: lv(2) + random(32) + sid_len(1)=0 + cipher_suite(2) + compression(1)=0
163 // + ext_list_len(2)=0 = 40 bytes total body
164 sh[1] = 0; sh[2] = 0; sh[3] = 40
165 sh[4] = 0x03; sh[5] = 0x03 // legacy_version
166 var sri: i64 = 0
167 while sri < 32 {
168 sh[6 + sri] = 0x70 + sri // random
169 sri = sri + 1
170 }
171 sh[38] = 0 // session_id len = 0
172 sh[39] = 0x13; sh[40] = 0x03 // cipher = ChaCha20-Poly1305-SHA256
173 sh[41] = 0 // compression = 0
174 sh[42] = 0; sh[43] = 0 // ext_list_len = 0
175
176 let p_lv: *i64 = sys_mmap(16) as *i64
177 let p_ro: *i64 = sys_mmap(16) as *i64
178 let p_cs: *i64 = sys_mmap(16) as *i64
179 let p_eo: *i64 = sys_mmap(16) as *i64
180 let p_el: *i64 = sys_mmap(16) as *i64
181 let pv: i64 = tls13_server_hello_parse(sh, 44, p_lv, p_ro, p_cs, p_eo, p_el)
182 if pv != NX_TLS13_HELLO_VERDICT_OK { return 150 }
183 if *p_lv != TLS_LEGACY_VERSION { return 151 }
184 if *p_ro != 6 { return 152 }
185 if (sh[*p_ro] & 0xff) != 0x70 { return 153 }
186 if *p_cs != NX_TLS13_CS_CHACHA20_POLY1305_SHA256 { return 154 }
187 if *p_el != 0 { return 155 }
188
189 // ---- Test H': truncated ServerHello rejected ----
190 let trunc: i64 = tls13_server_hello_parse(sh, 10, p_lv, p_ro, p_cs, p_eo, p_el)
191 if trunc != NX_TLS13_HELLO_VERDICT_TRUNCATED { return 160 }
192
193 // Wrong msg_type rejected
194 sh[0] = HT_CLIENT_HELLO & 0xff
195 let badmt: i64 = tls13_server_hello_parse(sh, 44, p_lv, p_ro, p_cs, p_eo, p_el)
196 if badmt != NX_TLS13_HELLO_VERDICT_BAD_HEADER { return 161 }
197
198 // ---- Test I: verdict gate ----
199 if nx_tls13_hello_verdict_is_valid(NX_TLS13_HELLO_VERDICT_OK) != 1 { return 170 }
200 if nx_tls13_hello_verdict_is_valid(NX_TLS13_HELLO_VERDICT_N) != 0 { return 171 }
201 if nx_tls13_hello_verdict_is_valid(0 - 1) != 0 { return 172 }
202
203 return 0
204}