blob: 51614a8ca33d7dba33ee41cc370e45cb0e879ba6 [file]
// 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 lifecycle controls
use alloc::boxed::Box;
use core::{
ffi::c_int,
future::poll_fn,
ops::{
Deref,
DerefMut, //
},
ptr::NonNull,
task::Poll, //
};
use crate::{
HandshakeCompleteMethods,
Methods,
abort_on_panic,
alerts::AlertDescription,
connection::{
Client,
Server,
TlsConnection,
TlsConnectionBuilder,
methods::HasTlsConnectionMethod, //
},
context::{
HasShutdown,
SupportedMode,
TlsMode, //
},
credentials::TlsCredential,
errors::{
Error,
TlsErrorReason,
TlsRetryReason, //
}, //
};
/// # Connection shutdown
impl<R, M> TlsConnection<R, M> {
/// Set whether shutting down this connection sends out a `close_notify` alert.
pub fn set_quiet_shutdown(&mut self, quiet: bool) -> &mut Self {
unsafe {
// Safety: the validity of the handle `self.0` is witnessed by `self`.
bssl_sys::SSL_set_quiet_shutdown(self.ptr(), if quiet { 1 } else { 0 });
}
self
}
/// Check whether shutting down this connection sends out a `close_notify` alert.
pub fn get_quiet_shutdown(&self) -> bool {
let rc = unsafe {
// Safety: the validity of the handle `self.0` is witnessed by `self`.
bssl_sys::SSL_get_quiet_shutdown(self.ptr())
};
rc == 1
}
}
/// # Connection initialisation state
///
/// There are methods and accessors that become available only when the connection is in the right
/// state.
///
/// Please refer to [`EstablishedTlsConnection`] and [`TlsConnectionInHandshake`] for allowed
/// operations.
impl<R, M> TlsConnection<R, M> {
/// Access handshake-related options if the connection is in handshake mode.
pub fn in_handshake<'a>(&'a mut self) -> Option<TlsConnectionInHandshake<'a, R, M>> {
if self.is_in_handshake() {
Some(TlsConnectionInHandshake(self))
} else {
None
}
}
/// Access handshake-related options if a handshake is completed and
/// the connection is initialised.
pub fn established<'a>(&'a mut self) -> Option<EstablishedTlsConnection<'a, R, M>> {
(!self.is_in_handshake()).then_some(EstablishedTlsConnection(self))
}
}
/// # Alerts
impl<R, M> TlsConnection<R, M>
where
M: HasTlsConnectionMethod,
{
fn translate_lifecycle_result(&mut self, rc: c_int) -> Result<Option<TlsRetryReason>, Error> {
let code = unsafe {
// Safety: inspecting the last error on an existing valid connection.
bssl_sys::SSL_get_error(self.ptr(), rc)
};
match code {
// Handshake or alert transmission completed cleanly; here it returns `Ok(None)` to indicate
// that no further progress or retry is required.
bssl_sys::SSL_ERROR_NONE => Ok(None),
// TODO(crbug.com/42290000): This should be handled within the library.
// A `close_notify` received mid-handshake or during fatal alert sending means the peer closed
// the connection before completing the handshake, which is a terminal error.
bssl_sys::SSL_ERROR_ZERO_RETURN => {
Err(Error::TlsReason(TlsErrorReason::Sslv3AlertCloseNotify))
}
// The handshake paused due to pending network I/O or an asynchronous callback such as private
// key operations.
// Returning `Ok(Some(reason))` preserves the exact suspension reason so the caller or async
// reactor can resolve it before driving the handshake again.
_ if let Ok(reason) = TlsRetryReason::try_from(code) => Ok(Some(reason)),
_ => Err(self.extract_tls_error(code)),
}
}
/// Send fatal alert.
///
/// This would usually lead to termination of the connection.
pub fn send_fatal_alert(
&mut self,
alert: AlertDescription,
) -> Result<Option<TlsRetryReason>, Error> {
let rc = unsafe {
// Safety: `self.0` is still a valid handle and `alert` is valid by construction.
bssl_sys::SSL_send_fatal_alert(self.ptr(), alert as u8)
};
self.translate_lifecycle_result(rc)
}
/// Send fatal alert asynchronously.
pub fn async_send_fatal_alert<'a>(
&'a mut self,
alert: AlertDescription,
) -> impl 'a + Send + Future<Output = Result<(), Error>> {
poll_fn(move |cx| {
self.set_waker(cx.waker());
match self.send_fatal_alert(alert) {
Ok(Some(TlsRetryReason::WantRead | TlsRetryReason::WantWrite)) => Poll::Pending,
Ok(None) => Poll::Ready(Ok(())),
Ok(Some(reason)) => unreachable!("unexpected retry reason {reason:?}"),
Err(e) => Poll::Ready(Err(e)),
}
})
}
}
impl<R> TlsConnection<R, TlsMode> {
/// Inspect if the connection is suspended for which reason, after invocation of I/O methods.
pub fn take_pending_reason(&mut self) -> Option<TlsRetryReason> {
self.get_connection_methods().take_pending_reason()
}
}
/// A handle to the connection that is valid only during handshake.
// NOTE(@xfding): this type is strictly for configuration of the connection during the handshake,
// and no methods should be allowed to drive the TLS state machine.
#[repr(transparent)]
pub struct TlsConnectionInHandshake<'a, R, M>(pub(crate) &'a mut TlsConnection<R, M>);
impl<R, M> TlsConnectionInHandshake<'_, R, M> {
pub(crate) fn ptr(&self) -> *mut bssl_sys::SSL {
self.0.ptr()
}
}
/// # Handshake
impl<R, M> TlsConnection<R, M>
where
M: SupportedMode,
{
/// Drive the handshake.
///
/// Call this method after the initial [`Self::accept`] or [`Self::connect`],
/// should the handshake be suspended.
///
/// This method returns `Ok(None)` to signal handshake completion;
/// otherwise, `Ok(Some(reason))` is returned and the suspension `reason` must be resolved first
/// before this method can make progress again.
pub fn do_handshake(&mut self) -> Result<Option<TlsRetryReason>, Error> {
let rc = unsafe {
// Safety: driving the handshake on a valid connection handle.
bssl_sys::SSL_do_handshake(self.ptr())
};
self.translate_lifecycle_result(rc)
}
}
impl<M> TlsConnection<Server, M>
where
M: SupportedMode,
{
/// Accept a connection by responding to `ClientHello` with `ServerHello`.
///
/// This method returns `Ok(None)` to signal handshake completion;
/// otherwise, given `Ok(Some(reason))` the suspension `reason` must be resolved first
/// before calling [`Self::do_handshake`] can make progress again.
pub fn accept(&mut self) -> Result<Option<TlsRetryReason>, Error> {
self.do_handshake()
}
}
impl<M> TlsConnection<Client, M>
where
M: SupportedMode,
{
/// Initiate a connection by sending a `ClientHello`.
///
/// This method returns `Ok(None)` to signal handshake completion;
/// otherwise, given `Ok(Some(reason))` the suspension `reason` must be resolved first
/// before calling [`Self::do_handshake`] can make progress again.
pub fn connect(&mut self) -> Result<Option<TlsRetryReason>, Error> {
self.do_handshake()
}
}
/// A handle to the connection that is valid only after initialization, or in other words after
/// handshake.
#[repr(transparent)]
pub struct EstablishedTlsConnection<'a, R, M = TlsMode>(&'a mut TlsConnection<R, M>);
impl<R, M> Deref for EstablishedTlsConnection<'_, R, M> {
type Target = TlsConnection<R, M>;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
impl<R, M> DerefMut for EstablishedTlsConnection<'_, R, M> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut *self.0
}
}
impl<R, M> EstablishedTlsConnection<'_, R, M>
where
M: HasTlsConnectionMethod + HasShutdown,
{
/// Perform shutdown on the write end.
///
/// If the method returns `Ok(Some(reason))`, the shutdown will not progress until I/O makes
/// progress.
pub fn sync_shutdown(&mut self) -> Result<Option<TlsRetryReason>, Error> {
// SSL_shutdown has two stages, sending close_notify and waiting for close_notify.
// We now believe that only the sending call, aka the first call, is useful.
// This method only sends close_notify, so it skips calling SSL_shutdown if close_notify has
// already been sent.
let rc = unsafe {
// Safety: we have exclusive access to the connection state.
bssl_sys::SSL_get_shutdown(self.ptr())
};
if rc & bssl_sys::SSL_SENT_SHUTDOWN != 0 {
return Ok(None);
}
let rc = unsafe {
// Safety: we have exclusive access to the connection state.
bssl_sys::SSL_shutdown(self.ptr())
};
if matches!(rc, 0 | 1) {
return Ok(None);
}
self.translate_lifecycle_result(rc)
}
}
impl<R, M> TlsConnection<R, M>
where
M: SupportedMode,
{
/// Perform asynchronous handshake, until completion or until pending on non-I/O operations.
///
/// The caller needs to ensure that any pending operations during the handshake are resolved,
/// before polling [`Self::async_handshake`] again.
pub fn async_handshake(
&mut self,
) -> impl Send + Future<Output = Result<Option<TlsRetryReason>, Error>> + '_ {
poll_fn(move |cx| {
self.set_waker(cx.waker());
match self.do_handshake() {
Ok(Some(TlsRetryReason::WantRead | TlsRetryReason::WantWrite)) => Poll::Pending,
Ok(Some(reason)) => Poll::Ready(Ok(Some(reason))),
Ok(None) => Poll::Ready(Ok(None)),
Err(e) => Poll::Ready(Err(e)),
}
})
}
/// Perform asynchronous handshake, until completion, knowing that all possible pending reasons
/// will resolve themselves.
///
/// The caller needs to ensure that any pending operations due to asynchronous operations such as
/// certificate verification and private key operations will eventually resolve and wake up
/// the handshake task.
/// Otherwise the handshake task will never complete.
pub fn async_nonstop_handshake(
&mut self,
) -> impl Send + Future<Output = Result<(), Error>> + '_ {
poll_fn(move |cx| {
self.set_waker(cx.waker());
match self.do_handshake() {
Ok(Some(_)) => Poll::Pending,
Ok(None) => Poll::Ready(Ok(())),
Err(e) => Poll::Ready(Err(e)),
}
})
}
}
bssl_macros::bssl_enum! {
enum InfoCallbackConnectionState : i32 {
ReadAlert = bssl_sys::SSL_CB_READ_ALERT as i32,
WriteAlert = bssl_sys::SSL_CB_WRITE_ALERT as i32,
HandshakeStart = bssl_sys::SSL_CB_HANDSHAKE_START as i32,
HandshakeDone = bssl_sys::SSL_CB_HANDSHAKE_DONE as i32,
}
}
/// A callback that allows you to inspect the handshake information when the handshake concludes
/// successfully.
pub trait HandshakeComplete: Send {
/// The callback to be called when handshake is complete with success.
fn handshake_complete(&mut self, hs: &HandshakeInfo);
}
/// A handle to extract handshake information, such as credential.
pub struct HandshakeInfo(NonNull<bssl_sys::SSL>);
impl HandshakeInfo {
/// Return the selected credential for the connection.
pub fn get_selected_credential(&self) -> Option<TlsCredential> {
let cred = unsafe {
// Safety: by BoringSSL invariant, `self.0` is still a valid connection handle.
bssl_sys::SSL_get0_selected_credential(self.0.as_ptr())
};
NonNull::new(cred as *mut _).map(TlsCredential::from_raw_and_upref)
}
}
unsafe extern "C" fn info_callback<M: Methods + HandshakeCompleteMethods>(
ssl: *const bssl_sys::SSL,
ty: c_int,
_: c_int,
) {
// Safety: the const-to-mut cast is safe; we only mutate our own method table, not SSL.
let Some(ssl) = NonNull::new(ssl as *mut _) else {
return;
};
let Some(state) = InfoCallbackConnectionState::try_from(ty).ok() else {
return;
};
abort_on_panic(move || {
match state {
InfoCallbackConnectionState::HandshakeDone => {
let Some(mut callback) = (unsafe {
// Safety: `ssl` is a valid handle passed in from BoringSSL.
M::from_ssl(ssl.as_ptr())
})
.and_then(|methods| methods.handshake_complete_methods()) else {
return;
};
callback.handshake_complete(&HandshakeInfo(ssl));
}
InfoCallbackConnectionState::ReadAlert
| InfoCallbackConnectionState::WriteAlert
| InfoCallbackConnectionState::HandshakeStart => {}
}
});
}
/// # Handshake-complete callback
impl<R, M> TlsConnectionBuilder<R, M>
where
M: HasTlsConnectionMethod,
{
/// Set a callback to be called when the handshake completes successfully.
///
/// The callback receives a [`HandshakeInfo`] handle that can be used to
/// inspect the result of the handshake, such as the selected credential.
pub fn with_handshake_complete_callback(
&mut self,
callback: impl HandshakeComplete + 'static,
) -> &mut Self {
self.as_in_handshake()
.set_handshake_complete_callback(callback);
self
}
}
/// # Handshake completion notification callback
impl<R, M> TlsConnectionInHandshake<'_, R, M>
where
M: HasTlsConnectionMethod,
{
/// Set a callback to be called when the handshake completes successfully.
pub fn set_handshake_complete_callback(
&mut self,
callback: impl HandshakeComplete + 'static,
) -> &mut Self {
unsafe {
// Safety: we only install our own callback function.
bssl_sys::SSL_set_info_callback(
self.ptr(),
Some(info_callback::<super::methods::RustConnectionMethods<M>>),
);
}
self.0.get_connection_methods().handshake_complete = Some(Box::new(callback));
self
}
}