#![allow(
clippy::undocumented_unsafe_blocks,
reason = "TLSWrap is an FFI-heavy Node parity port; safety invariants are documented on the surrounding methods and types."
)]
use std::cell::Cell;
use std::ffi::c_char;
use std::io::Read;
use std::io::Write;
use std::ptr::NonNull;
use std::rc::Rc;
use std::sync::Arc;
use deno_core::CppgcInherits;
use deno_core::GarbageCollected;
use deno_core::OpState;
use deno_core::ToJsBuffer;
use deno_core::op2;
use deno_core::uv_compat;
use deno_core::uv_compat::UV_EBADF;
use deno_core::uv_compat::UV_ECANCELED;
use deno_core::uv_compat::UV_EOF;
use deno_core::uv_compat::uv_buf_t;
use deno_core::uv_compat::uv_stream_t;
use deno_core::uv_compat::uv_write_t;
use deno_core::v8;
use deno_core::v8_static_strings;
use deno_node_crypto::x509::Certificate;
use deno_node_crypto::x509::CertificateObject;
use deno_tls::rustls;
use deno_tls::rustls_pemfile;
use crate::ops::handle_wrap::AsyncWrap;
use crate::ops::handle_wrap::HandleWrap;
use crate::ops::handle_wrap::OwnedPtr;
use crate::ops::handle_wrap::ProviderType;
use crate::ops::stream_wrap::LibUvStreamWrap;
use crate::ops::stream_wrap::StreamBaseState;
use crate::ops::stream_wrap::free_uv_buf;
use crate::ops::stream_wrap_state::ReadInterceptor;
use crate::ops::tls::NodeTlsState;
enum TlsConnection {
Client(rustls::ClientConnection),
Server(rustls::ServerConnection),
}
impl TlsConnection {
fn read_tls(&mut self, rd: &mut dyn Read) -> Result<usize, std::io::Error> {
match self {
TlsConnection::Client(c) => c.read_tls(rd),
TlsConnection::Server(c) => c.read_tls(rd),
}
}
fn write_tls(&mut self, wr: &mut dyn Write) -> Result<usize, std::io::Error> {
match self {
TlsConnection::Client(c) => c.write_tls(wr),
TlsConnection::Server(c) => c.write_tls(wr),
}
}
fn process_new_packets(&mut self) -> Result<rustls::IoState, rustls::Error> {
match self {
TlsConnection::Client(c) => c.process_new_packets(),
TlsConnection::Server(c) => c.process_new_packets(),
}
}
fn reader(&mut self) -> rustls::Reader<'_> {
match self {
TlsConnection::Client(c) => c.reader(),
TlsConnection::Server(c) => c.reader(),
}
}
fn writer(&mut self) -> rustls::Writer<'_> {
match self {
TlsConnection::Client(c) => c.writer(),
TlsConnection::Server(c) => c.writer(),
}
}
fn send_close_notify(&mut self) {
match self {
TlsConnection::Client(c) => c.send_close_notify(),
TlsConnection::Server(c) => c.send_close_notify(),
}
}
fn wants_write(&self) -> bool {
match self {
TlsConnection::Client(c) => c.wants_write(),
TlsConnection::Server(c) => c.wants_write(),
}
}
fn is_handshaking(&self) -> bool {
match self {
TlsConnection::Client(c) => c.is_handshaking(),
TlsConnection::Server(c) => c.is_handshaking(),
}
}
fn alpn_protocol(&self) -> Option<&[u8]> {
match self {
TlsConnection::Client(c) => c.alpn_protocol(),
TlsConnection::Server(c) => c.alpn_protocol(),
}
}
fn server_name(&self) -> Option<&str> {
match self {
TlsConnection::Client(_) => None,
TlsConnection::Server(c) => c.server_name(),
}
}
fn protocol_version(&self) -> Option<rustls::ProtocolVersion> {
match self {
TlsConnection::Client(c) => c.protocol_version(),
TlsConnection::Server(c) => c.protocol_version(),
}
}
fn negotiated_cipher_suite(&self) -> Option<rustls::SupportedCipherSuite> {
match self {
TlsConnection::Client(c) => c.negotiated_cipher_suite(),
TlsConnection::Server(c) => c.negotiated_cipher_suite(),
}
}
fn peer_certificates(
&self,
) -> Option<&[rustls::pki_types::CertificateDer<'static>]> {
match self {
TlsConnection::Client(c) => c.peer_certificates(),
TlsConnection::Server(c) => c.peer_certificates(),
}
}
fn handshake_kind(&self) -> Option<rustls::HandshakeKind> {
match self {
TlsConnection::Client(c) => c.handshake_kind(),
TlsConnection::Server(c) => c.handshake_kind(),
}
}
fn export_keying_material(
&self,
output: &mut [u8],
label: &[u8],
context: Option<&[u8]>,
) -> Result<(), rustls::Error> {
match self {
TlsConnection::Client(c) => c
.export_keying_material(&mut *output, label, context)
.map(|_| ()),
TlsConnection::Server(c) => c
.export_keying_material(&mut *output, label, context)
.map(|_| ()),
}
}
}
#[derive(serde::Serialize)]
struct PeerCertificateChain {
certificates: Vec<ToJsBuffer>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
enum Kind {
Client = 0,
Server = 1,
}
#[repr(usize)]
enum StreamBaseStateFields {
ReadBytesOrError = 0,
ArrayBufferOffset = 1,
BytesWritten = 2,
LastWriteWasAsync = 3,
}
const CLEAR_OUT_CHUNK_SIZE: usize = 16384;
struct EmitCtx {
isolate_ptr: v8::UnsafeRawIsolatePtr,
js_handle: v8::Global<v8::Object>,
loop_ptr: *mut uv_compat::uv_loop_t,
}
unsafe fn extract_emit_ctx(ptr: *mut TLSWrapInner) -> Option<EmitCtx> {
unsafe {
let isolate_ptr = (*ptr).isolate?;
let js_handle = (*ptr).js_handle.clone()?;
let loop_ptr = if (*ptr).cached_loop_ptr.is_null() {
(*ptr).underlying.loop_ptr()
} else {
(*ptr).cached_loop_ptr
};
Some(EmitCtx {
isolate_ptr,
js_handle,
loop_ptr,
})
}
}
unsafe fn clone_context_global(
isolate: &mut v8::Isolate,
ctx_ptr: *mut std::ffi::c_void,
) -> v8::Global<v8::Context> {
unsafe {
let raw = NonNull::new_unchecked(ctx_ptr as *mut v8::Context);
let global = v8::Global::from_raw(isolate, raw);
let cloned = global.clone();
global.into_raw();
cloned
}
}
struct ClearOutResult {
handshake_done: bool,
data: Vec<u8>,
got_eof: bool,
got_error: bool,
tls_error: Option<(String, String)>,
}
enum EncOutAction {
None,
WriteUv,
WriteJs,
InvokeQueued(i32),
}
unsafe fn do_emit_read_through_user_buffer(
ctx: &EmitCtx,
inner_ptr: *mut TLSWrapInner,
onread: Option<&v8::Global<v8::Function>>,
state: Option<&v8::Global<v8::Int32Array>>,
data: &[u8],
) {
let mut offset = 0;
while offset < data.len() {
let user_buf =
unsafe { (*inner_ptr).user_buffer.as_ref().map(|ub| (ub.ptr, ub.len)) };
let Some((ptr, len)) = user_buf else {
let remaining = &data[offset..];
unsafe {
do_emit_read(
ctx,
onread,
state,
remaining.len() as isize,
Some(remaining),
);
}
return;
};
if len == 0 {
return;
}
let chunk_len = std::cmp::min(data.len() - offset, len);
unsafe {
std::ptr::copy_nonoverlapping(
data[offset..offset + chunk_len].as_ptr(),
ptr,
chunk_len,
);
}
offset += chunk_len;
unsafe {
do_emit_read_undefined_arg(ctx, onread, state, chunk_len as isize);
}
}
}
unsafe fn do_emit_read_undefined_arg(
ctx: &EmitCtx,
onread: Option<&v8::Global<v8::Function>>,
state: Option<&v8::Global<v8::Int32Array>>,
nread: isize,
) {
let Some(state_global) = state else {
return;
};
unsafe {
let mut isolate = v8::Isolate::from_raw_isolate_ptr(ctx.isolate_ptr);
if ctx.loop_ptr.is_null() {
return;
}
let ctx_ptr = (*ctx.loop_ptr).data;
if ctx_ptr.is_null() {
return;
}
let context_global = clone_context_global(&mut isolate, ctx_ptr);
v8::scope!(let handle_scope, &mut isolate);
let context = v8::Local::new(handle_scope, context_global);
let scope = &mut v8::ContextScope::new(handle_scope, context);
let state_array: v8::Local<v8::Int32Array> =
v8::Local::new(scope, state_global);
state_array.set_index(
scope,
StreamBaseStateFields::ReadBytesOrError as u32,
v8::Integer::new(scope, nread as i32).into(),
);
state_array.set_index(
scope,
StreamBaseStateFields::ArrayBufferOffset as u32,
v8::Integer::new(scope, 0).into(),
);
let recv = v8::Local::new(scope, &ctx.js_handle);
let Some(onread) = onread else {
return;
};
let onread_fn = v8::Local::new(scope, onread);
let undef = v8::undefined(scope);
onread_fn.call(scope, recv.into(), &[undef.into()]);
}
}
unsafe fn do_emit_read(
ctx: &EmitCtx,
onread: Option<&v8::Global<v8::Function>>,
state: Option<&v8::Global<v8::Int32Array>>,
nread: isize,
data: Option<&[u8]>,
) {
let Some(state_global) = state else {
return;
};
unsafe {
let mut isolate = v8::Isolate::from_raw_isolate_ptr(ctx.isolate_ptr);
if ctx.loop_ptr.is_null() {
return;
}
let ctx_ptr = (*ctx.loop_ptr).data;
if ctx_ptr.is_null() {
return;
}
let context_global = clone_context_global(&mut isolate, ctx_ptr);
v8::scope!(let handle_scope, &mut isolate);
let context = v8::Local::new(handle_scope, context_global);
let scope = &mut v8::ContextScope::new(handle_scope, context);
let state_array: v8::Local<v8::Int32Array> =
v8::Local::new(scope, state_global);
state_array.set_index(
scope,
StreamBaseStateFields::ReadBytesOrError as u32,
v8::Integer::new(scope, nread as i32).into(),
);
state_array.set_index(
scope,
StreamBaseStateFields::ArrayBufferOffset as u32,
v8::Integer::new(scope, 0).into(),
);
let recv = v8::Local::new(scope, &ctx.js_handle);
let Some(onread) = onread else {
return;
};
let onread_fn = v8::Local::new(scope, onread);
if let Some(bytes) = data {
let len = bytes.len();
let store = v8::ArrayBuffer::new(scope, len);
let backing = store.get_backing_store();
for (i, byte) in bytes.iter().enumerate() {
backing[i].set(*byte);
}
let ab: v8::Local<v8::Value> = store.into();
onread_fn.call(scope, recv.into(), &[ab]);
} else {
let undef = v8::undefined(scope);
onread_fn.call(scope, recv.into(), &[undef.into()]);
}
}
}
unsafe fn do_emit_error(ctx: &EmitCtx, error_msg: &str, error_code: &str) {
unsafe {
let mut isolate = v8::Isolate::from_raw_isolate_ptr(ctx.isolate_ptr);
if ctx.loop_ptr.is_null() {
return;
}
let ctx_ptr = (*ctx.loop_ptr).data;
if ctx_ptr.is_null() {
return;
}
let context_global = clone_context_global(&mut isolate, ctx_ptr);
v8::scope!(let handle_scope, &mut isolate);
let context = v8::Local::new(handle_scope, context_global);
let scope = &mut v8::ContextScope::new(handle_scope, context);
let this = v8::Local::new(scope, &ctx.js_handle);
let msg = v8::String::new(scope, error_msg).unwrap();
let error = v8::Exception::error(scope, msg);
let error_obj = error.to_object(scope).unwrap();
let code_key =
v8::String::new_external_onebyte_static(scope, b"code").unwrap();
let code_val = v8::String::new(scope, error_code).unwrap();
error_obj.set(scope, code_key.into(), code_val.into());
let key =
v8::String::new_external_onebyte_static(scope, b"onerror").unwrap();
if let Some(val) = this.get(scope, key.into())
&& let Ok(func) = v8::Local::<v8::Function>::try_from(val)
{
func.call(scope, this.into(), &[error]);
}
}
}
unsafe fn do_emit_handshake_done(ctx: &EmitCtx) {
unsafe {
let mut isolate = v8::Isolate::from_raw_isolate_ptr(ctx.isolate_ptr);
if ctx.loop_ptr.is_null() {
return;
}
let ctx_ptr = (*ctx.loop_ptr).data;
if ctx_ptr.is_null() {
return;
}
let context_global = clone_context_global(&mut isolate, ctx_ptr);
v8::scope!(let handle_scope, &mut isolate);
let context = v8::Local::new(handle_scope, context_global);
let scope = &mut v8::ContextScope::new(handle_scope, context);
let this = v8::Local::new(scope, &ctx.js_handle);
let key =
v8::String::new_external_onebyte_static(scope, b"onhandshakedone")
.unwrap();
if let Some(val) = this.get(scope, key.into())
&& let Ok(func) = v8::Local::<v8::Function>::try_from(val)
{
func.call(scope, this.into(), &[]);
}
}
}
unsafe fn do_emit_client_hello(ctx: &EmitCtx) {
unsafe {
let mut isolate = v8::Isolate::from_raw_isolate_ptr(ctx.isolate_ptr);
if ctx.loop_ptr.is_null() {
return;
}
let ctx_ptr = (*ctx.loop_ptr).data;
if ctx_ptr.is_null() {
return;
}
let context_global = clone_context_global(&mut isolate, ctx_ptr);
v8::scope!(let handle_scope, &mut isolate);
let context = v8::Local::new(handle_scope, context_global);
let scope = &mut v8::ContextScope::new(handle_scope, context);
let this = v8::Local::new(scope, &ctx.js_handle);
let key =
v8::String::new_external_onebyte_static(scope, b"onclienthello").unwrap();
if let Some(val) = this.get(scope, key.into())
&& let Ok(func) = v8::Local::<v8::Function>::try_from(val)
{
func.call(scope, this.into(), &[]);
}
}
}
unsafe fn do_invoke_queued(
ctx: &EmitCtx,
write_obj: v8::Global<v8::Object>,
status: i32,
) {
unsafe {
let mut isolate = v8::Isolate::from_raw_isolate_ptr(ctx.isolate_ptr);
if ctx.loop_ptr.is_null() {
return;
}
let ctx_ptr = (*ctx.loop_ptr).data;
if ctx_ptr.is_null() {
return;
}
let context_global = clone_context_global(&mut isolate, ctx_ptr);
v8::scope!(let handle_scope, &mut isolate);
let context = v8::Local::new(handle_scope, context_global);
let scope = &mut v8::ContextScope::new(handle_scope, context);
let req_obj = v8::Local::new(scope, &write_obj);
let handle = v8::Local::new(scope, &ctx.js_handle);
let oncomplete_str =
v8::String::new_external_onebyte_static(scope, b"oncomplete").unwrap();
if let Some(oncomplete) = req_obj.get(scope, oncomplete_str.into())
&& let Ok(func) = v8::Local::<v8::Function>::try_from(oncomplete)
{
let status_val = v8::Integer::new(scope, status);
let undef = v8::undefined(scope);
func.call(
scope,
req_obj.into(),
&[status_val.into(), handle.into(), undef.into()],
);
}
}
}
#[allow(dead_code, reason = "retained for native TCP/Pipe enc-out path parity")]
unsafe fn do_enc_out_js(ctx: &EmitCtx, enc_data: Vec<u8>) {
unsafe {
let mut isolate = v8::Isolate::from_raw_isolate_ptr(ctx.isolate_ptr);
if ctx.loop_ptr.is_null() {
return;
}
let ctx_ptr = (*ctx.loop_ptr).data;
if ctx_ptr.is_null() {
return;
}
let context_global = clone_context_global(&mut isolate, ctx_ptr);
v8::scope!(let handle_scope, &mut isolate);
let context = v8::Local::new(handle_scope, context_global);
let scope = &mut v8::ContextScope::new(handle_scope, context);
let this = v8::Local::new(scope, &ctx.js_handle);
let key =
v8::String::new_external_onebyte_static(scope, b"encOut").unwrap();
if let Some(val) = this.get(scope, key.into())
&& let Ok(func) = v8::Local::<v8::Function>::try_from(val)
{
let ab = v8::ArrayBuffer::new(scope, enc_data.len());
let backing = ab.get_backing_store();
for (i, byte) in enc_data.iter().enumerate() {
backing[i].set(*byte);
}
func.call(scope, this.into(), &[ab.into()]);
}
}
}
unsafe fn prepare_invoke_queued(
ptr: *mut TLSWrapInner,
) -> Option<(v8::Global<v8::Object>, EmitCtx)> {
unsafe {
(*ptr).write_callback_scheduled = false;
let write_obj = (*ptr).current_write_obj.take()?;
(*ptr).current_write_bytes = 0;
let ctx = extract_emit_ctx(ptr)?;
Some((write_obj, ctx))
}
}
#[derive(Default)]
enum UnderlyingStream {
#[default]
None,
Uv { stream: *mut uv_stream_t },
Js {
loop_ptr: *mut uv_compat::uv_loop_t,
},
}
impl UnderlyingStream {
fn is_attached(&self) -> bool {
!matches!(self, UnderlyingStream::None)
}
#[allow(
dead_code,
reason = "Useful when debugging TLS/native stream attachment."
)]
fn uv_stream_ptr(&self) -> *mut uv_stream_t {
match self {
UnderlyingStream::Uv { stream } => *stream,
_ => std::ptr::null_mut(),
}
}
fn loop_ptr(&self) -> *mut uv_compat::uv_loop_t {
match self {
UnderlyingStream::Uv { .. } => {
debug_assert!(false, "use TLSWrapInner.cached_loop_ptr for Uv streams");
std::ptr::null_mut()
}
UnderlyingStream::Js { loop_ptr, .. } => *loop_ptr,
UnderlyingStream::None => std::ptr::null_mut(),
}
}
fn read_start(&mut self) {
match self {
UnderlyingStream::Uv { .. } => {
}
UnderlyingStream::Js { .. } => {
}
UnderlyingStream::None => {}
}
}
#[allow(
dead_code,
reason = "reserved for a future native read-stop path; today reads are stopped at the JS layer"
)]
fn read_stop(&self) {
match self {
UnderlyingStream::Uv { .. } => {
}
UnderlyingStream::Js { .. } => {
}
UnderlyingStream::None => {}
}
}
fn write(&self, write_req: Box<EncryptedWriteReq>) -> (*mut uv_write_t, i32) {
match self {
UnderlyingStream::Uv { stream } => {
if stream.is_null() {
return (std::ptr::null_mut(), UV_EBADF);
}
let mut write_req = write_req;
let data_len = write_req._data.len();
let buf = uv_buf_t {
base: write_req._data.as_mut_ptr() as *mut c_char,
len: data_len,
};
let req_ptr = &mut write_req.uv_req as *mut uv_write_t;
let _ = Box::into_raw(write_req); let ret = unsafe {
uv_compat::uv_write(req_ptr, *stream, &buf, 1, Some(enc_write_cb))
};
(req_ptr, ret)
}
UnderlyingStream::Js { .. } => {
(std::ptr::null_mut(), UV_EBADF)
}
UnderlyingStream::None => (std::ptr::null_mut(), UV_EBADF),
}
}
fn shutdown(&self) {
match self {
UnderlyingStream::Uv { stream } => {
if !stream.is_null() {
let req = Box::new(uv_compat::new_shutdown());
let req_ptr = Box::into_raw(req);
unsafe {
let ret =
uv_compat::uv_shutdown(req_ptr, *stream, Some(shutdown_cb));
if ret != 0 {
let _ = Box::from_raw(req_ptr);
}
}
}
}
UnderlyingStream::Js { .. } => {
}
UnderlyingStream::None => {}
}
}
#[allow(
dead_code,
reason = "reserved for a future native read-interception path; today TLS receives ciphertext via a JS-layer onread forwarder"
)]
fn set_read_interceptor(&self, interceptor: Option<ReadInterceptor>) {
if let UnderlyingStream::Uv { stream } = self {
LibUvStreamWrap::set_read_interceptor_for_stream(*stream, interceptor);
}
}
}
#[repr(C)]
struct EncryptedWriteReq {
uv_req: uv_write_t,
_data: Vec<u8>,
tls_wrap_inner: *mut TLSWrapInner,
has_write_callback: bool,
alive: Rc<Cell<bool>>,
}
struct TLSWrapInner {
tls_conn: Option<TlsConnection>,
kind: Kind,
enc_in: Vec<u8>,
started: bool,
established: bool,
shutdown: bool,
eof: bool,
cycling: bool,
session_was_set: bool,
has_buffered_cleartext: bool,
pending_clear_out: Vec<u8>,
pending_eof: bool,
in_dowrite: bool,
write_callback_scheduled: bool,
enc_writes_in_flight: u32,
pending_cleartext: Option<Vec<u8>>,
pending_enc_out: Vec<u8>,
underlying: UnderlyingStream,
js_handle: Option<v8::Global<v8::Object>>,
isolate: Option<v8::UnsafeRawIsolatePtr>,
stream_base_state: Option<v8::Global<v8::Int32Array>>,
onread: Option<v8::Global<v8::Function>>,
current_write_obj: Option<v8::Global<v8::Object>>,
current_write_bytes: usize,
bytes_read: u64,
bytes_written: u64,
alive: Rc<Cell<bool>>,
closing: bool,
error: Option<String>,
verify_error: VerifyErrorStore,
pending_client_config: Option<Arc<rustls::ClientConfig>>,
pending_server_name: Option<rustls::pki_types::ServerName<'static>>,
pending_server_config: Option<Arc<rustls::ServerConfig>>,
cached_loop_ptr: *mut uv_compat::uv_loop_t,
use_acceptor: bool,
acceptor: Option<rustls::server::Acceptor>,
accepted: Option<rustls::server::Accepted>,
client_hello_servername: Option<String>,
client_hello_alpn: Vec<Vec<u8>>,
user_buffer: Option<crate::ops::stream_wrap::UserBuffer>,
}
fn rustls_error_to_node_error(
e: &rustls::Error,
protocol_version: Option<rustls::ProtocolVersion>,
) -> (String, String) {
use rustls::Error as E;
match e {
E::InvalidMessage(_) => (
format!("{e} (SSL routines:ssl3_get_record:wrong version number)"),
"ERR_SSL_WRONG_VERSION_NUMBER".to_string(),
),
E::InvalidCertificate(cert_err) => {
let recorded = CURRENT_VERIFY_ERROR
.with(|c| c.borrow().clone())
.and_then(|store| {
store.lock().unwrap_or_else(|e| e.into_inner()).clone()
});
let code = recorded
.unwrap_or_else(|| cert_error_to_node_code(cert_err).to_string());
let msg = node_verify_error_message(&code)
.map(|s| s.to_string())
.unwrap_or_else(|| format!("{e}"));
(msg, code)
}
E::NoCertificatesPresented => (
format!("{e}"),
"ERR_SSL_PEER_DID_NOT_RETURN_A_CERTIFICATE".to_string(),
),
E::AlertReceived(alert) => {
use rustls::AlertDescription as AD;
let code = match *alert {
AD::HandshakeFailure => "SSLV3_ALERT_HANDSHAKE_FAILURE",
AD::BadCertificate => "SSLV3_ALERT_BAD_CERTIFICATE",
AD::UnsupportedCertificate => "SSLV3_ALERT_UNSUPPORTED_CERTIFICATE",
AD::CertificateRevoked => "SSLV3_ALERT_CERTIFICATE_REVOKED",
AD::CertificateExpired => "SSLV3_ALERT_CERTIFICATE_EXPIRED",
AD::CertificateUnknown => "SSLV3_ALERT_CERTIFICATE_UNKNOWN",
AD::IllegalParameter => "SSLV3_ALERT_ILLEGAL_PARAMETER",
AD::UnknownCA => "TLSV1_ALERT_UNKNOWN_CA",
AD::DecodeError => "SSLV3_ALERT_DECODE_ERROR",
AD::DecryptError => "SSLV3_ALERT_DECRYPT_ERROR",
AD::ProtocolVersion => "TLSV1_ALERT_PROTOCOL_VERSION",
AD::InsufficientSecurity => "TLSV1_ALERT_INSUFFICIENT_SECURITY",
AD::InternalError => "TLSV1_ALERT_INTERNAL_ERROR",
AD::InappropriateFallback => "TLSV1_ALERT_INAPPROPRIATE_FALLBACK",
AD::UserCanceled => "TLSV1_ALERT_USER_CANCELLED",
AD::NoRenegotiation => "TLSV1_ALERT_NO_RENEGOTIATION",
AD::CertificateRequired => {
if protocol_version == Some(rustls::ProtocolVersion::TLSv1_2) {
"SSLV3_ALERT_HANDSHAKE_FAILURE"
} else {
"TLSV13_ALERT_CERTIFICATE_REQUIRED"
}
}
AD::NoApplicationProtocol => "TLSV1_ALERT_NO_APPLICATION_PROTOCOL",
_ => "SSLV3_ALERT_HANDSHAKE_FAILURE",
};
(format!("{e}"), format!("ERR_SSL_{code}"))
}
E::NoApplicationProtocol => (
format!("{e}"),
"ERR_SSL_NO_APPLICATION_PROTOCOL".to_string(),
),
E::General(msg) if msg == "no server certificate chain resolved" => (
"no suitable signature algorithm".to_string(),
"ERR_SSL_NO_SUITABLE_SIGNATURE_ALGORITHM".to_string(),
),
_ => (
format!("{e}"),
"ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE".to_string(),
),
}
}
impl TLSWrapInner {
fn new(kind: Kind) -> Self {
Self {
tls_conn: None,
kind,
enc_in: Vec::with_capacity(4096),
started: false,
established: false,
shutdown: false,
eof: false,
cycling: false,
session_was_set: false,
has_buffered_cleartext: false,
pending_clear_out: Vec::new(),
pending_eof: false,
in_dowrite: false,
write_callback_scheduled: false,
enc_writes_in_flight: 0,
pending_cleartext: None,
pending_enc_out: Vec::new(),
underlying: UnderlyingStream::None,
js_handle: None,
isolate: None,
stream_base_state: None,
onread: None,
current_write_obj: None,
current_write_bytes: 0,
bytes_read: 0,
bytes_written: 0,
alive: Rc::new(Cell::new(true)),
closing: false,
error: None,
verify_error: Arc::new(std::sync::Mutex::new(None)),
pending_client_config: None,
pending_server_name: None,
pending_server_config: None,
cached_loop_ptr: std::ptr::null_mut(),
use_acceptor: false,
acceptor: None,
accepted: None,
client_hello_servername: None,
client_hello_alpn: Vec::new(),
user_buffer: None,
}
}
unsafe fn cycle(ptr: *mut TLSWrapInner) {
unsafe {
if (*ptr).cycling {
return;
}
(*ptr).cycling = true;
if (*ptr).acceptor.is_some() {
let got_hello = (*ptr).process_acceptor();
(*ptr).cycling = false;
if got_hello {
if let Some(ctx) = extract_emit_ctx(ptr) {
do_emit_client_hello(&ctx);
}
} else if let Some(ctx) = extract_emit_ctx(ptr) {
if let Some(error) = (*ptr).error.take() {
do_emit_error(
&ctx,
&error,
"ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE",
);
}
}
return;
}
let _verify_scope = VerifyErrorScope::enter((*ptr).verify_error.clone());
(*ptr).clear_in();
let result = (*ptr).clear_out_process();
let enc_action = (*ptr).enc_out_collect();
(*ptr).cycling = false;
TLSWrapInner::dispatch_clear_out_callbacks(ptr, &result);
if result.tls_error.is_some() {
return;
}
TLSWrapInner::do_enc_out_action(ptr, enc_action);
if result.handshake_done && !(*ptr).closing {
(*ptr).cycling = true;
(*ptr).clear_in();
let enc_action2 = (*ptr).enc_out_collect();
(*ptr).cycling = false;
TLSWrapInner::do_enc_out_action(ptr, enc_action2);
}
}
}
fn process_acceptor(&mut self) -> bool {
let acceptor = match self.acceptor.as_mut() {
Some(a) => a,
None => return false,
};
if !self.enc_in.is_empty() {
let mut cursor = std::io::Cursor::new(&self.enc_in[..]);
match acceptor.read_tls(&mut cursor) {
Ok(n) => {
self.enc_in.drain(..n);
}
Err(_) => {
self.error = Some("TLS acceptor read error".to_string());
return false;
}
}
}
let acceptor = self.acceptor.as_mut().unwrap();
match acceptor.accept() {
Ok(Some(accepted)) => {
let hello = accepted.client_hello();
self.client_hello_servername =
hello.server_name().map(|s| s.to_string());
self.client_hello_alpn = hello
.alpn()
.map(|iter| iter.map(|p| p.to_vec()).collect())
.unwrap_or_default();
self.acceptor = None;
self.accepted = Some(accepted);
true
}
Ok(None) => {
false
}
Err((err, _alert)) => {
self.acceptor = None;
self.error = Some(format!("TLS accept error: {err}"));
false
}
}
}
fn clear_in(&mut self) {
if !self.established || self.closing {
return;
}
let Some(ref mut conn) = self.tls_conn else {
return;
};
let Some(data) = self.pending_cleartext.take() else {
return;
};
if data.is_empty() {
return;
}
const MAX_CLEAR_IN: usize = 48 * 1024;
let feed_end = data.len().min(MAX_CLEAR_IN);
let mut offset = 0;
let mut write_error = false;
while offset < feed_end {
match conn.writer().write(&data[offset..feed_end]) {
Ok(0) => break,
Ok(n) => offset += n,
Err(e) => {
self.error = Some(format!("SSL write error: {e}"));
write_error = true;
break;
}
}
}
if offset < data.len() && !write_error {
self.pending_cleartext = Some(data[offset..].to_vec());
}
}
fn clear_out_process(&mut self) -> ClearOutResult {
let empty = ClearOutResult {
handshake_done: false,
data: Vec::new(),
got_eof: false,
got_error: false,
tls_error: None,
};
if self.eof {
return empty;
}
let Some(ref mut conn) = self.tls_conn else {
return empty;
};
let was_handshaking = conn.is_handshaking();
let mut data = Vec::new();
let mut got_eof = false;
let mut got_error = false;
let tls_error = None;
if !self.enc_in.is_empty() {
let mut total_consumed = 0usize;
loop {
let remaining = &self.enc_in[total_consumed..];
if remaining.is_empty() {
break;
}
let mut cursor = std::io::Cursor::new(remaining);
match conn.read_tls(&mut cursor) {
Ok(_) => {
let consumed = cursor.position() as usize;
if consumed == 0 {
break;
}
total_consumed += consumed;
}
Err(_) => break,
}
match conn.process_new_packets() {
Ok(io_state) => {
if io_state.peer_has_closed() {
got_eof = true;
self.eof = true;
}
}
Err(e) => {
if total_consumed > 0 {
self.enc_in.drain(..total_consumed);
}
let (error_msg, error_code) =
rustls_error_to_node_error(&e, conn.protocol_version());
self.error = Some(error_msg.clone());
self.enc_out_flush_only();
return ClearOutResult {
handshake_done: false,
data: Vec::new(),
got_eof: false,
got_error: false,
tls_error: Some((error_msg, error_code)),
};
}
}
{
let mut tmp = [0u8; CLEAR_OUT_CHUNK_SIZE];
loop {
match conn.reader().read(&mut tmp) {
Ok(0) => break,
Ok(n) => {
self.bytes_read += n as u64;
data.extend_from_slice(&tmp[..n]);
}
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => break,
Err(ref e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
self.eof = true;
got_eof = true;
break;
}
Err(_) => {
got_error = true;
break;
}
}
}
}
if got_eof || got_error {
break;
}
}
if total_consumed > 0 {
self.enc_in.drain(..total_consumed);
}
}
let is_handshaking_now = conn.is_handshaking();
let handshake_done =
was_handshaking && !is_handshaking_now && !self.established;
self.has_buffered_cleartext = false;
ClearOutResult {
handshake_done,
data,
got_eof,
got_error,
tls_error,
}
}
fn enc_out_collect(&mut self) -> EncOutAction {
let Some(ref mut conn) = self.tls_conn else {
return EncOutAction::None;
};
while conn.wants_write() {
let mut tmp = Vec::with_capacity(16384);
match conn.write_tls(&mut tmp) {
Ok(n) if n > 0 => {
self.pending_enc_out.extend_from_slice(&tmp);
}
_ => break,
}
}
if self.pending_enc_out.is_empty() {
if self.established
&& self.write_callback_scheduled
&& self.enc_writes_in_flight == 0
&& !self.in_dowrite
{
return EncOutAction::InvokeQueued(0);
}
return EncOutAction::None;
}
if self.current_write_obj.is_some() {
self.write_callback_scheduled = true;
}
if !self.underlying.is_attached() {
return EncOutAction::None;
}
match self.underlying {
UnderlyingStream::Uv { .. } => EncOutAction::WriteUv,
UnderlyingStream::Js { .. } => EncOutAction::WriteJs,
UnderlyingStream::None => EncOutAction::None,
}
}
fn enc_out_flush_only(&mut self) {
let Some(ref mut conn) = self.tls_conn else {
return;
};
while conn.wants_write() {
let mut tmp = Vec::with_capacity(16384);
match conn.write_tls(&mut tmp) {
Ok(n) if n > 0 => {
self.pending_enc_out.extend_from_slice(&tmp);
}
_ => break,
}
}
if self.pending_enc_out.is_empty() || !self.underlying.is_attached() {
return;
}
if let UnderlyingStream::Uv { .. } = self.underlying {
self.enc_out_uv();
}
}
unsafe fn dispatch_clear_out_callbacks(
ptr: *mut TLSWrapInner,
result: &ClearOutResult,
) {
unsafe {
if let Some((ref error_msg, ref error_code)) = result.tls_error {
if let Some(ctx) = extract_emit_ctx(ptr) {
do_emit_error(&ctx, error_msg, error_code);
}
return;
}
if result.handshake_done {
(*ptr).established = true;
if let Some(ctx) = extract_emit_ctx(ptr) {
do_emit_handshake_done(&ctx);
}
if !(*ptr).alive.get() {
return;
}
if (*ptr).shutdown {
if let Some(ref mut conn) = (*ptr).tls_conn {
conn.send_close_notify();
}
let enc_action = (*ptr).enc_out_collect();
TLSWrapInner::do_enc_out_action(ptr, enc_action);
(*ptr).underlying.shutdown();
}
}
if !result.data.is_empty() {
if (*ptr).onread.is_none() {
(*ptr).pending_clear_out.extend_from_slice(&result.data);
(*ptr).has_buffered_cleartext = true;
} else if let Some(ctx) = extract_emit_ctx(ptr) {
let onread = (*ptr).onread.clone();
let state = (*ptr).stream_base_state.clone();
if (*ptr).user_buffer.is_some() {
do_emit_read_through_user_buffer(
&ctx,
ptr,
onread.as_ref(),
state.as_ref(),
&result.data,
);
} else {
do_emit_read(
&ctx,
onread.as_ref(),
state.as_ref(),
result.data.len() as isize,
Some(&result.data),
);
}
}
if (*ptr).tls_conn.is_none() {
return;
}
}
if result.got_eof {
if (*ptr).onread.is_none() {
(*ptr).pending_eof = true;
(*ptr).has_buffered_cleartext = true;
} else if let Some(ctx) = extract_emit_ctx(ptr) {
let onread = (*ptr).onread.clone();
let state = (*ptr).stream_base_state.clone();
do_emit_read(
&ctx,
onread.as_ref(),
state.as_ref(),
UV_EOF as isize,
None,
);
}
} else if result.got_error
&& let Some(ctx) = extract_emit_ctx(ptr)
{
let onread = (*ptr).onread.clone();
let state = (*ptr).stream_base_state.clone();
do_emit_read(&ctx, onread.as_ref(), state.as_ref(), -1, None);
}
}
}
unsafe fn do_enc_out_action(ptr: *mut TLSWrapInner, action: EncOutAction) {
unsafe {
match action {
EncOutAction::None => {}
EncOutAction::WriteUv => {
(*ptr).enc_out_uv();
}
EncOutAction::WriteJs => {
}
EncOutAction::InvokeQueued(status) => {
if let Some((write_obj, ctx)) = prepare_invoke_queued(ptr) {
do_invoke_queued(&ctx, write_obj, status);
}
}
}
}
}
fn enc_out_uv(&mut self) {
let enc_data = std::mem::take(&mut self.pending_enc_out);
let has_write_cb = self.write_callback_scheduled;
let self_ptr = self as *mut TLSWrapInner;
let write_req = Box::new(EncryptedWriteReq {
uv_req: uv_compat::new_write(),
_data: enc_data,
tls_wrap_inner: self_ptr,
has_write_callback: has_write_cb,
alive: self.alive.clone(),
});
self.enc_writes_in_flight += 1;
let (req_ptr, ret) = self.underlying.write(write_req);
if ret != 0 {
self.enc_writes_in_flight -= 1;
let should_invoke = if !req_ptr.is_null() {
let reclaimed =
unsafe { Box::from_raw(req_ptr as *mut EncryptedWriteReq) };
if ret == UV_EBADF && !self.established {
self.pending_enc_out = reclaimed._data;
false
} else {
self.write_callback_scheduled
}
} else {
self.write_callback_scheduled
};
if should_invoke {
let ptr = self_ptr;
unsafe {
if let Some((write_obj, ctx)) = prepare_invoke_queued(ptr) {
do_invoke_queued(&ctx, write_obj, ret);
}
}
}
}
}
}
#[allow(
dead_code,
reason = "reserved for a future native read-interception path"
)]
unsafe fn tls_read_interceptor_cb(
tls_wrap: *mut std::ffi::c_void,
_stream: *mut uv_stream_t,
nread: isize,
buf: *const uv_buf_t,
) {
unsafe {
if tls_wrap.is_null() {
free_uv_buf(buf);
return;
}
let ptr = tls_wrap as *mut TLSWrapInner;
if (*ptr).eof {
free_uv_buf(buf);
return;
}
if nread < 0 {
free_uv_buf(buf);
let result = (*ptr).clear_out_process();
if nread == UV_EOF as isize {
(*ptr).eof = true;
}
if !result.data.is_empty()
&& let Some(ctx) = extract_emit_ctx(ptr)
{
let onread = (*ptr).onread.clone();
let state = (*ptr).stream_base_state.clone();
if (*ptr).user_buffer.is_some() {
do_emit_read_through_user_buffer(
&ctx,
ptr,
onread.as_ref(),
state.as_ref(),
&result.data,
);
} else {
do_emit_read(
&ctx,
onread.as_ref(),
state.as_ref(),
result.data.len() as isize,
Some(&result.data),
);
}
}
if let Some(ctx) = extract_emit_ctx(ptr) {
let onread = (*ptr).onread.clone();
let state = (*ptr).stream_base_state.clone();
do_emit_read(&ctx, onread.as_ref(), state.as_ref(), nread, None);
}
return;
}
if nread == 0 {
free_uv_buf(buf);
return;
}
let n = nread as usize;
let buf_ref = &*buf;
let slice = std::slice::from_raw_parts(buf_ref.base as *const u8, n);
(*ptr).enc_in.extend_from_slice(slice);
free_uv_buf(buf);
TLSWrapInner::cycle(ptr);
}
}
unsafe extern "C" fn shutdown_cb(
req: *mut uv_compat::uv_shutdown_t,
_status: i32,
) {
if !req.is_null() {
unsafe {
let _ = Box::from_raw(req);
}
}
}
unsafe extern "C" fn enc_write_cb(req: *mut uv_write_t, status: i32) {
unsafe {
let write_req = Box::from_raw(req as *mut EncryptedWriteReq);
if !write_req.tls_wrap_inner.is_null() && write_req.alive.get() {
let ptr = write_req.tls_wrap_inner;
(*ptr).enc_writes_in_flight =
(*ptr).enc_writes_in_flight.saturating_sub(1);
if (*ptr).enc_writes_in_flight == 0 && status >= 0 {
if (*ptr)
.pending_cleartext
.as_ref()
.is_some_and(|v| !v.is_empty())
{
(*ptr).clear_in();
}
let enc_action = (*ptr).enc_out_collect();
TLSWrapInner::do_enc_out_action(ptr, enc_action);
} else if (*ptr).enc_writes_in_flight == 0
&& (*ptr).write_callback_scheduled
{
if let Some((write_obj, ctx)) = prepare_invoke_queued(ptr) {
do_invoke_queued(&ctx, write_obj, status);
}
}
}
}
}
#[derive(CppgcInherits)]
#[cppgc_inherits_from(LibUvStreamWrap)]
#[repr(C)]
pub struct TLSWrap {
base: LibUvStreamWrap,
inner: OwnedPtr<TLSWrapInner>,
}
unsafe impl GarbageCollected for TLSWrap {
fn get_name(&self) -> &'static std::ffi::CStr {
c"TLSWrap"
}
fn trace(&self, visitor: &mut v8::cppgc::Visitor) {
self.base.trace(visitor);
}
}
impl Drop for TLSWrap {
fn drop(&mut self) {
self.teardown();
}
}
impl TLSWrap {
fn teardown(&self) {
let inner = unsafe { self.inner.as_mut() };
if inner.tls_conn.is_none() {
return;
}
inner.alive.set(false);
inner.tls_conn = None;
inner.js_handle = None;
inner.onread = None;
inner.stream_base_state = None;
inner.current_write_obj = None;
}
fn write_data(
&self,
req_wrap_obj: v8::Local<v8::Object>,
data: &[u8],
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
let byte_length = data.len();
let inner = unsafe { self.inner.as_mut() };
if inner.tls_conn.is_none() {
if !inner.started {
inner.current_write_obj = Some(v8::Global::new(scope, req_wrap_obj));
inner.current_write_bytes = byte_length;
inner.write_callback_scheduled = true;
let existing = inner.pending_cleartext.get_or_insert_with(Vec::new);
existing.extend_from_slice(data);
let state_global = &op_state.borrow::<StreamBaseState>().array;
let state_array = v8::Local::new(scope, state_global);
state_array.set_index(
scope,
StreamBaseStateFields::BytesWritten as u32,
v8::Number::new(scope, byte_length as f64).into(),
);
state_array.set_index(
scope,
StreamBaseStateFields::LastWriteWasAsync as u32,
v8::Integer::new(scope, 1).into(),
);
return 0;
}
inner.error = Some("Write after DestroySSL".to_string());
return -1;
}
inner.bytes_written += byte_length as u64;
if byte_length == 0 {
return 0;
}
inner.current_write_obj = Some(v8::Global::new(scope, req_wrap_obj));
inner.current_write_bytes = byte_length;
inner.pending_cleartext = Some(data.to_vec());
inner.in_dowrite = true;
inner.clear_in();
let enc_action = inner.enc_out_collect();
inner.in_dowrite = false;
let inner_ptr = inner as *mut TLSWrapInner;
unsafe { TLSWrapInner::do_enc_out_action(inner_ptr, enc_action) };
let state_global = &op_state.borrow::<StreamBaseState>().array;
let state_array = v8::Local::new(scope, state_global);
state_array.set_index(
scope,
StreamBaseStateFields::BytesWritten as u32,
v8::Number::new(scope, byte_length as f64).into(),
);
state_array.set_index(
scope,
StreamBaseStateFields::LastWriteWasAsync as u32,
v8::Integer::new(scope, 1).into(),
);
0
}
}
#[op2(inherit = LibUvStreamWrap)]
impl TLSWrap {
#[constructor]
#[cppgc]
fn new(
#[smi] kind: i32,
#[smi] _underlying_provider: i32,
op_state: &mut OpState,
) -> TLSWrap {
let kind = if kind == 1 {
Kind::Server
} else {
Kind::Client
};
let provider = ProviderType::TlsWrap as i32;
let base = LibUvStreamWrap::new(
HandleWrap::create(AsyncWrap::create(op_state, provider), None),
-1,
std::ptr::null(),
);
TLSWrap {
base,
inner: OwnedPtr::from_box(Box::new(TLSWrapInner::new(kind))),
}
}
#[nofast]
#[reentrant]
fn init_client_tls(
&self,
#[string] server_name: String,
context: v8::Local<v8::Object>,
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
let server_name = if server_name.is_empty() {
None
} else {
rustls::pki_types::ServerName::try_from(server_name).ok()
};
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
let client_config = match build_client_config(scope, context, op_state) {
Some((c, _)) => c,
None => return -1,
};
inner.pending_client_config = Some(Arc::new(client_config));
inner.pending_server_name = server_name;
0
}
#[nofast]
#[reentrant]
fn init_server_tls(
&self,
context: v8::Local<v8::Object>,
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
let (server_config, client_cert_verify_error) =
match build_server_config(scope, context, op_state) {
Some(c) => c,
None => {
return -1;
}
};
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
inner.pending_server_config = Some(Arc::new(server_config));
inner.verify_error = client_cert_verify_error;
0
}
#[nofast]
fn attach(
&self,
#[cppgc] tcp: &crate::ops::tcp_wrap::TCPWrap,
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
let stream = tcp.stream_ptr();
Self::do_attach_uv_stream(&self.inner, stream, scope, op_state)
}
#[nofast]
fn attach_pipe(
&self,
#[cppgc] pipe: &crate::ops::pipe_wrap::PipeWrap,
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
let stream = pipe.stream_ptr();
Self::do_attach_uv_stream(&self.inner, stream, scope, op_state)
}
#[nofast]
fn set_handle(
&self,
handle: v8::Local<v8::Object>,
scope: &mut v8::PinScope,
) {
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
inner.js_handle = Some(v8::Global::new(scope, handle));
}
#[nofast]
fn set_onread(
&self,
onread: v8::Local<v8::Function>,
scope: &mut v8::PinScope,
) {
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
inner.onread = Some(v8::Global::new(scope, onread));
}
#[fast]
#[rename("useUserBuffer")]
fn use_user_buffer_tls(
&self,
buffer: v8::Local<v8::Uint8Array>,
scope: &mut v8::PinScope,
) {
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
inner.user_buffer =
crate::ops::stream_wrap::UserBuffer::from_view(scope, buffer);
}
#[fast]
#[reentrant]
fn start(&self) -> i32 {
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
if inner.started {
if !inner.pending_enc_out.is_empty() {
let enc_action = inner.enc_out_collect();
let inner_ptr = inner as *mut TLSWrapInner;
unsafe { TLSWrapInner::do_enc_out_action(inner_ptr, enc_action) };
}
return 0;
}
inner.started = true;
match inner.kind {
Kind::Client => {
let Some(config) = inner.pending_client_config.take() else {
inner.error = Some("TLS config not initialized".to_string());
return -1;
};
let server_name = inner.pending_server_name.take();
let conn_result = match server_name {
Some(name) => rustls::ClientConnection::new(config, name),
None => {
let no_sni = rustls::pki_types::ServerName::IpAddress(
rustls::pki_types::IpAddr::from(std::net::Ipv4Addr::UNSPECIFIED),
);
rustls::ClientConnection::new(config, no_sni)
}
};
match conn_result {
Ok(conn) => {
inner.tls_conn = Some(TlsConnection::Client(conn));
}
Err(e) => {
inner.error = Some(format!("TLS connection error: {e}"));
return -1;
}
}
}
Kind::Server => {
if inner.use_acceptor {
inner.acceptor = Some(rustls::server::Acceptor::default());
} else {
let Some(config) = inner.pending_server_config.take() else {
inner.error = Some("TLS config not initialized".to_string());
return -1;
};
match rustls::ServerConnection::new(config) {
Ok(conn) => {
inner.tls_conn = Some(TlsConnection::Server(conn));
}
Err(e) => {
inner.error = Some(format!("TLS connection error: {e}"));
return -1;
}
}
}
}
}
inner.underlying.read_start();
let inner_ptr = inner as *mut TLSWrapInner;
unsafe { TLSWrapInner::cycle(inner_ptr) };
0
}
#[nofast]
#[reentrant]
fn read_start(
&self,
#[this] this: v8::Global<v8::Object>,
scope: &mut v8::PinScope,
) -> i32 {
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
let this_local = v8::Local::new(scope, &this);
let onread_key =
v8::String::new_external_onebyte_static(scope, b"onread").unwrap();
let Some(onread_val) = this_local.get(scope, onread_key.into()) else {
return UV_EBADF;
};
let Ok(onread) = v8::Local::<v8::Function>::try_from(onread_val) else {
return UV_EBADF;
};
inner.onread = Some(v8::Global::new(scope, onread));
let inner_ptr = inner as *mut TLSWrapInner;
let pending_data = std::mem::take(&mut inner.pending_clear_out);
let pending_eof = inner.pending_eof;
inner.pending_eof = false;
if (!pending_data.is_empty() || pending_eof)
&& let Some(ctx) = (unsafe { extract_emit_ctx(inner_ptr) })
{
if !pending_data.is_empty() {
let onread_clone = inner.onread.clone();
let state = inner.stream_base_state.clone();
let has_user_buffer = inner.user_buffer.is_some();
unsafe {
if has_user_buffer {
do_emit_read_through_user_buffer(
&ctx,
inner_ptr,
onread_clone.as_ref(),
state.as_ref(),
&pending_data,
);
} else {
do_emit_read(
&ctx,
onread_clone.as_ref(),
state.as_ref(),
pending_data.len() as isize,
Some(&pending_data),
);
}
}
}
if pending_eof {
let onread_clone = inner.onread.clone();
let state = inner.stream_base_state.clone();
unsafe {
do_emit_read(
&ctx,
onread_clone.as_ref(),
state.as_ref(),
UV_EOF as isize,
None,
);
}
}
}
let should_cycle;
if inner.underlying.is_attached() && inner.started {
should_cycle = !inner.enc_in.is_empty() || inner.has_buffered_cleartext;
if !matches!(inner.underlying, UnderlyingStream::Uv { .. }) {
inner.underlying.read_start();
}
} else {
should_cycle = false;
}
if should_cycle {
unsafe { TLSWrapInner::cycle(inner_ptr) };
}
0
}
#[fast]
fn read_stop(&self, _scope: &mut v8::PinScope) -> i32 {
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
inner.onread = None;
0
}
#[nofast]
fn writev(
&self,
req_wrap_obj: v8::Local<v8::Object>,
chunks: v8::Local<v8::Array>,
all_buffers: bool,
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
let mut data = Vec::new();
if all_buffers {
let len = chunks.length();
for i in 0..len {
let Some(chunk) = chunks.get_index(scope, i) else {
continue;
};
if let Ok(buf) = TryInto::<v8::Local<v8::Uint8Array>>::try_into(chunk) {
let byte_len = buf.byte_length();
let byte_off = buf.byte_offset();
let Some(ab) = buf.buffer(scope) else {
continue;
};
let Some(data_ptr) = ab.data() else {
continue;
};
let ptr = data_ptr.as_ptr() as *const u8;
let slice =
unsafe { std::slice::from_raw_parts(ptr.add(byte_off), byte_len) };
data.extend_from_slice(slice);
}
}
} else {
let len = chunks.length();
let count = len / 2;
for i in 0..count {
let Some(chunk) = chunks.get_index(scope, i * 2) else {
continue;
};
if let Ok(buf) = TryInto::<v8::Local<v8::Uint8Array>>::try_into(chunk) {
let byte_len = buf.byte_length();
let byte_off = buf.byte_offset();
let Some(ab) = buf.buffer(scope) else {
continue;
};
let Some(data_ptr) = ab.data() else {
continue;
};
let ptr = data_ptr.as_ptr() as *const u8;
let slice =
unsafe { std::slice::from_raw_parts(ptr.add(byte_off), byte_len) };
data.extend_from_slice(slice);
} else if let Ok(s) = TryInto::<v8::Local<v8::String>>::try_into(chunk)
{
let encoding_idx = i * 2 + 1;
let _ = chunks.get_index(scope, encoding_idx);
let len = s.utf8_length(scope);
let mut buf = Vec::with_capacity(len);
let written = s.write_utf8_uninit_v2(
scope,
buf.spare_capacity_mut(),
v8::WriteFlags::kReplaceInvalidUtf8,
None,
);
unsafe { buf.set_len(written) };
data.extend_from_slice(&buf);
}
}
}
self.write_data(req_wrap_obj, &data, scope, op_state)
}
#[nofast]
fn write_buffer(
&self,
req_wrap_obj: v8::Local<v8::Object>,
buffer: v8::Local<v8::Uint8Array>,
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
let byte_length = buffer.byte_length();
let byte_offset = buffer.byte_offset();
let Some(ab) = buffer.buffer(scope) else {
return -1;
};
let Some(data_ptr) = ab.data() else {
return -1;
};
let ptr = data_ptr.as_ptr() as *const u8;
let data =
unsafe { std::slice::from_raw_parts(ptr.add(byte_offset), byte_length) };
self.write_data(req_wrap_obj, data, scope, op_state)
}
#[nofast]
fn write_utf8_string(
&self,
req_wrap_obj: v8::Local<v8::Object>,
string: v8::Local<v8::String>,
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
let len = string.utf8_length(scope);
let mut buf = Vec::with_capacity(len);
let written = string.write_utf8_uninit_v2(
scope,
buf.spare_capacity_mut(),
v8::WriteFlags::kReplaceInvalidUtf8,
None,
);
unsafe { buf.set_len(written) };
self.write_data(req_wrap_obj, &buf, scope, op_state)
}
#[nofast]
fn write_ascii_string(
&self,
req_wrap_obj: v8::Local<v8::Object>,
string: v8::Local<v8::String>,
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
let len = string.utf8_length(scope);
let mut buf = Vec::with_capacity(len);
let written = string.write_utf8_uninit_v2(
scope,
buf.spare_capacity_mut(),
v8::WriteFlags::kReplaceInvalidUtf8,
None,
);
unsafe { buf.set_len(written) };
self.write_data(req_wrap_obj, &buf, scope, op_state)
}
#[nofast]
fn write_latin1_string(
&self,
req_wrap_obj: v8::Local<v8::Object>,
string: v8::Local<v8::String>,
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
let len = string.length();
let mut buf = Vec::with_capacity(len);
string.write_one_byte_uninit_v2(
scope,
0,
buf.spare_capacity_mut(),
v8::WriteFlags::empty(),
);
unsafe { buf.set_len(len) };
self.write_data(req_wrap_obj, &buf, scope, op_state)
}
#[nofast]
fn write_ucs2_string(
&self,
req_wrap_obj: v8::Local<v8::Object>,
string: v8::Local<v8::String>,
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
let len = string.length();
let mut buf16 = vec![0u16; len];
string.write_v2(scope, 0, &mut buf16, v8::WriteFlags::empty());
let buf: Vec<u8> = buf16.iter().flat_map(|&c| c.to_le_bytes()).collect();
self.write_data(req_wrap_obj, &buf, scope, op_state)
}
#[fast]
#[reentrant]
fn shutdown(
&self,
req_wrap_obj: v8::Local<v8::Object>,
scope: &mut v8::PinScope,
) -> i32 {
{
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
inner.shutdown = true;
let handshaking =
inner.tls_conn.as_ref().is_some_and(|c| c.is_handshaking());
if handshaking {
} else {
if let Some(ref mut conn) = inner.tls_conn {
conn.send_close_notify();
}
let enc_action = inner.enc_out_collect();
let inner_ptr = inner as *mut TLSWrapInner;
unsafe { TLSWrapInner::do_enc_out_action(inner_ptr, enc_action) };
inner.underlying.shutdown();
}
}
let oncomplete_key =
v8::String::new_external_onebyte_static(scope, b"oncomplete").unwrap();
if let Some(val) = req_wrap_obj.get(scope, oncomplete_key.into())
&& let Ok(func) = v8::Local::<v8::Function>::try_from(val)
{
let status = v8::Integer::new(scope, 0);
func.call(scope, req_wrap_obj.into(), &[status.into()]);
}
0
}
#[fast]
fn set_closing(&self) {
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
inner.closing = true;
}
#[nofast]
#[reentrant]
fn cancel_write(&self) {
let ptr = self.inner.as_mut_ptr();
unsafe {
if let Some((write_obj, ctx)) = prepare_invoke_queued(ptr) {
do_invoke_queued(&ctx, write_obj, UV_ECANCELED);
}
}
}
#[nofast]
fn destroy_ssl(&self) {
self.teardown();
}
#[fast]
fn get_alpn_negotiated_protocol(
&self,
out: v8::Local<v8::Object>,
scope: &mut v8::PinScope,
) -> i32 {
let inner = unsafe { &*self.inner.as_mut_ptr() };
let key =
v8::String::new_external_onebyte_static(scope, b"alpnProtocol").unwrap();
if let Some(ref conn) = inner.tls_conn
&& let Some(proto) = conn.alpn_protocol()
&& let Ok(s) = std::str::from_utf8(proto)
{
let val = v8::String::new(scope, s).unwrap();
out.set(scope, key.into(), val.into());
return 0;
}
let false_val = v8::Boolean::new(scope, false);
out.set(scope, key.into(), false_val.into());
0
}
#[fast]
fn get_protocol(
&self,
out: v8::Local<v8::Object>,
scope: &mut v8::PinScope,
) -> i32 {
let inner = unsafe { &*self.inner.as_mut_ptr() };
let key =
v8::String::new_external_onebyte_static(scope, b"protocol").unwrap();
if let Some(ref conn) = inner.tls_conn
&& let Some(version) = conn.protocol_version()
{
let name = match version {
rustls::ProtocolVersion::TLSv1_2 => "TLSv1.2",
rustls::ProtocolVersion::TLSv1_3 => "TLSv1.3",
_ => "unknown",
};
let val = v8::String::new(scope, name).unwrap();
out.set(scope, key.into(), val.into());
return 0;
}
-1
}
#[fast]
fn get_cipher(
&self,
out: v8::Local<v8::Object>,
scope: &mut v8::PinScope,
) -> i32 {
let inner = unsafe { &*self.inner.as_mut_ptr() };
if let Some(ref conn) = inner.tls_conn
&& let Some(suite) = conn.negotiated_cipher_suite()
{
let (openssl_name, iana_name) = cipher_suite_to_names(suite.suite());
let name_key =
v8::String::new_external_onebyte_static(scope, b"name").unwrap();
let name_str = v8::String::new(scope, openssl_name).unwrap();
out.set(scope, name_key.into(), name_str.into());
let standard_name_key =
v8::String::new_external_onebyte_static(scope, b"standardName")
.unwrap();
let standard_name_str = v8::String::new(scope, iana_name).unwrap();
out.set(scope, standard_name_key.into(), standard_name_str.into());
if let Some(version) = conn.protocol_version() {
let version_key =
v8::String::new_external_onebyte_static(scope, b"version").unwrap();
let version_str = match version {
rustls::ProtocolVersion::TLSv1_2 => "TLSv1.2",
rustls::ProtocolVersion::TLSv1_3 => "TLSv1.3",
_ => "unknown",
};
let v = v8::String::new(scope, version_str).unwrap();
out.set(scope, version_key.into(), v.into());
}
return 0;
}
-1
}
#[serde]
fn get_peer_certificate_chain(&self) -> Option<PeerCertificateChain> {
let inner = unsafe { &*self.inner.as_mut_ptr() };
let conn = inner.tls_conn.as_ref()?;
let certs = conn.peer_certificates()?;
if certs.is_empty() {
return None;
}
Some(PeerCertificateChain {
certificates: certs
.iter()
.map(|cert| cert.as_ref().to_vec().into())
.collect(),
})
}
#[serde]
fn get_peer_certificate(&self, detailed: bool) -> Option<CertificateObject> {
let inner = unsafe { &*self.inner.as_mut_ptr() };
let conn = inner.tls_conn.as_ref()?;
let certs = conn.peer_certificates()?;
let cert = certs.first()?;
let cert = Certificate::from_der(cert.as_ref()).ok()?;
cert.to_object(detailed).ok()
}
#[buffer]
fn get_finished(&self) -> Option<Box<[u8]>> {
let inner = unsafe { &*self.inner.as_mut_ptr() };
if !inner.established {
return None;
}
let conn = inner.tls_conn.as_ref()?;
let mut output = vec![0u8; 32];
let label = match inner.kind {
Kind::Client => b"EXPORTER_DENO_TLS_FINISHED_CLIENT" as &[u8],
Kind::Server => b"EXPORTER_DENO_TLS_FINISHED_SERVER" as &[u8],
};
conn.export_keying_material(&mut output, label, None).ok()?;
Some(output.into_boxed_slice())
}
#[buffer]
fn get_peer_finished(&self) -> Option<Box<[u8]>> {
let inner = unsafe { &*self.inner.as_mut_ptr() };
if !inner.established {
return None;
}
let conn = inner.tls_conn.as_ref()?;
let mut output = vec![0u8; 32];
let label = match inner.kind {
Kind::Client => b"EXPORTER_DENO_TLS_FINISHED_SERVER" as &[u8],
Kind::Server => b"EXPORTER_DENO_TLS_FINISHED_CLIENT" as &[u8],
};
conn.export_keying_material(&mut output, label, None).ok()?;
Some(output.into_boxed_slice())
}
#[fast]
fn is_established(&self) -> bool {
unsafe { &*self.inner.as_mut_ptr() }.established
}
#[fast]
fn get_bytes_read(&self) -> f64 {
unsafe { &*self.inner.as_mut_ptr() }.bytes_read as f64
}
#[fast]
fn get_bytes_written(&self) -> f64 {
unsafe { &*self.inner.as_mut_ptr() }.bytes_written as f64
}
#[nofast]
#[reentrant]
fn set_alpn_protocols(
&self,
protocols: v8::Local<v8::Value>,
scope: &mut v8::PinScope,
) {
let mut alpn = Vec::new();
if let Ok(arr) = v8::Local::<v8::Array>::try_from(protocols) {
for i in 0..arr.length() {
if let Some(val) = arr.get_index(scope, i)
&& let Ok(s) = v8::Local::<v8::String>::try_from(val)
{
let len = s.utf8_length(scope);
let mut buf = vec![0u8; len];
s.write_utf8_v2(scope, &mut buf, v8::WriteFlags::default(), None);
alpn.push(buf);
}
}
} else if let Ok(uint8) = v8::Local::<v8::Uint8Array>::try_from(protocols) {
let len = uint8.byte_length();
let mut data = vec![0u8; len];
uint8.copy_contents(&mut data);
let mut i = 0;
while i < data.len() {
let plen = data[i] as usize;
i += 1;
if i + plen > data.len() {
break;
}
alpn.push(data[i..i + plen].to_vec());
i += plen;
}
}
if alpn.is_empty() {
return;
}
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
if let Some(ref config) = inner.pending_client_config {
let mut new_config = rustls::ClientConfig::clone(config);
new_config.alpn_protocols = alpn.clone();
inner.pending_client_config = Some(Arc::new(new_config));
}
if let Some(ref config) = inner.pending_server_config {
let mut new_config = rustls::ServerConfig::clone(config);
new_config.alpn_protocols = alpn;
inner.pending_server_config = Some(Arc::new(new_config));
}
}
#[fast]
fn set_servername(&self, #[string] name: &str) {
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
if !inner.started
&& let Ok(server_name) =
rustls::pki_types::ServerName::try_from(name.to_string())
{
inner.pending_server_name = Some(server_name);
}
}
#[fast]
fn enable_client_hello_cb(&self) {
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
inner.use_acceptor = true;
}
#[string]
fn get_servername(&self) -> Option<String> {
let inner = unsafe { &*self.inner.as_mut_ptr() };
if let Some(ref conn) = inner.tls_conn
&& let Some(name) = conn.server_name()
{
return Some(name.to_string());
}
inner.client_hello_servername.clone()
}
#[serde]
fn get_client_hello_alpn(&self) -> Vec<String> {
let inner = unsafe { &*self.inner.as_mut_ptr() };
inner
.client_hello_alpn
.iter()
.filter_map(|p| std::str::from_utf8(p).ok().map(|s| s.to_string()))
.collect()
}
#[nofast]
#[reentrant]
fn finish_accept(
&self,
context: v8::Local<v8::Object>,
alpn_protocol: v8::Local<v8::Value>,
scope: &mut v8::PinScope,
) -> i32 {
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
let accepted = match inner.accepted.take() {
Some(a) => a,
None => {
inner.error = Some("No pending Accepted".to_string());
return -1;
}
};
let op_state_rc = deno_core::JsRuntime::op_state_from(scope);
let (mut server_config, client_cert_verify_error) = {
let mut op_state = op_state_rc.borrow_mut();
match build_server_config(scope, context, &mut op_state) {
Some(c) => c,
None => {
inner.error = Some("Failed to build server config".to_string());
return -1;
}
}
};
inner.verify_error = client_cert_verify_error;
if let Ok(s) = v8::Local::<v8::String>::try_from(alpn_protocol) {
let len = s.utf8_length(scope);
let mut buf = vec![0u8; len];
s.write_utf8_v2(scope, &mut buf, v8::WriteFlags::default(), None);
server_config.alpn_protocols = vec![buf];
} else if let Some(ref config) = inner.pending_server_config {
server_config.alpn_protocols = config.alpn_protocols.clone();
}
match accepted.into_connection(Arc::new(server_config)) {
Ok(conn) => {
inner.tls_conn = Some(TlsConnection::Server(conn));
}
Err((e, mut alert)) => {
let (error_msg, error_code) = rustls_error_to_node_error(&e, None);
inner.error = Some(error_msg.clone());
let mut alert_bytes = Vec::new();
let _ = alert.write_all(&mut alert_bytes);
if !alert_bytes.is_empty() {
inner.pending_enc_out.extend_from_slice(&alert_bytes);
if inner.underlying.is_attached()
&& let UnderlyingStream::Uv { .. } = inner.underlying
{
inner.enc_out_uv();
}
}
let inner_ptr = inner as *mut TLSWrapInner;
unsafe {
if let Some(ctx) = extract_emit_ctx(inner_ptr) {
do_emit_error(&ctx, &error_msg, &error_code);
}
}
return -1;
}
}
let inner_ptr = inner as *mut TLSWrapInner;
unsafe { TLSWrapInner::cycle(inner_ptr) };
0
}
#[fast]
#[reentrant]
fn receive(&self, #[buffer] data: &[u8]) {
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
inner.enc_in.extend_from_slice(data);
let inner_ptr = inner as *mut TLSWrapInner;
unsafe { TLSWrapInner::cycle(inner_ptr) };
}
#[string]
fn verify_error(&self) -> String {
let inner = unsafe { &*self.inner.as_mut_ptr() };
inner
.verify_error
.lock()
.unwrap_or_else(|e| e.into_inner())
.clone()
.unwrap_or_default()
}
#[fast]
fn set_verify_mode(&self, _request_cert: bool, _reject_unauthorized: bool) {
}
#[fast]
fn enable_session_callbacks(&self) {
}
#[fast]
fn set_session(&self, #[buffer] _session: &[u8]) {
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
inner.session_was_set = true;
}
#[fast]
fn is_session_reused(&self) -> bool {
let inner = unsafe { &*self.inner.as_mut_ptr() };
if let Some(ref conn) = inner.tls_conn {
matches!(conn.handshake_kind(), Some(rustls::HandshakeKind::Resumed))
} else {
false
}
}
#[nofast]
fn attach_js_stream(
&self,
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
let loop_ = &**op_state.borrow::<Box<uv_compat::uv_loop_t>>()
as *const uv_compat::uv_loop_t
as *mut uv_compat::uv_loop_t;
inner.underlying = UnderlyingStream::Js { loop_ptr: loop_ };
inner.isolate = Some(unsafe { scope.as_raw_isolate_ptr() });
let state_global = &op_state.borrow::<StreamBaseState>().array;
inner.stream_base_state =
Some(v8::Global::new(scope, v8::Local::new(scope, state_global)));
0
}
#[fast]
#[reentrant]
fn read_buffer(&self, #[buffer] data: &[u8]) {
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
inner.enc_in.extend_from_slice(data);
let inner_ptr = inner as *mut TLSWrapInner;
unsafe { TLSWrapInner::cycle(inner_ptr) };
}
#[buffer]
fn drain_enc_out(&self) -> Box<[u8]> {
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
if !matches!(inner.underlying, UnderlyingStream::Js { .. })
|| inner.pending_enc_out.is_empty()
{
return Box::new([]);
}
std::mem::take(&mut inner.pending_enc_out).into_boxed_slice()
}
#[fast]
#[reentrant]
fn emit_eof(&self) {
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
if inner.eof {
return;
}
let result = inner.clear_out_process();
inner.eof = true;
let inner_ptr = inner as *mut TLSWrapInner;
unsafe {
TLSWrapInner::dispatch_clear_out_callbacks(inner_ptr, &result);
if (*inner_ptr).onread.is_none() {
(*inner_ptr).pending_eof = true;
(*inner_ptr).has_buffered_cleartext = true;
} else if let Some(ctx) = extract_emit_ctx(inner_ptr) {
let onread = (*inner_ptr).onread.clone();
let state = (*inner_ptr).stream_base_state.clone();
do_emit_read(
&ctx,
onread.as_ref(),
state.as_ref(),
deno_core::uv_compat::UV_EOF as isize,
None,
);
}
}
}
}
fn get_js_string(
scope: &mut v8::PinScope,
obj: v8::Local<v8::Object>,
key: &str,
) -> Option<String> {
let k = v8::String::new(scope, key).unwrap();
obj.get(scope, k.into()).and_then(|v| {
if v.is_undefined() || v.is_null() {
None
} else {
v.to_string(scope).map(|s| s.to_rust_string_lossy(scope))
}
})
}
fn get_js_bool(
scope: &mut v8::PinScope,
obj: v8::Local<v8::Object>,
key: &str,
default: bool,
) -> bool {
let k = v8::String::new(scope, key).unwrap();
obj
.get(scope, k.into())
.and_then(|v| {
if v.is_undefined() || v.is_null() {
None
} else {
Some(v.boolean_value(scope))
}
})
.unwrap_or(default)
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum ProtocolVersionSelection {
Default,
Tls12Only,
Tls13Only,
Unsupported,
}
fn protocol_version_number(version: &str) -> Option<i32> {
match version {
"TLSv1" => Some(0x0301),
"TLSv1.1" => Some(0x0302),
"TLSv1.2" => Some(0x0303),
"TLSv1.3" => Some(0x0304),
_ => None,
}
}
fn get_protocol_versions(
scope: &mut v8::PinScope,
context: v8::Local<v8::Object>,
) -> ProtocolVersionSelection {
let min_version = get_js_string(scope, context, "minVersion")
.unwrap_or_else(|| "TLSv1.2".to_string());
let max_version = get_js_string(scope, context, "maxVersion")
.unwrap_or_else(|| "TLSv1.3".to_string());
let Some(min) = protocol_version_number(&min_version) else {
return ProtocolVersionSelection::Default;
};
let Some(max) = protocol_version_number(&max_version) else {
return ProtocolVersionSelection::Default;
};
let allow_tls12 = min <= 0x0303 && max >= 0x0303;
let allow_tls13 = min <= 0x0304 && max >= 0x0304;
match (allow_tls12, allow_tls13) {
(true, true) => ProtocolVersionSelection::Default,
(true, false) => ProtocolVersionSelection::Tls12Only,
(false, true) => ProtocolVersionSelection::Tls13Only,
(false, false) => ProtocolVersionSelection::Unsupported,
}
}
type VerifyErrorStore = Arc<std::sync::Mutex<Option<String>>>;
thread_local! {
static CURRENT_VERIFY_ERROR: std::cell::RefCell<Option<VerifyErrorStore>> =
const { std::cell::RefCell::new(None) };
}
struct VerifyErrorScope {
prev: Option<VerifyErrorStore>,
}
impl VerifyErrorScope {
fn enter(store: VerifyErrorStore) -> Self {
let prev = CURRENT_VERIFY_ERROR.with(|c| c.borrow_mut().replace(store));
VerifyErrorScope { prev }
}
}
impl Drop for VerifyErrorScope {
fn drop(&mut self) {
let prev = self.prev.take();
CURRENT_VERIFY_ERROR.with(|c| *c.borrow_mut() = prev);
}
}
fn store_verify_error(fallback: &VerifyErrorStore, code: String) {
let stored = CURRENT_VERIFY_ERROR.with(|c| c.borrow().clone());
let target = stored.as_ref().unwrap_or(fallback);
*target.lock().unwrap_or_else(|e| e.into_inner()) = Some(code);
}
#[derive(Debug)]
struct NodeServerCertVerifier {
inner: Arc<rustls::client::WebPkiServerVerifier>,
verify_error: VerifyErrorStore,
empty_explicit_ca: bool,
root_cert_ders: Vec<Vec<u8>>,
strict_verify: bool,
}
fn cipher_suite_to_names(
suite: rustls::CipherSuite,
) -> (&'static str, &'static str) {
use rustls::CipherSuite as CS;
match suite {
CS::TLS13_AES_128_GCM_SHA256 => {
("TLS_AES_128_GCM_SHA256", "TLS_AES_128_GCM_SHA256")
}
CS::TLS13_AES_256_GCM_SHA384 => {
("TLS_AES_256_GCM_SHA384", "TLS_AES_256_GCM_SHA384")
}
CS::TLS13_CHACHA20_POLY1305_SHA256 => (
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_CHACHA20_POLY1305_SHA256",
),
CS::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 => (
"ECDHE-RSA-AES128-GCM-SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
),
CS::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 => (
"ECDHE-RSA-AES256-GCM-SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
),
CS::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => (
"ECDHE-RSA-CHACHA20-POLY1305",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
),
CS::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 => (
"ECDHE-ECDSA-AES128-GCM-SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
),
CS::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 => (
"ECDHE-ECDSA-AES256-GCM-SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
),
CS::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 => (
"ECDHE-ECDSA-CHACHA20-POLY1305",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
),
_ => {
("unknown", "unknown")
}
}
}
fn filter_unsupported_cert_version(
result: Result<
rustls::client::danger::HandshakeSignatureValid,
rustls::Error,
>,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
match result {
Err(rustls::Error::InvalidCertificate(
rustls::CertificateError::Other(ref other),
)) if other
.0
.downcast_ref::<webpki::Error>()
.is_some_and(|e| matches!(e, webpki::Error::UnsupportedCertVersion)) =>
{
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
Err(rustls::Error::InvalidCertificate(
rustls::CertificateError::BadEncoding,
)) => Ok(rustls::client::danger::HandshakeSignatureValid::assertion()),
other => other,
}
}
fn der_read_element(data: &[u8]) -> Option<(&[u8], &[u8])> {
if data.is_empty() {
return None;
}
let len_start = 1;
let first_len = *data.get(len_start)?;
let (content_len, header_len) = if first_len < 0x80 {
(first_len as usize, 2)
} else {
let num_bytes = (first_len & 0x7F) as usize;
if num_bytes == 0 || num_bytes > 4 || data.len() < 2 + num_bytes {
return None;
}
let mut len = 0usize;
for i in 0..num_bytes {
len = (len << 8) | (data[2 + i] as usize);
}
(len, 2 + num_bytes)
};
let total = header_len + content_len;
if data.len() < total {
return None;
}
Some((&data[..total], &data[total..]))
}
fn der_skip_element(data: &[u8]) -> Option<&[u8]> {
der_read_element(data).map(|(_, rest)| rest)
}
fn extract_issuer_and_subject(cert_der: &[u8]) -> Option<(&[u8], &[u8])> {
let (cert_elem, _) = der_read_element(cert_der)?;
let tbs_content = &cert_elem[cert_elem.len() - der_content_len(cert_elem)?..];
let (tbs_elem, _) = der_read_element(tbs_content)?;
let mut pos = &tbs_elem[tbs_elem.len() - der_content_len(tbs_elem)?..];
if pos.first() == Some(&0xA0) {
pos = der_skip_element(pos)?;
}
pos = der_skip_element(pos)?;
pos = der_skip_element(pos)?;
let (issuer, pos) = der_read_element(pos)?;
let pos = der_skip_element(pos)?;
let (subject, _) = der_read_element(pos)?;
Some((issuer, subject))
}
fn der_content_len(element: &[u8]) -> Option<usize> {
let first_len = *element.get(1)?;
if first_len < 0x80 {
Some(first_len as usize)
} else {
let num_bytes = (first_len & 0x7F) as usize;
let mut len = 0usize;
for i in 0..num_bytes {
len = (len << 8) | (*element.get(2 + i)? as usize);
}
Some(len)
}
}
fn is_self_signed(cert_der: &[u8]) -> bool {
extract_issuer_and_subject(cert_der)
.is_some_and(|(issuer, subject)| issuer == subject)
}
fn verify_chain_structure(
end_entity: &[u8],
intermediates: &[rustls::pki_types::CertificateDer<'_>],
root_cert_ders: &[Vec<u8>],
) -> Result<(), &'static str> {
let ee = extract_issuer_and_subject(end_entity)
.ok_or("UNABLE_TO_VERIFY_LEAF_SIGNATURE")?;
let inter: Vec<_> = intermediates
.iter()
.filter_map(|c| extract_issuer_and_subject(c.as_ref()))
.collect();
let roots: Vec<_> = root_cert_ders
.iter()
.filter_map(|c| extract_issuer_and_subject(c))
.collect();
let mut current_issuer = ee.0;
let end_entity_subject = ee.1;
for _ in 0..(intermediates.len() + 2) {
if roots.iter().any(|(_, subject)| *subject == current_issuer) {
return Ok(()); }
if let Some((inter_issuer, _)) =
inter.iter().find(|(_, subject)| *subject == current_issuer)
{
if *inter_issuer == current_issuer {
return Err("SELF_SIGNED_CERT_IN_CHAIN");
}
current_issuer = inter_issuer;
} else {
break;
}
}
if current_issuer == end_entity_subject {
Err("DEPTH_ZERO_SELF_SIGNED_CERT")
} else if intermediates.is_empty() {
Err("UNABLE_TO_VERIFY_LEAF_SIGNATURE")
} else {
Err("UNABLE_TO_GET_ISSUER_CERT_LOCALLY")
}
}
fn node_verify_error_message(code: &str) -> Option<&'static str> {
match code {
"CERT_HAS_EXPIRED" => Some("certificate has expired"),
"CERT_NOT_YET_VALID" => Some("certificate is not yet valid"),
"DEPTH_ZERO_SELF_SIGNED_CERT" => Some("self-signed certificate"),
"SELF_SIGNED_CERT_IN_CHAIN" => {
Some("self-signed certificate in certificate chain")
}
"UNABLE_TO_GET_ISSUER_CERT" => Some("unable to get issuer certificate"),
"UNABLE_TO_GET_ISSUER_CERT_LOCALLY" => {
Some("unable to get local issuer certificate")
}
"UNABLE_TO_VERIFY_LEAF_SIGNATURE" => {
Some("unable to verify the first certificate")
}
_ => None,
}
}
fn cert_error_to_node_code(err: &rustls::CertificateError) -> &'static str {
use rustls::CertificateError as CE;
match err {
CE::UnknownIssuer => "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
CE::NotValidYet => "CERT_NOT_YET_VALID",
CE::Expired => "CERT_HAS_EXPIRED",
CE::Revoked => "CERT_REVOKED",
CE::NotValidForName | CE::NotValidForNameContext { .. } => {
"ERR_TLS_CERT_ALTNAME_INVALID"
}
CE::InvalidPurpose => "INVALID_PURPOSE",
CE::Other(other) => {
let msg = format!("{other}");
if msg.contains("SelfSigned") {
"DEPTH_ZERO_SELF_SIGNED_CERT"
} else if msg.contains("CaUsedAsEndEntity") {
"DEPTH_ZERO_SELF_SIGNED_CERT"
} else {
"UNABLE_TO_VERIFY_LEAF_SIGNATURE"
}
}
_ => "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
}
}
impl NodeServerCertVerifier {
fn record_or_fail(
&self,
err: rustls::Error,
code: String,
) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
store_verify_error(&self.verify_error, code);
if self.strict_verify {
Err(err)
} else {
Ok(rustls::client::danger::ServerCertVerified::assertion())
}
}
}
impl rustls::client::danger::ServerCertVerifier for NodeServerCertVerifier {
fn verify_server_cert(
&self,
end_entity: &rustls::pki_types::CertificateDer<'_>,
intermediates: &[rustls::pki_types::CertificateDer<'_>],
server_name: &rustls::pki_types::ServerName<'_>,
ocsp: &[u8],
now: rustls::pki_types::UnixTime,
) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
match self.inner.verify_server_cert(
end_entity,
intermediates,
server_name,
ocsp,
now,
) {
Ok(v) => {
if self.empty_explicit_ca {
let code = verify_chain_structure(
end_entity.as_ref(),
intermediates,
&self.root_cert_ders,
)
.err()
.unwrap_or("UNABLE_TO_VERIFY_LEAF_SIGNATURE");
self.record_or_fail(
rustls::Error::InvalidCertificate(
rustls::CertificateError::UnknownIssuer,
),
code.to_string(),
)
} else {
Ok(v)
}
}
Err(rustls::Error::InvalidCertificate(ref cert_error)) => {
if matches!(
cert_error,
rustls::CertificateError::NotValidForName
| rustls::CertificateError::NotValidForNameContext { .. }
) {
return Ok(rustls::client::danger::ServerCertVerified::assertion());
}
let is_v1_error = matches!(
cert_error,
rustls::CertificateError::BadEncoding
) || matches!(
cert_error,
rustls::CertificateError::Other(other) if other
.0
.downcast_ref::<webpki::Error>()
.is_some_and(|e| matches!(e, webpki::Error::UnsupportedCertVersion))
);
if is_v1_error {
match verify_chain_structure(
end_entity.as_ref(),
intermediates,
&self.root_cert_ders,
) {
Ok(()) => {
return Ok(
rustls::client::danger::ServerCertVerified::assertion(),
);
}
Err(code) => {
return self.record_or_fail(
rustls::Error::InvalidCertificate(cert_error.clone()),
code.to_string(),
);
}
}
}
if matches!(cert_error, rustls::CertificateError::UnknownIssuer) {
let code = verify_chain_structure(
end_entity.as_ref(),
intermediates,
&self.root_cert_ders,
)
.err()
.unwrap_or("UNABLE_TO_VERIFY_LEAF_SIGNATURE");
return self.record_or_fail(
rustls::Error::InvalidCertificate(cert_error.clone()),
code.to_string(),
);
}
if let rustls::CertificateError::Other(other) = cert_error
&& let Some(webpki_err) = other.0.downcast_ref::<webpki::Error>()
{
if matches!(webpki_err, webpki::Error::CaUsedAsEndEntity) {
let ee_bytes: &[u8] = end_entity.as_ref();
let is_trusted =
self.root_cert_ders.iter().any(|r| r.as_slice() == ee_bytes);
if is_trusted {
return Ok(
rustls::client::danger::ServerCertVerified::assertion(),
);
}
}
}
let code = cert_error_to_node_code(cert_error);
self.record_or_fail(
rustls::Error::InvalidCertificate(cert_error.clone()),
code.to_string(),
)
}
Err(e) => Err(e),
}
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &rustls::pki_types::CertificateDer<'_>,
dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error>
{
filter_unsupported_cert_version(
self.inner.verify_tls12_signature(message, cert, dss),
)
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &rustls::pki_types::CertificateDer<'_>,
dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error>
{
filter_unsupported_cert_version(
self.inner.verify_tls13_signature(message, cert, dss),
)
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
self.inner.supported_verify_schemes()
}
}
impl TLSWrap {
fn do_attach_uv_stream(
inner_ptr: &OwnedPtr<TLSWrapInner>,
stream: *mut uv_compat::uv_stream_t,
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
if stream.is_null() {
return UV_EBADF;
}
let inner = unsafe { &mut *inner_ptr.as_mut_ptr() };
inner.underlying = UnderlyingStream::Uv { stream };
inner.isolate = Some(unsafe { scope.as_raw_isolate_ptr() });
inner.cached_loop_ptr = unsafe { (*stream).loop_ };
let state_global = &op_state.borrow::<StreamBaseState>().array;
inner.stream_base_state =
Some(v8::Global::new(scope, v8::Local::new(scope, state_global)));
0
}
}
fn normalize_pem_headers(pem: &[u8]) -> std::borrow::Cow<'_, [u8]> {
let needs_rewrite = pem
.windows(b"TRUSTED CERTIFICATE".len())
.any(|w| w == b"TRUSTED CERTIFICATE")
|| pem
.windows(b"X509 CERTIFICATE".len())
.any(|w| w == b"X509 CERTIFICATE");
if !needs_rewrite {
return std::borrow::Cow::Borrowed(pem);
}
let s = String::from_utf8_lossy(pem);
let s = s
.replace("TRUSTED CERTIFICATE", "CERTIFICATE")
.replace("X509 CERTIFICATE", "CERTIFICATE");
std::borrow::Cow::Owned(s.into_bytes())
}
fn build_client_config(
scope: &mut v8::PinScope,
context: v8::Local<v8::Object>,
op_state: &mut OpState,
) -> Option<(rustls::ClientConfig, VerifyErrorStore)> {
use deno_net::DefaultTlsOptions;
use deno_tls::TlsKeys;
use deno_tls::TlsKeysHolder;
let reject_unauthorized =
get_js_bool(scope, context, "rejectUnauthorized", true);
let use_default_ca = get_js_bool(scope, context, "useDefaultCA", true);
let protocol_versions = match get_protocol_versions(scope, context) {
ProtocolVersionSelection::Default => {
&[&rustls::version::TLS13, &rustls::version::TLS12][..]
}
ProtocolVersionSelection::Tls12Only => &[&rustls::version::TLS12][..],
ProtocolVersionSelection::Tls13Only => &[&rustls::version::TLS13][..],
ProtocolVersionSelection::Unsupported => return None,
};
let mut ca_certs = Vec::new();
let ca_key = v8::String::new(scope, "ca").unwrap();
if let Some(ca_val) = context.get(scope, ca_key.into()) {
if let Ok(arr) = v8::Local::<v8::Array>::try_from(ca_val) {
for i in 0..arr.length() {
if let Some(v) = arr.get_index(scope, i)
&& let Some(s) = v.to_string(scope)
{
ca_certs.push(s.to_rust_string_lossy(scope).into_bytes());
}
}
} else if !ca_val.is_undefined()
&& !ca_val.is_null()
&& let Some(s) = ca_val.to_string(scope)
{
ca_certs.push(s.to_rust_string_lossy(scope).into_bytes());
}
}
let mut root_cert_store = op_state
.borrow::<DefaultTlsOptions>()
.root_cert_store()
.ok()
.flatten();
if use_default_ca
&& let Some(node_tls_state) = op_state.try_borrow::<NodeTlsState>()
&& let Some(custom_ca_certs) = &node_tls_state.custom_ca_certs
{
root_cert_store = Some(rustls::RootCertStore::empty());
ca_certs
.extend(custom_ca_certs.iter().map(|cert| cert.clone().into_bytes()));
} else if !use_default_ca {
root_cert_store = Some(rustls::RootCertStore::empty());
}
let cert_str = get_js_string(scope, context, "cert");
let key_str = get_js_string(scope, context, "key");
let tls_keys = if let (Some(cert), Some(key)) = (cert_str, key_str) {
let certs: Vec<_> =
rustls_pemfile::certs(&mut std::io::BufReader::new(cert.as_bytes()))
.filter_map(|r| r.ok())
.collect();
let private_key =
rustls_pemfile::private_key(&mut std::io::BufReader::new(key.as_bytes()))
.ok()
.flatten();
if let Some(private_key) = private_key {
TlsKeysHolder::from(TlsKeys::Static(deno_tls::TlsKey(certs, private_key)))
} else {
TlsKeysHolder::from(TlsKeys::Null)
}
} else {
TlsKeysHolder::from(TlsKeys::Null)
};
let mut root_cert_store =
root_cert_store.unwrap_or_else(deno_tls::create_default_root_cert_store);
let empty_explicit_ca = !use_default_ca && ca_certs.is_empty();
let mut root_cert_ders: Vec<Vec<u8>> = Vec::new();
for cert in &ca_certs {
let normalized = normalize_pem_headers(cert);
let reader =
&mut std::io::BufReader::new(std::io::Cursor::new(normalized.as_ref()));
for parsed in rustls_pemfile::certs(reader) {
match parsed {
Ok(cert) => {
root_cert_ders.push(cert.as_ref().to_vec());
if let Err(e) = root_cert_store.add(cert) {
log::warn!("TLSWrap: ignoring invalid CA certificate: {e}");
}
}
Err(e) => {
log::warn!("TLSWrap: failed to parse CA PEM entry: {e}");
}
}
}
}
let maybe_cert_chain_and_key = tls_keys.take();
let is_default_path = ca_certs.is_empty()
&& use_default_ca
&& op_state
.try_borrow::<NodeTlsState>()
.is_none_or(|s| s.custom_ca_certs.is_none())
&& matches!(maybe_cert_chain_and_key, TlsKeys::Null);
let config_builder =
rustls::ClientConfig::builder_with_protocol_versions(protocol_versions)
.with_root_certificates(root_cert_store.clone());
let mut config = match maybe_cert_chain_and_key {
TlsKeys::Static(deno_tls::TlsKey(cert_chain, private_key)) => {
let provider = config_builder.crypto_provider().clone();
let signing_key = provider
.key_provider
.load_private_key(private_key.clone_key())
.ok()?;
let certified_key =
rustls::sign::CertifiedKey::new(cert_chain, signing_key);
match certified_key.keys_match() {
Ok(()) => {}
Err(rustls::Error::InvalidCertificate(
rustls::CertificateError::Other(ref other),
)) if other.0.downcast_ref::<webpki::Error>().is_some_and(|e| {
matches!(e, webpki::Error::UnsupportedCertVersion)
}) => {}
Err(_) => return None,
}
let resolver =
Arc::new(StaticClientCertResolver(Arc::new(certified_key)));
let mut cfg = config_builder.with_no_client_auth();
cfg.client_auth_cert_resolver = resolver;
cfg
}
TlsKeys::Null => config_builder.with_no_client_auth(),
TlsKeys::Resolver(_) => return None,
};
if let Some(node_tls_state) = op_state.try_borrow::<NodeTlsState>() {
let store = if reject_unauthorized {
node_tls_state.client_session_store.clone()
} else {
node_tls_state.client_session_store_insecure.clone()
};
config.resumption = rustls::client::Resumption::store(store);
}
let (final_verify_error, verifier_arc): (
VerifyErrorStore,
Option<Arc<dyn rustls::client::danger::ServerCertVerifier>>,
) = if is_default_path {
let state = op_state.borrow_mut::<NodeTlsState>();
let cached_verifier = if reject_unauthorized {
&mut state.cached_default_verifier
} else {
&mut state.cached_insecure_verifier
};
if let Some((v, e)) = cached_verifier.clone() {
(e, Some(v))
} else {
let verifier_result = rustls::client::WebPkiServerVerifier::builder(
Arc::new(root_cert_store),
)
.build();
match verifier_result {
Ok(inner) => {
let store: VerifyErrorStore = Default::default();
let v: Arc<dyn rustls::client::danger::ServerCertVerifier> =
Arc::new(NodeServerCertVerifier {
inner,
verify_error: store.clone(),
empty_explicit_ca: false,
root_cert_ders,
strict_verify: reject_unauthorized,
});
*cached_verifier = Some((v.clone(), store.clone()));
(store, Some(v))
}
Err(_) => (Default::default(), None),
}
}
} else {
let store: VerifyErrorStore = Default::default();
let verifier_root_store = if empty_explicit_ca {
deno_tls::create_default_root_cert_store()
} else {
root_cert_store.clone()
};
let verifier_result = rustls::client::WebPkiServerVerifier::builder(
Arc::new(verifier_root_store),
)
.build();
let v: Option<Arc<dyn rustls::client::danger::ServerCertVerifier>> =
verifier_result.ok().map(|inner| {
Arc::new(NodeServerCertVerifier {
inner,
verify_error: store.clone(),
empty_explicit_ca,
root_cert_ders,
strict_verify: reject_unauthorized,
}) as Arc<dyn rustls::client::danger::ServerCertVerifier>
});
(store, v)
};
if let Some(v) = verifier_arc {
config.dangerous().set_certificate_verifier(v);
}
if is_default_path {
let state = op_state.borrow_mut::<NodeTlsState>();
let cached_no_client_auth = if reject_unauthorized {
&mut state.cached_no_client_auth
} else {
&mut state.cached_insecure_no_client_auth
};
let resolver = cached_no_client_auth
.get_or_insert_with(|| config.client_auth_cert_resolver.clone())
.clone();
config.client_auth_cert_resolver = resolver;
}
Some((config, final_verify_error))
}
#[derive(Debug)]
struct NodeClientCertVerifier {
inner: Arc<dyn rustls::server::danger::ClientCertVerifier>,
root_cert_ders: Vec<Vec<u8>>,
reject_unauthorized: bool,
verify_error: VerifyErrorStore,
}
impl rustls::server::danger::ClientCertVerifier for NodeClientCertVerifier {
fn offer_client_auth(&self) -> bool {
true
}
fn client_auth_mandatory(&self) -> bool {
self.reject_unauthorized
}
fn root_hint_subjects(&self) -> &[rustls::DistinguishedName] {
self.inner.root_hint_subjects()
}
fn verify_client_cert(
&self,
end_entity: &rustls::pki_types::CertificateDer<'_>,
intermediates: &[rustls::pki_types::CertificateDer<'_>],
now: rustls::pki_types::UnixTime,
) -> Result<rustls::server::danger::ClientCertVerified, rustls::Error> {
let ee_bytes: &[u8] = end_entity.as_ref();
if self.root_cert_ders.iter().any(|r| r.as_slice() == ee_bytes) {
return Ok(rustls::server::danger::ClientCertVerified::assertion());
}
match self
.inner
.verify_client_cert(end_entity, intermediates, now)
{
Ok(v) => Ok(v),
Err(e) => {
let code = if let rustls::Error::InvalidCertificate(ref cert_err) = e {
cert_error_to_node_code(cert_err).to_string()
} else {
format!("{e}")
};
*self.verify_error.lock().unwrap_or_else(|p| p.into_inner()) =
Some(code);
Ok(rustls::server::danger::ClientCertVerified::assertion())
}
}
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &rustls::pki_types::CertificateDer<'_>,
dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error>
{
match self.inner.verify_tls12_signature(message, cert, dss) {
Ok(v) => Ok(v),
Err(rustls::Error::InvalidCertificate(
rustls::CertificateError::Other(ref other),
)) if other.0.downcast_ref::<webpki::Error>().is_some_and(|e| {
matches!(e, webpki::Error::UnsupportedCertVersion)
}) =>
{
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
Err(e) => Err(e),
}
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &rustls::pki_types::CertificateDer<'_>,
dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error>
{
match self.inner.verify_tls13_signature(message, cert, dss) {
Ok(v) => Ok(v),
Err(rustls::Error::InvalidCertificate(
rustls::CertificateError::Other(ref other),
)) if other.0.downcast_ref::<webpki::Error>().is_some_and(|e| {
matches!(e, webpki::Error::UnsupportedCertVersion)
}) =>
{
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
Err(e) => Err(e),
}
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
self.inner.supported_verify_schemes()
}
}
#[derive(Debug)]
struct NodeClientCertVerifierNoRoots {
reject_unauthorized: bool,
verify_error: VerifyErrorStore,
}
impl rustls::server::danger::ClientCertVerifier
for NodeClientCertVerifierNoRoots
{
fn offer_client_auth(&self) -> bool {
true
}
fn client_auth_mandatory(&self) -> bool {
self.reject_unauthorized
}
fn root_hint_subjects(&self) -> &[rustls::DistinguishedName] {
&[]
}
fn verify_client_cert(
&self,
end_entity: &rustls::pki_types::CertificateDer<'_>,
intermediates: &[rustls::pki_types::CertificateDer<'_>],
_now: rustls::pki_types::UnixTime,
) -> Result<rustls::server::danger::ClientCertVerified, rustls::Error> {
let code =
if intermediates.is_empty() && is_self_signed(end_entity.as_ref()) {
"DEPTH_ZERO_SELF_SIGNED_CERT"
} else {
"UNABLE_TO_GET_ISSUER_CERT"
};
*self.verify_error.lock().unwrap_or_else(|p| p.into_inner()) =
Some(code.to_string());
Ok(rustls::server::danger::ClientCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &rustls::pki_types::CertificateDer<'_>,
dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error>
{
let algos = &rustls::crypto::aws_lc_rs::default_provider()
.signature_verification_algorithms;
match rustls::crypto::verify_tls12_signature(message, cert, dss, algos) {
Ok(v) => Ok(v),
Err(rustls::Error::InvalidCertificate(
rustls::CertificateError::Other(ref other),
)) if other.0.downcast_ref::<webpki::Error>().is_some_and(|e| {
matches!(e, webpki::Error::UnsupportedCertVersion)
}) =>
{
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
Err(e) => Err(e),
}
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &rustls::pki_types::CertificateDer<'_>,
dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error>
{
let algos = &rustls::crypto::aws_lc_rs::default_provider()
.signature_verification_algorithms;
match rustls::crypto::verify_tls13_signature(message, cert, dss, algos) {
Ok(v) => Ok(v),
Err(rustls::Error::InvalidCertificate(
rustls::CertificateError::Other(ref other),
)) if other.0.downcast_ref::<webpki::Error>().is_some_and(|e| {
matches!(e, webpki::Error::UnsupportedCertVersion)
}) =>
{
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
Err(e) => Err(e),
}
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
rustls::crypto::aws_lc_rs::default_provider()
.signature_verification_algorithms
.supported_schemes()
}
}
fn build_server_config(
scope: &mut v8::PinScope,
context: v8::Local<v8::Object>,
op_state: &mut OpState,
) -> Option<(rustls::ServerConfig, VerifyErrorStore)> {
let protocol_versions = match get_protocol_versions(scope, context) {
ProtocolVersionSelection::Default => {
&[&rustls::version::TLS13, &rustls::version::TLS12][..]
}
ProtocolVersionSelection::Tls12Only => &[&rustls::version::TLS12][..],
ProtocolVersionSelection::Tls13Only => &[&rustls::version::TLS13][..],
ProtocolVersionSelection::Unsupported => return None,
};
let cert_str = get_js_string(scope, context, "cert");
let key_str = get_js_string(scope, context, "key");
let no_server_cert = cert_str.is_none() && key_str.is_none();
let (mut certs, private_key) = if no_server_cert {
(Vec::new(), None)
} else {
let cert_str = cert_str?;
let key_str = key_str?;
let certs: Vec<_> =
rustls_pemfile::certs(&mut std::io::BufReader::new(cert_str.as_bytes()))
.filter_map(|r| r.ok())
.collect();
let private_key = rustls_pemfile::private_key(
&mut std::io::BufReader::new(key_str.as_bytes()),
)
.ok()
.flatten()?;
(certs, Some(private_key))
};
{
let ca_key = v8::String::new(scope, "ca").unwrap();
if let Some(ca_val) = context.get(scope, ca_key.into()) {
let mut ca_pems: Vec<Vec<u8>> = Vec::new();
if let Ok(arr) = v8::Local::<v8::Array>::try_from(ca_val) {
for i in 0..arr.length() {
if let Some(v) = arr.get_index(scope, i)
&& let Some(s) = v.to_string(scope)
{
ca_pems.push(s.to_rust_string_lossy(scope).into_bytes());
}
}
} else if !ca_val.is_undefined()
&& !ca_val.is_null()
&& let Some(s) = ca_val.to_string(scope)
{
ca_pems.push(s.to_rust_string_lossy(scope).into_bytes());
}
for pem in &ca_pems {
let normalized = normalize_pem_headers(pem);
let reader = &mut std::io::BufReader::new(std::io::Cursor::new(
normalized.as_ref(),
));
certs.extend(rustls_pemfile::certs(reader).flatten());
}
}
}
let request_cert = get_js_bool(scope, context, "requestCert", false);
let reject_unauthorized =
get_js_bool(scope, context, "rejectUnauthorized", true);
let builder =
rustls::ServerConfig::builder_with_protocol_versions(protocol_versions);
let (builder, client_cert_verify_error) = if request_cert {
let mut root_cert_store = rustls::RootCertStore::empty();
let mut root_cert_ders: Vec<Vec<u8>> = Vec::new();
v8_static_strings! {
CA = "ca",
}
let ca_key = CA.v8_string(scope).unwrap();
if let Some(ca_val) = context.get(scope, ca_key.into()) {
let mut ca_pems: Vec<Vec<u8>> = Vec::new();
if let Ok(arr) = v8::Local::<v8::Array>::try_from(ca_val) {
for i in 0..arr.length() {
if let Some(v) = arr.get_index(scope, i)
&& let Some(s) = v.to_string(scope)
{
ca_pems.push(s.to_rust_string_lossy(scope).into_bytes());
}
}
} else if !ca_val.is_undefined()
&& !ca_val.is_null()
&& let Some(s) = ca_val.to_string(scope)
{
ca_pems.push(s.to_rust_string_lossy(scope).into_bytes());
}
for pem in &ca_pems {
let normalized = normalize_pem_headers(pem);
let reader = &mut std::io::BufReader::new(std::io::Cursor::new(
normalized.as_ref(),
));
for parsed in rustls_pemfile::certs(reader) {
match parsed {
Ok(cert) => {
root_cert_ders.push(cert.as_ref().to_vec());
if let Err(e) = root_cert_store.add(cert) {
log::debug!(
"TLSWrap: ignoring invalid client CA certificate: {e}"
);
}
}
Err(e) => {
log::debug!("TLSWrap: failed to parse client CA PEM entry: {e}");
}
}
}
}
}
let client_verify_error: VerifyErrorStore = Default::default();
if root_cert_store.is_empty() {
(
builder.with_client_cert_verifier(Arc::new(
NodeClientCertVerifierNoRoots {
reject_unauthorized,
verify_error: client_verify_error.clone(),
},
)),
client_verify_error,
)
} else {
let mut verifier_builder = rustls::server::WebPkiClientVerifier::builder(
Arc::new(root_cert_store),
);
if !reject_unauthorized {
verifier_builder = verifier_builder.allow_unauthenticated();
}
match verifier_builder.build() {
Ok(inner) => (
builder.with_client_cert_verifier(Arc::new(NodeClientCertVerifier {
inner,
root_cert_ders,
reject_unauthorized,
verify_error: client_verify_error.clone(),
})),
client_verify_error,
),
Err(e) => {
log::debug!("TLSWrap: failed to build client cert verifier: {e}");
return None;
}
}
}
} else {
(builder.with_no_client_auth(), Default::default())
};
let Some(private_key) = private_key else {
return Some((
builder.with_cert_resolver(Arc::new(NoCertResolver)),
client_cert_verify_error,
));
};
let provider = builder.crypto_provider().clone();
let signing_key = provider.key_provider.load_private_key(private_key).ok()?;
let certified_key = rustls::sign::CertifiedKey::new(certs, signing_key);
match certified_key.keys_match() {
Ok(()) => {}
Err(rustls::Error::InvalidCertificate(
rustls::CertificateError::Other(ref other),
)) if other
.0
.downcast_ref::<webpki::Error>()
.is_some_and(|e| matches!(e, webpki::Error::UnsupportedCertVersion)) => {}
Err(e) => {
log::debug!("TLSWrap: cert/key validation failed: {e}");
return None;
}
}
let resolver = rustls::sign::SingleCertAndKey::from(certified_key);
let mut server_config = builder.with_cert_resolver(Arc::new(resolver));
let ticketer = {
let state = op_state.borrow_mut::<NodeTlsState>();
if let Some(t) = &state.server_ticketer {
Some(t.clone())
} else {
match rustls::crypto::aws_lc_rs::Ticketer::new() {
Ok(t) => {
state.server_ticketer = Some(t.clone());
Some(t)
}
Err(e) => {
log::debug!("TLSWrap: failed to build session ticketer: {e}");
None
}
}
}
};
if let Some(t) = ticketer {
server_config.ticketer = t;
}
server_config.session_storage =
rustls::server::ServerSessionMemoryCache::new(256);
Some((server_config, client_cert_verify_error))
}
#[derive(Debug)]
struct StaticClientCertResolver(Arc<rustls::sign::CertifiedKey>);
impl rustls::client::ResolvesClientCert for StaticClientCertResolver {
fn resolve(
&self,
_root_hint_subjects: &[&[u8]],
_sigschemes: &[rustls::SignatureScheme],
) -> Option<Arc<rustls::sign::CertifiedKey>> {
Some(self.0.clone())
}
fn has_certs(&self) -> bool {
true
}
}
#[derive(Debug)]
struct NoCertResolver;
impl rustls::server::ResolvesServerCert for NoCertResolver {
fn resolve(
&self,
_client_hello: rustls::server::ClientHello<'_>,
) -> Option<Arc<rustls::sign::CertifiedKey>> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clear_out_process_bails_when_eof_set() {
let mut inner = TLSWrapInner::new(Kind::Client);
let result = inner.clear_out_process();
assert!(result.data.is_empty());
assert!(!result.got_eof);
inner.eof = true;
let result = inner.clear_out_process();
assert!(result.data.is_empty());
assert!(!result.got_eof);
inner.eof = false;
let result = inner.clear_out_process();
assert!(result.data.is_empty());
}
#[test]
fn alive_flag_lifecycle() {
let inner = TLSWrapInner::new(Kind::Client);
assert!(inner.alive.get());
let alive_clone = inner.alive.clone();
inner.alive.set(false);
assert!(!alive_clone.get());
}
#[test]
fn cycling_guard_prevents_reentry() {
let mut inner = TLSWrapInner::new(Kind::Client);
assert!(!inner.cycling);
inner.cycling = true;
assert!(inner.cycling);
inner.cycling = false;
assert!(!inner.cycling);
}
}