Skip to content

Commit dafeb0f

Browse files
committed
feat(tls): implement TLS module for secure connections
Add client-side TLS support with the following features: - tls.connect() for creating secure outbound connections - tls.createSecureContext() for reusable TLS configurations - TLSSocket class with full event support (secureConnect, data, end, close, error, keylog) - Custom CA certificates for private PKI - Client certificate authentication (mTLS) for zero-trust environments - ALPN protocol negotiation for HTTP/2 support - TLS version control (minVersion/maxVersion) - SNI (Server Name Indication) support - keylog event for TLS debugging with Wireshark - getCiphers(), checkServerIdentity(), rootCertificates This implementation focuses on Lambda-relevant client-side functionality. Server-side APIs (createServer, Server class) are intentionally omitted as they are not applicable to serverless environments. Fixes #1 Fixes #1102
1 parent fcdf73a commit dafeb0f

File tree

14 files changed

+2560
-39
lines changed

14 files changed

+2560
-39
lines changed

API.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,29 @@ Everything else inherited from [Uint8Array](https://developer.mozilla.org/en-US/
245245

246246
[createServer](https://nodejs.org/api/net.html#netcreateserveroptions-connectionlistener)
247247

248+
## tls
249+
250+
> [!WARNING]
251+
> These APIs uses native streams that is not 100% compatible with the Node.js Streams API.
252+
253+
[connect](https://nodejs.org/api/tls.html#tlsconnectoptions-callback)
254+
255+
[createSecureContext](https://nodejs.org/api/tls.html#tlscreatesecurecontextoptions)
256+
257+
[getCiphers](https://nodejs.org/api/tls.html#tlsgetciphers)
258+
259+
[rootCertificates](https://nodejs.org/api/tls.html#tlsrootcertificates)
260+
261+
[checkServerIdentity](https://nodejs.org/api/tls.html#tlscheckserveridentityhostname-cert)
262+
263+
[DEFAULT_MIN_VERSION](https://nodejs.org/api/tls.html#tlsdefault_min_version)
264+
265+
[DEFAULT_MAX_VERSION](https://nodejs.org/api/tls.html#tlsdefault_max_version)
266+
267+
[TLSSocket](https://nodejs.org/api/tls.html#class-tlstlssocket)
268+
269+
[SecureContext](https://nodejs.org/api/tls.html#tlscreatesecurecontextoptions)
270+
248271
## os
249272

250273
[arch](https://nodejs.org/api/os.html#osarch)

Cargo.lock

Lines changed: 32 additions & 33 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ const ES_BUILD_OPTIONS = {
8585
"net",
8686
"node:net",
8787
"os",
88+
"tls",
89+
"node:tls",
8890
"node:os",
8991
"path",
9092
"node:path",

llrt_modules/src/module_builder.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ impl Default for ModuleBuilder {
161161
.with_global(crate::modules::timers::init)
162162
.with_module(crate::modules::timers::TimersModule);
163163
}
164+
#[cfg(feature = "tls")]
165+
{
166+
builder = builder.with_module(crate::modules::tls::TlsModule);
167+
}
164168
#[cfg(feature = "tty")]
165169
{
166170
builder = builder.with_module(crate::modules::tty::TtyModule);

modules/llrt_http/src/agent.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ impl Agent {
4545
let config = llrt_tls::build_client_config(llrt_tls::BuildClientConfigOptions {
4646
reject_unauthorized,
4747
ca,
48+
cert: None,
49+
key: None,
50+
key_log: None,
4851
})
4952
.or_throw_msg(&ctx, "Failed to build TLS config")?;
5053
let client =

modules/llrt_tls/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,27 @@ name = "llrt_tls"
1212
path = "src/lib.rs"
1313

1414
[dependencies]
15+
base64-simd = { version = "0.8", features = ["alloc"], default-features = false }
16+
itoa = { version = "1", default-features = false }
17+
llrt_buffer = { version = "0.7.0-beta", path = "../llrt_buffer" }
18+
llrt_context = { version = "0.7.0-beta", path = "../../libs/llrt_context" }
19+
llrt_events = { version = "0.7.0-beta", path = "../llrt_events" }
20+
llrt_stream = { version = "0.7.0-beta", path = "../llrt_stream" }
21+
llrt_utils = { version = "0.7.0-beta", path = "../../libs/llrt_utils", default-features = false }
1522
once_cell = { version = "1", features = ["std"], default-features = false }
23+
rquickjs = { git = "https://github.com/DelSkayn/rquickjs.git", version = "0.10.0", default-features = false }
1624
rustls = { version = "0.23", features = [
1725
"std",
1826
"ring",
1927
"tls12",
2028
], default-features = false }
29+
rustls-pki-types = { version = "1", default-features = false }
30+
tokio = { version = "1", features = ["net"], default-features = false }
31+
tokio-rustls = { version = "0.26", default-features = false }
32+
tracing = { version = "0.1", default-features = false }
2133
webpki-roots = { version = "1", default-features = false }
2234

2335
[dev-dependencies]
36+
llrt_test = { path = "../../libs/llrt_test" }
37+
rand = { version = "0.10.0-rc.5", features = ["alloc"], default-features = false }
38+
tokio = { version = "1", features = ["macros", "rt"] }

modules/llrt_tls/src/config.rs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::sync::{Arc, OnceLock};
55
use once_cell::sync::Lazy;
66
use rustls::{
77
crypto::ring,
8-
pki_types::{pem::PemObject, CertificateDer},
8+
pki_types::{pem::PemObject, CertificateDer, PrivateKeyDer},
99
ClientConfig, RootCertStore, SupportedProtocolVersion,
1010
};
1111
use webpki_roots::TLS_SERVER_ROOTS;
@@ -47,12 +47,20 @@ pub static TLS_CONFIG: Lazy<Result<ClientConfig, Box<dyn std::error::Error + Sen
4747
build_client_config(BuildClientConfigOptions {
4848
reject_unauthorized: true,
4949
ca: None,
50+
cert: None,
51+
key: None,
52+
key_log: None,
5053
})
5154
});
5255

5356
pub struct BuildClientConfigOptions {
5457
pub reject_unauthorized: bool,
5558
pub ca: Option<Vec<Vec<u8>>>,
59+
/// Client certificate in PEM format for mTLS
60+
pub cert: Option<Vec<u8>>,
61+
/// Client private key in PEM format for mTLS
62+
pub key: Option<Vec<u8>>,
63+
pub key_log: Option<Arc<dyn rustls::KeyLog>>,
5664
}
5765

5866
pub fn build_client_config(
@@ -72,11 +80,11 @@ pub fn build_client_config(
7280
builder
7381
.dangerous()
7482
.with_custom_certificate_verifier(Arc::new(NoCertificateVerification::new(provider)))
75-
} else if let Some(ca) = options.ca {
83+
} else if let Some(ca) = &options.ca {
7684
let mut root_certificates = RootCertStore::empty();
7785

7886
for cert in ca {
79-
root_certificates.add(CertificateDer::from_pem_slice(&cert)?)?;
87+
root_certificates.add(CertificateDer::from_pem_slice(cert)?)?;
8088
}
8189
builder.with_root_certificates(root_certificates)
8290
} else {
@@ -93,5 +101,24 @@ pub fn build_client_config(
93101
builder.with_root_certificates(root_certificates)
94102
};
95103

96-
Ok(builder.with_no_client_auth())
104+
// Client authentication (mTLS)
105+
let mut config = if let (Some(cert_pem), Some(key_pem)) = (&options.cert, &options.key) {
106+
// Parse client certificate chain
107+
let certs: Vec<CertificateDer<'static>> =
108+
CertificateDer::pem_slice_iter(cert_pem).collect::<std::result::Result<Vec<_>, _>>()?;
109+
110+
// Parse private key
111+
let key = PrivateKeyDer::from_pem_slice(key_pem)?;
112+
113+
builder.with_client_auth_cert(certs, key)?
114+
} else {
115+
builder.with_no_client_auth()
116+
};
117+
118+
// Set key log if provided
119+
if let Some(key_log) = options.key_log {
120+
config.key_log = key_log;
121+
}
122+
123+
Ok(config)
97124
}

0 commit comments

Comments
 (0)