blob: 032cec2ff43c0e1df26bf816131690a27cde94f2 [file] [edit]
// 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,
string::ToString, //
};
use core::{
future::poll_fn,
ops::{
Deref,
DerefMut, //
},
task::Poll, //
};
use crate::{
alerts::AlertDescription,
check_tls_error,
connection::{
Client,
Server,
TlsConnection,
methods::HasTlsConnectionMethod, //
},
context::{
HasBasicIo,
SupportedMode,
TlsMode, //
},
errors::{
Error,
TlsErrorReason,
TlsRetryReason, //
},
io::IoStatus, //
};
/// # 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>> {
let session = unsafe {
// Safety: the validity of the handle `self.0` is witnessed by `self`.
bssl_sys::SSL_get_session(self.ptr())
};
if session.is_null() {
return None;
}
Some(EstablishedTlsConnection(self))
}
}
bssl_macros::bssl_enum! {
/// TLS data-pending reasons
pub enum TlsPendingData: i32 {
/// TLS connection wants to read more data.
WantRead = bssl_sys::SSL_READING as i32,
/// TLS connection wants to write more data.
WantWrite = bssl_sys::SSL_WRITING as i32,
}
}
/// # Connection state
///
/// When operations on [`TlsConnection`] return with pending status,
/// there will be reasons why the operations should be retried.
impl<R, M> TlsConnection<R, M> {
/// Check the connection if it needs additional data.
pub fn wants_data(&self) -> Option<TlsPendingData> {
let code = unsafe {
// Safety: the validity of the handle is witnessed by `self`.
bssl_sys::SSL_want(self.ptr())
};
let code = i32::try_from(code).ok()?;
TlsPendingData::try_from(code).ok()
}
}
/// # Alerts
impl<R, M> TlsConnection<R, M>
where
M: HasTlsConnectionMethod,
{
/// 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> {
Ok(check_tls_error!(self.ptr(), {
// 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)
}))
}
/// 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.
#[repr(transparent)]
pub struct TlsConnectionInHandshake<'a, R, M>(pub(crate) &'a mut TlsConnection<R, M>);
impl<R, M> Deref for TlsConnectionInHandshake<'_, R, M> {
type Target = TlsConnection<R, M>;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
impl<R, M> DerefMut for TlsConnectionInHandshake<'_, R, M> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut *self.0
}
}
/// # Handshake
impl<R, M> TlsConnectionInHandshake<'_, R, M>
where
M: HasTlsConnectionMethod,
{
/// 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 conn = self.ptr();
Ok(check_tls_error!(conn, bssl_sys::SSL_do_handshake(conn)))
}
}
impl<M> TlsConnectionInHandshake<'_, Server, M>
where
M: HasTlsConnectionMethod,
{
/// 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> TlsConnectionInHandshake<'_, Client, M>
where
M: HasTlsConnectionMethod,
{
/// 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<'a, R, M> EstablishedTlsConnection<'a, R, M> {
/// Get the current session.
pub fn get_session(&self) -> Option<crate::sessions::TlsSession> {
let session = unsafe {
// Safety: self.ptr() is valid.
bssl_sys::SSL_get1_session(self.ptr())
};
core::ptr::NonNull::new(session).map(crate::sessions::TlsSession)
}
}
impl<R, M> EstablishedTlsConnection<'_, R, M>
where
M: HasTlsConnectionMethod + HasBasicIo,
{
/// Perform synchronising shutdown.
///
/// If the method returns `Ok(None)`, the shutdown will not progress until I/O makes progress.
///
/// # Shutdown protocol
/// A live connection can be actively shut down by calling this method at most two times.
/// The first call will send `close_notify` down the transport.
/// On `Ok` the first call is considered successful with the following return value.
/// - [`ShutdownStatus::CloseNotifyReceived`] signifies that a `close_notify` is received from the peer, too.
/// - [`ShutdownStatus::CloseNotifyPosted`] signifies that a `close_notify` from our end is sent but that from the peer
/// has not arrived.
///
/// In case of no reception of peer `close_notify`, it is necessary to call this method again.
/// There are two possible outcomes.
/// - [`ShutdownStatus::RemainingApplicationData`] signifies that there are pending application data.
/// Process it until the stream ends.
/// - [`ShutdownStatus::CloseNotifyReceived`] signifies that a `close_notify` is received from the peer, too.
/// The connection is then in terminal state.
/// To process the remaining application data, normal reading should continue until the end of
/// stream, at which [`Self::sync_shutdown`] can be called again to set the connection to the terminal state.
pub fn sync_shutdown(&mut self) -> Result<Option<ShutdownStatus>, Error> {
let rc = unsafe {
// Safety: we have exclusive access to the connection state.
bssl_sys::SSL_shutdown(self.ptr())
};
if self.is_write_closed() {
return Ok(Some(ShutdownStatus::EndOfStream));
}
match rc {
0 => Ok(Some(ShutdownStatus::CloseNotifyPosted)),
1 => Ok(Some(ShutdownStatus::CloseNotifyReceived)),
_ => match self.categorise_error_for_io(rc) {
Ok(IoStatus::Ok(_)) => unreachable!(),
Ok(IoStatus::Empty | IoStatus::EndOfStream) => {
Ok(Some(ShutdownStatus::EndOfStream))
}
Ok(IoStatus::Retry(TlsRetryReason::WantRead | TlsRetryReason::WantWrite)) => {
Ok(None)
}
Ok(IoStatus::Retry(reason)) => panic!("unexpected retry reason {reason:?}"),
Err(Error::TlsReason(TlsErrorReason::ApplicationDataOnShutdown)) => {
Ok(Some(ShutdownStatus::RemainingApplicationData))
}
Err(Error::Library(0, _, _)) => Ok(Some(ShutdownStatus::CloseNotifyReceived)),
Ok(IoStatus::Err) => Err(Error::Unknown(Box::new("transport error".to_string()))),
Err(e) => Err(e),
},
}
}
}
impl<R, M> TlsConnectionInHandshake<'_, 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 [`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)),
}
})
}
}
/// Shutdown progress
pub enum ShutdownStatus {
/// `close_notify` has been sent.
CloseNotifyPosted,
/// Peer `close_notify` has been received. The connection is now in terminal state.
CloseNotifyReceived,
/// There are remaining application data. Consume them first before calling `shutdown` again.
RemainingApplicationData,
/// The read half of the connection reaches the end of the stream.
EndOfStream,
}