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}