From f41ae942c9f2ebead3f579b15b851e2d2ffbc6ba Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Thu, 11 Sep 2025 16:05:41 +0800 Subject: [PATCH 1/3] refactor(ffi): use nix to setup_ulp --- Cargo.lock | 8 +++---- ktls/Cargo.toml | 4 ++-- ktls/src/ffi.rs | 57 ++++++++++++++++++++++++++++++++----------------- ktls/src/lib.rs | 39 +++++++++++++++++---------------- 4 files changed, 65 insertions(+), 43 deletions(-) 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..5216ec8 100644 --- a/ktls/src/ffi.rs +++ b/ktls/src/ffi.rs @@ -1,6 +1,9 @@ +use std::io; +use std::os::fd::AsFd; use std::os::unix::prelude::RawFd; use ktls_sys::bindings as ktls; +use nix::sys::socket::{setsockopt, sockopt}; use rustls::internal::msgs::enums::AlertLevel; use rustls::internal::msgs::message::Message; use rustls::{AlertDescription, ConnectionTrafficSecrets, SupportedCipherSuite}; @@ -11,12 +14,6 @@ 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; @@ -26,21 +23,43 @@ 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()); - } +/// 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)) } +} - Ok(()) +impl From for io::Error { + fn from(err: SetupUlpError) -> Self { + io::Error::other(err) + } } #[derive(Clone, Copy, Debug)] diff --git a/ktls/src/lib.rs b/ktls/src/lib.rs index 68902cd..e3f845f 100644 --- a/ktls/src/lib.rs +++ b/ktls/src/lib.rs @@ -11,7 +11,8 @@ mod ktls_stream; 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; @@ -26,8 +27,7 @@ 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::ffi::{setup_ulp, CryptoInfo, KtlsCompatibilityError, SetupUlpError}; pub use crate::ktls_stream::KtlsStream; #[derive(Debug, Default)] @@ -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"), @@ -204,22 +207,22 @@ fn sample_cipher_setup(sock: &TcpStream, cipher_suite: SupportedCipherSuite) -> }) } }; - let fd = sock.as_raw_fd(); + let fd = socket.as_raw_fd(); - setup_ulp(fd).map_err(Error::UlpError)?; + ffi::setup_ulp(socket).map_err(Error::UlpError)?; - setup_tls_info(fd, ffi::Direction::Tx, crypto_info)?; + ffi::setup_tls_info(fd, ffi::Direction::Tx, crypto_info)?; Ok(()) } #[derive(thiserror::Error, Debug)] pub enum Error { - #[error("failed to enable TLS ULP (upper level protocol): {0}")] - UlpError(#[source] std::io::Error), + #[error(transparent)] + UlpError(#[from] ffi::SetupUlpError), #[error("kTLS compatibility error: {0}")] - KtlsCompatibility(#[from] KtlsCompatibilityError), + KtlsCompatibility(#[from] ffi::KtlsCompatibilityError), #[error("failed to export secrets")] ExportSecrets(#[source] rustls::Error), @@ -245,7 +248,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 +257,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 +271,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 +280,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 +326,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 +339,13 @@ fn setup_inner(fd: RawFd, conn: Connection) -> Result<(), Error> { Err(err) => return Err(Error::ExportSecrets(err)), }; - ffi::setup_ulp(fd).map_err(Error::UlpError)?; + ffi::setup_ulp(socket).map_err(Error::UlpError)?; let tx = CryptoInfo::from_rustls(cipher_suite, secrets.tx)?; - setup_tls_info(fd, ffi::Direction::Tx, tx)?; + ffi::setup_tls_info(socket.as_fd().as_raw_fd(), ffi::Direction::Tx, tx)?; let rx = CryptoInfo::from_rustls(cipher_suite, secrets.rx)?; - setup_tls_info(fd, ffi::Direction::Rx, rx)?; + ffi::setup_tls_info(socket.as_fd().as_raw_fd(), ffi::Direction::Rx, rx)?; Ok(()) } From ce40fc26ad43a1256852bd26d0d17a01beeda566 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Thu, 11 Sep 2025 16:23:35 +0800 Subject: [PATCH 2/3] refactor(ffi): setup_tls_params (previous `setup_tls_info`) refactor(ffi): `ffi::CryptoInfo` is renamed to `ffi::TlsCryptoInfo`, and no longer exported --- ktls/src/ffi.rs | 295 +++++++++++++++++++++++------------------------- ktls/src/lib.rs | 68 ++++++----- 2 files changed, 175 insertions(+), 188 deletions(-) diff --git a/ktls/src/ffi.rs b/ktls/src/ffi.rs index 5216ec8..7bd8c12 100644 --- a/ktls/src/ffi.rs +++ b/ktls/src/ffi.rs @@ -1,12 +1,13 @@ -use std::io; -use std::os::fd::AsFd; +use std::os::fd::{AsFd, AsRawFd}; use std::os::unix::prelude::RawFd; +use std::{io, mem}; use ktls_sys::bindings as ktls; use nix::sys::socket::{setsockopt, sockopt}; +use rustls::crypto::cipher::NONCE_LEN; use rustls::internal::msgs::enums::AlertLevel; use rustls::internal::msgs::message::Message; -use rustls::{AlertDescription, ConnectionTrafficSecrets, SupportedCipherSuite}; +use rustls::{AlertDescription, ConnectionTrafficSecrets, ExtractedSecrets, SupportedCipherSuite}; 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); @@ -17,12 +18,6 @@ pub(crate) const TLS_1_3_VERSION_NUMBER: u16 = (((ktls::TLS_1_3_VERSION_MAJOR & /// `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; - /// Sets the TLS Upper Layer Protocol (ULP). /// /// This should be called before performing any I/O operations on the @@ -62,82 +57,131 @@ impl From for io::Error { } } -#[derive(Clone, Copy, Debug)] -pub enum Direction { - // Transmit - Tx, - // Receive - Rx, -} +/// 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)?; -impl From for libc::c_int { - fn from(val: Direction) -> Self { - match val { - Direction::Tx => TLS_TX, - Direction::Rx => TLX_RX, - } - } + Ok(()) } -#[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), +#[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 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, - } +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) } - /// 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::(), + /// 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(()) } } -#[derive(thiserror::Error, Debug)] -pub enum KtlsCompatibilityError { - #[error("cipher suite not supported with kTLS: {0:?}")] - UnsupportedCipherSuite(SupportedCipherSuite), - - #[error("wrong size key")] +#[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("wrong size iv")] - WrongSizeIv, + #[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) + } } -impl CryptoInfo { - /// Try to convert rustls cipher suite and secrets into a `CryptoInfo`. - pub fn from_rustls( +impl TlsCryptoInfo { + /// Extract the [`TlsCryptoInfo`] from the given + /// [`SupportedCipherSuite`] and [`ConnectionTrafficSecrets`]. + fn extract( cipher_suite: SupportedCipherSuite, (seq, secrets): (u64, ConnectionTrafficSecrets), - ) -> Result { + ) -> Result { let version = match cipher_suite { - SupportedCipherSuite::Tls12(..) => TLS_1_2_VERSION_NUMBER, - SupportedCipherSuite::Tls13(..) => TLS_1_3_VERSION_NUMBER, + #[cfg(feature = "tls12")] + SupportedCipherSuite::Tls12(..) => libc::TLS_1_2_VERSION, + SupportedCipherSuite::Tls13(..) => libc::TLS_1_3_VERSION, }; Ok(match secrets { @@ -146,117 +190,64 @@ impl CryptoInfo { // 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. - 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 { + 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: ktls::TLS_CIPHER_AES_GCM_256 as _, + cipher_type: libc::TLS_CIPHER_AES_GCM_128, }, - iv: iv - .as_ref() - .get(4..) - .expect("AES-GCM-256 iv is 8 bytes") - .try_into() - .expect("AES-GCM-256 iv is 8 bytes"), + iv: iv_and_salt[4..].try_into().unwrap(), key: key .as_ref() .try_into() - .expect("AES-GCM-256 key is 32 bytes"), - salt: iv + .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() - .get(..4) - .expect("AES-GCM-256 salt is 4 bytes") .try_into() - .expect("AES-GCM-256 salt is 4 bytes"), + .map_err(|_| InvalidCryptoInfo::WrongSizeKey)?, + salt: iv_and_salt[..4].try_into().unwrap(), rec_seq: seq.to_be_bytes(), }) } ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv } => { - CryptoInfo::Chacha20Poly1305(ktls::tls12_crypto_info_chacha20_poly1305 { - info: ktls::tls_crypto_info { + Self::Chacha20Poly1305(libc::tls12_crypto_info_chacha20_poly1305 { + info: libc::tls_crypto_info { version, - cipher_type: ktls::TLS_CIPHER_CHACHA20_POLY1305 as _, + cipher_type: libc::TLS_CIPHER_CHACHA20_POLY1305, }, - iv: iv - .as_ref() - .try_into() - .expect("Chacha20-Poly1305 iv is 12 bytes"), + iv: iv.as_ref().try_into().unwrap(), key: key .as_ref() .try_into() - .expect("Chacha20-Poly1305 key is 32 bytes"), - salt: ktls::__IncompleteArrayField::new(), + .map_err(|_| InvalidCryptoInfo::WrongSizeKey)?, + salt: [], rec_seq: seq.to_be_bytes(), }) } _ => { - return Err(KtlsCompatibilityError::UnsupportedCipherSuite(cipher_suite)); + return Err(InvalidCryptoInfo::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 e3f845f..039d249 100644 --- a/ktls/src/lib.rs +++ b/ktls/src/lib.rs @@ -15,7 +15,6 @@ 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")] @@ -27,7 +26,7 @@ use tokio::net::{TcpListener, TcpStream}; pub use crate::async_read_ready::AsyncReadReady; pub use crate::cork_stream::CorkStream; -pub use crate::ffi::{setup_ulp, CryptoInfo, KtlsCompatibilityError, SetupUlpError}; +pub use crate::ffi::{setup_ulp, SetupUlpError}; pub use crate::ktls_stream::KtlsStream; #[derive(Debug, Default)] @@ -174,31 +173,35 @@ fn sample_cipher_setup( }; 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 => { + ffi::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 => { + ffi::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 { + ffi::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(), @@ -207,11 +210,12 @@ fn sample_cipher_setup( }) } }; - let fd = socket.as_raw_fd(); ffi::setup_ulp(socket).map_err(Error::UlpError)?; - ffi::setup_tls_info(fd, ffi::Direction::Tx, crypto_info)?; + crypto_info + .set_tx(socket) + .map_err(Error::TlsCryptoInfoError)?; Ok(()) } @@ -221,9 +225,6 @@ pub enum Error { #[error(transparent)] UlpError(#[from] ffi::SetupUlpError), - #[error("kTLS compatibility error: {0}")] - KtlsCompatibility(#[from] ffi::KtlsCompatibilityError), - #[error("failed to export secrets")] ExportSecrets(#[source] rustls::Error), @@ -340,12 +341,7 @@ fn setup_inner(socket: &S, conn: Connection) -> Result<(), Error> { }; ffi::setup_ulp(socket).map_err(Error::UlpError)?; - - let tx = CryptoInfo::from_rustls(cipher_suite, secrets.tx)?; - ffi::setup_tls_info(socket.as_fd().as_raw_fd(), ffi::Direction::Tx, tx)?; - - let rx = CryptoInfo::from_rustls(cipher_suite, secrets.rx)?; - ffi::setup_tls_info(socket.as_fd().as_raw_fd(), ffi::Direction::Rx, rx)?; + ffi::setup_tls_params(socket, cipher_suite, secrets).map_err(Error::TlsCryptoInfoError)?; Ok(()) } From 219e0f0f0eb7cf98ef623c252375aaf1ba994a98 Mon Sep 17 00:00:00 2001 From: Hantong Chen Date: Thu, 11 Sep 2025 16:28:20 +0800 Subject: [PATCH 3/3] refactor(setup): move `setup_*` and related stuff to separate module --- ktls/src/ffi.rs | 236 +------------------------------------------ ktls/src/lib.rs | 17 ++-- ktls/src/setup.rs | 252 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 262 insertions(+), 243 deletions(-) create mode 100644 ktls/src/setup.rs diff --git a/ktls/src/ffi.rs b/ktls/src/ffi.rs index 7bd8c12..a3ebc61 100644 --- a/ktls/src/ffi.rs +++ b/ktls/src/ffi.rs @@ -1,13 +1,9 @@ -use std::os::fd::{AsFd, AsRawFd}; use std::os::unix::prelude::RawFd; -use std::{io, mem}; use ktls_sys::bindings as ktls; -use nix::sys::socket::{setsockopt, sockopt}; -use rustls::crypto::cipher::NONCE_LEN; use rustls::internal::msgs::enums::AlertLevel; use rustls::internal::msgs::message::Message; -use rustls::{AlertDescription, ConnectionTrafficSecrets, ExtractedSecrets, 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); @@ -18,236 +14,6 @@ pub(crate) const TLS_1_3_VERSION_NUMBER: u16 = (((ktls::TLS_1_3_VERSION_MAJOR & /// `setsockopt` level constant: TLS const SOL_TLS: libc::c_int = 282; -/// 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(()) - } -} - -#[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) - } -} - -impl TlsCryptoInfo { - /// 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)); - } - }) - } -} - 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 039d249..2df1baf 100644 --- a/ktls/src/lib.rs +++ b/ktls/src/lib.rs @@ -7,6 +7,7 @@ mod async_read_ready; mod cork_stream; mod ffi; mod ktls_stream; +mod setup; use std::future::Future; use std::io; @@ -26,8 +27,8 @@ use tokio::net::{TcpListener, TcpStream}; pub use crate::async_read_ready::AsyncReadReady; pub use crate::cork_stream::CorkStream; -pub use crate::ffi::{setup_ulp, SetupUlpError}; pub use crate::ktls_stream::KtlsStream; +pub use crate::setup::{setup_ulp, SetupUlpError}; #[derive(Debug, Default)] pub struct CompatibleCiphers { @@ -174,7 +175,7 @@ fn sample_cipher_setup( let crypto_info = match kcs.typ { KtlsCipherType::AesGcm128 => { - ffi::TlsCryptoInfo::AesGcm128(libc::tls12_crypto_info_aes_gcm_128 { + 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 _, @@ -186,7 +187,7 @@ fn sample_cipher_setup( }) } KtlsCipherType::AesGcm256 => { - ffi::TlsCryptoInfo::AesGcm256(libc::tls12_crypto_info_aes_gcm_256 { + 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 _, @@ -198,7 +199,7 @@ fn sample_cipher_setup( }) } KtlsCipherType::Chacha20Poly1305 => { - ffi::TlsCryptoInfo::Chacha20Poly1305(libc::tls12_crypto_info_chacha20_poly1305 { + setup::TlsCryptoInfo::Chacha20Poly1305(libc::tls12_crypto_info_chacha20_poly1305 { info: libc::tls_crypto_info { version: ffi_version, cipher_type: libc::TLS_CIPHER_CHACHA20_POLY1305 as _, @@ -211,7 +212,7 @@ fn sample_cipher_setup( } }; - ffi::setup_ulp(socket).map_err(Error::UlpError)?; + setup::setup_ulp(socket).map_err(Error::UlpError)?; crypto_info .set_tx(socket) @@ -223,7 +224,7 @@ fn sample_cipher_setup( #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] - UlpError(#[from] ffi::SetupUlpError), + UlpError(#[from] setup::SetupUlpError), #[error("failed to export secrets")] ExportSecrets(#[source] rustls::Error), @@ -340,8 +341,8 @@ fn setup_inner(socket: &S, conn: Connection) -> Result<(), Error> { Err(err) => return Err(Error::ExportSecrets(err)), }; - ffi::setup_ulp(socket).map_err(Error::UlpError)?; - ffi::setup_tls_params(socket, cipher_suite, secrets).map_err(Error::TlsCryptoInfoError)?; + 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) + } +}