#![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_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_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_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 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(
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 onread_fn = if let Some(onread) = onread {
v8::Local::new(scope, onread)
} else {
let key =
v8::String::new_external_onebyte_static(scope, b"onread").unwrap();
match recv.get(scope, key.into()) {
Some(val) => match v8::Local::<v8::Function>::try_from(val) {
Ok(f) => f,
Err(_) => return,
},
None => return,
}
};
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_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()],
);
}
}
}
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 = "will be used when libuv_stream::TCP is replaced"
)]
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 = "may be used when libuv_stream::TCP is replaced by LibUvStreamWrap-based TCPWrap"
)]
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,
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>>,
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,
}
fn rustls_error_to_node_error(e: &rustls::Error) -> (String, String) {
use rustls::Error as E;
match e {
E::InvalidCertificate(cert_err) => {
let reason = format!("{cert_err}");
let code = if reason.contains("UnknownIssuer") {
"UNABLE_TO_VERIFY_LEAF_SIGNATURE"
} else if reason.contains("NotValidYet") {
"CERT_NOT_YET_VALID"
} else if reason.contains("Expired") {
"CERT_HAS_EXPIRED"
} else if reason.contains("NotValidForName") {
"ERR_TLS_CERT_ALTNAME_INVALID"
} else if reason.contains("CaUsedAsEndEntity")
|| reason.contains("IssuerNotCrlSigner")
|| reason.contains("InvalidPurpose")
{
"UNABLE_TO_VERIFY_LEAF_SIGNATURE"
} else if reason.contains("SelfSigned") {
"DEPTH_ZERO_SELF_SIGNED_CERT"
} else {
"ERR_SSL_SSLV3_ALERT_CERTIFICATE_UNKNOWN"
};
(format!("{e}"), format!("ERR_SSL_{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",
_ => "SSLV3_ALERT_HANDSHAKE_FAILURE",
};
(format!("{e}"), format!("ERR_SSL_{code}"))
}
E::NoApplicationProtocol => (
format!("{e}"),
"ERR_SSL_NO_APPLICATION_PROTOCOL".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,
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)),
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(),
}
}
unsafe fn cycle(ptr: *mut TLSWrapInner) {
unsafe {
if (*ptr).cycling {
return;
}
(*ptr).cycling = true;
(*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);
}
}
fn clear_in(&mut self) {
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);
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 !result.data.is_empty() {
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(),
result.data.len() as isize,
Some(&result.data),
);
}
if (*ptr).tls_conn.is_none() {
return;
}
}
if result.got_eof {
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 => {
let enc_data = std::mem::take(&mut (*ptr).pending_enc_out);
if let Some(ctx) = extract_emit_ctx(ptr) {
do_enc_out_js(&ctx, enc_data);
}
if (*ptr).write_callback_scheduled
&& !(*ptr).in_dowrite
&& let Some((write_obj, ctx)) = prepare_invoke_queued(ptr)
{
do_invoke_queued(&ctx, write_obj, 0);
}
}
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 = "will be used when TCPWrap replaces libuv_stream::TCP"
)]
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();
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);
}
}
#[allow(dead_code, reason = "used by tls_read_interceptor_cb")]
fn free_uv_buf(buf: *const uv_buf_t) {
unsafe {
if !(*buf).base.is_null() && (*buf).len > 0 {
let layout = std::alloc::Layout::from_size_align((*buf).len, 1).unwrap();
std::alloc::dealloc((*buf).base as *mut u8, layout);
}
}
}
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 {
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 {
let result = inner.clear_out_process();
let enc_action = inner.enc_out_collect();
let inner_ptr = inner as *mut TLSWrapInner;
unsafe {
TLSWrapInner::dispatch_clear_out_callbacks(inner_ptr, &result);
TLSWrapInner::do_enc_out_action(inner_ptr, enc_action);
}
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 {
match rustls::pki_types::ServerName::try_from(server_name) {
Ok(name) => Some(name),
Err(_) => return -1,
}
};
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
let verify_error = inner.verify_error.clone();
let client_config =
match build_client_config(scope, context, op_state, verify_error) {
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 = match build_server_config(scope, context) {
Some(c) => c,
None => {
return -1;
}
};
let inner = unsafe { &mut *self.inner.as_mut_ptr() };
inner.pending_server_config = Some(Arc::new(server_config));
0
}
#[nofast]
fn attach(
&self,
#[cppgc] tcp: &crate::ops::libuv_stream::TCP,
scope: &mut v8::PinScope,
op_state: &mut OpState,
) -> i32 {
let stream = tcp.stream();
if stream.is_null() {
return UV_EBADF;
}
let inner = unsafe { &mut *self.inner.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
}
#[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]
#[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 => {
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
}
#[fast]
#[reentrant]
fn read_start(
&self,
#[this] this: v8::Global<v8::Object>,
scope: &mut v8::PinScope,
_op_state: &mut OpState,
) -> 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 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 {
let inner_ptr = inner as *mut TLSWrapInner;
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 ab = buf.buffer(scope).unwrap();
let ptr = ab.data().unwrap().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 ab = buf.buffer(scope).unwrap();
let ptr = ab.data().unwrap().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 ab = buffer.buffer(scope).unwrap();
let data_ptr = ab.data().unwrap().as_ptr() as *const u8;
let data = unsafe {
std::slice::from_raw_parts(data_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() };
if let Some(ref mut conn) = inner.tls_conn {
conn.send_close_notify();
}
inner.shutdown = true;
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
}
#[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]
#[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) };
}
#[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 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>>>;
#[derive(Debug)]
struct NodeServerCertVerifier {
inner: Arc<rustls::client::WebPkiServerVerifier>,
verify_error: VerifyErrorStore,
root_cert_ders: Vec<Vec<u8>>,
}
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.to_string().contains("UnsupportedCertVersion") => {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
Err(rustls::Error::InvalidCertificate(
rustls::CertificateError::BadEncoding,
)) => Ok(rustls::client::danger::HandshakeSignatureValid::assertion()),
other => other,
}
}
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 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) => 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());
}
if let rustls::CertificateError::Other(other) = cert_error
&& format!("{other}").contains("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.verify_error.lock().unwrap_or_else(|e| e.into_inner()) =
Some(code.to_string());
Ok(rustls::client::danger::ServerCertVerified::assertion())
}
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()
}
}
fn build_client_config(
scope: &mut v8::PinScope,
context: v8::Local<v8::Object>,
op_state: &mut OpState,
verify_error: VerifyErrorStore,
) -> Option<rustls::ClientConfig> {
use deno_net::DefaultTlsOptions;
use deno_tls::TlsKeys;
use deno_tls::TlsKeysHolder;
let _reject_unauthorized =
get_js_bool(scope, context, "rejectUnauthorized", 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 ca_certs.is_empty()
&& 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 = custom_ca_certs
.iter()
.map(|cert| cert.clone().into_bytes())
.collect();
}
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(rustls::RootCertStore::empty);
let mut root_cert_ders: Vec<Vec<u8>> = Vec::new();
for cert in &ca_certs {
let reader = &mut std::io::BufReader::new(std::io::Cursor::new(cert));
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 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)) => {
config_builder
.with_client_auth_cert(cert_chain, private_key.clone_key())
.ok()?
}
TlsKeys::Null => config_builder.with_no_client_auth(),
TlsKeys::Resolver(_) => return None,
};
if let Some(node_tls_state) = op_state.try_borrow::<NodeTlsState>() {
config.resumption = rustls::client::Resumption::store(
node_tls_state.client_session_store.clone(),
);
}
let verifier_result =
rustls::client::WebPkiServerVerifier::builder(Arc::new(root_cert_store))
.build();
if let Ok(inner) = verifier_result {
config.dangerous().set_certificate_verifier(Arc::new(
NodeServerCertVerifier {
inner,
verify_error,
root_cert_ders,
},
));
}
Some(config)
}
fn build_server_config(
scope: &mut v8::PinScope,
context: v8::Local<v8::Object>,
) -> Option<rustls::ServerConfig> {
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 = match get_js_string(scope, context, "cert") {
Some(value) => value,
None => {
return None;
}
};
let key_str = match get_js_string(scope, context, "key") {
Some(value) => value,
None => {
return None;
}
};
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()?;
rustls::ServerConfig::builder_with_protocol_versions(protocol_versions)
.with_no_client_auth()
.with_single_cert(certs, private_key)
.ok()
}
#[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);
}
}