nx_tls13_server_session.nx source
↩ module page · 271 lines · 12176 B
1// nx_tls13_server_session.nx -- TLS 1.3 SERVER session state + state
2// machine. The inverse of nx_tls13_client_session.
3//
4// =====================================================================
5// SUPERIOR-CAPABILITY MOTIVATION (per the 2026-05-20 cardinal):
6//
7// The substrate already ships full TLS 1.3 CLIENT side (handshake +
8// ALPN + record layer + auth) -- nishifamily.com fetches HTTPS today
9// bits-up. But to SERVE nishifamily.com itself bits-up we need
10// server-side handshake. No substrate currently does TLS 1.3 server
11// in NishiLang; this file is the first stone.
12//
13// Per docs/NISHIFAMILY_COM_ALL_BITS_UP_STATUS_2026_05_20.md, TLS
14// 1.3 server is the single biggest gap remaining for "all things
15// bits-up on nishifamily.com." Once landed, Phase D-E of the
16// master roadmap composes the shipped HTTP server + ACME + DNS +
17// static-site-builder into a full sovereign HTTPS stack.
18// =====================================================================
19//
20// State machine (inverse of client; RFC 8446 §4):
21//
22// SSTATE_INIT
23// -> SSTATE_CH_RECEIVED (peer ClientHello parsed)
24// -> SSTATE_SH_SENT (we emitted ServerHello + key share)
25// -> SSTATE_EE_SENT (EncryptedExtensions written)
26// -> SSTATE_CERT_SENT (server Certificate)
27// -> SSTATE_CV_SENT (CertificateVerify)
28// -> SSTATE_SF_SENT (server Finished)
29// -> SSTATE_CLIENT_FIN_RECV (peer Finished parsed + verified)
30// -> SSTATE_CONNECTED (app data flow OK)
31//
32// Public API (this commit):
33// struct Tls13ServerSession { /* see below */ }
34// nx_tls13_server_session_new(server_random, server_x25519_priv)
35// -> *Tls13ServerSession
36// nx_tls13_server_state_is_valid(s) -> 0|1
37//
38// Queued for next session (each its own commit, matching the
39// client-session arc structure):
40// nx_tls13_server_session_recv_ch parse ClientHello
41// nx_tls13_server_session_emit_sh emit ServerHello + KEX
42// nx_tls13_server_session_emit_ee_cert emit EE + Certificate
43// nx_tls13_server_session_emit_cv_sf emit CertificateVerify + Finished
44// nx_tls13_server_session_recv_cf parse client Finished
45// nx_tls13_server_session_derive_app handshake -> app traffic keys
46//
47// Sealed verdict enum (mirrors client-session pattern):
48// NX_TLS13_SSESSION_OK positive return / bytes-written / success
49// NX_TLS13_SSESSION_BAD_STATE called step outside expected state
50// NX_TLS13_SSESSION_BUF_OVERFLOW output buffer too small
51// NX_TLS13_SSESSION_INTERNAL internal emit/parse returned negative
52// NX_TLS13_SSESSION_PROTOCOL_ERR peer sent malformed handshake bytes
53//
54// license_tier: ORIGINAL
55
56import "nx_syscalls.nx"
57import "nx_x25519.nx"
58import "nx_x25519_ephemeral.nx"
59import "nx_tls13.nx"
60import "nx_tls13_transcript.nx"
61import "nx_p256_ecdh.nx"
62
63// ===== state sealed enum ==========================================
64
65const NX_TLS13_SSTATE_INIT: i64 = 0
66const NX_TLS13_SSTATE_CH_RECEIVED: i64 = 1
67const NX_TLS13_SSTATE_SH_SENT: i64 = 2
68const NX_TLS13_SSTATE_EE_SENT: i64 = 3
69const NX_TLS13_SSTATE_CERT_SENT: i64 = 4
70const NX_TLS13_SSTATE_CV_SENT: i64 = 5
71const NX_TLS13_SSTATE_SF_SENT: i64 = 6
72const NX_TLS13_SSTATE_CLIENT_FIN_RECV: i64 = 7
73const NX_TLS13_SSTATE_CONNECTED: i64 = 8
74const NX_TLS13_SSTATE_N: i64 = 9
75
76func nx_tls13_server_state_is_valid(s: i64) -> i64 {
77 if s < 0 { return 0 }
78 if s >= NX_TLS13_SSTATE_N { return 0 }
79 return 1
80}
81
82// ===== verdicts ====================================================
83
84const NX_TLS13_SSESSION_OK: i64 = 0
85const NX_TLS13_SSESSION_BAD_STATE: i64 = 1
86const NX_TLS13_SSESSION_BUF_OVERFLOW: i64 = 2
87const NX_TLS13_SSESSION_INTERNAL: i64 = 3
88const NX_TLS13_SSESSION_PROTOCOL_ERR: i64 = 4
89// recv_ch: ClientHello carried no usable X25519 key_share but DID list
90// X25519 in supported_groups -> caller must send a HelloRetryRequest
91// and read a second ClientHello. Not an error; a handshake fork.
92const NX_TLS13_SSESSION_NEED_HRR: i64 = 5
93
94// ===== struct ======================================================
95//
96// Mirrors Tls13ClientSession (208 bytes, 26 fields) with the
97// perspective flipped (server's own random + key, peer's = client_*).
98// Most fields are zero at session-new and populated by subsequent
99// orchestrator steps as the handshake progresses.
100
101struct Tls13ServerSession {
102 // Filled at session-new:
103 server_random: *u8, // 32 bytes
104 x25519_priv: *u8, // 32 bytes (server's ephemeral)
105 x25519_pub: *u8, // 32 bytes (derived)
106 transcript: *u8, // running transcript-hash state
107
108 // Populated by recv_ch step:
109 state: i64,
110 client_x25519_pub: *u8, // peer key share extension value
111 // RFC 8446 §4.1.3 requires the server to byte-for-byte echo the
112 // client's legacy_session_id in the ServerHello. Captured here
113 // by recv_ch + replayed by emit_sh. Added 2026-05-20 -- without
114 // this, real clients (openssl, browsers) abort with "invalid
115 // session id" mid-handshake.
116 client_session_id: *u8, // up to 32 bytes
117 client_session_id_len: i64,
118
119 // Populated by SH-derive step (after ServerHello sent):
120 handshake_secret: *u8,
121 client_hs_traffic_secret: *u8,
122 server_hs_traffic_secret: *u8,
123 client_hs_traffic_key: *u8,
124 server_hs_traffic_key: *u8,
125 client_hs_iv: *u8,
126 server_hs_iv: *u8,
127
128 // Record-layer sequence numbers per direction:
129 client_seq: i64,
130 server_seq: i64,
131
132 // App-traffic state derived after CONNECTED:
133 master_secret: *u8,
134 client_app_traffic_secret: *u8,
135 server_app_traffic_secret: *u8,
136 client_app_traffic_key: *u8,
137 server_app_traffic_key: *u8,
138 client_app_iv: *u8,
139 server_app_iv: *u8,
140 client_app_seq: i64,
141 server_app_seq: i64,
142
143 // Negotiated AEAD cipher suite (TLS 1.3 wire value): 0x1303
144 // ChaCha20-Poly1305 (default) or 0x1301 AES-128-GCM. Set by
145 // recv_ch from the client's offered list; both use SHA-256 so the
146 // key schedule/transcript are identical -- only the AEAD + key
147 // length (32 vs 16) differ. Added at the END so existing field
148 // offsets are unchanged.
149 cipher_suite: i64,
150
151 // P-256 key exchange (rung: server-side of B4-P256-KEYSHARE,
152 // 2026-06-10). kex_group records which group recv_ch accepted
153 // (29 = x25519 default, 23 = secp256r1 fallback); emit_sh and
154 // derive_hs branch on it. The server P-256 ephemeral is derived
155 // in session-new from the same caller-supplied 32 priv bytes
156 // (domain-separated SHA-256 mod n) so the 2-arg constructor
157 // contract is unchanged. Added at the END: prior offsets stable.
158 kex_group: i64,
159 client_p256_pub: *u8, // 65 bytes, 0x04 || X || Y (recv_ch)
160 p256_priv: *u8, // 32 bytes (derived)
161 p256_pub: *u8, // 65 bytes (derived)
162
163 // TLS-1.2 INTEROP FIX (2026-07-31). RFC 8446 4.2 and RFC 5246 both forbid a server from
164 // sending an extension the client did not offer. We emitted supported_versions
165 // UNCONDITIONALLY in ServerHello, so a TLS-1.2-only ClientHello got back a 1.3-only
166 // extension and every conformant client aborted -- OpenSSL with
167 // u0027tls_collect_extensions: unsolicited extensionu0027. Net effect: NO 1.2-only client could
168 // reach ANY site we serve, and it was invisible because curl/browsers/our own client all
169 // negotiate 1.3 and never exercise that path. recv_ch now records what was actually
170 // offered and emit_sh refuses to invent it. Same failure family as client_session_id
171 // above, and the same fix: echo/answer only what the peer actually sent.
172 // Added at the END so prior field offsets are unchanged.
173 client_offered_sup_ver: i64,
174}
175
176const NX_TLS13_SSESSION_BYTES: i64 = 272 // 34 fields × 8 bytes (added client_offered_sup_ver 2026-07-31)
177
178// ===== construction ================================================
179//
180// Caller provides server-side entropy (server_random) + ephemeral
181// X25519 private key. We derive the matching public key.
182// Transcript hash state is initialised but empty (first input will
183// be the peer's ClientHello bytes in the recv_ch step).
184//
185// Returns 0-ptr on bad input.
186
187func nx_tls13_server_session_new(server_random: *u8,
188 server_x25519_priv: *u8) -> *Tls13ServerSession {
189 if (server_random as i64) == 0 { return 0 as *Tls13ServerSession }
190 if (server_x25519_priv as i64) == 0 { return 0 as *Tls13ServerSession }
191
192 let s: *Tls13ServerSession = (sys_mmap(NX_TLS13_SSESSION_BYTES)) as *Tls13ServerSession
193
194 // Copy caller-supplied random + priv into owned buffers.
195 let rnd_buf: *u8 = sys_mmap(32)
196 let pri_buf: *u8 = sys_mmap(32)
197 var i: i64 = 0
198 while i < 32 {
199 rnd_buf[i] = server_random[i]
200 pri_buf[i] = server_x25519_priv[i]
201 i = i + 1
202 }
203
204 // Derive server's X25519 public key from the private key.
205 let pub_buf: *u8 = sys_mmap(32)
206 x25519_keypair_public(pri_buf, pub_buf)
207
208 // Allocate transcript hash state via the canonical constructor.
209 // BUG-FIX 2026-05-20: previously used raw sys_mmap(256) which left
210 // the SHA-256 IV at all-zero instead of the FIPS 180-4 constants
211 // (0x6a09e667 ..). All transcript-hash-dependent derivations
212 // (handshake_secret -> traffic keys -> AEAD key/iv) silently
213 // diverged from openssl, producing "decryption failed or bad
214 // record mac" on the first encrypted record (EncryptedExtensions).
215 // Composes the canonical nx_tls13_transcript_new which calls
216 // sha256_init internally (same path the client session uses).
217 // See [[project-tls-server-session-id-fix-2026-05-20]] +
218 // [[feedback-no-false-ok-substrate-honesty-audit]].
219 let trans_buf: *u8 = nx_tls13_transcript_new()
220
221 s.server_random = rnd_buf
222 s.x25519_priv = pri_buf
223 s.x25519_pub = pub_buf
224 s.transcript = trans_buf
225
226 s.state = NX_TLS13_SSTATE_INIT
227 s.client_x25519_pub = 0 as *u8
228
229 // Zero-init derived-secret fields (populated by later steps).
230 s.handshake_secret = 0 as *u8
231 s.client_hs_traffic_secret = 0 as *u8
232 s.server_hs_traffic_secret = 0 as *u8
233 s.client_hs_traffic_key = 0 as *u8
234 s.server_hs_traffic_key = 0 as *u8
235 s.client_hs_iv = 0 as *u8
236 s.server_hs_iv = 0 as *u8
237 s.client_seq = 0
238 s.server_seq = 0
239 s.master_secret = 0 as *u8
240 s.client_app_traffic_secret = 0 as *u8
241 s.server_app_traffic_secret = 0 as *u8
242 s.client_app_traffic_key = 0 as *u8
243 s.server_app_traffic_key = 0 as *u8
244 s.client_app_iv = 0 as *u8
245 s.server_app_iv = 0 as *u8
246 s.client_app_seq = 0
247 s.server_app_seq = 0
248 // Safe default: the proven ChaCha20-Poly1305 path. recv_ch only
249 // negotiates AES-128-GCM when the client doesn't offer ChaCha20, so
250 // any negotiation gap leaves browsers on the unchanged path.
251 s.cipher_suite = 0x1303
252
253 // P-256 ephemeral (server side of B4): derived, not separately
254 // supplied. kex_group stays x25519 unless recv_ch flips it.
255 s.kex_group = 29
256 s.client_p256_pub = 0 as *u8
257 s.p256_priv = sys_mmap(32)
258 s.p256_pub = sys_mmap(72)
259 p256_ecdh_derive_priv(pri_buf, s.p256_priv)
260 p256_ecdh_pub(s.p256_priv, s.p256_pub)
261
262 return s
263}
264
265// AEAD key length for a TLS 1.3 cipher suite: AES-128-GCM (0x1301) = 16
266// bytes, ChaCha20-Poly1305 (0x1303) = 32. Nonce/IV is 12 for both; both
267// use SHA-256, so secrets/transcript are identical across the two.
268func nx_tls13_aead_key_len(cipher_suite: i64) -> i64 {
269 if cipher_suite == 0x1301 { return 16 }
270 return 32
271}