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