code wiki / (root) / nx_tls13_hello_test.nx

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}