nx_pages_https_daemon.nx source
↩ module page · 359 lines · 13860 B
1// nx_pages_https_daemon.nx -- D1.3 of NISHI_DEPLOY_ROADMAP. Composes
2// nx_edge_daemon's TLS 1.3 handshake stones with nx_pages_vhost +
3// nx_pages_static for full HTTPS hosting of nishifamily.com (and any
4// vhost configured in the table).
5//
6// Pre-existing pieces (no changes needed):
7// nx_edge_daemon -- handshake driver (recv_ch, emit_sh,
8// derive_hs, derive_traffic, emit_ee,
9// emit_certificate, emit_cv, emit_sf,
10// recv_cf) + TCP socket setup
11// nx_tls13_server_session_app_recv / app_send -- AEAD record I/O
12// nx_pages_vhost -- Host-header dispatch (D1.1)
13// nx_pages_static -- file serve with mime + 200/404/405
14// nx_http.nx http_parse -- full HTTP/1.1 request parse (FIXED
15// earlier in NISHI_DEPLOY_ROADMAP)
16//
17// D1.3 work in this file:
18// - Replace nx_edge_handle_connection's "read encrypted, ignore it,
19// send a single static HTML" with "decrypt, parse, vhost-dispatch,
20// serve, encrypt".
21// - Mount a multi-vhost configuration suitable for nishifamily.com.
22//
23// Per [[NISHI_DEPLOY_ROADMAP]] D1.3. D1.4 (HTTP->HTTPS on :80) and
24// D1.5 (ACME auto-renew loop) layer on top of this.
25
26import "nx_syscalls.nx"
27import "nx_x25519_ephemeral.nx"
28import "nx_tls13.nx"
29import "nx_tls13_record.nx"
30import "nx_tls13_server_session.nx"
31import "nx_tls13_server_session_recv_ch.nx"
32import "nx_tls13_server_session_emit_sh.nx"
33import "nx_tls13_server_session_derive_hs.nx"
34import "nx_tls13_server_session_derive_traffic.nx"
35import "nx_tls13_server_session_emit_ee.nx"
36import "nx_tls13_server_session_emit_certificate.nx"
37import "nx_tls13_server_session_emit_cv.nx"
38import "nx_tls13_server_session_emit_sf.nx"
39import "nx_tls13_server_session_recv_cf.nx"
40import "nx_tls13_server_session_app_data.nx"
41import "nx_http.nx"
42import "nx_http_server.nx"
43import "nx_pages_vhost.nx"
44import "nx_pages_static.nx"
45import "nx_pages_config.nx"
46const NX_MAGIC_2048: i64 = 2048
47const NX_MAGIC_4096: i64 = 4096
48
49const NX_PHTTPS_OK: i64 = 0
50const NX_PHTTPS_ERR_HANDSHAKE: i64 = 1
51const NX_PHTTPS_ERR_APP_RECV: i64 = 2
52const NX_PHTTPS_ERR_PARSE: i64 = 3
53const NX_PHTTPS_ERR_APP_SEND: i64 = 4
54const NX_PHTTPS_ERR_SOCKET_WRITE: i64 = 5
55
56const NX_PHTTPS_PORT: i64 = 8443
57const NX_PHTTPS_REQ_REC_CAP: i64 = 16384
58const NX_PHTTPS_PLAIN_CAP: i64 = 16384
59const NX_PHTTPS_RESP_CAP: i64 = 1048576 // 1 MB max response body
60const NX_PHTTPS_OUT_REC_CAP: i64 = 1048768 // resp + TLS record overhead
61const NX_PHTTPS_BACKLOG: i64 = 64
62
63// ===== Server-side TLS 1.3 handshake (verbatim from nx_edge_daemon =====
64//
65// Identical to nx_edge_handle_connection lines 63-137; extracted as a
66// reusable helper. Returns a connected *Tls13ServerSession or null on
67// any handshake failure. Caller is responsible for sys_close(client_fd)
68// + freeing session resources.
69
70func _phttps_handshake(
71 client_fd: i64,
72 cert_der: *u8, cert_der_len: i64,
73 server_ecdsa_priv: *u8,
74 server_random: *u8, server_x25519_priv: *u8
75) -> *Tls13ServerSession {
76 let ch_buf: *u8 = sys_mmap(NX_MAGIC_2048)
77 let ch_n: i64 = sys_read(client_fd, ch_buf, NX_MAGIC_2048)
78 if ch_n <= 0 { return 0 as *Tls13ServerSession }
79
80 let s: *Tls13ServerSession = nx_tls13_server_session_new(
81 server_random, server_x25519_priv)
82 if (s as i64) == 0 { return 0 as *Tls13ServerSession }
83
84 if nx_tls13_server_session_recv_ch(s, ch_buf, ch_n) != NX_TLS13_SSESSION_OK {
85 return 0 as *Tls13ServerSession
86 }
87
88 let flight_cap: i64 = NX_MAGIC_4096
89 let flight: *u8 = sys_mmap(flight_cap)
90 var fw: i64 = 0
91
92 let sh_buf: *u8 = sys_mmap(512)
93 let n_sh: i64 = nx_tls13_server_session_emit_sh(s, sh_buf, 512)
94 if n_sh <= 0 { return 0 as *Tls13ServerSession }
95 var i: i64 = 0
96 while i < n_sh { flight[fw + i] = sh_buf[i]; i = i + 1 }
97 fw = fw + n_sh
98
99 if nx_tls13_server_session_derive_hs_secrets(s) != NX_TLS13_SSESSION_OK {
100 return 0 as *Tls13ServerSession
101 }
102 if nx_tls13_server_session_derive_traffic(s) != NX_TLS13_SSESSION_OK {
103 return 0 as *Tls13ServerSession
104 }
105
106 let ee_buf: *u8 = sys_mmap(256)
107 let n_ee: i64 = nx_tls13_server_session_emit_ee(s, ee_buf, 256)
108 if n_ee <= 0 { return 0 as *Tls13ServerSession }
109 var j: i64 = 0
110 while j < n_ee { flight[fw + j] = ee_buf[j]; j = j + 1 }
111 fw = fw + n_ee
112
113 let cert_buf: *u8 = sys_mmap(NX_MAGIC_2048)
114 let n_cert: i64 = nx_tls13_server_session_emit_certificate(
115 s, cert_der, cert_der_len, cert_buf, NX_MAGIC_2048)
116 if n_cert <= 0 { return 0 as *Tls13ServerSession }
117 var k: i64 = 0
118 while k < n_cert { flight[fw + k] = cert_buf[k]; k = k + 1 }
119 fw = fw + n_cert
120
121 let cv_buf: *u8 = sys_mmap(512)
122 let n_cv: i64 = nx_tls13_server_session_emit_cv(
123 s, server_ecdsa_priv, cv_buf, 512)
124 if n_cv <= 0 { return 0 as *Tls13ServerSession }
125 var l: i64 = 0
126 while l < n_cv { flight[fw + l] = cv_buf[l]; l = l + 1 }
127 fw = fw + n_cv
128
129 let sf_buf: *u8 = sys_mmap(256)
130 let n_sf: i64 = nx_tls13_server_session_emit_sf(s, sf_buf, 256)
131 if n_sf <= 0 { return 0 as *Tls13ServerSession }
132 var m: i64 = 0
133 while m < n_sf { flight[fw + m] = sf_buf[m]; m = m + 1 }
134 fw = fw + n_sf
135
136 let wn: i64 = sys_write(client_fd, flight, fw)
137 if wn != fw { return 0 as *Tls13ServerSession }
138
139 let cf_buf: *u8 = sys_mmap(256)
140 let cf_n: i64 = sys_read(client_fd, cf_buf, 256)
141 if cf_n <= 0 { return 0 as *Tls13ServerSession }
142 if nx_tls13_server_session_recv_cf(s, cf_buf, cf_n) != NX_TLS13_SSESSION_OK {
143 return 0 as *Tls13ServerSession
144 }
145
146 return s
147}
148
149// ===== Per-connection: handshake + decrypt + parse + serve + encrypt ====
150
151func nx_pages_https_handle_connection(
152 client_fd: i64,
153 cert_der: *u8, cert_der_len: i64,
154 server_ecdsa_priv: *u8,
155 server_random: *u8, server_x25519_priv: *u8,
156 table: *NxVHostTable
157) -> i64 {
158 let s: *Tls13ServerSession = _phttps_handshake(
159 client_fd, cert_der, cert_der_len, server_ecdsa_priv,
160 server_random, server_x25519_priv)
161 if (s as i64) == 0 { return NX_PHTTPS_ERR_HANDSHAKE }
162
163 // Read the encrypted HTTP request record.
164 let rec_buf: *u8 = sys_mmap(NX_PHTTPS_REQ_REC_CAP)
165 let rec_n: i64 = sys_read(client_fd, rec_buf, NX_PHTTPS_REQ_REC_CAP)
166 if rec_n <= 0 { return NX_PHTTPS_ERR_APP_RECV }
167
168 // Decrypt to plaintext HTTP request bytes.
169 let plain: *u8 = sys_mmap(NX_PHTTPS_PLAIN_CAP)
170 let plain_n: i64 = nx_tls13_server_session_app_recv(s, rec_buf, rec_n,
171 plain, NX_PHTTPS_PLAIN_CAP)
172 if plain_n <= 0 { return NX_PHTTPS_ERR_APP_RECV }
173
174 // Parse the plaintext HTTP request.
175 let req: *HttpRequest = sys_mmap(NX_MAGIC_2048) as *HttpRequest
176 if http_parse(plain, plain_n, req) != 0 { return NX_PHTTPS_ERR_PARSE }
177
178 // Vhost-dispatch on Host header.
179 let root_off: *i64 = sys_mmap(8) as *i64
180 let root_len: *i64 = sys_mmap(8) as *i64
181 let resp_buf: *u8 = sys_mmap(NX_PHTTPS_RESP_CAP)
182 let resp_n: *i64 = sys_mmap(8) as *i64
183 resp_n[0] = 0
184 let v: i64 = nx_vhost_dispatch(plain, req, table, root_off, root_len)
185
186 if v == NX_VHOST_NO_MATCH {
187 nx_pages_emit_404(resp_buf, resp_n, NX_PHTTPS_RESP_CAP)
188 }
189 if v == NX_VHOST_NO_HOST_HDR {
190 nx_pages_emit_405(resp_buf, resp_n, NX_PHTTPS_RESP_CAP)
191 }
192 if v == NX_VHOST_OK { resp_n[0] = 0 }
193 if v == NX_VHOST_FALLBACK { resp_n[0] = 0 }
194 if v == NX_VHOST_OK {
195 var method_kind: i64 = 0
196 if req.method_len == 3 {
197 if plain[req.method_off] == 0x47 {
198 if plain[req.method_off + 1] == 0x45 {
199 if plain[req.method_off + 2] == 0x54 { method_kind = 1 }
200 }
201 }
202 }
203 let path: *u8 = ((plain as i64) + req.path_off) as *u8
204 let root: *u8 = ((table.arena as i64) + root_off[0]) as *u8
205 nx_pages_serve_file(path, req.path_len, root, root_len[0],
206 method_kind, resp_buf, NX_PHTTPS_RESP_CAP, resp_n)
207 }
208 if v == NX_VHOST_FALLBACK {
209 var method_kind2: i64 = 0
210 if req.method_len == 3 {
211 if plain[req.method_off] == 0x47 {
212 if plain[req.method_off + 1] == 0x45 {
213 if plain[req.method_off + 2] == 0x54 { method_kind2 = 1 }
214 }
215 }
216 }
217 let path2: *u8 = ((plain as i64) + req.path_off) as *u8
218 let root2: *u8 = ((table.arena as i64) + root_off[0]) as *u8
219 nx_pages_serve_file(path2, req.path_len, root2, root_len[0],
220 method_kind2, resp_buf, NX_PHTTPS_RESP_CAP, resp_n)
221 }
222
223 // Encrypt response into a TLS application_data record.
224 let out_rec: *u8 = sys_mmap(NX_PHTTPS_OUT_REC_CAP)
225 let out_n: i64 = nx_tls13_server_session_app_send(
226 s, resp_buf, resp_n[0], out_rec, NX_PHTTPS_OUT_REC_CAP)
227 if out_n <= 0 { return NX_PHTTPS_ERR_APP_SEND }
228
229 if sys_write(client_fd, out_rec, out_n) != out_n {
230 return NX_PHTTPS_ERR_SOCKET_WRITE
231 }
232 return NX_PHTTPS_OK
233}
234
235// ===== Daemon entry =====================================================
236//
237// Loads cert + ECDSA priv (DER + raw 32B) from disk, builds a vhost
238// table, binds :8443, accept-loops. V1 hardcoded paths + vhost map;
239// D1.6 will load from a config file.
240
241func _phttps_setup_vhosts_hardcoded(t: *NxVHostTable, cursor: *i64) -> i64 {
242 nx_vhost_add(t, cursor,
243 "nishifamily.com" as *u8,
244 "/share/nishi-pages/dist" as *u8)
245 nx_vhost_add(t, cursor,
246 "www.nishifamily.com" as *u8,
247 "/share/nishi-pages/dist" as *u8)
248 nx_vhost_set_fallback(t, 0 as i64)
249 return 0
250}
251
252// D1.6.2: prefer /etc/nx_pages.conf; fall back to hardcoded defaults.
253func _phttps_setup_vhosts(t: *NxVHostTable, cursor: *i64) -> i64 {
254 let conf_path: *u8 = "/etc/nx_pages.conf" as *u8
255 let rc: i64 = nx_pages_config_load(conf_path, t, cursor)
256 if rc == NX_PCFG_OK { return 0 }
257 return _phttps_setup_vhosts_hardcoded(t, cursor)
258}
259
260func nx_pages_https_daemon_run(
261 cert_der_path: *u8,
262 priv_path: *u8,
263 port_host: i64,
264 server_random: *u8,
265 server_x_priv: *u8,
266 max_conns: i64
267) -> i64 {
268 // Load cert DER.
269 let cert_len_box: *i64 = sys_mmap(8) as *i64
270 let cert_der: *u8 = sys_read_file(cert_der_path, cert_len_box)
271 if (cert_der as i64) == 0 { return NX_PHTTPS_ERR_HANDSHAKE }
272 if cert_len_box[0] <= 0 { return NX_PHTTPS_ERR_HANDSHAKE }
273
274 // Load ECDSA priv (raw 32B).
275 let priv_len_box: *i64 = sys_mmap(8) as *i64
276 let priv: *u8 = sys_read_file(priv_path, priv_len_box)
277 if (priv as i64) == 0 { return NX_PHTTPS_ERR_HANDSHAKE }
278 if priv_len_box[0] != 32 { return NX_PHTTPS_ERR_HANDSHAKE }
279
280 // Build vhost table.
281 let arena: *u8 = sys_mmap(NX_MAGIC_4096)
282 let vhosts: *i64 = sys_mmap(16 * NX_VHOST_STRIDE * 8) as *i64
283 let table: *NxVHostTable = sys_mmap(40) as *NxVHostTable
284 let cursor: *i64 = sys_mmap(8) as *i64
285 cursor[0] = 0
286 nx_vhost_table_init(table, arena, NX_MAGIC_4096 as i64, vhosts)
287 _phttps_setup_vhosts(table, cursor)
288
289 // Listen.
290 let addr_buf: *u8 = sys_mmap(16)
291 nx_http_server_addr_any(addr_buf, port_host)
292 let listen_verdict: *i64 = sys_mmap(8) as *i64
293 let listen_fd: i64 = nx_http_server_listen(addr_buf, NX_PHTTPS_BACKLOG, listen_verdict)
294 if listen_fd < 0 { return NX_PHTTPS_ERR_HANDSHAKE }
295
296 let banner: *u8 = "nx_pages_https_daemon: listening on :8443\n" as *u8
297 var bn: i64 = 0
298 while banner[bn] != 0 { bn = bn + 1 }
299 sys_write(1 as i64, banner, bn)
300
301 var served: i64 = 0
302 var keep_going: i64 = 1
303 while keep_going == 1 {
304 let av: *i64 = sys_mmap(8) as *i64
305 let cfd: i64 = nx_http_server_accept_one(listen_fd, av)
306 if cfd >= 0 {
307 nx_pages_https_handle_connection(cfd, cert_der, cert_len_box[0],
308 priv, server_random, server_x_priv,
309 table)
310 sys_close(cfd)
311 served = served + 1
312 if max_conns >= 0 {
313 if served >= max_conns { keep_going = 0 }
314 }
315 }
316 }
317 sys_close(listen_fd)
318 return NX_PHTTPS_OK
319}
320
321// V1 main: tries production cert paths (NAS /volume1/homes/elderwesto/
322// nishitls/) first; falls back to /tmp test paths for local smoke.
323// V2 will read paths from /etc/nx_pages.conf (cert_path= / key_path=).
324func main() -> i64 {
325 let server_random: *u8 = sys_mmap(32)
326 let server_x_priv: *u8 = sys_mmap(32)
327 var i: i64 = 0
328 while i < 32 {
329 server_random[i] = (i + 1) as u8
330 server_x_priv[i] = (i + 100) as u8
331 i = i + 1
332 }
333
334 // Production NAS layout (set up by nx_edge_tls deploy 2026-05-20).
335 let prod_cert: *u8 = "/volume1/homes/elderwesto/nishitls/nishi_tls.crt.der" as *u8
336 let prod_key: *u8 = "/volume1/homes/elderwesto/nishitls/nishi_tls.priv.bin" as *u8
337 let test_cert: *u8 = "/tmp/nx-test-cert.der" as *u8
338 let test_key: *u8 = "/tmp/nx-test-key.bin" as *u8
339
340 // Try production paths first. sys_read_file returns null on missing.
341 let probe_box: *i64 = sys_mmap(8) as *i64
342 let prod_probe: *u8 = sys_read_file(prod_cert, probe_box)
343 var cert_path: *u8 = prod_cert
344 var key_path: *u8 = prod_key
345 if (prod_probe as i64) == 0 {
346 cert_path = test_cert
347 key_path = test_key
348 }
349 if probe_box[0] <= 0 {
350 cert_path = test_cert
351 key_path = test_key
352 }
353
354 return nx_pages_https_daemon_run(
355 cert_path, key_path,
356 NX_PHTTPS_PORT,
357 server_random, server_x_priv,
358 0 - 1 as i64)
359}