nx_tls13_server.nx source
↩ module page · 268 lines · 10570 B
1// nx_tls13_server.nx -- TLS 1.3 server-side handshake (sovereign loopback).
2//
3// Phase 0b sovereign-loopback brick per
4// docs/NISHI_TLS13_GAP_AUDIT.md. Mirrors nx_tls13_client.nx so the
5// substrate can be proven end-to-end without depending on any
6// external TLS server (Cloudflare, openssl s_server, etc). The
7// loopback test in nx_tls13_loopback_test.nx wires this module
8// against nx_tls13_client.nx and verifies both sides reach
9// CONNECTED with byte-identical handshake keys.
10//
11// Per user (2026-05-16): "i dont want anything but sovereign".
12// External-server interop is a SEPARATE optional check, never the
13// substrate's correctness proof.
14//
15// What it does today:
16// - parse ClientHello: extract random / session_id / extensions
17// offsets so caller can walk to find key_share + supported_groups
18// - emit minimal ServerHello with TLS 1.3 supported_versions +
19// X25519 key_share + chosen cipher (ChaCha20-Poly1305-SHA256)
20// - server state machine: WAIT_CH -> WAIT_CF -> CONNECTED
21// (caller drives WAIT_CH by calling parse + emit; this module's
22// dispatcher handles WAIT_CF when the client_Finished arrives)
23// - sealed verdict + validity gate
24//
25// What it doesn't do yet:
26// - signature over CertificateVerify (currently the loopback
27// test uses a placeholder; Gap I when wired will sign with
28// ed25519 over the transcript)
29// - HelloRetryRequest emission (the server here ALWAYS accepts
30// the client's first key_share if X25519; if the client offered
31// a group we don't support, today we just fail; queued: emit
32// HRR with our preferred group)
33// - ALPN negotiation (CH offers a list; server picks one; queued)
34// - PSK acceptance / 0-RTT (Gap M)
35//
36// KAT verified:
37// - end-to-end via nx_tls13_loopback_test.nx: client + server both
38// using shipped substrate reach CONNECTED, derive identical
39// handshake_secret + traffic secrets, verify each other's MACs
40//
41// Composes with:
42// - nx_tls13 (handshake type constants + u16/u24 helpers)
43// - nx_tls13_hello (NX_TLS13_CS_* cipher suite constants)
44// - nx_tls13_ext (parser for ServerHello's key_share echo not
45// needed here -- server EMITS them)
46// - nx_tls13_finished + nx_tls13_schedule (CF MAC verify)
47// - nx_tls13_transcript (running hash)
48//
49// license_tier: INDEPENDENT_REDERIVE
50// genealogy_id: international-research-sources/ietf/rfc_8446
51// lineage_id: nishi_tls13_server_q10
52
53// nx_safety_envelope:
54// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
55// sil_target: SIL1
56// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
57// verdict: NOT_YET_EVALUATED
58
59import "nx_syscalls.nx"
60import "nx_tls13.nx"
61import "nx_tls13_hello.nx"
62import "nx_tls13_finished.nx"
63import "nx_tls13_transcript.nx"
64import "nx_tls13_schedule.nx"
65
66const NX_TLS13_SSTATE_WAIT_CH: i64 = 1
67const NX_TLS13_SSTATE_WAIT_CF: i64 = 2
68const NX_TLS13_SSTATE_CONNECTED: i64 = 3
69const NX_TLS13_SSTATE_N: i64 = 4
70
71const NX_TLS13_SERVER_VERDICT_OK: i64 = 1
72const NX_TLS13_SERVER_VERDICT_BAD_MSG_TYPE: i64 = 2
73const NX_TLS13_SERVER_VERDICT_BAD_CH: i64 = 3
74const NX_TLS13_SERVER_VERDICT_BAD_FORMAT: i64 = 4
75const NX_TLS13_SERVER_VERDICT_CF_BAD_MAC: i64 = 5
76const NX_TLS13_SERVER_VERDICT_BUF_OVERFLOW: i64 = 6
77const NX_TLS13_SERVER_VERDICT_N: i64 = 7
78
79const NX_TLS13_SERVER_HASH_LEN: i64 = 32
80
81// Parse ClientHello, expose key field offsets within `buf`. Caller
82// uses tls13_ext_find (shipped) on the returned extensions blob to
83// locate key_share, supported_versions, server_name, etc.
84//
85// On success: *out_random_off points to byte 0 of the 32-byte
86// client random; *out_sid_off/len describe the legacy_session_id
87// (may be empty or 32-byte middlebox-compat random);
88// *out_ext_off/len describe the extensions blob.
89func tls13_server_parse_client_hello(
90 buf: *u8, n: i64,
91 out_random_off: *i64,
92 out_sid_off: *i64, out_sid_len: *i64,
93 out_ext_off: *i64, out_ext_len: *i64
94) -> i64 {
95 if n < 4 { return NX_TLS13_SERVER_VERDICT_BAD_CH }
96 if (buf[0] & 0xff) != HT_CLIENT_HELLO { return NX_TLS13_SERVER_VERDICT_BAD_MSG_TYPE }
97 let body_len: i64 = ((buf[1] & 0xff) << 16) | ((buf[2] & 0xff) << 8) | (buf[3] & 0xff)
98 if 4 + body_len > n { return NX_TLS13_SERVER_VERDICT_BAD_CH }
99 var o: i64 = 4
100 // legacy_version (2)
101 if o + 2 > n { return NX_TLS13_SERVER_VERDICT_BAD_CH }
102 o = o + 2
103 // random (32)
104 if o + 32 > n { return NX_TLS13_SERVER_VERDICT_BAD_CH }
105 *out_random_off = o
106 o = o + 32
107 // session_id (1 byte length + bytes)
108 if o + 1 > n { return NX_TLS13_SERVER_VERDICT_BAD_CH }
109 let sid_len: i64 = buf[o] & 0xff
110 o = o + 1
111 if o + sid_len > n { return NX_TLS13_SERVER_VERDICT_BAD_CH }
112 *out_sid_off = o
113 *out_sid_len = sid_len
114 o = o + sid_len
115 // cipher_suites (2-byte length + suite ids)
116 if o + 2 > n { return NX_TLS13_SERVER_VERDICT_BAD_CH }
117 let cs_list_len: i64 = tls_read_u16_be(buf, o)
118 o = o + 2 + cs_list_len
119 if o > n { return NX_TLS13_SERVER_VERDICT_BAD_CH }
120 // compression methods (1 byte length + bytes)
121 if o + 1 > n { return NX_TLS13_SERVER_VERDICT_BAD_CH }
122 let cm_len: i64 = buf[o] & 0xff
123 o = o + 1 + cm_len
124 if o > n { return NX_TLS13_SERVER_VERDICT_BAD_CH }
125 // extensions (2-byte length + extensions blob)
126 if o + 2 > n { return NX_TLS13_SERVER_VERDICT_BAD_CH }
127 let ext_list_len: i64 = tls_read_u16_be(buf, o)
128 o = o + 2
129 if o + ext_list_len > n { return NX_TLS13_SERVER_VERDICT_BAD_CH }
130 *out_ext_off = o
131 *out_ext_len = ext_list_len
132 return NX_TLS13_SERVER_VERDICT_OK
133}
134
135// Emit a minimal ServerHello. Cipher chosen: ChaCha20-Poly1305-SHA256
136// (matches our AEAD). Extensions emitted: supported_versions
137// (server variant, just 0x0304) + key_share (server variant, single
138// KeyShareEntry with X25519 + our pubkey). session_id_echo is
139// copied verbatim from the client (middlebox compatibility).
140//
141// Output: bytes written including 4-byte handshake header.
142func tls13_server_emit_server_hello(
143 server_random32: *u8,
144 sid_echo: *u8, sid_echo_len: i64,
145 server_pubkey32: *u8,
146 out: *u8, out_cap: i64
147) -> i64 {
148 // header(4) + version(2) + random(32) + sid_len(1) + sid(<=32)
149 // + cipher(2) + compress(1) + ext_list_len(2) + sv_ext(6) + ks_ext(40)
150 // worst case = 4 + 2 + 32 + 1 + 32 + 2 + 1 + 2 + 6 + 40 = 122
151 let need: i64 = 122 + sid_echo_len
152 if out_cap < need { return 0 - NX_TLS13_SERVER_VERDICT_BUF_OVERFLOW }
153 var o: i64 = 4 // reserve 4 for handshake header
154 // legacy_version 0x0303
155 tls_write_u16_be(out, o, TLS_LEGACY_VERSION)
156 o = o + 2
157 // random
158 var i: i64 = 0
159 while i < 32 {
160 out[o + i] = server_random32[i]
161 i = i + 1
162 }
163 o = o + 32
164 // session_id_echo
165 out[o] = sid_echo_len & 0xff
166 o = o + 1
167 var j: i64 = 0
168 while j < sid_echo_len {
169 out[o + j] = sid_echo[j]
170 j = j + 1
171 }
172 o = o + sid_echo_len
173 // cipher_suite (single)
174 tls_write_u16_be(out, o, NX_TLS13_CS_CHACHA20_POLY1305_SHA256)
175 o = o + 2
176 // compression method = 0
177 out[o] = 0
178 o = o + 1
179 // extensions list_len placeholder
180 let ext_list_off: i64 = o
181 o = o + 2
182 let ext_data_start: i64 = o
183 // supported_versions: 0x002b + len(2)=2 + version(2)=0x0304 = 6 bytes
184 tls_write_u16_be(out, o, EXT_SUPPORTED_VERSIONS)
185 o = o + 2
186 tls_write_u16_be(out, o, 2)
187 o = o + 2
188 tls_write_u16_be(out, o, TLS_13_VERSION)
189 o = o + 2
190 // key_share server-variant: 0x0033 + len(2)=36 + group(2) + ke_len(2)=32 + ke(32) = 40 bytes
191 tls_write_u16_be(out, o, EXT_KEY_SHARE)
192 o = o + 2
193 tls_write_u16_be(out, o, 36)
194 o = o + 2
195 tls_write_u16_be(out, o, NG_X25519)
196 o = o + 2
197 tls_write_u16_be(out, o, 32)
198 o = o + 2
199 var k: i64 = 0
200 while k < 32 {
201 out[o + k] = server_pubkey32[k]
202 k = k + 1
203 }
204 o = o + 32
205 // Backfill ext list length
206 let ext_data_len: i64 = o - ext_data_start
207 tls_write_u16_be(out, ext_list_off, ext_data_len)
208 // Backfill handshake header
209 let body_len: i64 = o - 4
210 out[0] = HT_SERVER_HELLO & 0xff
211 tls_write_u24_be(out, 1, body_len)
212 return o
213}
214
215// Server state-machine dispatcher: handle a client_Finished message.
216//
217// Inputs:
218// msg, msg_len = the decrypted handshake message bytes
219// (HT_FINISHED + uint24=32 + 32-byte MAC)
220// client_hs_traffic_secret = chts; the same secret the client
221// derived; server uses it to verify CF
222// transcript_state = running transcript over CH+SH+EE+
223// Cert+CV+SF (server has fed all of
224// those in already)
225// Outputs:
226// *out_new_state = CONNECTED on success
227func tls13_server_dispatch_client_finished(
228 msg: *u8, msg_len: i64,
229 client_hs_traffic_secret: *u8,
230 transcript_state: *u8,
231 out_new_state: *i64
232) -> i64 {
233 if msg_len < 4 { return NX_TLS13_SERVER_VERDICT_BAD_FORMAT }
234 if (msg[0] & 0xff) != HT_FINISHED { return NX_TLS13_SERVER_VERDICT_BAD_MSG_TYPE }
235 let body_len: i64 = ((msg[1] & 0xff) << 16) | ((msg[2] & 0xff) << 8) | (msg[3] & 0xff)
236 if body_len != NX_TLS13_SERVER_HASH_LEN { return NX_TLS13_SERVER_VERDICT_BAD_FORMAT }
237 if 4 + body_len > msg_len { return NX_TLS13_SERVER_VERDICT_BAD_FORMAT }
238 // Snapshot transcript BEFORE feeding CF -- the CF MAC is over
239 // CH..server_Finished (which we've already fed in).
240 let th: *u8 = sys_mmap(64)
241 nx_tls13_transcript_snapshot(transcript_state, th)
242 // finished_key from chts
243 let c_fk: *u8 = sys_mmap(64)
244 tls13_finished_key(client_hs_traffic_secret, NX_TLS13_SERVER_HASH_LEN, c_fk)
245 let received_mac: *u8 = msg + 4
246 let v: i64 = nx_tls13_finished_verify(
247 c_fk, NX_TLS13_SERVER_HASH_LEN,
248 th, NX_TLS13_SERVER_HASH_LEN,
249 received_mac, NX_TLS13_SERVER_HASH_LEN
250 )
251 if v != NX_TLS13_FIN_VERDICT_OK { return NX_TLS13_SERVER_VERDICT_CF_BAD_MAC }
252 nx_tls13_transcript_update(transcript_state, msg, msg_len)
253 *out_new_state = NX_TLS13_SSTATE_CONNECTED
254 return NX_TLS13_SERVER_VERDICT_OK
255}
256
257// Sealed-enum validity gates.
258func nx_tls13_server_verdict_is_valid(v: i64) -> i64 {
259 if v < 0 { return 0 }
260 if v >= NX_TLS13_SERVER_VERDICT_N { return 0 }
261 return 1
262}
263
264func nx_tls13_sstate_is_valid(s: i64) -> i64 {
265 if s < 0 { return 0 }
266 if s >= NX_TLS13_SSTATE_N { return 0 }
267 return 1
268}