code wiki / (root) / nx_tls13_server_session_emit_sh.nx

nx_tls13_server_session_emit_sh.nx source

↩ module page · 263 lines · 10145 B

1// nx_tls13_server_session_emit_sh.nx -- server-side ServerHello 2// emitter + session orchestrator step (CH_RECEIVED -> SH_SENT). 3// 4// Inverse of the existing tls13_server_hello_parse (which the CLIENT 5// uses to consume what we emit here). Round-trip proof in the 6// accompanying smoke: server emits SH, client's parser consumes it. 7// 8// What this file ships: 9// - tls13_ext_emit_supported_versions_server (single u16, no list) 10// - tls13_ext_emit_key_share_server_x25519 (single KeyShareEntry) 11// - tls13_server_hello_emit (full body + handshake header) 12// - nx_tls13_server_session_emit_sh (session step, transcript update) 13// 14// Per RFC 8446 §4.1.3, ServerHello body layout: 15// ProtocolVersion legacy_version = 0x0303; 16// Random server_random[32]; 17// opaque legacy_session_id_echo<0..32>; 18// CipherSuite cipher_suite; (2 bytes, single) 19// uint8 legacy_compression_method; (always 0) 20// Extension extensions<6..2^16-1>; 21// 22// Extensions emitted by this server: 23// supported_versions(43) -- single u16 selected_version = 0x0304 24// key_share(51) -- single KeyShareEntry { group, ke_len, ke } 25// 26// Cipher suite chosen: TLS_CHACHA20_POLY1305_SHA256 (the only AEAD 27// the substrate currently implements; matches the client side's 28// substrate-honesty cipher list in tls13_client_hello_emit). 29// 30// State machine: requires session.state == CH_RECEIVED; advances to 31// SH_SENT on success. 32// 33// license_tier: ORIGINAL 34 35import "nx_syscalls.nx" 36import "nx_tls13.nx" 37import "nx_tls13_hello.nx" 38import "nx_tls13_ext.nx" 39import "nx_tls13_transcript.nx" 40import "nx_tls13_server_session.nx" 41 42const NX_TLS13_SH_TLS13_VERSION: i64 = 0x0304 // TLS 1.3 selector 43 44// ----- server supported_versions extension ----- 45// 46// Layout: ext_type(2) + ext_len(2) + selected_version(2) = 6 bytes. 47// Per RFC 8446 §4.2.1: in ServerHello the supported_versions extension 48// data is just a single ProtocolVersion (no list wrapper). 49// 50// Inverse of tls13_ext_parse_supported_versions_server (already in 51// nx_tls13_ext.nx). 52 53func tls13_ext_emit_supported_versions_server(out: *u8, out_cap: i64) -> i64 { 54 if out_cap < 6 { return 0 - NX_TLS13_EXT_VERDICT_BUF_OVERFLOW } 55 tls_write_u16_be(out, 0, EXT_SUPPORTED_VERSIONS) 56 tls_write_u16_be(out, 2, 2) // ext_data_len 57 tls_write_u16_be(out, 4, NX_TLS13_SH_TLS13_VERSION) // 0x0304 58 return 6 59} 60 61// ----- server key_share extension ----- 62// 63// Layout: ext_type(2) + ext_len(2) + group(2) + ke_len(2) + ke(32) = 40 bytes. 64// Per RFC 8446 §4.2.8: in ServerHello the key_share extension data 65// is a SINGLE KeyShareEntry (no client_shares list wrapper). 66 67func tls13_ext_emit_key_share_server_x25519( 68 server_pub32: *u8, 69 out: *u8, out_cap: i64 70) -> i64 { 71 if out_cap < 40 { return 0 - NX_TLS13_EXT_VERDICT_BUF_OVERFLOW } 72 tls_write_u16_be(out, 0, EXT_KEY_SHARE) 73 tls_write_u16_be(out, 2, 36) // ext_data_len = group+ke_len+ke 74 tls_write_u16_be(out, 4, NG_X25519) // group = 0x001d 75 tls_write_u16_be(out, 6, 32) // ke_len 76 var i: i64 = 0 77 while i < 32 { 78 out[8 + i] = server_pub32[i] 79 i = i + 1 80 } 81 return 40 82} 83 84// secp256r1 server KeyShareEntry (rung B4 server side, 2026-06-10): 85// ext_type(2) + ext_len(2) + group(2) + ke_len(2) + ke(65) = 73 bytes. 86func tls13_ext_emit_key_share_server_p256( 87 server_pub65: *u8, 88 out: *u8, out_cap: i64 89) -> i64 { 90 if out_cap < 73 { return 0 - NX_TLS13_EXT_VERDICT_BUF_OVERFLOW } 91 tls_write_u16_be(out, 0, EXT_KEY_SHARE) 92 tls_write_u16_be(out, 2, 69) // ext_data_len = group+ke_len+ke 93 tls_write_u16_be(out, 4, NG_SECP256R1) // group = 0x0017 94 tls_write_u16_be(out, 6, 65) // ke_len 95 var i: i64 = 0 96 while i < 65 { 97 out[8 + i] = server_pub65[i] 98 i = i + 1 99 } 100 return 73 101} 102 103// ----- ServerHello emit (handshake header + body) ----- 104// 105// Output: total bytes written, or negative NX_TLS13_HELLO_VERDICT_*. 106 107// v1 contract kept stable: x25519 key_share. Delegates to emit2. 108func tls13_server_hello_emit( 109 server_random32: *u8, 110 cipher_suite: i64, 111 sid_echo: *u8, sid_echo_len: i64, 112 server_pub32: *u8, 113 out: *u8, out_cap: i64 114) -> i64 { 115 return tls13_server_hello_emit2( 116 server_random32, cipher_suite, sid_echo, sid_echo_len, 117 29, server_pub32, 0 as *u8, out, out_cap) 118} 119 120// v2 (rung B4 server side): kex_group picks the KeyShareEntry -- 121// 29 = x25519 (server_pub32), 23 = secp256r1 (server_p256_pub65). 122func tls13_server_hello_emit2( 123 server_random32: *u8, 124 cipher_suite: i64, 125 sid_echo: *u8, sid_echo_len: i64, 126 kex_group: i64, 127 server_pub32: *u8, 128 server_p256_pub65: *u8, 129 out: *u8, out_cap: i64 130) -> i64 { 131 // hdr(4) + version(2) + random(32) + sid_echo_len(1) + sid_echo 132 // + cipher_suite(2) + compression(1) + ext_list_len(2) + 133 // supported_versions(6) + key_share(40) = ~90 + sid_echo 134 if out_cap < 128 + sid_echo_len { return 0 - NX_TLS13_HELLO_VERDICT_BUF_OVERFLOW } 135 if sid_echo_len < 0 { return 0 - NX_TLS13_HELLO_VERDICT_BAD_HEADER } 136 if sid_echo_len > 32 { return 0 - NX_TLS13_HELLO_VERDICT_BAD_HEADER } 137 138 var o: i64 = 4 // reserve 4 bytes for handshake header 139 tls_write_u16_be(out, o, TLS_LEGACY_VERSION) 140 o = o + 2 141 // server_random[32] 142 var i: i64 = 0 143 while i < 32 { 144 out[o + i] = server_random32[i] 145 i = i + 1 146 } 147 o = o + 32 148 // legacy_session_id_echo 149 out[o] = (sid_echo_len & 0xff) as u8 150 o = o + 1 151 var j: i64 = 0 152 while j < sid_echo_len { 153 out[o + j] = sid_echo[j] 154 j = j + 1 155 } 156 o = o + sid_echo_len 157 // cipher_suite (single u16) 158 tls_write_u16_be(out, o, cipher_suite) 159 o = o + 2 160 // legacy_compression_method = 0 161 out[o] = 0 162 o = o + 1 163 // extensions list_len placeholder 164 let ext_list_len_off: i64 = o 165 o = o + 2 166 let ext_data_start: i64 = o 167 168 let r1: i64 = tls13_ext_emit_supported_versions_server(out + o, out_cap - o) 169 if r1 < 0 { return r1 } 170 o = o + r1 171 172 var r2: i64 = 0 173 if kex_group == 23 { 174 r2 = tls13_ext_emit_key_share_server_p256(server_p256_pub65, out + o, out_cap - o) 175 } 176 if kex_group != 23 { 177 r2 = tls13_ext_emit_key_share_server_x25519(server_pub32, out + o, out_cap - o) 178 } 179 if r2 < 0 { return r2 } 180 o = o + r2 181 182 let ext_data_len: i64 = o - ext_data_start 183 tls_write_u16_be(out, ext_list_len_off, ext_data_len) 184 185 // Backfill handshake header (msg_type = HT_SERVER_HELLO = 2) 186 let body_len: i64 = o - 4 187 out[0] = 2 188 tls_write_u24_be(out, 1, body_len) 189 return o 190} 191 192// ----- session orchestrator step ----- 193// 194// Emits a ServerHello into out_buf and updates the session: 195// - state advances CH_RECEIVED -> SH_SENT 196// - transcript hash updated with the emitted bytes 197// 198// Returns POSITIVE bytes-written on success, or NEGATIVE 199// -(NX_TLS13_SSESSION_*) verdict. 200 201func nx_tls13_server_session_emit_sh( 202 session: *Tls13ServerSession, 203 out: *u8, out_cap: i64 204) -> i64 { 205 if (session as i64) == 0 { return 0 - NX_TLS13_SSESSION_BAD_STATE } 206 if (out as i64) == 0 { return 0 - NX_TLS13_SSESSION_BAD_STATE } 207 if session.state != NX_TLS13_SSTATE_CH_RECEIVED { 208 return 0 - NX_TLS13_SSESSION_BAD_STATE 209 } 210 211 // TLS-1.2 INTEROP FIX (2026-07-31). We unconditionally emit supported_versions below, which 212 // RFC 8446 4.2 and RFC 5246 both FORBID when the client did not offer it. A TLS-1.2-only 213 // ClientHello therefore received a ServerHello carrying a 1.3-only extension and every 214 // conformant client aborted -- OpenSSL with u0027tls_collect_extensions: unsolicited extensionu0027, 215 // 61 bytes in, no certificate, no cipher. Net effect: NO 1.2-only client could reach ANY site 216 // we serve. Invisible for months because curl, browsers and our own client all negotiate 1.3 217 // and never take this path. 218 // 219 // This is a 1.3-ONLY server, so the correct answer to a ClientHello without supported_versions 220 // is to REFUSE THE HANDSHAKE, not to invent an extension the peer never asked for. Refusing 221 // here (rather than gating the extension inside emit2) keeps the emitter signature and its 222 // five existing tests untouched, and gives the caller a clean protocol error to turn into a 223 // fatal alert instead of a malformed ServerHello the peer must guess at. 224 if session.client_offered_sup_ver == 0 { 225 return 0 - NX_TLS13_SSESSION_PROTOCOL_ERR 226 } 227 228 // Echo the client's session_id byte-for-byte per RFC 8446 §4.1.3. 229 // Captured by recv_ch into session.client_session_id (up to 32 230 // bytes). Empty if the client sent no session_id (rare; openssl, 231 // browsers, curl all send a 32-byte sid for middlebox compat). 232 // 233 // Wire layout: 5-byte TLS record header + handshake message body. 234 // Record header MUST be at offset 0..4; body starts at offset 5. 235 // Without this, clients (openssl, curl, browsers) see byte 5 as 236 // record_type (SSL3_GET_RECORD: wrong version number). 237 if out_cap < 5 { return 0 - NX_TLS13_SSESSION_BUF_OVERFLOW } 238 let n: i64 = tls13_server_hello_emit2( 239 session.server_random, 240 session.cipher_suite, 241 session.client_session_id, session.client_session_id_len, 242 session.kex_group, 243 session.x25519_pub, 244 session.p256_pub, 245 out + 5, out_cap - 5) 246 if n < 0 { 247 // Map HELLO verdict to SSESSION verdict. 248 return 0 - NX_TLS13_SSESSION_BUF_OVERFLOW 249 } 250 251 // Backfill TLS record header: type(1)=Handshake(22), version(2)=0x0303, length(2)=n 252 out[0] = 22 as u8 253 out[1] = 0x03 as u8 254 out[2] = 0x03 as u8 255 out[3] = ((n >> 8) & 0xff) as u8 256 out[4] = (n & 0xff) as u8 257 258 // Update transcript hash with the SH handshake body (NOT the record header). 259 // Transcript per RFC 8446 §4.4.1 captures handshake messages, not record layer. 260 nx_tls13_transcript_update(session.transcript, out + 5, n) 261 session.state = NX_TLS13_SSTATE_SH_SENT 262 return n + 5 263}