diff options
| author | mo khan <mo@mokhan.ca> | 2025-07-10 13:11:11 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2025-07-10 13:11:11 -0600 |
| commit | 01959b16a21b22b5df5f16569c2a8e8f92beecef (patch) | |
| tree | 32afa5d747c5466345c59ec52161a7cba3d6d755 /vendor/rustls/src/conn | |
| parent | ff30574117a996df332e23d1fb6f65259b316b5b (diff) | |
chore: vendor dependencies
Diffstat (limited to 'vendor/rustls/src/conn')
| -rw-r--r-- | vendor/rustls/src/conn/kernel.rs | 268 | ||||
| -rw-r--r-- | vendor/rustls/src/conn/unbuffered.rs | 613 |
2 files changed, 881 insertions, 0 deletions
diff --git a/vendor/rustls/src/conn/kernel.rs b/vendor/rustls/src/conn/kernel.rs new file mode 100644 index 00000000..b90fad39 --- /dev/null +++ b/vendor/rustls/src/conn/kernel.rs @@ -0,0 +1,268 @@ +//! Kernel connection API. +//! +//! This module gives you the bare minimum you need to implement a TLS connection +//! that does its own encryption and decryption while still using rustls to manage +//! connection secrets and session tickets. It is intended for use cases like kTLS +//! where you want to use rustls to establish the connection but want to use +//! something else to do the encryption/decryption after that. +//! +//! There are only two things that [`KernelConnection`] is able to do: +//! 1. Compute new traffic secrets when a key update occurs. +//! 2. Save received session tickets sent by a server peer. +//! +//! That's it. Everything else you will need to implement yourself. +//! +//! # Entry Point +//! The entry points into this API are +//! [`UnbufferedClientConnection::dangerous_into_kernel_connection`][client-into] +//! and +//! [`UnbufferedServerConnection::dangerous_into_kernel_connection`][server-into]. +//! +//! In order to actually create an [`KernelConnection`] all of the following +//! must be true: +//! - the connection must have completed its handshake, +//! - the connection must have no buffered TLS data waiting to be sent, and, +//! - the config used to create the connection must have `enable_extract_secrets` +//! set to true. +//! +//! This sounds fairly complicated to achieve at first glance. However, if you +//! drive an unbuffered connection through the handshake until it returns +//! [`WriteTraffic`] then it will end up in an appropriate state to convert +//! into an external connection. +//! +//! [client-into]: crate::client::UnbufferedClientConnection::dangerous_into_kernel_connection +//! [server-into]: crate::server::UnbufferedServerConnection::dangerous_into_kernel_connection +//! [`WriteTraffic`]: crate::unbuffered::ConnectionState::WriteTraffic +//! +//! # Cipher Suite Confidentiality Limits +//! Some cipher suites (notably AES-GCM) have vulnerabilities where they are no +//! longer secure once a certain number of messages have been sent. Normally, +//! rustls tracks how many messages have been written or read and will +//! automatically either refresh keys or emit an error when approaching the +//! confidentiality limit of the cipher suite. +//! +//! [`KernelConnection`] has no way to track this. It is the responsibility +//! of the user of the API to track approximately how many messages have been +//! sent and either refresh the traffic keys or abort the connection before the +//! confidentiality limit is reached. +//! +//! You can find the current confidentiality limit by looking at +//! [`CipherSuiteCommon::confidentiality_limit`] for the cipher suite selected +//! by the connection. +//! +//! [`CipherSuiteCommon::confidentiality_limit`]: crate::CipherSuiteCommon::confidentiality_limit +//! [`KernelConnection`]: crate::kernel::KernelConnection + +use alloc::boxed::Box; +use core::marker::PhantomData; + +use crate::client::ClientConnectionData; +use crate::common_state::Protocol; +use crate::msgs::codec::Codec; +use crate::msgs::handshake::{CertificateChain, NewSessionTicketPayloadTls13}; +use crate::quic::Quic; +use crate::{CommonState, ConnectionTrafficSecrets, Error, ProtocolVersion, SupportedCipherSuite}; + +/// A kernel connection. +/// +/// This does not directly wrap a kernel connection, rather it gives you the +/// minimal interfaces you need to implement a well-behaved TLS connection on +/// top of kTLS. +/// +/// See the [`crate::kernel`] module docs for more details. +pub struct KernelConnection<Data> { + state: Box<dyn KernelState>, + + peer_certificates: Option<CertificateChain<'static>>, + quic: Quic, + + negotiated_version: ProtocolVersion, + protocol: Protocol, + suite: SupportedCipherSuite, + + _data: PhantomData<Data>, +} + +impl<Data> KernelConnection<Data> { + pub(crate) fn new(state: Box<dyn KernelState>, common: CommonState) -> Result<Self, Error> { + Ok(Self { + state, + + peer_certificates: common.peer_certificates, + quic: common.quic, + negotiated_version: common + .negotiated_version + .ok_or(Error::HandshakeNotComplete)?, + protocol: common.protocol, + suite: common + .suite + .ok_or(Error::HandshakeNotComplete)?, + + _data: PhantomData, + }) + } + + /// Retrieves the ciphersuite agreed with the peer. + pub fn negotiated_cipher_suite(&self) -> SupportedCipherSuite { + self.suite + } + + /// Retrieves the protocol version agreed with the peer. + pub fn protocol_version(&self) -> ProtocolVersion { + self.negotiated_version + } + + /// Update the traffic secret used for encrypting messages sent to the peer. + /// + /// Returns the new traffic secret and initial sequence number to use. + /// + /// In order to use the new secret you should send a TLS 1.3 key update to + /// the peer and then use the new traffic secrets to encrypt any future + /// messages. + /// + /// Note that it is only possible to update the traffic secrets on a TLS 1.3 + /// connection. Attempting to do so on a non-TLS 1.3 connection will result + /// in an error. + pub fn update_tx_secret(&mut self) -> Result<(u64, ConnectionTrafficSecrets), Error> { + // The sequence number always starts at 0 after a key update. + self.state + .update_secrets(Direction::Transmit) + .map(|secret| (0, secret)) + } + + /// Update the traffic secret used for decrypting messages received from the + /// peer. + /// + /// Returns the new traffic secret and initial sequence number to use. + /// + /// You should call this method once you receive a TLS 1.3 key update message + /// from the peer. + /// + /// Note that it is only possible to update the traffic secrets on a TLS 1.3 + /// connection. Attempting to do so on a non-TLS 1.3 connection will result + /// in an error. + pub fn update_rx_secret(&mut self) -> Result<(u64, ConnectionTrafficSecrets), Error> { + // The sequence number always starts at 0 after a key update. + self.state + .update_secrets(Direction::Receive) + .map(|secret| (0, secret)) + } +} + +impl KernelConnection<ClientConnectionData> { + /// Handle a `new_session_ticket` message from the peer. + /// + /// This will register the session ticket within with rustls so that it can + /// be used to establish future TLS connections. + /// + /// # Getting the right payload + /// + /// This method expects to be passed the inner payload of the handshake + /// message. This means that you will need to parse the header of the + /// handshake message in order to determine the correct payload to pass in. + /// The message format is described in [RFC 8446 section 4][0]. `payload` + /// should not include the `msg_type` or `length` fields. + /// + /// Code to parse out the payload should look something like this + /// ```no_run + /// use rustls::{ContentType, HandshakeType}; + /// use rustls::kernel::KernelConnection; + /// use rustls::client::ClientConnectionData; + /// + /// # fn doctest(conn: &mut KernelConnection<ClientConnectionData>, typ: ContentType, message: &[u8]) -> Result<(), rustls::Error> { + /// let conn: &mut KernelConnection<ClientConnectionData> = // ... + /// # conn; + /// let typ: ContentType = // ... + /// # typ; + /// let mut message: &[u8] = // ... + /// # message; + /// + /// // Processing for other messages not included in this example + /// assert_eq!(typ, ContentType::Handshake); + /// + /// // There may be multiple handshake payloads within a single handshake message. + /// while !message.is_empty() { + /// let (typ, len, rest) = match message { + /// &[typ, a, b, c, ref rest @ ..] => ( + /// HandshakeType::from(typ), + /// u32::from_be_bytes([0, a, b, c]) as usize, + /// rest + /// ), + /// _ => panic!("error handling not included in this example") + /// }; + /// + /// // Processing for other messages not included in this example. + /// assert_eq!(typ, HandshakeType::NewSessionTicket); + /// assert!(rest.len() >= len, "invalid handshake message"); + /// + /// let (payload, rest) = rest.split_at(len); + /// message = rest; + /// + /// conn.handle_new_session_ticket(payload)?; + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// This method will return an error if: + /// - This connection is not a TLS 1.3 connection (in TLS 1.2 session tickets + /// are sent as part of the handshake). + /// - The provided payload is not a valid `new_session_ticket` payload or has + /// extra unparsed trailing data. + /// - An error occurs while the connection updates the session ticket store. + /// + /// [0]: https://datatracker.ietf.org/doc/html/rfc8446#section-4 + pub fn handle_new_session_ticket(&mut self, payload: &[u8]) -> Result<(), Error> { + // We want to return a more specific error here first if this is called + // on a non-TLS 1.3 connection since a parsing error isn't the real issue + // here. + if self.protocol_version() != ProtocolVersion::TLSv1_3 { + return Err(Error::General( + "TLS 1.2 session tickets may not be sent once the handshake has completed".into(), + )); + } + + let nst = NewSessionTicketPayloadTls13::read_bytes(payload)?; + let mut cx = KernelContext { + peer_certificates: self.peer_certificates.as_ref(), + protocol: self.protocol, + quic: &self.quic, + }; + self.state + .handle_new_session_ticket(&mut cx, &nst) + } +} + +pub(crate) trait KernelState: Send + Sync { + /// Update the traffic secret for the specified direction on the connection. + fn update_secrets(&mut self, dir: Direction) -> Result<ConnectionTrafficSecrets, Error>; + + /// Handle a new session ticket. + /// + /// This will only ever be called for client connections, as [`KernelConnection`] + /// only exposes the relevant API for client connections. + fn handle_new_session_ticket( + &mut self, + cx: &mut KernelContext<'_>, + message: &NewSessionTicketPayloadTls13, + ) -> Result<(), Error>; +} + +pub(crate) struct KernelContext<'a> { + pub(crate) peer_certificates: Option<&'a CertificateChain<'static>>, + pub(crate) protocol: Protocol, + pub(crate) quic: &'a Quic, +} + +impl KernelContext<'_> { + pub(crate) fn is_quic(&self) -> bool { + self.protocol == Protocol::Quic + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(crate) enum Direction { + Transmit, + Receive, +} diff --git a/vendor/rustls/src/conn/unbuffered.rs b/vendor/rustls/src/conn/unbuffered.rs new file mode 100644 index 00000000..84fe3f09 --- /dev/null +++ b/vendor/rustls/src/conn/unbuffered.rs @@ -0,0 +1,613 @@ +//! Unbuffered connection API + +use alloc::vec::Vec; +use core::num::NonZeroUsize; +use core::{fmt, mem}; +#[cfg(feature = "std")] +use std::error::Error as StdError; + +use super::UnbufferedConnectionCommon; +use crate::Error; +use crate::client::ClientConnectionData; +use crate::msgs::deframer::buffers::DeframerSliceBuffer; +use crate::server::ServerConnectionData; + +impl UnbufferedConnectionCommon<ClientConnectionData> { + /// Processes the TLS records in `incoming_tls` buffer until a new [`UnbufferedStatus`] is + /// reached. + pub fn process_tls_records<'c, 'i>( + &'c mut self, + incoming_tls: &'i mut [u8], + ) -> UnbufferedStatus<'c, 'i, ClientConnectionData> { + self.process_tls_records_common(incoming_tls, |_| false, |_, _| unreachable!()) + } +} + +impl UnbufferedConnectionCommon<ServerConnectionData> { + /// Processes the TLS records in `incoming_tls` buffer until a new [`UnbufferedStatus`] is + /// reached. + pub fn process_tls_records<'c, 'i>( + &'c mut self, + incoming_tls: &'i mut [u8], + ) -> UnbufferedStatus<'c, 'i, ServerConnectionData> { + self.process_tls_records_common( + incoming_tls, + |conn| conn.peek_early_data().is_some(), + |conn, incoming_tls| ReadEarlyData::new(conn, incoming_tls).into(), + ) + } +} + +impl<Data> UnbufferedConnectionCommon<Data> { + fn process_tls_records_common<'c, 'i>( + &'c mut self, + incoming_tls: &'i mut [u8], + mut early_data_available: impl FnMut(&mut Self) -> bool, + early_data_state: impl FnOnce(&'c mut Self, &'i mut [u8]) -> ConnectionState<'c, 'i, Data>, + ) -> UnbufferedStatus<'c, 'i, Data> { + let mut buffer = DeframerSliceBuffer::new(incoming_tls); + let mut buffer_progress = self.core.hs_deframer.progress(); + + let (discard, state) = loop { + if early_data_available(self) { + break ( + buffer.pending_discard(), + early_data_state(self, incoming_tls), + ); + } + + if !self + .core + .common_state + .received_plaintext + .is_empty() + { + break ( + buffer.pending_discard(), + ReadTraffic::new(self, incoming_tls).into(), + ); + } + + if let Some(chunk) = self + .core + .common_state + .sendable_tls + .pop() + { + break ( + buffer.pending_discard(), + EncodeTlsData::new(self, chunk).into(), + ); + } + + let deframer_output = + match self + .core + .deframe(None, buffer.filled_mut(), &mut buffer_progress) + { + Err(err) => { + buffer.queue_discard(buffer_progress.take_discard()); + return UnbufferedStatus { + discard: buffer.pending_discard(), + state: Err(err), + }; + } + Ok(r) => r, + }; + + if let Some(msg) = deframer_output { + let mut state = + match mem::replace(&mut self.core.state, Err(Error::HandshakeNotComplete)) { + Ok(state) => state, + Err(e) => { + buffer.queue_discard(buffer_progress.take_discard()); + self.core.state = Err(e.clone()); + return UnbufferedStatus { + discard: buffer.pending_discard(), + state: Err(e), + }; + } + }; + + match self.core.process_msg(msg, state, None) { + Ok(new) => state = new, + + Err(e) => { + buffer.queue_discard(buffer_progress.take_discard()); + self.core.state = Err(e.clone()); + return UnbufferedStatus { + discard: buffer.pending_discard(), + state: Err(e), + }; + } + } + + buffer.queue_discard(buffer_progress.take_discard()); + + self.core.state = Ok(state); + } else if self.wants_write { + break ( + buffer.pending_discard(), + TransmitTlsData { conn: self }.into(), + ); + } else if self + .core + .common_state + .has_received_close_notify + && !self.emitted_peer_closed_state + { + self.emitted_peer_closed_state = true; + break (buffer.pending_discard(), ConnectionState::PeerClosed); + } else if self + .core + .common_state + .has_received_close_notify + && self + .core + .common_state + .has_sent_close_notify + { + break (buffer.pending_discard(), ConnectionState::Closed); + } else if self + .core + .common_state + .may_send_application_data + { + break ( + buffer.pending_discard(), + ConnectionState::WriteTraffic(WriteTraffic { conn: self }), + ); + } else { + break (buffer.pending_discard(), ConnectionState::BlockedHandshake); + } + }; + + UnbufferedStatus { + discard, + state: Ok(state), + } + } +} + +/// The current status of the `UnbufferedConnection*` +#[must_use] +#[derive(Debug)] +pub struct UnbufferedStatus<'c, 'i, Data> { + /// Number of bytes to discard + /// + /// After the `state` field of this object has been handled, `discard` bytes must be + /// removed from the *front* of the `incoming_tls` buffer that was passed to + /// the [`UnbufferedConnectionCommon::process_tls_records`] call that returned this object. + /// + /// This discard operation MUST happen *before* + /// [`UnbufferedConnectionCommon::process_tls_records`] is called again. + pub discard: usize, + + /// The current state of the handshake process + /// + /// This value MUST be handled prior to calling + /// [`UnbufferedConnectionCommon::process_tls_records`] again. See the documentation on the + /// variants of [`ConnectionState`] for more details. + pub state: Result<ConnectionState<'c, 'i, Data>, Error>, +} + +/// The state of the [`UnbufferedConnectionCommon`] object +#[non_exhaustive] // for forwards compatibility; to support caller-side certificate verification +pub enum ConnectionState<'c, 'i, Data> { + /// One, or more, application data records are available + /// + /// See [`ReadTraffic`] for more details on how to use the enclosed object to access + /// the received data. + ReadTraffic(ReadTraffic<'c, 'i, Data>), + + /// Connection has been cleanly closed by the peer. + /// + /// This state is encountered at most once by each connection -- it is + /// "edge" triggered, rather than "level" triggered. + /// + /// It delimits the data received from the peer, meaning you can be sure you + /// have received all the data the peer sent. + /// + /// No further application data will be received from the peer, so no further + /// `ReadTraffic` states will be produced. + /// + /// However, it is possible to _send_ further application data via `WriteTraffic` + /// states, or close the connection cleanly by calling + /// [`WriteTraffic::queue_close_notify()`]. + PeerClosed, + + /// Connection has been cleanly closed by both us and the peer. + /// + /// This is a terminal state. No other states will be produced for this + /// connection. + Closed, + + /// One, or more, early (RTT-0) data records are available + ReadEarlyData(ReadEarlyData<'c, 'i, Data>), + + /// A Handshake record is ready for encoding + /// + /// Call [`EncodeTlsData::encode`] on the enclosed object, providing an `outgoing_tls` + /// buffer to store the encoding + EncodeTlsData(EncodeTlsData<'c, Data>), + + /// Previously encoded handshake records need to be transmitted + /// + /// Transmit the contents of the `outgoing_tls` buffer that was passed to previous + /// [`EncodeTlsData::encode`] calls to the peer. + /// + /// After transmitting the contents, call [`TransmitTlsData::done`] on the enclosed object. + /// The transmitted contents MUST not be sent to the peer more than once so they SHOULD be + /// discarded at this point. + /// + /// At some stages of the handshake process, it's possible to send application-data alongside + /// handshake records. Call [`TransmitTlsData::may_encrypt_app_data`] on the enclosed + /// object to probe if that's allowed. + TransmitTlsData(TransmitTlsData<'c, Data>), + + /// More TLS data is needed to continue with the handshake + /// + /// Request more data from the peer and append the contents to the `incoming_tls` buffer that + /// was passed to [`UnbufferedConnectionCommon::process_tls_records`]. + BlockedHandshake, + + /// The handshake process has been completed. + /// + /// [`WriteTraffic::encrypt`] can be called on the enclosed object to encrypt application + /// data into an `outgoing_tls` buffer. Similarly, [`WriteTraffic::queue_close_notify`] can + /// be used to encrypt a close_notify alert message into a buffer to signal the peer that the + /// connection is being closed. Data written into `outgoing_buffer` by either method MAY be + /// transmitted to the peer during this state. + /// + /// Once this state has been reached, data MAY be requested from the peer and appended to an + /// `incoming_tls` buffer that will be passed to a future + /// [`UnbufferedConnectionCommon::process_tls_records`] invocation. When enough data has been + /// appended to `incoming_tls`, [`UnbufferedConnectionCommon::process_tls_records`] will yield + /// the [`ConnectionState::ReadTraffic`] state. + WriteTraffic(WriteTraffic<'c, Data>), +} + +impl<'c, 'i, Data> From<ReadTraffic<'c, 'i, Data>> for ConnectionState<'c, 'i, Data> { + fn from(v: ReadTraffic<'c, 'i, Data>) -> Self { + Self::ReadTraffic(v) + } +} + +impl<'c, 'i, Data> From<ReadEarlyData<'c, 'i, Data>> for ConnectionState<'c, 'i, Data> { + fn from(v: ReadEarlyData<'c, 'i, Data>) -> Self { + Self::ReadEarlyData(v) + } +} + +impl<'c, Data> From<EncodeTlsData<'c, Data>> for ConnectionState<'c, '_, Data> { + fn from(v: EncodeTlsData<'c, Data>) -> Self { + Self::EncodeTlsData(v) + } +} + +impl<'c, Data> From<TransmitTlsData<'c, Data>> for ConnectionState<'c, '_, Data> { + fn from(v: TransmitTlsData<'c, Data>) -> Self { + Self::TransmitTlsData(v) + } +} + +impl<Data> fmt::Debug for ConnectionState<'_, '_, Data> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::ReadTraffic(..) => f.debug_tuple("ReadTraffic").finish(), + + Self::PeerClosed => write!(f, "PeerClosed"), + + Self::Closed => write!(f, "Closed"), + + Self::ReadEarlyData(..) => f.debug_tuple("ReadEarlyData").finish(), + + Self::EncodeTlsData(..) => f.debug_tuple("EncodeTlsData").finish(), + + Self::TransmitTlsData(..) => f + .debug_tuple("TransmitTlsData") + .finish(), + + Self::BlockedHandshake => f + .debug_tuple("BlockedHandshake") + .finish(), + + Self::WriteTraffic(..) => f.debug_tuple("WriteTraffic").finish(), + } + } +} + +/// Application data is available +pub struct ReadTraffic<'c, 'i, Data> { + conn: &'c mut UnbufferedConnectionCommon<Data>, + // for forwards compatibility; to support in-place decryption in the future + _incoming_tls: &'i mut [u8], + + // owner of the latest chunk obtained in `next_record`, as borrowed by + // `AppDataRecord` + chunk: Option<Vec<u8>>, +} + +impl<'c, 'i, Data> ReadTraffic<'c, 'i, Data> { + fn new(conn: &'c mut UnbufferedConnectionCommon<Data>, _incoming_tls: &'i mut [u8]) -> Self { + Self { + conn, + _incoming_tls, + chunk: None, + } + } + + /// Decrypts and returns the next available app-data record + // TODO deprecate in favor of `Iterator` implementation, which requires in-place decryption + pub fn next_record(&mut self) -> Option<Result<AppDataRecord<'_>, Error>> { + self.chunk = self + .conn + .core + .common_state + .received_plaintext + .pop(); + self.chunk.as_ref().map(|chunk| { + Ok(AppDataRecord { + discard: 0, + payload: chunk, + }) + }) + } + + /// Returns the payload size of the next app-data record *without* decrypting it + /// + /// Returns `None` if there are no more app-data records + pub fn peek_len(&self) -> Option<NonZeroUsize> { + self.conn + .core + .common_state + .received_plaintext + .peek() + .and_then(|ch| NonZeroUsize::new(ch.len())) + } +} + +/// Early application-data is available. +pub struct ReadEarlyData<'c, 'i, Data> { + conn: &'c mut UnbufferedConnectionCommon<Data>, + + // for forwards compatibility; to support in-place decryption in the future + _incoming_tls: &'i mut [u8], + + // owner of the latest chunk obtained in `next_record`, as borrowed by + // `AppDataRecord` + chunk: Option<Vec<u8>>, +} + +impl<'c, 'i> ReadEarlyData<'c, 'i, ServerConnectionData> { + fn new( + conn: &'c mut UnbufferedConnectionCommon<ServerConnectionData>, + _incoming_tls: &'i mut [u8], + ) -> Self { + Self { + conn, + _incoming_tls, + chunk: None, + } + } + + /// decrypts and returns the next available app-data record + // TODO deprecate in favor of `Iterator` implementation, which requires in-place decryption + pub fn next_record(&mut self) -> Option<Result<AppDataRecord<'_>, Error>> { + self.chunk = self.conn.pop_early_data(); + self.chunk.as_ref().map(|chunk| { + Ok(AppDataRecord { + discard: 0, + payload: chunk, + }) + }) + } + + /// returns the payload size of the next app-data record *without* decrypting it + /// + /// returns `None` if there are no more app-data records + pub fn peek_len(&self) -> Option<NonZeroUsize> { + self.conn + .peek_early_data() + .and_then(|ch| NonZeroUsize::new(ch.len())) + } +} + +/// A decrypted application-data record +pub struct AppDataRecord<'i> { + /// Number of additional bytes to discard + /// + /// This number MUST be added to the value of [`UnbufferedStatus.discard`] *prior* to the + /// discard operation. See [`UnbufferedStatus.discard`] for more details + pub discard: usize, + + /// The payload of the app-data record + pub payload: &'i [u8], +} + +/// Allows encrypting app-data +pub struct WriteTraffic<'c, Data> { + conn: &'c mut UnbufferedConnectionCommon<Data>, +} + +impl<Data> WriteTraffic<'_, Data> { + /// Encrypts `application_data` into the `outgoing_tls` buffer + /// + /// Returns the number of bytes that were written into `outgoing_tls`, or an error if + /// the provided buffer is too small. In the error case, `outgoing_tls` is not modified + pub fn encrypt( + &mut self, + application_data: &[u8], + outgoing_tls: &mut [u8], + ) -> Result<usize, EncryptError> { + self.conn + .core + .maybe_refresh_traffic_keys(); + self.conn + .core + .common_state + .write_plaintext(application_data.into(), outgoing_tls) + } + + /// Encrypts a close_notify warning alert in `outgoing_tls` + /// + /// Returns the number of bytes that were written into `outgoing_tls`, or an error if + /// the provided buffer is too small. In the error case, `outgoing_tls` is not modified + pub fn queue_close_notify(&mut self, outgoing_tls: &mut [u8]) -> Result<usize, EncryptError> { + self.conn + .core + .common_state + .eager_send_close_notify(outgoing_tls) + } + + /// Arranges for a TLS1.3 `key_update` to be sent. + /// + /// This consumes the `WriteTraffic` state: to actually send the message, + /// call [`UnbufferedConnectionCommon::process_tls_records`] again which will + /// return a `ConnectionState::EncodeTlsData` that emits the `key_update` + /// message. + /// + /// See [`ConnectionCommon::refresh_traffic_keys()`] for full documentation, + /// including why you might call this and in what circumstances it will fail. + /// + /// [`ConnectionCommon::refresh_traffic_keys()`]: crate::ConnectionCommon::refresh_traffic_keys + pub fn refresh_traffic_keys(self) -> Result<(), Error> { + self.conn.core.refresh_traffic_keys() + } +} + +/// A handshake record must be encoded +pub struct EncodeTlsData<'c, Data> { + conn: &'c mut UnbufferedConnectionCommon<Data>, + chunk: Option<Vec<u8>>, +} + +impl<'c, Data> EncodeTlsData<'c, Data> { + fn new(conn: &'c mut UnbufferedConnectionCommon<Data>, chunk: Vec<u8>) -> Self { + Self { + conn, + chunk: Some(chunk), + } + } + + /// Encodes a handshake record into the `outgoing_tls` buffer + /// + /// Returns the number of bytes that were written into `outgoing_tls`, or an error if + /// the provided buffer is too small. In the error case, `outgoing_tls` is not modified + pub fn encode(&mut self, outgoing_tls: &mut [u8]) -> Result<usize, EncodeError> { + let Some(chunk) = self.chunk.take() else { + return Err(EncodeError::AlreadyEncoded); + }; + + let required_size = chunk.len(); + + if required_size > outgoing_tls.len() { + self.chunk = Some(chunk); + Err(InsufficientSizeError { required_size }.into()) + } else { + let written = chunk.len(); + outgoing_tls[..written].copy_from_slice(&chunk); + + self.conn.wants_write = true; + + Ok(written) + } + } +} + +/// Previously encoded TLS data must be transmitted +pub struct TransmitTlsData<'c, Data> { + pub(crate) conn: &'c mut UnbufferedConnectionCommon<Data>, +} + +impl<Data> TransmitTlsData<'_, Data> { + /// Signals that the previously encoded TLS data has been transmitted + pub fn done(self) { + self.conn.wants_write = false; + } + + /// Returns an adapter that allows encrypting application data + /// + /// If allowed at this stage of the handshake process + pub fn may_encrypt_app_data(&mut self) -> Option<WriteTraffic<'_, Data>> { + if self + .conn + .core + .common_state + .may_send_application_data + { + Some(WriteTraffic { conn: self.conn }) + } else { + None + } + } +} + +/// Errors that may arise when encoding a handshake record +#[derive(Debug)] +pub enum EncodeError { + /// Provided buffer was too small + InsufficientSize(InsufficientSizeError), + + /// The handshake record has already been encoded; do not call `encode` again + AlreadyEncoded, +} + +impl From<InsufficientSizeError> for EncodeError { + fn from(v: InsufficientSizeError) -> Self { + Self::InsufficientSize(v) + } +} + +impl fmt::Display for EncodeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InsufficientSize(InsufficientSizeError { required_size }) => write!( + f, + "cannot encode due to insufficient size, {required_size} bytes are required" + ), + Self::AlreadyEncoded => "cannot encode, data has already been encoded".fmt(f), + } + } +} + +#[cfg(feature = "std")] +impl StdError for EncodeError {} + +/// Errors that may arise when encrypting application data +#[derive(Debug)] +pub enum EncryptError { + /// Provided buffer was too small + InsufficientSize(InsufficientSizeError), + + /// Encrypter has been exhausted + EncryptExhausted, +} + +impl From<InsufficientSizeError> for EncryptError { + fn from(v: InsufficientSizeError) -> Self { + Self::InsufficientSize(v) + } +} + +impl fmt::Display for EncryptError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InsufficientSize(InsufficientSizeError { required_size }) => write!( + f, + "cannot encrypt due to insufficient size, {required_size} bytes are required" + ), + Self::EncryptExhausted => f.write_str("encrypter has been exhausted"), + } + } +} + +#[cfg(feature = "std")] +impl StdError for EncryptError {} + +/// Provided buffer was too small +#[derive(Clone, Copy, Debug)] +pub struct InsufficientSizeError { + /// buffer must be at least this size + pub required_size: usize, +} |
