blob: 7944996ff32a2c7e3a45d33c16d1c5b7937e7793 [file] [log] [blame]
// Copyright 2026 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! TLS Connection
use alloc::boxed::Box;
use core::{
ffi::c_int,
marker::PhantomData,
mem::transmute,
ops::{Deref, DerefMut},
ptr::NonNull,
task::Waker,
};
use crate::{
config::ProtocolVersion,
connection::methods::waker_data_ref_from_ssl,
context::TlsMode,
errors::{Error, TlsRetryReason},
io::IoStatus,
};
mod credentials;
pub mod lifecycle;
pub(crate) mod methods;
pub mod transport;
/// Server role - the connection runs as a server.
pub enum Server {}
/// Client role - the connection runs as a client.
pub enum Client {}
/// TLS Connection
///
/// `Role` is expected to be either [`Server`] or [`Client`] and
/// `Mode` is expected to be either [`TlsMode`] or [`QuicMode`].
/// These generics will govern the capabilities that respective TLS connection role can access,
// NOTE: any method that involves I/O must require exclusive access, enforced by requiring `&mut`.
pub struct TlsConnection<Role, Mode = TlsMode> {
ptr: NonNull<bssl_sys::SSL>,
_p: PhantomData<fn() -> (Role, Mode)>,
}
/// Safety: all internal states of `bssl_sys::SSL` that this connection are either exclusively owned
/// or reference-counted but immutable, including `SSL_SESSION`s and `SSL_CTX`s.
unsafe impl<R, M> Send for TlsConnection<R, M> {}
impl<R, M> Drop for TlsConnection<R, M> {
fn drop(&mut self) {
unsafe {
// Safety: the connection is still held as valid.
bssl_sys::SSL_free(self.ptr());
}
}
}
/// Tls Connection Reference
// NOTE: Do not expose access to this type by-value!
#[repr(transparent)]
pub struct TlsConnectionRef<R, M = TlsMode>(NonNull<bssl_sys::SSL>, PhantomData<fn() -> (R, M)>);
impl<R, M> Deref for TlsConnection<R, M> {
type Target = TlsConnectionRef<R, M>;
fn deref(&self) -> &Self::Target {
unsafe {
// Safety: `TlsConnectionRef` is a transparent wrapper around `NonNull<bssl_sys::SSL>`.
transmute(&self.ptr)
}
}
}
impl<R, M> DerefMut for TlsConnection<R, M> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe {
// Safety: `TlsConnectionRef` is a transparent wrapper around `NonNull<bssl_sys::SSL>`.
transmute(&mut self.ptr)
}
}
}
/// Safety: all internal states of `bssl_sys::SSL` that this connection are either exclusively owned
/// or reference-counted but immutable, including `SSL_SESSION`s and `SSL_CTX`s.
unsafe impl<R, M> Send for TlsConnectionRef<R, M> {}
impl<R, M> TlsConnection<R, M>
where
M: methods::HasTlsConnectionMethod,
{
pub(crate) fn from_ssl(ptr: NonNull<bssl_sys::ssl_st>) -> Self {
let idx = M::registration();
let data = Box::into_raw(Box::new(methods::RustConnectionMethods::<M>::new())) as _;
unsafe {
// Safety:
// - `M::registration` will return a valid ex-data index.
// - `data` should be valid by non-null invariant.
bssl_sys::SSL_set_ex_data(ptr.as_ptr(), idx, data);
}
Self {
ptr,
_p: PhantomData,
}
}
}
impl<R, M> TlsConnectionRef<R, M> {
#[allow(unused)]
pub(crate) fn categorise_error_for_io(&self, rc: c_int) -> Result<IoStatus, Error> {
let reason = unsafe {
// Safety: we only want to extract the last I/O error on an existing valid connection.
bssl_sys::SSL_get_error(self.ptr(), rc)
};
let res = match TlsRetryReason::try_from(reason) {
Ok(TlsRetryReason::PeerCloseNotify) => Ok(IoStatus::EndOfStream),
Ok(reason) => Ok(IoStatus::Retry(reason)),
Err(_) => Err(Error::extract_lib_err()),
};
unsafe {
// Safety: we only clear the error on the current thread.
bssl_sys::ERR_clear_error();
}
res
}
}
impl<R, M> TlsConnectionRef<R, M>
where
M: methods::HasTlsConnectionMethod,
{
fn get_connection_methods(&mut self) -> &mut methods::RustConnectionMethods<M> {
unsafe {
// Safety: the validity of the handle `self.0` is witnessed by `self`.
get_connection_methods(self.ptr())
}
}
fn get_connection_methods_ref(&self) -> &methods::RustConnectionMethods<M> {
unsafe {
// Safety: the validity of the handle `self.0` is witnessed by `self`.
get_connection_methods_ref(self.ptr())
}
}
}
impl<R, M> TlsConnectionRef<R, M>
where
M: methods::HasTlsConnectionMethod,
{
/// This method sets up waker for BIO and various handlers.
pub(crate) fn set_waker(&mut self, waker: &Waker) {
let waker_data = unsafe {
// Safety:
// - `self.ptr()` is a valid `SSL` handle created from `from_ssl` in this crate.
// - The data was previously initialized as `Box<Option<Waker>>`.
waker_data_ref_from_ssl(self.0)
};
if let Some(other_waker) = waker_data
&& waker.will_wake(other_waker)
{
// We do not have to put this waker in the mailbox.
} else {
*waker_data = Some(waker.clone());
}
let methods = self.get_connection_methods();
methods.set_waker(waker);
}
}
/// Safety:
/// - caller must pass in the SSL handle constructed by this crate.
/// - the handle must outlives `'a` for exclusive access.
unsafe fn get_connection_methods<'a, M: methods::HasTlsConnectionMethod>(
ssl: *mut bssl_sys::SSL,
) -> &'a mut methods::RustConnectionMethods<M> {
let methods = unsafe {
// Safety: this instance of SSL should be constructed by this crate.
bssl_sys::SSL_get_ex_data(ssl, M::registration())
};
if methods.is_null() {
panic!("context method is missing")
}
unsafe {
// Safety: `methods` must be constructed by `new_inner`
&mut *(methods as *mut _)
}
}
/// Safety:
/// - caller must pass in the SSL handle constructed by this crate.
/// - the handle must outlives `'a` for shared access.
unsafe fn get_connection_methods_ref<'a, M: methods::HasTlsConnectionMethod>(
ssl: *mut bssl_sys::SSL,
) -> &'a methods::RustConnectionMethods<M> {
let methods = unsafe {
// Safety: this instance of SSL should be constructed by this crate.
bssl_sys::SSL_get_ex_data(ssl, M::registration())
};
if methods.is_null() {
panic!("connection method is missing")
}
unsafe {
// Safety: `methods` must be constructed by `new_inner`
&*(methods as *const _)
}
}
impl<R, M> TlsConnectionRef<R, M> {
pub(crate) fn ptr(&self) -> *mut bssl_sys::SSL {
self.0.as_ptr()
}
/// Check if the connection is DTLS.
pub fn is_dtls(&self) -> bool {
unsafe {
// Safety: the SSL handle is still valid witnessed by self
bssl_sys::SSL_is_dtls(self.ptr()) == 1
}
}
/// Check if the connection is server.
///
/// This method returns `false` if it is a client connection.
pub fn is_server(&self) -> bool {
unsafe {
// Safety: the SSL handle is still valid witnessed by self
bssl_sys::SSL_is_server(self.ptr()) == 1
}
}
/// Check if a handshake is underway on this connection.
pub fn is_in_handshake(&self) -> bool {
unsafe {
// Safety: the SSL handle is still valid witnessed by self
bssl_sys::SSL_in_init(self.ptr()) == 1
}
}
/// Get the protocol version of this TLS connection.
///
/// This method call returns `None` if the connection has not completed handshake.
pub fn get_protocol_version(&self) -> Option<ProtocolVersion> {
if self.is_in_handshake() {
return None;
}
let ret = unsafe {
// Safety:
// - the handle is still valid witnessed by self;
// - the connection has completed handshake.
bssl_sys::SSL_version(self.ptr())
};
ret.try_into().ok()
}
}