Skip to main content

nodejs/stdlib/
tls.rs

1//! Node `tls` module: real TLS over blocking `rustls` (`rustls::StreamOwned`
2//! wrapping a `std::net::TcpStream`).
3//!
4//! Threading model mirrors `net` (see `host::run_event_loop`): background threads
5//! only move raw bytes and post `IoTask` closures onto the host channel; every
6//! JS-visible effect (building the `TLSSocket`, emitting `secureConnect`/`data`/
7//! `end`/`close`, calling listeners) happens on the main thread when the loop runs
8//! the posted closure. Background closures NEVER capture a `Value` (the heap is a
9//! main-thread `thread_local`); they capture only `Send` data (`u64` ids, byte
10//! vectors, `TcpStream`s, channel senders) and look the emitter up by id inside
11//! the posted `IoTask`.
12//!
13//! Per TLS connection there is ONE owner thread that solely owns the
14//! `StreamOwned`. It reads with a short socket read-timeout (so a `WouldBlock`
15//! lets it loop) and drains an mpsc channel of `WriteCmd`s produced by the main
16//! thread (`socket.write`/`socket.end`). Because reads and writes share the one
17//! rustls `Connection`, keeping both on a single thread avoids splitting the
18//! stateful cipher across threads.
19
20use crate::host::{invoke, with_host, IoTask, JsObj};
21use fusevm::Value;
22use indexmap::IndexMap;
23use once_cell::sync::OnceCell;
24use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime};
25use rustls::{
26    ClientConfig, ClientConnection, ConnectionCommon, DigitallySignedStruct, RootCertStore,
27    ServerConfig, ServerConnection, SideData, SignatureScheme, StreamOwned,
28};
29use std::collections::HashMap;
30use std::io::{Read, Write};
31use std::net::TcpStream;
32use std::ops::{Deref, DerefMut};
33use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
34use std::sync::mpsc::{Receiver, Sender};
35use std::sync::Arc;
36
37/// `tls` module functions routed through `stdlib::call`.
38pub const MODULE_METHODS: &[&str] = &[
39    "connect",
40    "createServer",
41    "createSecureContext",
42    "checkServerIdentity",
43    "convertALPNProtocols",
44    "getCiphers",
45    "getCACertificates",
46    "setDefaultCACertificates",
47    "getCertificateCompressionAlgorithms",
48];
49
50/// The standard OpenSSL cipher-suite names (lowercase) reported by
51/// `tls.getCiphers()`, matching Node v26's fixed list (TLS 1.2 suites plus the
52/// `tls_*` TLS 1.3 suites). A fixed protocol constant, not a runtime value.
53const CIPHERS: &[&str] = &[
54    "aes128-gcm-sha256",
55    "aes128-sha",
56    "aes128-sha256",
57    "aes256-gcm-sha384",
58    "aes256-sha",
59    "aes256-sha256",
60    "dhe-psk-aes128-cbc-sha",
61    "dhe-psk-aes128-cbc-sha256",
62    "dhe-psk-aes128-gcm-sha256",
63    "dhe-psk-aes256-cbc-sha",
64    "dhe-psk-aes256-cbc-sha384",
65    "dhe-psk-aes256-gcm-sha384",
66    "dhe-psk-chacha20-poly1305",
67    "dhe-rsa-aes128-gcm-sha256",
68    "dhe-rsa-aes128-sha",
69    "dhe-rsa-aes128-sha256",
70    "dhe-rsa-aes256-gcm-sha384",
71    "dhe-rsa-aes256-sha",
72    "dhe-rsa-aes256-sha256",
73    "dhe-rsa-chacha20-poly1305",
74    "ecdhe-ecdsa-aes128-gcm-sha256",
75    "ecdhe-ecdsa-aes128-sha",
76    "ecdhe-ecdsa-aes128-sha256",
77    "ecdhe-ecdsa-aes256-gcm-sha384",
78    "ecdhe-ecdsa-aes256-sha",
79    "ecdhe-ecdsa-aes256-sha384",
80    "ecdhe-ecdsa-chacha20-poly1305",
81    "ecdhe-psk-aes128-cbc-sha",
82    "ecdhe-psk-aes128-cbc-sha256",
83    "ecdhe-psk-aes256-cbc-sha",
84    "ecdhe-psk-aes256-cbc-sha384",
85    "ecdhe-psk-chacha20-poly1305",
86    "ecdhe-rsa-aes128-gcm-sha256",
87    "ecdhe-rsa-aes128-sha",
88    "ecdhe-rsa-aes128-sha256",
89    "ecdhe-rsa-aes256-gcm-sha384",
90    "ecdhe-rsa-aes256-sha",
91    "ecdhe-rsa-aes256-sha384",
92    "ecdhe-rsa-chacha20-poly1305",
93    "psk-aes128-cbc-sha",
94    "psk-aes128-cbc-sha256",
95    "psk-aes128-gcm-sha256",
96    "psk-aes256-cbc-sha",
97    "psk-aes256-cbc-sha384",
98    "psk-aes256-gcm-sha384",
99    "psk-chacha20-poly1305",
100    "rsa-psk-aes128-cbc-sha",
101    "rsa-psk-aes128-cbc-sha256",
102    "rsa-psk-aes128-gcm-sha256",
103    "rsa-psk-aes256-cbc-sha",
104    "rsa-psk-aes256-cbc-sha384",
105    "rsa-psk-aes256-gcm-sha384",
106    "rsa-psk-chacha20-poly1305",
107    "srp-aes-128-cbc-sha",
108    "srp-aes-256-cbc-sha",
109    "srp-rsa-aes-128-cbc-sha",
110    "srp-rsa-aes-256-cbc-sha",
111    "tls_aes_128_ccm_8_sha256",
112    "tls_aes_128_ccm_sha256",
113    "tls_aes_128_gcm_sha256",
114    "tls_aes_256_gcm_sha384",
115    "tls_chacha20_poly1305_sha256",
116];
117
118/// Instance method names for the two `@@native` tags this module owns, exposed to
119/// `stdlib::instance_has_method` (property reads that yield a bound method).
120pub const SERVER_METHODS: &[&str] = &["listen", "close", "address"];
121pub const SOCKET_METHODS: &[&str] = &[
122    "write",
123    "end",
124    "destroy",
125    "setEncoding",
126    "setKeepAlive",
127    "setNoDelay",
128    "setTimeout",
129    "ref",
130    "unref",
131    "pause",
132    "resume",
133];
134
135/// A native-thread hook run on the main thread for each freshly-handshaked
136/// connection. Set by `https` to attach its request parser; `None` for a plain
137/// `tls` server (which emits `secureConnect`/`connection` and calls its listener).
138pub type ConnHook = std::rc::Rc<dyn Fn(&Value, &Value, u64) -> Result<(), String>>;
139
140/// A write request handed from the main thread to a socket's owner thread.
141enum WriteCmd {
142    Data(Vec<u8>),
143    Shutdown,
144}
145
146/// Process-global monotonic id source for TLS sockets. Unlike `net`, ids are
147/// generated on background owner threads (before their main-thread record
148/// exists), so a lock-free atomic is used instead of a main-thread counter.
149static NEXT_TLS_ID: AtomicU64 = AtomicU64::new(1);
150static NEXT_SERVER_ID: AtomicU64 = AtomicU64::new(1);
151
152fn next_tls_id() -> u64 {
153    NEXT_TLS_ID.fetch_add(1, Ordering::Relaxed)
154}
155fn next_server_id() -> u64 {
156    NEXT_SERVER_ID.fetch_add(1, Ordering::Relaxed)
157}
158
159/// Main-thread record for a listening TLS server.
160struct TlsServerRec {
161    emitter: Value,
162    stop: Arc<AtomicBool>,
163    conn_hook: Option<ConnHook>,
164    /// Optional JS `connectionListener` (`(socket) => …`) for a plain tls server.
165    listener: Option<Value>,
166}
167
168/// Main-thread record for a live TLS socket.
169struct TlsSocketRec {
170    emitter: Value,
171    /// Queue of writes for the socket's owner thread.
172    tx: Sender<WriteCmd>,
173}
174
175#[derive(Default)]
176struct TlsState {
177    servers: HashMap<u64, TlsServerRec>,
178    sockets: HashMap<u64, TlsSocketRec>,
179}
180
181thread_local! {
182    static TLS: std::cell::RefCell<TlsState> = std::cell::RefCell::new(TlsState::default());
183    /// `ServerConfig`s built by `createServer` before `listen` assigns a server id
184    /// (mirrors `net::PENDING_HOOKS`).
185    static PENDING_CONFIGS: std::cell::RefCell<Vec<(Value, Arc<ServerConfig>)>> =
186        const { std::cell::RefCell::new(Vec::new()) };
187    static PENDING_HOOKS: std::cell::RefCell<Vec<(Value, ConnHook)>> =
188        const { std::cell::RefCell::new(Vec::new()) };
189    /// User-supplied default CA certificates (PEM strings) set via
190    /// `setDefaultCACertificates`; read back by `getCACertificates('default')`.
191    static DEFAULT_CA_CERTS: std::cell::RefCell<Vec<String>> =
192        const { std::cell::RefCell::new(Vec::new()) };
193}
194
195// ── shared object / prop helpers (same shape as net) ─────────────────────────
196
197fn get_prop(recv: &Value, key: &str) -> Option<Value> {
198    with_host(|h| match h.get(recv) {
199        Some(JsObj::Object(p)) => p.get(key).cloned(),
200        _ => None,
201    })
202}
203
204fn set_prop(recv: &Value, key: &str, val: Value) {
205    with_host(|h| {
206        if let Some(JsObj::Object(p)) = h.get_mut(recv) {
207            p.insert(key.to_string(), val);
208        }
209    });
210}
211
212fn u64_prop(recv: &Value, key: &str) -> Option<u64> {
213    get_prop(recv, key).map(|v| with_host(|h| h.to_number(&v)) as u64)
214}
215
216/// Delegate the EventEmitter surface to `events`; `None` for a non-emitter method.
217fn emitter_dispatch(recv: &Value, method: &str, args: &[Value]) -> Option<Result<Value, String>> {
218    super::events::METHODS
219        .contains(&method)
220        .then(|| super::events::instance_call(recv, method, args.to_vec()))
221}
222
223/// Raw bytes of a `write`/`end` argument (Buffer bytes or a string's UTF-8),
224/// shared with the option-reading path for `key`/`cert` Buffers.
225fn value_bytes(v: Option<&Value>) -> Vec<u8> {
226    let Some(v) = v else { return Vec::new() };
227    let is_buffer =
228        with_host(|h| matches!(h.get(v), Some(JsObj::Object(p)) if p.contains_key("@@bytes")));
229    if is_buffer {
230        return with_host(|h| match h.get(v) {
231            Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|b| h.get(b)) {
232                Some(JsObj::Array(items)) => items.iter().map(|x| h.to_number(x) as u8).collect(),
233                _ => Vec::new(),
234            },
235            _ => Vec::new(),
236        });
237    }
238    with_host(|h| h.str_of(v)).into_bytes()
239}
240
241// ── module entry ─────────────────────────────────────────────────────────────
242
243/// `stdlib::call` entry for `tls.<method>`.
244pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
245    match method {
246        "connect" => Some(connect(args)),
247        "createServer" => Some(create_server(args)),
248        // A secure context is just the parsed key/cert pair; we build the real
249        // `ServerConfig` lazily in `createServer`, so expose an opaque holder.
250        "createSecureContext" => Some(Ok(with_host(|h| {
251            let mut m = IndexMap::new();
252            m.insert("@@native".into(), h.new_str("SecureContext"));
253            if let Some(o) = args.first() {
254                m.insert("@@options".into(), o.clone());
255            }
256            h.new_object(m)
257        }))),
258        "checkServerIdentity" => Some(check_server_identity(args)),
259        "convertALPNProtocols" => Some(convert_alpn_protocols(args)),
260        "getCiphers" => Some(Ok(with_host(|h| {
261            let items = CIPHERS.iter().map(|c| h.new_str(*c)).collect();
262            h.new_array(items)
263        }))),
264        "getCACertificates" => Some(get_ca_certificates(args)),
265        "setDefaultCACertificates" => Some(set_default_ca_certificates(args)),
266        // rustls' default configuration does not negotiate TLS certificate
267        // compression (RFC 8879), so no algorithm is actually supported. Node v26
268        // likewise returns `[]` here.
269        "getCertificateCompressionAlgorithms" => Some(Ok(with_host(|h| h.new_array(Vec::new())))),
270        _ => None,
271    }
272}
273
274// ── tls.checkServerIdentity / ALPN / CA certificates ─────────────────────────
275
276/// Read an array of strings (or a single string) from a value; Buffers/other
277/// element types are stringified. Empty when the value is absent or not iterable.
278fn read_string_array(v: Option<&Value>) -> Vec<String> {
279    let Some(v) = v else { return Vec::new() };
280    with_host(|h| match h.get(v) {
281        Some(JsObj::Array(items)) => items.iter().map(|x| h.str_of(x)).collect(),
282        _ if h.as_str(v).is_some() => vec![h.str_of(v)],
283        _ => Vec::new(),
284    })
285}
286
287/// Does `host` match the certificate identity `pattern`, honouring a single
288/// leftmost-label wildcard (`*.example.com` matches `a.example.com` but not
289/// `example.com` nor `a.b.example.com`)? Case-insensitive.
290fn host_matches(host: &str, pattern: &str) -> bool {
291    let host = host.trim().to_ascii_lowercase();
292    let pattern = pattern.trim().to_ascii_lowercase();
293    if let Some(suffix) = pattern.strip_prefix("*.") {
294        // Wildcard covers exactly one leftmost label.
295        match host.split_once('.') {
296            Some((_, rest)) => rest == suffix,
297            None => false,
298        }
299    } else {
300        host == pattern
301    }
302}
303
304/// `tls.checkServerIdentity(hostname, cert)` — best-effort string comparison of
305/// `hostname` against the certificate's `subjectaltname` DNS entries (falling
306/// back to `subject.CN` when no SAN is present). Returns `undefined` on a match,
307/// or an `ERR_TLS_CERT_ALTNAME_INVALID`-shaped Error object on mismatch.
308fn check_server_identity(args: &[Value]) -> Result<Value, String> {
309    let host = super::arg_str(args, 0);
310    let cert = args.get(1).cloned().unwrap_or(Value::Undef);
311
312    // Collect DNS altnames from `subjectaltname` ("DNS:a.com, DNS:*.b.com").
313    let altname_str = get_prop(&cert, "subjectaltname")
314        .filter(|v| with_host(|h| h.as_str(v)).is_some())
315        .map(|v| with_host(|h| h.str_of(&v)))
316        .unwrap_or_default();
317    let dns_names: Vec<String> = altname_str
318        .split(',')
319        .filter_map(|e| e.trim().strip_prefix("DNS:").map(|s| s.trim().to_string()))
320        .filter(|s| !s.is_empty())
321        .collect();
322
323    let matched = if !dns_names.is_empty() {
324        dns_names.iter().any(|p| host_matches(&host, p))
325    } else {
326        // No SAN: fall back to the subject Common Name.
327        let cn = get_prop(&cert, "subject")
328            .and_then(|s| get_prop(&s, "CN"))
329            .filter(|v| with_host(|h| h.as_str(v)).is_some())
330            .map(|v| with_host(|h| h.str_of(&v)))
331            .unwrap_or_default();
332        !cn.is_empty() && host_matches(&host, &cn)
333    };
334
335    if matched {
336        return Ok(Value::Undef);
337    }
338    let reason = if !altname_str.is_empty() {
339        format!("Host: {host}. is not in the cert's altnames: {altname_str}")
340    } else {
341        format!("Host: {host}. is not cert's CN")
342    };
343    let message = format!("Hostname/IP does not match certificate's altnames: {reason}");
344    Ok(with_host(|h| {
345        let mut m = IndexMap::new();
346        m.insert("message".into(), h.new_str(message));
347        m.insert("reason".into(), h.new_str(reason));
348        m.insert("host".into(), h.new_str(host));
349        m.insert("code".into(), h.new_str("ERR_TLS_CERT_ALTNAME_INVALID"));
350        m.insert("cert".into(), cert);
351        h.new_object(m)
352    }))
353}
354
355/// `tls.convertALPNProtocols(protocols, out)` — encode `protocols` into the wire
356/// format (each entry prefixed by a single length byte), store it on
357/// `out.ALPNProtocols` as a Buffer, and return that Buffer.
358fn convert_alpn_protocols(args: &[Value]) -> Result<Value, String> {
359    let protocols = read_string_array(args.first());
360    let mut wire = Vec::new();
361    for p in &protocols {
362        let bytes = p.as_bytes();
363        // Node clamps to 255 (a single length byte); over-long entries throw, but
364        // best-effort here simply truncates the encoded length.
365        wire.push(bytes.len().min(255) as u8);
366        wire.extend_from_slice(&bytes[..bytes.len().min(255)]);
367    }
368    let buf = super::buffer::from_bytes(&wire);
369    if let Some(out) = args.get(1).filter(|v| matches!(v, Value::Obj(_))) {
370        set_prop(out, "ALPNProtocols", buf.clone());
371    }
372    Ok(buf)
373}
374
375/// `tls.getCACertificates([type])` — return PEM strings for the requested store.
376///
377/// LIMITATION: the bundled `'default'` Mozilla store cannot be reproduced from
378/// `webpki-roots`, which ships extracted *trust anchors* (subject + SPKI + name
379/// constraints), not full X.509 certificates — so no faithful PEM can be built
380/// from them. `'default'` therefore returns whatever was installed via
381/// `setDefaultCACertificates` (empty until then); `'system'`/`'extra'`/`'bundled'`
382/// return `[]`.
383fn get_ca_certificates(args: &[Value]) -> Result<Value, String> {
384    let kind = if args.is_empty() {
385        "default".to_string()
386    } else {
387        super::arg_str(args, 0)
388    };
389    let certs: Vec<String> = match kind.as_str() {
390        "default" => DEFAULT_CA_CERTS.with(|c| c.borrow().clone()),
391        _ => Vec::new(),
392    };
393    Ok(with_host(|h| {
394        let items = certs.into_iter().map(|c| h.new_str(c)).collect();
395        h.new_array(items)
396    }))
397}
398
399/// `tls.setDefaultCACertificates(certs)` — store `certs` (PEM strings or Buffers)
400/// as the process default CA set for later `getCACertificates('default')` reads.
401fn set_default_ca_certificates(args: &[Value]) -> Result<Value, String> {
402    let certs = read_string_array(args.first());
403    DEFAULT_CA_CERTS.with(|c| *c.borrow_mut() = certs);
404    Ok(Value::Undef)
405}
406
407// ── TLS crypto config ────────────────────────────────────────────────────────
408
409/// The shared verifying client config (explicit aws-lc-rs crypto provider +
410/// webpki-roots trust anchors). Built once. Uses `builder_with_provider` rather
411/// than `builder()` so it never depends on a process-default provider being
412/// pre-installed (the insecure path installs none).
413fn verifying_client_config() -> Arc<ClientConfig> {
414    static CFG: OnceCell<Arc<ClientConfig>> = OnceCell::new();
415    CFG.get_or_init(|| {
416        let root_store = RootCertStore {
417            roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
418        };
419        let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider());
420        let cfg = ClientConfig::builder_with_provider(provider)
421            .with_safe_default_protocol_versions()
422            .expect("aws-lc-rs provider supports the default protocol versions")
423            .with_root_certificates(root_store)
424            .with_no_client_auth();
425        Arc::new(cfg)
426    })
427    .clone()
428}
429
430/// A client config that accepts any server certificate (`rejectUnauthorized:
431/// false`). Signature verification is still delegated to the default provider's
432/// algorithms; only chain-to-a-trust-anchor and hostname checks are skipped.
433fn insecure_client_config() -> Arc<ClientConfig> {
434    static CFG: OnceCell<Arc<ClientConfig>> = OnceCell::new();
435    CFG.get_or_init(|| {
436        let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider());
437        let cfg = ClientConfig::builder_with_provider(provider.clone())
438            .with_safe_default_protocol_versions()
439            .expect("aws-lc-rs provider supports the default protocol versions")
440            .dangerous()
441            .with_custom_certificate_verifier(Arc::new(NoVerify(provider)))
442            .with_no_client_auth();
443        Arc::new(cfg)
444    })
445    .clone()
446}
447
448/// A `ServerCertVerifier` that skips certificate-chain/hostname validation but
449/// still checks the handshake signature against the default provider's algorithms.
450#[derive(Debug)]
451struct NoVerify(Arc<rustls::crypto::CryptoProvider>);
452
453impl rustls::client::danger::ServerCertVerifier for NoVerify {
454    fn verify_server_cert(
455        &self,
456        _end_entity: &CertificateDer<'_>,
457        _intermediates: &[CertificateDer<'_>],
458        _server_name: &ServerName<'_>,
459        _ocsp: &[u8],
460        _now: UnixTime,
461    ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
462        Ok(rustls::client::danger::ServerCertVerified::assertion())
463    }
464    fn verify_tls12_signature(
465        &self,
466        message: &[u8],
467        cert: &CertificateDer<'_>,
468        dss: &DigitallySignedStruct,
469    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
470        rustls::crypto::verify_tls12_signature(
471            message,
472            cert,
473            dss,
474            &self.0.signature_verification_algorithms,
475        )
476    }
477    fn verify_tls13_signature(
478        &self,
479        message: &[u8],
480        cert: &CertificateDer<'_>,
481        dss: &DigitallySignedStruct,
482    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
483        rustls::crypto::verify_tls13_signature(
484            message,
485            cert,
486            dss,
487            &self.0.signature_verification_algorithms,
488        )
489    }
490    fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
491        self.0.signature_verification_algorithms.supported_schemes()
492    }
493}
494
495/// The shared client `ClientConfig` for a given `rejectUnauthorized` setting.
496/// Public so the `https` client (`https.request`/`https.get`) reuses the same
497/// trust configuration as `tls.connect`.
498pub fn client_config(reject_unauthorized: bool) -> Arc<ClientConfig> {
499    if reject_unauthorized {
500        verifying_client_config()
501    } else {
502        insecure_client_config()
503    }
504}
505
506/// Build a `ServerConfig` from PEM `key`+`cert` bytes.
507pub fn build_server_config(cert_pem: &[u8], key_pem: &[u8]) -> Result<Arc<ServerConfig>, String> {
508    let certs: Vec<CertificateDer<'static>> = rustls_pemfile::certs(&mut &cert_pem[..])
509        .collect::<Result<_, _>>()
510        .map_err(|e| format!("Error: tls: bad certificate PEM: {e}"))?;
511    if certs.is_empty() {
512        return Err("Error: tls: no certificates found in `cert`".to_string());
513    }
514    let key: PrivateKeyDer<'static> = rustls_pemfile::private_key(&mut &key_pem[..])
515        .map_err(|e| format!("Error: tls: bad private key PEM: {e}"))?
516        .ok_or_else(|| "Error: tls: no private key found in `key`".to_string())?;
517    let cfg = ServerConfig::builder()
518        .with_no_client_auth()
519        .with_single_cert(certs, key)
520        .map_err(|e| format!("Error: tls: invalid key/cert: {e}"))?;
521    Ok(Arc::new(cfg))
522}
523
524// ── tls.connect (client) ─────────────────────────────────────────────────────
525
526/// `tls.connect(options[, cb])` / `tls.connect(port[, host][, options][, cb])`.
527/// Returns a `TLSSocket` immediately; the TCP connect + handshake run on a
528/// background thread and emit `secureConnect` (or `error`) when complete.
529pub fn connect(args: &[Value]) -> Result<Value, String> {
530    let mut port: u16 = 0;
531    let mut host = "localhost".to_string();
532    let mut servername: Option<String> = None;
533    let mut reject_unauthorized = true;
534    let mut cb: Option<Value> = None;
535
536    for a in args {
537        let n = with_host(|h| h.to_number(a));
538        if with_host(|h| crate::host::is_callable(h, a)) {
539            cb = Some(a.clone());
540        } else if !n.is_nan() && matches!(a, Value::Float(_) | Value::Int(_)) {
541            port = n as u16;
542        } else if with_host(|h| h.as_str(a)).is_some() {
543            host = with_host(|h| h.str_of(a));
544        } else if matches!(a, Value::Obj(_)) {
545            // Options object.
546            if let Some(p) = get_prop(a, "port") {
547                port = with_host(|h| h.to_number(&p)) as u16;
548            }
549            for key in ["host", "hostname"] {
550                if let Some(hv) = get_prop(a, key).filter(|v| with_host(|h| h.as_str(v)).is_some())
551                {
552                    host = with_host(|h| h.str_of(&hv));
553                }
554            }
555            if let Some(sv) =
556                get_prop(a, "servername").filter(|v| with_host(|h| h.as_str(v)).is_some())
557            {
558                servername = Some(with_host(|h| h.str_of(&sv)));
559            }
560            if let Some(rv) = get_prop(a, "rejectUnauthorized") {
561                reject_unauthorized = with_host(|h| h.truthy(&rv));
562            }
563        }
564    }
565    let servername = servername.unwrap_or_else(|| host.clone());
566
567    // Build the socket object + register its write channel on the main thread.
568    let sock_id = next_tls_id();
569    let (tx, rx) = std::sync::mpsc::channel::<WriteCmd>();
570    let mut extra = IndexMap::new();
571    extra.insert("@@tlsid".into(), Value::Float(sock_id as f64));
572    extra.insert("authorized".into(), Value::Bool(reject_unauthorized));
573    extra.insert("encrypted".into(), Value::Bool(true));
574    let socket = new_emitter_object("TLSSocket", extra);
575    TLS.with(|s| {
576        s.borrow_mut().sockets.insert(
577            sock_id,
578            TlsSocketRec {
579                emitter: socket.clone(),
580                tx,
581            },
582        );
583    });
584    with_host(|h| h.incr_handle());
585    // `tls.connect(opts, cb)` registers `cb` as a one-shot `secureConnect` listener.
586    if let Some(cb) = cb {
587        super::events::instance_call(
588            &socket,
589            "once",
590            vec![with_host(|h| h.new_str("secureConnect")), cb],
591        )?;
592    }
593
594    let io_tx = with_host(|h| h.io_sender());
595    let config = if reject_unauthorized {
596        verifying_client_config()
597    } else {
598        insecure_client_config()
599    };
600
601    std::thread::spawn(move || {
602        let server_name = match ServerName::try_from(servername.clone()) {
603            Ok(n) => n,
604            Err(_) => {
605                post_socket_error(
606                    &io_tx,
607                    sock_id,
608                    format!("Error: tls: invalid servername '{servername}'"),
609                );
610                return;
611            }
612        };
613        let mut sock = match TcpStream::connect((host.as_str(), port)) {
614            Ok(s) => s,
615            Err(e) => {
616                post_socket_error(
617                    &io_tx,
618                    sock_id,
619                    format!("Error: connect ECONNREFUSED {host}:{port}: {e}"),
620                );
621                return;
622            }
623        };
624        let mut conn = match ClientConnection::new(config, server_name) {
625            Ok(c) => c,
626            Err(e) => {
627                post_socket_error(&io_tx, sock_id, format!("Error: tls: {e}"));
628                return;
629            }
630        };
631        // Drive the handshake to completion (blocking).
632        if let Err(e) = conn.complete_io(&mut sock) {
633            post_socket_error(&io_tx, sock_id, format!("Error: tls handshake failed: {e}"));
634            return;
635        }
636        let _ = io_tx.send(Box::new(move || on_secure_connect(sock_id)));
637        let stream = StreamOwned::new(conn, sock);
638        owner_loop(stream, sock_id, rx, io_tx);
639    });
640
641    Ok(socket)
642}
643
644/// Emit `secureConnect` on a freshly-handshaked socket (runs on the main thread).
645fn on_secure_connect(sock_id: u64) -> Result<(), String> {
646    let socket = TLS.with(|s| s.borrow().sockets.get(&sock_id).map(|r| r.emitter.clone()));
647    if let Some(socket) = socket {
648        super::events::instance_call(
649            &socket,
650            "emit",
651            vec![with_host(|h| h.new_str("secureConnect"))],
652        )?;
653    }
654    Ok(())
655}
656
657/// Post an `error` event (then close) for a socket whose connect/handshake failed.
658fn post_socket_error(io_tx: &Sender<IoTask>, sock_id: u64, msg: String) {
659    let _ = io_tx.send(Box::new(move || {
660        let socket = TLS.with(|s| s.borrow().sockets.get(&sock_id).map(|r| r.emitter.clone()));
661        if let Some(socket) = socket {
662            let err = with_host(|h| {
663                let mut m = IndexMap::new();
664                m.insert("message".into(), h.new_str(msg.clone()));
665                h.new_object(m)
666            });
667            super::events::instance_call(
668                &socket,
669                "emit",
670                vec![with_host(|h| h.new_str("error")), err],
671            )?;
672        }
673        on_socket_close(sock_id)
674    }));
675}
676
677// ── tls.createServer ─────────────────────────────────────────────────────────
678
679/// `tls.createServer([options][, secureConnectionListener])`. Parses `key`+`cert`
680/// into a `ServerConfig` eagerly (so a bad cert throws synchronously) and returns
681/// a `TLSServer` emitter.
682pub fn create_server(args: &[Value]) -> Result<Value, String> {
683    let mut options: Option<Value> = None;
684    let mut listener: Option<Value> = None;
685    for a in args {
686        if with_host(|h| crate::host::is_callable(h, a)) {
687            listener = Some(a.clone());
688        } else if matches!(a, Value::Obj(_)) {
689            options = Some(a.clone());
690        }
691    }
692    let opts = options.ok_or_else(|| {
693        crate::host::type_error("tls.createServer requires an options object with `key` and `cert`")
694    })?;
695    let cert = value_bytes(get_prop(&opts, "cert").as_ref());
696    let key = value_bytes(get_prop(&opts, "key").as_ref());
697    if cert.is_empty() || key.is_empty() {
698        return Err(crate::host::type_error(
699            "tls.createServer requires `key` and `cert`",
700        ));
701    }
702    let config = build_server_config(&cert, &key)?;
703
704    let mut extra = IndexMap::new();
705    if let Some(cb) = listener {
706        extra.insert("@@connListener".into(), cb);
707    }
708    let server = new_emitter_object("TLSServer", extra);
709    PENDING_CONFIGS.with(|p| p.borrow_mut().push((server.clone(), config)));
710    Ok(server)
711}
712
713/// Build a TLS server backed by a caller-supplied per-connection hook (used by
714/// `https::create_server`). Returns the `TLSServer` emitter; the caller stores its
715/// `requestListener` and registers the hook.
716pub fn create_server_with_config(
717    config: Arc<ServerConfig>,
718    hook: ConnHook,
719    request_listener: Value,
720) -> Value {
721    let mut extra = IndexMap::new();
722    extra.insert("@@requestListener".into(), request_listener);
723    let server = new_emitter_object("TLSServer", extra);
724    PENDING_CONFIGS.with(|p| p.borrow_mut().push((server.clone(), config)));
725    PENDING_HOOKS.with(|p| p.borrow_mut().push((server.clone(), hook)));
726    server
727}
728
729fn take_pending_config(server: &Value) -> Option<Arc<ServerConfig>> {
730    PENDING_CONFIGS.with(|p| {
731        let mut p = p.borrow_mut();
732        p.iter()
733            .position(|(s, _)| s == server)
734            .map(|pos| p.remove(pos).1)
735    })
736}
737fn take_pending_hook(server: &Value) -> Option<ConnHook> {
738    PENDING_HOOKS.with(|p| {
739        let mut p = p.borrow_mut();
740        p.iter()
741            .position(|(s, _)| s == server)
742            .map(|pos| p.remove(pos).1)
743    })
744}
745
746// ── instance dispatch (TLSServer / TLSSocket) ────────────────────────────────
747
748pub fn instance_call(
749    tag: &str,
750    recv: &Value,
751    method: &str,
752    args: Vec<Value>,
753) -> Result<Value, String> {
754    match tag {
755        "TLSServer" => server_call(recv, method, args),
756        "TLSSocket" => socket_call(recv, method, args),
757        _ => Err(crate::host::type_error(&format!(
758            "{method} is not a function"
759        ))),
760    }
761}
762
763fn server_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
764    if let Some(r) = emitter_dispatch(recv, method, &args) {
765        return r;
766    }
767    match method {
768        "listen" => server_listen(recv, &args),
769        "close" => server_close(recv, &args),
770        "address" => Ok(get_prop(recv, "@@address").unwrap_or(Value::Undef)),
771        _ => Err(crate::host::type_error(&format!(
772            "server.{method} is not a function"
773        ))),
774    }
775}
776
777/// `server.listen(port[, host][, callback])`. Binds on the main thread, spawns the
778/// accept loop, and fires `listening` + callback asynchronously.
779fn server_listen(recv: &Value, args: &[Value]) -> Result<Value, String> {
780    let port = with_host(|h| args.first().map(|v| h.to_number(v)).unwrap_or(0.0)) as u16;
781    let mut host = "0.0.0.0".to_string();
782    let mut cb: Option<Value> = None;
783    for a in &args[1.min(args.len())..] {
784        if with_host(|h| h.as_str(a)).is_some() {
785            host = with_host(|h| h.str_of(a));
786        } else if with_host(|h| crate::host::is_callable(h, a)) {
787            cb = Some(a.clone());
788        }
789    }
790
791    let config = take_pending_config(recv)
792        .ok_or_else(|| crate::host::type_error("tls server has no secure context"))?;
793    let listener = std::net::TcpListener::bind((host.as_str(), port))
794        .map_err(|e| format!("Error: listen EADDRINUSE: {e}"))?;
795    let local = listener.local_addr().ok();
796
797    let id = next_server_id();
798    set_prop(recv, "@@serverid", Value::Float(id as f64));
799    if let Some(addr) = local {
800        let mut a = IndexMap::new();
801        a.insert("port".into(), Value::Float(addr.port() as f64));
802        a.insert(
803            "address".into(),
804            with_host(|h| h.new_str(addr.ip().to_string())),
805        );
806        a.insert(
807            "family".into(),
808            with_host(|h| h.new_str(if addr.is_ipv6() { "IPv6" } else { "IPv4" })),
809        );
810        let addr_obj = with_host(|h| h.new_object(a));
811        set_prop(recv, "@@address", addr_obj);
812    }
813    let conn_hook = take_pending_hook(recv);
814    let request_listener = get_prop(recv, "@@requestListener");
815    let plain_listener = get_prop(recv, "@@connListener");
816    let stop = Arc::new(AtomicBool::new(false));
817    TLS.with(|s| {
818        s.borrow_mut().servers.insert(
819            id,
820            TlsServerRec {
821                emitter: recv.clone(),
822                stop: stop.clone(),
823                conn_hook,
824                listener: plain_listener,
825            },
826        );
827    });
828    // Preserve the https request listener where the hook can find it (already on
829    // the object as `@@requestListener`); nothing else to stash.
830    let _ = request_listener;
831    with_host(|h| h.incr_handle());
832
833    let io_tx = with_host(|h| h.io_sender());
834    listener.set_nonblocking(true).ok();
835    std::thread::spawn(move || {
836        loop {
837            if stop.load(Ordering::Acquire) {
838                break;
839            }
840            match listener.accept() {
841                Ok((stream, _addr)) => {
842                    let cfg = config.clone();
843                    let tx = io_tx.clone();
844                    // One handshake+owner thread per connection.
845                    std::thread::spawn(move || accept_connection(id, stream, cfg, tx));
846                }
847                Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
848                    std::thread::sleep(std::time::Duration::from_millis(5));
849                }
850                Err(_) => break,
851            }
852        }
853    });
854
855    let server = recv.clone();
856    let _ = with_host(|h| h.io_sender()).send(Box::new(move || {
857        super::events::instance_call(&server, "emit", vec![with_host(|h| h.new_str("listening"))])?;
858        if let Some(cb) = cb {
859            invoke(&cb, Vec::new(), None)?;
860        }
861        Ok(())
862    }));
863    Ok(recv.clone())
864}
865
866/// A background per-connection thread: complete the server handshake, then post an
867/// `IoTask` that surfaces the socket to JS, and finally become its owner loop.
868fn accept_connection(
869    server_id: u64,
870    mut sock: TcpStream,
871    config: Arc<ServerConfig>,
872    io_tx: Sender<IoTask>,
873) {
874    let mut conn = match ServerConnection::new(config) {
875        Ok(c) => c,
876        Err(_) => return,
877    };
878    if conn.complete_io(&mut sock).is_err() {
879        return;
880    }
881    let sock_id = next_tls_id();
882    let (tx, rx) = std::sync::mpsc::channel::<WriteCmd>();
883    let tx_for_main = tx;
884    let _ = io_tx.send(Box::new(move || {
885        on_server_connection(server_id, sock_id, tx_for_main)
886    }));
887    let stream = StreamOwned::new(conn, sock);
888    owner_loop(stream, sock_id, rx, io_tx);
889}
890
891/// Main-thread handler for a newly-handshaked server connection: build the
892/// `TLSSocket`, register it, run the server's hook/listener, emit events.
893fn on_server_connection(server_id: u64, sock_id: u64, tx: Sender<WriteCmd>) -> Result<(), String> {
894    let server = TLS.with(|s| {
895        s.borrow()
896            .servers
897            .get(&server_id)
898            .map(|r| r.emitter.clone())
899    });
900    let Some(server) = server else { return Ok(()) };
901
902    let mut extra = IndexMap::new();
903    extra.insert("@@tlsid".into(), Value::Float(sock_id as f64));
904    extra.insert("encrypted".into(), Value::Bool(true));
905    let socket = new_emitter_object("TLSSocket", extra);
906    TLS.with(|s| {
907        s.borrow_mut().sockets.insert(
908            sock_id,
909            TlsSocketRec {
910                emitter: socket.clone(),
911                tx,
912            },
913        );
914    });
915    with_host(|h| h.incr_handle());
916
917    // `secureConnection` is the tls server event; also emit `connection` for parity.
918    super::events::instance_call(
919        &server,
920        "emit",
921        vec![with_host(|h| h.new_str("secureConnection")), socket.clone()],
922    )?;
923    super::events::instance_call(
924        &server,
925        "emit",
926        vec![with_host(|h| h.new_str("connection")), socket.clone()],
927    )?;
928
929    // https attaches a request-parser hook; a plain tls server runs its listener.
930    let hook = TLS.with(|s| {
931        s.borrow()
932            .servers
933            .get(&server_id)
934            .and_then(|r| r.conn_hook.clone())
935    });
936    if let Some(hook) = hook {
937        hook(&server, &socket, sock_id)?;
938    } else {
939        let listener = TLS.with(|s| {
940            s.borrow()
941                .servers
942                .get(&server_id)
943                .and_then(|r| r.listener.clone())
944        });
945        if let Some(cb) = listener {
946            invoke(&cb, vec![socket.clone()], None)?;
947        }
948    }
949    Ok(())
950}
951
952fn server_close(recv: &Value, args: &[Value]) -> Result<Value, String> {
953    if let Some(id) = u64_prop(recv, "@@serverid") {
954        let rec = TLS.with(|s| s.borrow_mut().servers.remove(&id));
955        if let Some(rec) = rec {
956            rec.stop.store(true, Ordering::Release);
957            with_host(|h| h.decr_handle());
958            let _ = with_host(|h| h.io_sender()).send(Box::new(|| Ok(())));
959        }
960    }
961    if let Some(cb) = args
962        .first()
963        .filter(|v| with_host(|h| crate::host::is_callable(h, v)))
964    {
965        invoke(cb, Vec::new(), None)?;
966    }
967    super::events::instance_call(recv, "emit", vec![with_host(|h| h.new_str("close"))])?;
968    Ok(recv.clone())
969}
970
971// ── TLSSocket instance methods ───────────────────────────────────────────────
972
973fn socket_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
974    if let Some(r) = emitter_dispatch(recv, method, &args) {
975        return r;
976    }
977    match method {
978        "write" => {
979            if let Some(id) = u64_prop(recv, "@@tlsid") {
980                socket_write(id, &value_bytes(args.first()));
981            }
982            Ok(Value::Bool(true))
983        }
984        "end" => {
985            if let Some(id) = u64_prop(recv, "@@tlsid") {
986                if let Some(chunk) = args.first().filter(|v| !matches!(v, Value::Undef)) {
987                    socket_write(id, &value_bytes(Some(chunk)));
988                }
989                socket_end(id);
990            }
991            Ok(recv.clone())
992        }
993        "destroy" => {
994            if let Some(id) = u64_prop(recv, "@@tlsid") {
995                socket_end(id);
996            }
997            Ok(recv.clone())
998        }
999        "setEncoding" | "setTimeout" | "setNoDelay" | "setKeepAlive" | "ref" | "unref"
1000        | "pause" | "resume" => Ok(recv.clone()),
1001        _ => Err(crate::host::type_error(&format!(
1002            "socket.{method} is not a function"
1003        ))),
1004    }
1005}
1006
1007/// Queue plaintext to be written by the socket's owner thread (no-op if closed).
1008/// Public so `https` server responses can write through the TLS channel.
1009pub fn socket_write(sock_id: u64, data: &[u8]) {
1010    let tx = TLS.with(|s| s.borrow().sockets.get(&sock_id).map(|r| r.tx.clone()));
1011    if let Some(tx) = tx {
1012        let _ = tx.send(WriteCmd::Data(data.to_vec()));
1013    }
1014}
1015
1016/// Signal end-of-write (TLS close-notify + TCP write shutdown) on a socket.
1017pub fn socket_end(sock_id: u64) {
1018    let tx = TLS.with(|s| s.borrow().sockets.get(&sock_id).map(|r| r.tx.clone()));
1019    if let Some(tx) = tx {
1020        let _ = tx.send(WriteCmd::Shutdown);
1021    }
1022}
1023
1024// ── the per-socket owner thread ──────────────────────────────────────────────
1025
1026/// Sole owner of one connection's `StreamOwned`. Reads with a short socket
1027/// read-timeout (a `WouldBlock` just loops) and drains the write channel. Posts
1028/// `data`/`end`/`close` `IoTask`s to the main thread. Generic over client/server
1029/// connections (both deref to `ConnectionCommon`), mirroring `StreamOwned`'s
1030/// bounds.
1031fn owner_loop<C, S>(
1032    mut stream: StreamOwned<C, TcpStream>,
1033    sock_id: u64,
1034    rx: Receiver<WriteCmd>,
1035    io_tx: Sender<IoTask>,
1036) where
1037    C: DerefMut + Deref<Target = ConnectionCommon<S>>,
1038    S: SideData,
1039{
1040    stream
1041        .sock
1042        .set_read_timeout(Some(std::time::Duration::from_millis(20)))
1043        .ok();
1044    let mut buf = [0u8; 16384];
1045    loop {
1046        // 1) drain any queued writes.
1047        let mut shutdown = false;
1048        loop {
1049            match rx.try_recv() {
1050                Ok(WriteCmd::Data(bytes)) => {
1051                    if stream
1052                        .write_all(&bytes)
1053                        .and_then(|_| stream.flush())
1054                        .is_err()
1055                    {
1056                        let _ = io_tx.send(Box::new(move || on_socket_close(sock_id)));
1057                        return;
1058                    }
1059                }
1060                Ok(WriteCmd::Shutdown) => shutdown = true,
1061                Err(std::sync::mpsc::TryRecvError::Empty) => break,
1062                Err(std::sync::mpsc::TryRecvError::Disconnected) => break,
1063            }
1064        }
1065        if shutdown {
1066            stream.conn.send_close_notify();
1067            let _ = stream.flush();
1068            let _ = stream.sock.shutdown(std::net::Shutdown::Write);
1069        }
1070
1071        // 2) read whatever plaintext is available.
1072        match stream.read(&mut buf) {
1073            Ok(0) => {
1074                let _ = io_tx.send(Box::new(move || on_socket_end(sock_id)));
1075                return;
1076            }
1077            Ok(n) => {
1078                let bytes = buf[..n].to_vec();
1079                let _ = io_tx.send(Box::new(move || on_socket_data(sock_id, bytes)));
1080            }
1081            Err(ref e)
1082                if matches!(
1083                    e.kind(),
1084                    std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
1085                ) =>
1086            {
1087                // No plaintext ready; loop to service writes again.
1088                continue;
1089            }
1090            Err(_) => {
1091                let _ = io_tx.send(Box::new(move || on_socket_close(sock_id)));
1092                return;
1093            }
1094        }
1095    }
1096}
1097
1098// ── main-thread socket event handlers (run from posted IoTasks) ──────────────
1099
1100fn on_socket_data(sock_id: u64, bytes: Vec<u8>) -> Result<(), String> {
1101    let socket = TLS.with(|s| s.borrow().sockets.get(&sock_id).map(|r| r.emitter.clone()));
1102    let Some(socket) = socket else { return Ok(()) };
1103    // Feed the https request parser first (no-op unless this is an https conn).
1104    super::https::feed(sock_id, &socket, &bytes)?;
1105    let chunk = super::buffer::from_bytes(&bytes);
1106    super::events::instance_call(
1107        &socket,
1108        "emit",
1109        vec![with_host(|h| h.new_str("data")), chunk],
1110    )?;
1111    Ok(())
1112}
1113
1114fn on_socket_end(sock_id: u64) -> Result<(), String> {
1115    let socket = TLS.with(|s| s.borrow().sockets.get(&sock_id).map(|r| r.emitter.clone()));
1116    if let Some(socket) = socket {
1117        super::events::instance_call(&socket, "emit", vec![with_host(|h| h.new_str("end"))])?;
1118    }
1119    on_socket_close(sock_id)
1120}
1121
1122fn on_socket_close(sock_id: u64) -> Result<(), String> {
1123    let rec = TLS.with(|s| s.borrow_mut().sockets.remove(&sock_id));
1124    super::https::drop_conn(sock_id);
1125    if let Some(rec) = rec {
1126        super::events::instance_call(
1127            &rec.emitter,
1128            "emit",
1129            vec![with_host(|h| h.new_str("close"))],
1130        )?;
1131        with_host(|h| h.decr_handle());
1132        let _ = with_host(|h| h.io_sender()).send(Box::new(|| Ok(())));
1133    }
1134    Ok(())
1135}
1136
1137// ── shared emitter constructor (same shape as net) ───────────────────────────
1138
1139/// Build a native emitter object (`@@native` tag + `@@on`/`@@once` maps + extras),
1140/// sharing the EventEmitter shape with `events`/`net`.
1141pub fn new_emitter_object(tag: &str, mut extra: IndexMap<String, Value>) -> Value {
1142    with_host(|h| {
1143        let on = h.new_object(IndexMap::new());
1144        let once = h.new_object(IndexMap::new());
1145        let mut m = IndexMap::new();
1146        m.insert("@@native".into(), h.new_str(tag));
1147        m.insert("@@on".into(), on);
1148        m.insert("@@once".into(), once);
1149        for (k, v) in extra.drain(..) {
1150            m.insert(k, v);
1151        }
1152        h.new_object(m)
1153    })
1154}