diff --git a/Cargo.lock b/Cargo.lock index f34db36..353dcc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -371,9 +371,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.171" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libloading" @@ -459,9 +459,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.29.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ "bitflags", "cfg-if", diff --git a/ktls/Cargo.toml b/ktls/Cargo.toml index 73217ca..83124df 100644 --- a/ktls/Cargo.toml +++ b/ktls/Cargo.toml @@ -14,9 +14,9 @@ repository.workspace = true [dependencies] futures-util = "0.3.30" ktls-sys = "1.0.1" -libc = { version = "0.2.155", features = ["const-extern-fn"] } +libc = { version = "0.2.175", features = ["const-extern-fn"] } memoffset = "0.9.1" -nix = { version = "0.29.0", features = ["socket", "uio", "net"] } +nix = { version = "0.30.1", features = ["socket", "uio", "net"] } num_enum = "0.7.3" pin-project-lite = "0.2.14" rustls = { version = "0.23.12", default-features = false } diff --git a/ktls/src/ffi.rs b/ktls/src/ffi.rs index aa1d25d..a3ebc61 100644 --- a/ktls/src/ffi.rs +++ b/ktls/src/ffi.rs @@ -3,7 +3,7 @@ use std::os::unix::prelude::RawFd; use ktls_sys::bindings as ktls; use rustls::internal::msgs::enums::AlertLevel; use rustls::internal::msgs::message::Message; -use rustls::{AlertDescription, ConnectionTrafficSecrets, SupportedCipherSuite}; +use rustls::AlertDescription; pub(crate) const TLS_1_2_VERSION_NUMBER: u16 = (((ktls::TLS_1_2_VERSION_MAJOR & 0xFF) as u16) << 8) | ((ktls::TLS_1_2_VERSION_MINOR & 0xFF) as u16); @@ -11,233 +11,9 @@ pub(crate) const TLS_1_2_VERSION_NUMBER: u16 = (((ktls::TLS_1_2_VERSION_MAJOR & pub(crate) const TLS_1_3_VERSION_NUMBER: u16 = (((ktls::TLS_1_3_VERSION_MAJOR & 0xFF) as u16) << 8) | ((ktls::TLS_1_3_VERSION_MINOR & 0xFF) as u16); -/// `setsockopt` level constant: TCP -const SOL_TCP: libc::c_int = 6; - -/// `setsockopt` SOL_TCP name constant: "upper level protocol" -const TCP_ULP: libc::c_int = 31; - /// `setsockopt` level constant: TLS const SOL_TLS: libc::c_int = 282; -/// `setsockopt` SOL_TLS level constant: transmit (write) -const TLS_TX: libc::c_int = 1; - -/// `setsockopt` SOL_TLS level constant: receive (read) -const TLX_RX: libc::c_int = 2; - -pub fn setup_ulp(fd: RawFd) -> std::io::Result<()> { - unsafe { - if libc::setsockopt( - fd, - SOL_TCP, - TCP_ULP, - "tls".as_ptr() as *const libc::c_void, - 3, - ) < 0 - { - return Err(std::io::Error::last_os_error()); - } - } - - Ok(()) -} - -#[derive(Clone, Copy, Debug)] -pub enum Direction { - // Transmit - Tx, - // Receive - Rx, -} - -impl From for libc::c_int { - fn from(val: Direction) -> Self { - match val { - Direction::Tx => TLS_TX, - Direction::Rx => TLX_RX, - } - } -} - -#[allow(dead_code)] -pub enum CryptoInfo { - AesGcm128(ktls::tls12_crypto_info_aes_gcm_128), - AesGcm256(ktls::tls12_crypto_info_aes_gcm_256), - AesCcm128(ktls::tls12_crypto_info_aes_ccm_128), - Chacha20Poly1305(ktls::tls12_crypto_info_chacha20_poly1305), - Sm4Gcm(ktls::tls12_crypto_info_sm4_gcm), - Sm4Ccm(ktls::tls12_crypto_info_sm4_ccm), -} - -impl CryptoInfo { - /// Return the system struct as a pointer. - pub fn as_ptr(&self) -> *const libc::c_void { - match self { - CryptoInfo::AesGcm128(info) => info as *const _ as *const libc::c_void, - CryptoInfo::AesGcm256(info) => info as *const _ as *const libc::c_void, - CryptoInfo::AesCcm128(info) => info as *const _ as *const libc::c_void, - CryptoInfo::Chacha20Poly1305(info) => info as *const _ as *const libc::c_void, - CryptoInfo::Sm4Gcm(info) => info as *const _ as *const libc::c_void, - CryptoInfo::Sm4Ccm(info) => info as *const _ as *const libc::c_void, - } - } - - /// Return the system struct size. - pub fn size(&self) -> usize { - match self { - CryptoInfo::AesGcm128(_) => std::mem::size_of::(), - CryptoInfo::AesGcm256(_) => std::mem::size_of::(), - CryptoInfo::AesCcm128(_) => std::mem::size_of::(), - CryptoInfo::Chacha20Poly1305(_) => { - std::mem::size_of::() - } - CryptoInfo::Sm4Gcm(_) => std::mem::size_of::(), - CryptoInfo::Sm4Ccm(_) => std::mem::size_of::(), - } - } -} - -#[derive(thiserror::Error, Debug)] -pub enum KtlsCompatibilityError { - #[error("cipher suite not supported with kTLS: {0:?}")] - UnsupportedCipherSuite(SupportedCipherSuite), - - #[error("wrong size key")] - WrongSizeKey, - - #[error("wrong size iv")] - WrongSizeIv, -} - -impl CryptoInfo { - /// Try to convert rustls cipher suite and secrets into a `CryptoInfo`. - pub fn from_rustls( - cipher_suite: SupportedCipherSuite, - (seq, secrets): (u64, ConnectionTrafficSecrets), - ) -> Result { - let version = match cipher_suite { - SupportedCipherSuite::Tls12(..) => TLS_1_2_VERSION_NUMBER, - SupportedCipherSuite::Tls13(..) => TLS_1_3_VERSION_NUMBER, - }; - - Ok(match secrets { - ConnectionTrafficSecrets::Aes128Gcm { key, iv } => { - // see https://github.com/rustls/rustls/issues/1833, between - // rustls 0.21 and 0.22, the extract_keys codepath was changed, - // so, for TLS 1.2, both GCM-128 and GCM-256 return the - // Aes128Gcm variant. - - match key.as_ref().len() { - 16 => CryptoInfo::AesGcm128(ktls::tls12_crypto_info_aes_gcm_128 { - info: ktls::tls_crypto_info { - version, - cipher_type: ktls::TLS_CIPHER_AES_GCM_128 as _, - }, - iv: iv - .as_ref() - .get(4..) - .expect("AES-GCM-128 iv is 8 bytes") - .try_into() - .expect("AES-GCM-128 iv is 8 bytes"), - key: key - .as_ref() - .try_into() - .expect("AES-GCM-128 key is 16 bytes"), - salt: iv - .as_ref() - .get(..4) - .expect("AES-GCM-128 salt is 4 bytes") - .try_into() - .expect("AES-GCM-128 salt is 4 bytes"), - rec_seq: seq.to_be_bytes(), - }), - 32 => CryptoInfo::AesGcm256(ktls::tls12_crypto_info_aes_gcm_256 { - info: ktls::tls_crypto_info { - version, - cipher_type: ktls::TLS_CIPHER_AES_GCM_256 as _, - }, - iv: iv - .as_ref() - .get(4..) - .expect("AES-GCM-256 iv is 8 bytes") - .try_into() - .expect("AES-GCM-256 iv is 8 bytes"), - key: key - .as_ref() - .try_into() - .expect("AES-GCM-256 key is 32 bytes"), - salt: iv - .as_ref() - .get(..4) - .expect("AES-GCM-256 salt is 4 bytes") - .try_into() - .expect("AES-GCM-256 salt is 4 bytes"), - rec_seq: seq.to_be_bytes(), - }), - _ => unreachable!("GCM key length is not 16 or 32"), - } - } - ConnectionTrafficSecrets::Aes256Gcm { key, iv } => { - CryptoInfo::AesGcm256(ktls::tls12_crypto_info_aes_gcm_256 { - info: ktls::tls_crypto_info { - version, - cipher_type: ktls::TLS_CIPHER_AES_GCM_256 as _, - }, - iv: iv - .as_ref() - .get(4..) - .expect("AES-GCM-256 iv is 8 bytes") - .try_into() - .expect("AES-GCM-256 iv is 8 bytes"), - key: key - .as_ref() - .try_into() - .expect("AES-GCM-256 key is 32 bytes"), - salt: iv - .as_ref() - .get(..4) - .expect("AES-GCM-256 salt is 4 bytes") - .try_into() - .expect("AES-GCM-256 salt is 4 bytes"), - rec_seq: seq.to_be_bytes(), - }) - } - ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv } => { - CryptoInfo::Chacha20Poly1305(ktls::tls12_crypto_info_chacha20_poly1305 { - info: ktls::tls_crypto_info { - version, - cipher_type: ktls::TLS_CIPHER_CHACHA20_POLY1305 as _, - }, - iv: iv - .as_ref() - .try_into() - .expect("Chacha20-Poly1305 iv is 12 bytes"), - key: key - .as_ref() - .try_into() - .expect("Chacha20-Poly1305 key is 32 bytes"), - salt: ktls::__IncompleteArrayField::new(), - rec_seq: seq.to_be_bytes(), - }) - } - _ => { - return Err(KtlsCompatibilityError::UnsupportedCipherSuite(cipher_suite)); - } - }) - } -} - -pub fn setup_tls_info(fd: RawFd, dir: Direction, info: CryptoInfo) -> Result<(), crate::Error> { - let ret = unsafe { libc::setsockopt(fd, SOL_TLS, dir.into(), info.as_ptr(), info.size() as _) }; - if ret < 0 { - return Err(crate::Error::TlsCryptoInfoError( - std::io::Error::last_os_error(), - )); - } - Ok(()) -} - const TLS_SET_RECORD_TYPE: libc::c_int = 1; const ALERT: u8 = 0x15; diff --git a/ktls/src/lib.rs b/ktls/src/lib.rs index 68902cd..2df1baf 100644 --- a/ktls/src/lib.rs +++ b/ktls/src/lib.rs @@ -7,14 +7,15 @@ mod async_read_ready; mod cork_stream; mod ffi; mod ktls_stream; +mod setup; use std::future::Future; use std::io; use std::net::SocketAddr; -use std::os::unix::prelude::{AsRawFd, RawFd}; +use std::os::fd::AsFd; +use std::os::unix::prelude::AsRawFd; use futures_util::future::try_join_all; -use ktls_sys::bindings as sys; #[cfg(feature = "aws_lc_rs")] use rustls::crypto::aws_lc_rs::cipher_suite; #[cfg(feature = "ring")] @@ -26,9 +27,8 @@ use tokio::net::{TcpListener, TcpStream}; pub use crate::async_read_ready::AsyncReadReady; pub use crate::cork_stream::CorkStream; -pub use crate::ffi::CryptoInfo; -use crate::ffi::{KtlsCompatibilityError, setup_tls_info, setup_ulp}; pub use crate::ktls_stream::KtlsStream; +pub use crate::setup::{setup_ulp, SetupUlpError}; #[derive(Debug, Default)] pub struct CompatibleCiphers { @@ -159,7 +159,10 @@ impl CompatibleCiphers { } } -fn sample_cipher_setup(sock: &TcpStream, cipher_suite: SupportedCipherSuite) -> Result<(), Error> { +fn sample_cipher_setup( + socket: &TcpStream, + cipher_suite: SupportedCipherSuite, +) -> Result<(), Error> { let kcs = match KtlsCipherSuite::try_from(cipher_suite) { Ok(kcs) => kcs, Err(_) => panic!("unsupported cipher suite"), @@ -171,31 +174,35 @@ fn sample_cipher_setup(sock: &TcpStream, cipher_suite: SupportedCipherSuite) -> }; let crypto_info = match kcs.typ { - KtlsCipherType::AesGcm128 => CryptoInfo::AesGcm128(sys::tls12_crypto_info_aes_gcm_128 { - info: sys::tls_crypto_info { - version: ffi_version, - cipher_type: sys::TLS_CIPHER_AES_GCM_128 as _, - }, - iv: Default::default(), - key: Default::default(), - salt: Default::default(), - rec_seq: Default::default(), - }), - KtlsCipherType::AesGcm256 => CryptoInfo::AesGcm256(sys::tls12_crypto_info_aes_gcm_256 { - info: sys::tls_crypto_info { - version: ffi_version, - cipher_type: sys::TLS_CIPHER_AES_GCM_256 as _, - }, - iv: Default::default(), - key: Default::default(), - salt: Default::default(), - rec_seq: Default::default(), - }), + KtlsCipherType::AesGcm128 => { + setup::TlsCryptoInfo::AesGcm128(libc::tls12_crypto_info_aes_gcm_128 { + info: libc::tls_crypto_info { + version: ffi_version, + cipher_type: libc::TLS_CIPHER_AES_GCM_128 as _, + }, + iv: Default::default(), + key: Default::default(), + salt: Default::default(), + rec_seq: Default::default(), + }) + } + KtlsCipherType::AesGcm256 => { + setup::TlsCryptoInfo::AesGcm256(libc::tls12_crypto_info_aes_gcm_256 { + info: libc::tls_crypto_info { + version: ffi_version, + cipher_type: libc::TLS_CIPHER_AES_GCM_256 as _, + }, + iv: Default::default(), + key: Default::default(), + salt: Default::default(), + rec_seq: Default::default(), + }) + } KtlsCipherType::Chacha20Poly1305 => { - CryptoInfo::Chacha20Poly1305(sys::tls12_crypto_info_chacha20_poly1305 { - info: sys::tls_crypto_info { + setup::TlsCryptoInfo::Chacha20Poly1305(libc::tls12_crypto_info_chacha20_poly1305 { + info: libc::tls_crypto_info { version: ffi_version, - cipher_type: sys::TLS_CIPHER_CHACHA20_POLY1305 as _, + cipher_type: libc::TLS_CIPHER_CHACHA20_POLY1305 as _, }, iv: Default::default(), key: Default::default(), @@ -204,22 +211,20 @@ fn sample_cipher_setup(sock: &TcpStream, cipher_suite: SupportedCipherSuite) -> }) } }; - let fd = sock.as_raw_fd(); - setup_ulp(fd).map_err(Error::UlpError)?; + setup::setup_ulp(socket).map_err(Error::UlpError)?; - setup_tls_info(fd, ffi::Direction::Tx, crypto_info)?; + crypto_info + .set_tx(socket) + .map_err(Error::TlsCryptoInfoError)?; Ok(()) } #[derive(thiserror::Error, Debug)] pub enum Error { - #[error("failed to enable TLS ULP (upper level protocol): {0}")] - UlpError(#[source] std::io::Error), - - #[error("kTLS compatibility error: {0}")] - KtlsCompatibility(#[from] KtlsCompatibilityError), + #[error(transparent)] + UlpError(#[from] setup::SetupUlpError), #[error("failed to export secrets")] ExportSecrets(#[source] rustls::Error), @@ -245,7 +250,7 @@ pub async fn config_ktls_server( mut stream: tokio_rustls::server::TlsStream>, ) -> Result, Error> where - IO: AsRawFd + AsyncRead + AsyncReadReady + AsyncWrite + Unpin, + IO: AsFd + AsRawFd + AsyncRead + AsyncReadReady + AsyncWrite + Unpin, { stream.get_mut().0.corked = true; let drained = drain(&mut stream) @@ -254,7 +259,7 @@ where let (io, conn) = stream.into_inner(); let io = io.io; - setup_inner(io.as_raw_fd(), Connection::Server(conn))?; + setup_inner(&io, Connection::Server(conn))?; Ok(KtlsStream::new(io, drained)) } @@ -268,7 +273,7 @@ pub async fn config_ktls_client( mut stream: tokio_rustls::client::TlsStream>, ) -> Result, Error> where - IO: AsRawFd + AsyncRead + AsyncWrite + Unpin, + IO: AsFd + AsRawFd + AsyncRead + AsyncWrite + Unpin, { stream.get_mut().0.corked = true; let drained = drain(&mut stream) @@ -277,7 +282,7 @@ where let (io, conn) = stream.into_inner(); let io = io.io; - setup_inner(io.as_raw_fd(), Connection::Client(conn))?; + setup_inner(&io, Connection::Client(conn))?; Ok(KtlsStream::new(io, drained)) } @@ -323,7 +328,7 @@ async fn drain(stream: &mut (impl AsyncRead + Unpin)) -> std::io::Result Result<(), Error> { +fn setup_inner(socket: &S, conn: Connection) -> Result<(), Error> { let cipher_suite = match conn.negotiated_cipher_suite() { Some(cipher_suite) => cipher_suite, None => { @@ -336,13 +341,8 @@ fn setup_inner(fd: RawFd, conn: Connection) -> Result<(), Error> { Err(err) => return Err(Error::ExportSecrets(err)), }; - ffi::setup_ulp(fd).map_err(Error::UlpError)?; - - let tx = CryptoInfo::from_rustls(cipher_suite, secrets.tx)?; - setup_tls_info(fd, ffi::Direction::Tx, tx)?; - - let rx = CryptoInfo::from_rustls(cipher_suite, secrets.rx)?; - setup_tls_info(fd, ffi::Direction::Rx, rx)?; + setup::setup_ulp(socket).map_err(Error::UlpError)?; + setup::setup_tls_params(socket, cipher_suite, secrets).map_err(Error::TlsCryptoInfoError)?; Ok(()) } diff --git a/ktls/src/setup.rs b/ktls/src/setup.rs new file mode 100644 index 0000000..732df24 --- /dev/null +++ b/ktls/src/setup.rs @@ -0,0 +1,252 @@ +//! Transport Layer Security (TLS) is a Upper Layer Protocol (ULP) that runs +//! over TCP. TLS provides end-to-end data integrity and confidentiality. +//! +//! Once the TCP connection is established, sets the TLS ULP, which allows us to +//! set/get TLS socket options. +//! +//! This module provides the [`setup_ulp`] function, which sets the ULP (Upper +//! Layer Protocol) to TLS for a TCP socket. The user can also determine whether +//! the kernel supports kTLS with [`setup_ulp`]. +//! +//! After the TLS handshake is completed, we have all the parameters required to +//! move the data-path to the kernel. There is a separate socket option for +//! moving the transmit and the receive into the kernel. +//! +//! This module provides the low-level [`setup_tls_params`] function, which sets +//! the Kernel TLS parameters on the TCP socket, allowing the kernel to handle +//! encryption and decryption of the TLS data. + +use std::os::fd::{AsFd, AsRawFd}; +use std::{io, mem}; + +use nix::sys::socket::{setsockopt, sockopt}; +use rustls::crypto::cipher::NONCE_LEN; +use rustls::{ConnectionTrafficSecrets, ExtractedSecrets, SupportedCipherSuite}; + +/// Sets the TLS Upper Layer Protocol (ULP). +/// +/// This should be called before performing any I/O operations on the +/// socket. +/// +/// # Errors +/// +/// [`SetupUlpError`]. The caller may check if the error is due to the system +/// not supporting kTLS (e.g., kernel module `tls` not being enabled or the +/// kernel version being too old) with [`SetupUlpError::is_ktls_unsupported`]. +pub fn setup_ulp(socket: &S) -> Result<(), SetupUlpError> { + setsockopt(socket, sockopt::TcpUlp::default(), b"tls") + .map_err(io::Error::from) + .map_err(SetupUlpError) +} + +#[derive(Debug, thiserror::Error)] +#[error("Failed to set TLS ULP, error: {0}")] +/// An error that occurred while configuring the ULP. +/// +/// This error wraps the underlying `io::Error` that caused the failure. +/// The caller may check if the error is due to the system not supporting kTLS +/// (e.g., kernel module `tls` not being enabled or the kernel version being too +/// old). +pub struct SetupUlpError(#[source] io::Error); + +impl SetupUlpError { + /// Returns `true` if the error is due to the system not supporting kTLS. + pub fn is_ktls_unsupported(&self) -> bool { + matches!(self.0.raw_os_error(), Some(libc::ENOENT)) + } +} + +impl From for io::Error { + fn from(err: SetupUlpError) -> Self { + io::Error::other(err) + } +} + +/// Sets the kTLS parameters on the socket after the TLS handshake is completed. +/// +/// ## Errors +/// +/// * Invalid crypto materials. +/// * Syscall error. +pub(crate) fn setup_tls_params( + socket: &S, + cipher_suite: SupportedCipherSuite, + secrets: ExtractedSecrets, +) -> io::Result<()> { + TlsCryptoInfo::extract(cipher_suite, secrets.tx)?.set_tx(socket)?; + TlsCryptoInfo::extract(cipher_suite, secrets.rx)?.set_rx(socket)?; + + Ok(()) +} + +#[repr(C)] +#[allow(unused)] +/// A wrapper around the `libc::tls12_crypto_info_*` structs, use with setting +/// up the kTLS r/w parameters on the TCP socket. +/// +/// This is originated from the `nix` crate, which currently does not support +/// `AES-128-CCM` or `SM4-*`, so we implement our own version here. +pub(crate) enum TlsCryptoInfo { + AesGcm128(libc::tls12_crypto_info_aes_gcm_128), + AesGcm256(libc::tls12_crypto_info_aes_gcm_256), + AesCcm128(libc::tls12_crypto_info_aes_ccm_128), + Chacha20Poly1305(libc::tls12_crypto_info_chacha20_poly1305), + Sm4Gcm(libc::tls12_crypto_info_sm4_gcm), + Sm4Ccm(libc::tls12_crypto_info_sm4_ccm), +} + +impl TlsCryptoInfo { + /// Sets the kTLS parameters on the given file descriptor, assuming that the + /// [`TlsCryptoInfo`] is *extract* from the sequence number and + /// secrets for the "tx" (transmit) direction. + pub(crate) fn set_tx(self, socket: &S) -> io::Result<()> { + self.set(socket, libc::TLS_TX) + } + + /// Sets the kTLS parameters on the given file descriptor, assuming that the + /// [`TlsCryptoInfo`] is *extract* from the sequence number and + /// secrets for the "rx" (receive) direction. + pub(crate) fn set_rx(self, socket: &S) -> io::Result<()> { + self.set(socket, libc::TLS_RX) + } + + /// Sets the kTLS parameters on the given file descriptor. + fn set(&self, socket: &S, direction: libc::c_int) -> io::Result<()> { + let (ffi_ptr, ffi_len) = match self { + Self::AesGcm128(crypto_info) => ( + <*const _>::cast(crypto_info), + mem::size_of_val(crypto_info) as libc::socklen_t, + ), + Self::AesGcm256(crypto_info) => ( + <*const _>::cast(crypto_info), + mem::size_of_val(crypto_info) as libc::socklen_t, + ), + Self::AesCcm128(crypto_info) => ( + <*const _>::cast(crypto_info), + mem::size_of_val(crypto_info) as libc::socklen_t, + ), + Self::Chacha20Poly1305(crypto_info) => ( + <*const _>::cast(crypto_info), + mem::size_of_val(crypto_info) as libc::socklen_t, + ), + Self::Sm4Gcm(crypto_info) => ( + <*const _>::cast(crypto_info), + mem::size_of_val(crypto_info) as libc::socklen_t, + ), + Self::Sm4Ccm(crypto_info) => ( + <*const _>::cast(crypto_info), + mem::size_of_val(crypto_info) as libc::socklen_t, + ), + }; + + // SAFETY: syscall + let ret = unsafe { + libc::setsockopt( + socket.as_fd().as_raw_fd(), + libc::SOL_TLS, + direction, + ffi_ptr, + ffi_len, + ) + }; + + if ret < 0 { + return Err(io::Error::last_os_error()); + } + + Ok(()) + } + + /// Extract the [`TlsCryptoInfo`] from the given + /// [`SupportedCipherSuite`] and [`ConnectionTrafficSecrets`]. + fn extract( + cipher_suite: SupportedCipherSuite, + (seq, secrets): (u64, ConnectionTrafficSecrets), + ) -> Result { + let version = match cipher_suite { + #[cfg(feature = "tls12")] + SupportedCipherSuite::Tls12(..) => libc::TLS_1_2_VERSION, + SupportedCipherSuite::Tls13(..) => libc::TLS_1_3_VERSION, + }; + + Ok(match secrets { + ConnectionTrafficSecrets::Aes128Gcm { key, iv } => { + // see https://github.com/rustls/rustls/issues/1833, between + // rustls 0.21 and 0.22, the extract_keys codepath was changed, + // so, for TLS 1.2, both GCM-128 and GCM-256 return the + // Aes128Gcm variant. + // + // This issue is fixed since rustls 0.23. + + let iv_and_salt: &[u8; NONCE_LEN] = iv.as_ref().try_into().unwrap(); + + Self::AesGcm128(libc::tls12_crypto_info_aes_gcm_128 { + info: libc::tls_crypto_info { + version, + cipher_type: libc::TLS_CIPHER_AES_GCM_128, + }, + iv: iv_and_salt[4..].try_into().unwrap(), + key: key + .as_ref() + .try_into() + .map_err(|_| InvalidCryptoInfo::WrongSizeKey)?, + salt: iv_and_salt[..4].try_into().unwrap(), + rec_seq: seq.to_be_bytes(), + }) + } + ConnectionTrafficSecrets::Aes256Gcm { key, iv } => { + let iv_and_salt: &[u8; NONCE_LEN] = iv.as_ref().try_into().unwrap(); + + Self::AesGcm256(libc::tls12_crypto_info_aes_gcm_256 { + info: libc::tls_crypto_info { + version, + cipher_type: libc::TLS_CIPHER_AES_GCM_256, + }, + iv: iv_and_salt[4..].try_into().unwrap(), + key: key + .as_ref() + .try_into() + .map_err(|_| InvalidCryptoInfo::WrongSizeKey)?, + salt: iv_and_salt[..4].try_into().unwrap(), + rec_seq: seq.to_be_bytes(), + }) + } + ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv } => { + Self::Chacha20Poly1305(libc::tls12_crypto_info_chacha20_poly1305 { + info: libc::tls_crypto_info { + version, + cipher_type: libc::TLS_CIPHER_CHACHA20_POLY1305, + }, + iv: iv.as_ref().try_into().unwrap(), + key: key + .as_ref() + .try_into() + .map_err(|_| InvalidCryptoInfo::WrongSizeKey)?, + salt: [], + rec_seq: seq.to_be_bytes(), + }) + } + _ => { + return Err(InvalidCryptoInfo::UnsupportedCipherSuite(cipher_suite)); + } + }) + } +} + +#[derive(Debug, thiserror::Error)] +/// Crypto material is invalid, e.g., wrong size key or IV. +enum InvalidCryptoInfo { + #[error("Wrong size key")] + /// The provided key has an incorrect size (unlikely). + WrongSizeKey, + + #[error("The negotiated cipher suite [{0:?}] is not supported by the current kernel")] + /// The negotiated cipher suite is not supported by the current kernel. + UnsupportedCipherSuite(SupportedCipherSuite), +} + +impl From for io::Error { + fn from(err: InvalidCryptoInfo) -> Self { + io::Error::other(err) + } +}