code wiki / (root) / nx_pages_https_daemon.nx

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}