From d24c9765a4a6f6f9eef245e86dc4908144d95cbc Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 5 Oct 2023 16:53:43 +0200 Subject: [PATCH 01/27] add std feature Since aws-lc-rs doesn't support no-std it's moved from the default features to the std features. Similarly we must tweak our `once_cell` usage to provide the `race` feature for builds without `std`. See the upstream[0] docs section on "Does this crate support no_std?" for some important caveats. [0]: https://docs.rs/once_cell/latest/once_cell/ --- provider-example/Cargo.toml | 2 +- rustls/Cargo.toml | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/provider-example/Cargo.toml b/provider-example/Cargo.toml index 1ae2918516..dedca2f25d 100644 --- a/provider-example/Cargo.toml +++ b/provider-example/Cargo.toml @@ -18,7 +18,7 @@ p256 = { version = "0.13.2", default-features = false, features = ["alloc", "ecd pkcs8 = "0.10.2" pki-types = { package = "rustls-pki-types", version = "1" } rand_core = { version = "0.6", features = ["getrandom"] } -rustls = { path = "../rustls", default-features = false, features = ["logging", "tls12"] } +rustls = { path = "../rustls", default-features = false, features = ["logging", "std", "tls12"] } rsa = { version = "0.9", features = ["sha2"], default-features = false } sha2 = { version = "0.10", default-features = false } signature = "2" diff --git a/rustls/Cargo.toml b/rustls/Cargo.toml index ff4555c446..666446bf92 100644 --- a/rustls/Cargo.toml +++ b/rustls/Cargo.toml @@ -19,20 +19,21 @@ rustversion = { version = "1.0.6", optional = true } aws-lc-rs = { version = "1.6", optional = true, default-features = false, features = ["aws-lc-sys"] } log = { version = "0.4.4", optional = true } # remove once our MSRV is >= 1.70 -once_cell = "1" +once_cell = { version = "1.16", default-features = false, features = ["alloc", "race"] } ring = { version = "0.17", optional = true } subtle = { version = "2.5.0", default-features = false } -webpki = { package = "rustls-webpki", version = "0.102.2", features = ["std"], default-features = false } -pki-types = { package = "rustls-pki-types", version = "1.2", features = ["std"] } +webpki = { package = "rustls-webpki", version = "0.102.2", features = ["alloc"], default-features = false } +pki-types = { package = "rustls-pki-types", version = "1.2", features = ["alloc"] } zeroize = "1.7" [features] -default = ["aws_lc_rs", "logging", "tls12"] +default = ["logging", "std", "tls12"] +std = ["aws_lc_rs", "webpki/std", "pki-types/std", "once_cell/std"] logging = ["log"] -aws_lc_rs = ["dep:aws-lc-rs", "webpki/aws_lc_rs"] +aws_lc_rs = ["dep:aws-lc-rs", "webpki/aws_lc_rs", "std"] ring = ["dep:ring", "webpki/ring"] tls12 = [] -read_buf = ["rustversion"] +read_buf = ["rustversion", "std"] fips = ["aws_lc_rs", "aws-lc-rs?/fips"] [dev-dependencies] From ddc42517202a5af77845d0e8e85d1b2b5de58555 Mon Sep 17 00:00:00 2001 From: Christian Poveda Date: Wed, 6 Dec 2023 15:31:26 -0500 Subject: [PATCH 02/27] no_std: rm use of `std` in `builder` --- rustls/src/builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rustls/src/builder.rs b/rustls/src/builder.rs index 03a70408f6..aad5276654 100644 --- a/rustls/src/builder.rs +++ b/rustls/src/builder.rs @@ -3,10 +3,10 @@ use crate::versions; use crate::{crypto::CryptoProvider, msgs::handshake::ALL_KEY_EXCHANGE_ALGORITHMS}; use alloc::format; +use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt; use core::marker::PhantomData; -use std::sync::Arc; #[cfg(doc)] use crate::{ClientConfig, ServerConfig}; From 39c8f74107b6bc7c6469ee8c0b428b414a93c735 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 5 Oct 2023 17:45:21 +0200 Subject: [PATCH 03/27] no-std: add TimeProvider to ClientConfig For no-std users, the new `ClientConfig::builder_with_details()` must be used, which requires a `TimeProvider` implementation up-front. For std builds, the `ClientConfig` uses the `DefaultTimeProvider` for existing `ClientConfig::builder*` functions. --- rustls/src/builder.rs | 4 +++ rustls/src/client/builder.rs | 5 +++ rustls/src/client/client_conn.rs | 53 ++++++++++++++++++++++++++++++-- rustls/src/client/hs.rs | 9 ++++-- rustls/src/client/tls12.rs | 17 ++++++++-- rustls/src/client/tls13.rs | 13 ++++++-- rustls/src/lib.rs | 2 ++ rustls/src/server/server_conn.rs | 6 +++- rustls/src/time_provider.rs | 30 ++++++++++++++++++ rustls/tests/api.rs | 18 ++++++++--- 10 files changed, 141 insertions(+), 16 deletions(-) create mode 100644 rustls/src/time_provider.rs diff --git a/rustls/src/builder.rs b/rustls/src/builder.rs index aad5276654..4ccd82b891 100644 --- a/rustls/src/builder.rs +++ b/rustls/src/builder.rs @@ -1,4 +1,5 @@ use crate::error::Error; +use crate::time_provider::TimeProvider; use crate::versions; use crate::{crypto::CryptoProvider, msgs::handshake::ALL_KEY_EXCHANGE_ALGORITHMS}; @@ -184,6 +185,7 @@ impl fmt::Debug for ConfigBuilder, + pub(crate) time_provider: Arc, } impl ConfigBuilder { @@ -248,6 +250,7 @@ impl ConfigBuilder { state: WantsVerifier { provider: self.state.provider, versions: versions::EnabledVersions::new(versions), + time_provider: self.state.time_provider, }, side: self.side, }) @@ -261,6 +264,7 @@ impl ConfigBuilder { pub struct WantsVerifier { pub(crate) provider: Arc, pub(crate) versions: versions::EnabledVersions, + pub(crate) time_provider: Arc, } /// Helper trait to abstract [`ConfigBuilder`] over building a [`ClientConfig`] or [`ServerConfig`]. diff --git a/rustls/src/client/builder.rs b/rustls/src/client/builder.rs index f57f38fc39..ac2df168c1 100644 --- a/rustls/src/client/builder.rs +++ b/rustls/src/client/builder.rs @@ -5,6 +5,7 @@ use crate::crypto::CryptoProvider; use crate::error::Error; use crate::key_log::NoKeyLog; use crate::msgs::handshake::CertificateChain; +use crate::time_provider::TimeProvider; use crate::webpki::{self, WebPkiServerVerifier}; use crate::{verify, versions}; @@ -56,6 +57,7 @@ impl ConfigBuilder { provider: self.state.provider, versions: self.state.versions, verifier, + time_provider: self.state.time_provider, }, side: PhantomData, } @@ -94,6 +96,7 @@ pub(super) mod danger { provider: self.cfg.state.provider, versions: self.cfg.state.versions, verifier, + time_provider: self.cfg.state.time_provider, }, side: PhantomData, } @@ -110,6 +113,7 @@ pub struct WantsClientCert { provider: Arc, versions: versions::EnabledVersions, verifier: Arc, + time_provider: Arc, } impl ConfigBuilder { @@ -161,6 +165,7 @@ impl ConfigBuilder { enable_early_data: false, #[cfg(feature = "tls12")] require_ems: cfg!(feature = "fips"), + time_provider: self.state.time_provider, } } } diff --git a/rustls/src/client/client_conn.rs b/rustls/src/client/client_conn.rs index f46f42ec4c..a8e6560258 100644 --- a/rustls/src/client/client_conn.rs +++ b/rustls/src/client/client_conn.rs @@ -11,15 +11,20 @@ use crate::msgs::handshake::ClientExtension; use crate::msgs::persist; use crate::sign; use crate::suites::{ExtractedSecrets, SupportedCipherSuite}; +#[cfg(feature = "std")] +use crate::time_provider::DefaultTimeProvider; +use crate::time_provider::TimeProvider; use crate::unbuffered::{EncryptError, TransmitTlsData}; use crate::versions; use crate::KeyLog; -use crate::{verify, WantsVerifier, WantsVersions}; +#[cfg(feature = "std")] +use crate::WantsVerifier; +use crate::{verify, WantsVersions}; use super::handy::{ClientSessionMemoryCache, NoClientSessionStorage}; use super::hs; -use pki_types::ServerName; +use pki_types::{ServerName, UnixTime}; use alloc::sync::Arc; use alloc::vec::Vec; @@ -206,6 +211,9 @@ pub struct ClientConfig { #[cfg(feature = "tls12")] pub require_ems: bool, + /// Provides the current system time + pub time_provider: Arc, + /// Source of randomness and other crypto. pub(super) provider: Arc, @@ -223,6 +231,7 @@ impl ClientConfig { /// and safe protocol version defaults. /// /// For more information, see the [`ConfigBuilder`] documentation. + #[cfg(feature = "std")] pub fn builder() -> ConfigBuilder { Self::builder_with_protocol_versions(versions::DEFAULT_VERSIONS) } @@ -239,6 +248,7 @@ impl ClientConfig { /// the crate features and process default. /// /// For more information, see the [`ConfigBuilder`] documentation. + #[cfg(feature = "std")] pub fn builder_with_protocol_versions( versions: &[&'static versions::SupportedProtocolVersion], ) -> ConfigBuilder { @@ -260,11 +270,41 @@ impl ClientConfig { /// version is not supported by the provider's ciphersuites. /// /// For more information, see the [`ConfigBuilder`] documentation. + #[cfg(feature = "std")] pub fn builder_with_provider( provider: Arc, ) -> ConfigBuilder { ConfigBuilder { - state: WantsVersions { provider }, + state: WantsVersions { + provider, + time_provider: Arc::new(DefaultTimeProvider), + }, + side: PhantomData, + } + } + /// Create a builder for a client configuration with no default implementation details. + /// + /// This API must be used by `no_std` users. + /// + /// You must provide a specific [`TimeProvider`]. + /// + /// You must provide a specific [`CryptoProvider`]. + /// + /// This will use the provider's configured ciphersuites. You must additionally choose + /// which protocol versions to enable, using `with_protocol_versions` or + /// `with_safe_default_protocol_versions` and handling the `Result` in case a protocol + /// version is not supported by the provider's ciphersuites. + /// + /// For more information, see the [`ConfigBuilder`] documentation. + pub fn builder_with_details( + provider: Arc, + time_provider: Arc, + ) -> ConfigBuilder { + ConfigBuilder { + state: WantsVersions { + provider, + time_provider, + }, side: PhantomData, } } @@ -327,6 +367,12 @@ impl ClientConfig { .copied() .find(|skxg| skxg.name() == group) } + + pub(super) fn current_time(&self) -> Result { + self.time_provider + .current_time() + .ok_or(Error::FailedToGetCurrentTime) + } } impl Clone for ClientConfig { @@ -345,6 +391,7 @@ impl Clone for ClientConfig { enable_early_data: self.enable_early_data, #[cfg(feature = "tls12")] require_ems: self.require_ems, + time_provider: Arc::clone(&self.time_provider), } } } diff --git a/rustls/src/client/hs.rs b/rustls/src/client/hs.rs index d3ce656280..053e077a95 100644 --- a/rustls/src/client/hs.rs +++ b/rustls/src/client/hs.rs @@ -30,7 +30,7 @@ use crate::client::client_conn::ClientConnectionData; use crate::client::common::ClientHelloDetails; use crate::client::{tls13, ClientConfig}; -use pki_types::{ServerName, UnixTime}; +use pki_types::ServerName; use alloc::borrow::ToOwned; use alloc::boxed::Box; @@ -68,7 +68,12 @@ fn find_session( None }) .and_then(|resuming| { - let retrieved = persist::Retrieved::new(resuming, UnixTime::now()); + let now = config + .current_time() + .map_err(|_err| debug!("Could not get current time: {_err}")) + .ok()?; + + let retrieved = persist::Retrieved::new(resuming, now); match retrieved.has_expired() { false => Some(retrieved), true => None, diff --git a/rustls/src/client/tls12.rs b/rustls/src/client/tls12.rs index 197317785b..65ffbb428e 100644 --- a/rustls/src/client/tls12.rs +++ b/rustls/src/client/tls12.rs @@ -28,7 +28,7 @@ use crate::client::common::ClientAuthDetails; use crate::client::common::ServerCertDetails; use crate::client::{hs, ClientConfig}; -use pki_types::{ServerName, UnixTime}; +use pki_types::ServerName; use subtle::ConstantTimeEq; use alloc::borrow::ToOwned; @@ -857,6 +857,9 @@ impl State for ExpectServerDone<'_> { .cert_chain .split_first() .ok_or(Error::NoCertificatesPresented)?; + + let now = st.config.current_time()?; + let cert_verified = st .config .verifier @@ -865,7 +868,7 @@ impl State for ExpectServerDone<'_> { intermediates, &st.server_name, &st.server_cert.ocsp_response, - UnixTime::now(), + now, ) .map_err(|err| { cx.common @@ -1163,6 +1166,14 @@ impl ExpectFinished { return; } + let now = match self.config.current_time() { + Ok(now) => now, + Err(_) => { + debug!("Could not get current time"); + return; + } + }; + let session_value = persist::Tls12ClientSessionValue::new( self.secrets.suite(), self.session_id, @@ -1172,7 +1183,7 @@ impl ExpectFinished { .peer_certificates .clone() .unwrap_or_default(), - UnixTime::now(), + now, lifetime, self.using_ems, ); diff --git a/rustls/src/client/tls13.rs b/rustls/src/client/tls13.rs index ba1b8287b5..d9ddd65c7d 100644 --- a/rustls/src/client/tls13.rs +++ b/rustls/src/client/tls13.rs @@ -40,7 +40,7 @@ use crate::client::common::ServerCertDetails; use crate::client::common::{ClientAuthDetails, ClientHelloDetails}; use crate::client::{hs, ClientConfig, ClientSessionStore}; -use pki_types::{ServerName, UnixTime}; +use pki_types::ServerName; use subtle::ConstantTimeEq; use alloc::boxed::Box; @@ -713,6 +713,9 @@ impl State for ExpectCertificateVerify<'_> { .cert_chain .split_first() .ok_or(Error::NoCertificatesPresented)?; + + let now = self.config.current_time()?; + let cert_verified = self .config .verifier @@ -721,7 +724,7 @@ impl State for ExpectCertificateVerify<'_> { intermediates, &self.server_name, &self.server_cert.ocsp_response, - UnixTime::now(), + now, ) .map_err(|err| { cx.common @@ -968,6 +971,7 @@ impl State for ExpectFinished { .start_traffic(&mut cx.sendable_plaintext); let st = ExpectTraffic { + config: Arc::clone(&st.config), session_storage: Arc::clone(&st.config.resumption.store), server_name: st.server_name, suite: st.suite, @@ -993,6 +997,7 @@ impl State for ExpectFinished { // In this state we can be sent tickets, key updates, // and application data. struct ExpectTraffic { + config: Arc, session_storage: Arc, server_name: ServerName<'static>, suite: &'static Tls13CipherSuite, @@ -1021,6 +1026,8 @@ impl ExpectTraffic { .key_schedule .resumption_master_secret_and_derive_ticket_psk(&handshake_hash, &nst.nonce.0); + let now = self.config.current_time()?; + #[allow(unused_mut)] let mut value = persist::Tls13ClientSessionValue::new( self.suite, @@ -1030,7 +1037,7 @@ impl ExpectTraffic { .peer_certificates .clone() .unwrap_or_default(), - UnixTime::now(), + now, nst.lifetime, nst.age_add, nst.max_early_data_size() diff --git a/rustls/src/lib.rs b/rustls/src/lib.rs index cb0366fd25..9d8cbfc6e8 100644 --- a/rustls/src/lib.rs +++ b/rustls/src/lib.rs @@ -612,3 +612,5 @@ pub mod ticketer; /// This is the rustls manual. pub mod manual; + +pub mod time_provider; diff --git a/rustls/src/server/server_conn.rs b/rustls/src/server/server_conn.rs index 79f7d2b22b..7190144336 100644 --- a/rustls/src/server/server_conn.rs +++ b/rustls/src/server/server_conn.rs @@ -10,6 +10,7 @@ use crate::msgs::base::Payload; use crate::msgs::handshake::{ClientHelloPayload, ProtocolName, ServerExtension}; use crate::msgs::message::Message; use crate::suites::ExtractedSecrets; +use crate::time_provider::{DefaultTimeProvider, TimeProvider}; use crate::vecbuf::ChunkVecBuffer; use crate::verify; use crate::versions; @@ -400,7 +401,10 @@ impl ServerConfig { provider: Arc, ) -> ConfigBuilder { ConfigBuilder { - state: WantsVersions { provider }, + state: WantsVersions { + provider, + time_provider: Arc::new(DefaultTimeProvider), + }, side: PhantomData, } } diff --git a/rustls/src/time_provider.rs b/rustls/src/time_provider.rs new file mode 100644 index 0000000000..e43f4d6ddf --- /dev/null +++ b/rustls/src/time_provider.rs @@ -0,0 +1,30 @@ +//! The library's source of time. + +use core::fmt::Debug; + +use pki_types::UnixTime; + +/// An object that provides the current time. +/// +/// This is used to, for example, check if a certificate has expired during +/// certificate validation, or to check the age of a ticket. +pub trait TimeProvider: Debug + Send + Sync { + /// Returns the current wall time. + /// + /// This is not required to be monotonic. + /// + /// Return `None` if unable to retrieve the time. + fn current_time(&self) -> Option; +} + +#[derive(Debug)] +#[cfg(feature = "std")] +/// Default `TimeProvider` implementation that uses `std` +pub struct DefaultTimeProvider; + +#[cfg(feature = "std")] +impl TimeProvider for DefaultTimeProvider { + fn current_time(&self) -> Option { + Some(UnixTime::now()) + } +} diff --git a/rustls/tests/api.rs b/rustls/tests/api.rs index eb36ac1ee4..5040c334d0 100644 --- a/rustls/tests/api.rs +++ b/rustls/tests/api.rs @@ -332,6 +332,16 @@ fn config_builder_for_server_rejects_incompatible_cipher_suites() { ); } +#[test] +fn config_builder_for_client_with_time() { + ClientConfig::builder_with_details( + provider::default_provider().into(), + Arc::new(rustls::time_provider::DefaultTimeProvider), + ) + .with_safe_default_protocol_versions() + .unwrap(); +} + #[test] fn buffered_client_data_sent() { let server_config = Arc::new(make_server_config(KeyType::Rsa)); @@ -497,10 +507,10 @@ fn test_config_builders_debug() { } .into(), ); - assert_eq!("ConfigBuilder { state: WantsVersions { provider: CryptoProvider { cipher_suites: [TLS13_CHACHA20_POLY1305_SHA256], kx_groups: [X25519], signature_verification_algorithms: WebPkiSupportedAlgorithms { all: [ .. ], mapping: [ECDSA_NISTP384_SHA384, ECDSA_NISTP256_SHA256, ED25519, RSA_PSS_SHA512, RSA_PSS_SHA384, RSA_PSS_SHA256, RSA_PKCS1_SHA512, RSA_PKCS1_SHA384, RSA_PKCS1_SHA256] }, secure_random: Ring, key_provider: Ring } } }", format!("{:?}", b)); + assert_eq!("ConfigBuilder { state: WantsVersions { provider: CryptoProvider { cipher_suites: [TLS13_CHACHA20_POLY1305_SHA256], kx_groups: [X25519], signature_verification_algorithms: WebPkiSupportedAlgorithms { all: [ .. ], mapping: [ECDSA_NISTP384_SHA384, ECDSA_NISTP256_SHA256, ED25519, RSA_PSS_SHA512, RSA_PSS_SHA384, RSA_PSS_SHA256, RSA_PKCS1_SHA512, RSA_PKCS1_SHA384, RSA_PKCS1_SHA256] }, secure_random: Ring, key_provider: Ring }, time_provider: DefaultTimeProvider } }", format!("{:?}", b)); let b = server_config_builder_with_versions(&[&rustls::version::TLS13]); assert_eq!( - "ConfigBuilder { state: WantsVerifier { provider: CryptoProvider { cipher_suites: [TLS13_AES_256_GCM_SHA384, TLS13_AES_128_GCM_SHA256, TLS13_CHACHA20_POLY1305_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256], kx_groups: [X25519, secp256r1, secp384r1], signature_verification_algorithms: WebPkiSupportedAlgorithms { all: [ .. ], mapping: [ECDSA_NISTP384_SHA384, ECDSA_NISTP256_SHA256, ED25519, RSA_PSS_SHA512, RSA_PSS_SHA384, RSA_PSS_SHA256, RSA_PKCS1_SHA512, RSA_PKCS1_SHA384, RSA_PKCS1_SHA256] }, secure_random: Ring, key_provider: Ring }, versions: [TLSv1_3] } }", + "ConfigBuilder { state: WantsVerifier { provider: CryptoProvider { cipher_suites: [TLS13_AES_256_GCM_SHA384, TLS13_AES_128_GCM_SHA256, TLS13_CHACHA20_POLY1305_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256], kx_groups: [X25519, secp256r1, secp384r1], signature_verification_algorithms: WebPkiSupportedAlgorithms { all: [ .. ], mapping: [ECDSA_NISTP384_SHA384, ECDSA_NISTP256_SHA256, ED25519, RSA_PSS_SHA512, RSA_PSS_SHA384, RSA_PSS_SHA256, RSA_PKCS1_SHA512, RSA_PKCS1_SHA384, RSA_PKCS1_SHA256] }, secure_random: Ring, key_provider: Ring }, versions: [TLSv1_3], time_provider: DefaultTimeProvider } }", format!("{:?}", b) ); let b = b.with_no_client_auth(); @@ -514,10 +524,10 @@ fn test_config_builders_debug() { } .into(), ); - assert_eq!("ConfigBuilder { state: WantsVersions { provider: CryptoProvider { cipher_suites: [TLS13_CHACHA20_POLY1305_SHA256], kx_groups: [X25519], signature_verification_algorithms: WebPkiSupportedAlgorithms { all: [ .. ], mapping: [ECDSA_NISTP384_SHA384, ECDSA_NISTP256_SHA256, ED25519, RSA_PSS_SHA512, RSA_PSS_SHA384, RSA_PSS_SHA256, RSA_PKCS1_SHA512, RSA_PKCS1_SHA384, RSA_PKCS1_SHA256] }, secure_random: Ring, key_provider: Ring } } }", format!("{:?}", b)); + assert_eq!("ConfigBuilder { state: WantsVersions { provider: CryptoProvider { cipher_suites: [TLS13_CHACHA20_POLY1305_SHA256], kx_groups: [X25519], signature_verification_algorithms: WebPkiSupportedAlgorithms { all: [ .. ], mapping: [ECDSA_NISTP384_SHA384, ECDSA_NISTP256_SHA256, ED25519, RSA_PSS_SHA512, RSA_PSS_SHA384, RSA_PSS_SHA256, RSA_PKCS1_SHA512, RSA_PKCS1_SHA384, RSA_PKCS1_SHA256] }, secure_random: Ring, key_provider: Ring }, time_provider: DefaultTimeProvider } }", format!("{:?}", b)); let b = client_config_builder_with_versions(&[&rustls::version::TLS13]); assert_eq!( - "ConfigBuilder { state: WantsVerifier { provider: CryptoProvider { cipher_suites: [TLS13_AES_256_GCM_SHA384, TLS13_AES_128_GCM_SHA256, TLS13_CHACHA20_POLY1305_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256], kx_groups: [X25519, secp256r1, secp384r1], signature_verification_algorithms: WebPkiSupportedAlgorithms { all: [ .. ], mapping: [ECDSA_NISTP384_SHA384, ECDSA_NISTP256_SHA256, ED25519, RSA_PSS_SHA512, RSA_PSS_SHA384, RSA_PSS_SHA256, RSA_PKCS1_SHA512, RSA_PKCS1_SHA384, RSA_PKCS1_SHA256] }, secure_random: Ring, key_provider: Ring }, versions: [TLSv1_3] } }", + "ConfigBuilder { state: WantsVerifier { provider: CryptoProvider { cipher_suites: [TLS13_AES_256_GCM_SHA384, TLS13_AES_128_GCM_SHA256, TLS13_CHACHA20_POLY1305_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256], kx_groups: [X25519, secp256r1, secp384r1], signature_verification_algorithms: WebPkiSupportedAlgorithms { all: [ .. ], mapping: [ECDSA_NISTP384_SHA384, ECDSA_NISTP256_SHA256, ED25519, RSA_PSS_SHA512, RSA_PSS_SHA384, RSA_PSS_SHA256, RSA_PKCS1_SHA512, RSA_PKCS1_SHA384, RSA_PKCS1_SHA256] }, secure_random: Ring, key_provider: Ring }, versions: [TLSv1_3], time_provider: DefaultTimeProvider } }", format!("{:?}", b) ); } From 256e3c754bae62b0c92b22e049291915ce861640 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 5 Oct 2023 16:57:25 +0200 Subject: [PATCH 04/27] no-std: remove field from `OtherError` --- rustls/src/error.rs | 88 +++++++++++++++++++++++++++------------- rustls/src/webpki/mod.rs | 14 +++++-- 2 files changed, 70 insertions(+), 32 deletions(-) diff --git a/rustls/src/error.rs b/rustls/src/error.rs index 3ac65772ba..f9d4ddd9d3 100644 --- a/rustls/src/error.rs +++ b/rustls/src/error.rs @@ -4,7 +4,6 @@ use crate::rand; use alloc::format; use alloc::string::String; -use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt; use std::error::Error as StdError; @@ -387,7 +386,7 @@ impl From for AlertDescription { // certificate_unknown // Some other (unspecified) issue arose in processing the // certificate, rendering it unacceptable. - Other(_) => Self::CertificateUnknown, + Other(..) => Self::CertificateUnknown, } } } @@ -544,43 +543,64 @@ impl From for Error { } } -/// Any other error that cannot be expressed by a more specific [`Error`] variant. -/// -/// For example, an `OtherError` could be produced by a custom crypto provider -/// exposing a provider specific error. -/// -/// Enums holding this type will never compare equal to each other. -#[derive(Debug, Clone)] -pub struct OtherError(pub Arc); +mod other_error { + #[cfg(feature = "std")] + use alloc::sync::Arc; + use core::fmt; + #[cfg(feature = "std")] + use std::error::Error as StdError; -impl PartialEq for OtherError { - fn eq(&self, _other: &Self) -> bool { - false + use super::Error; + + /// Any other error that cannot be expressed by a more specific [`Error`] variant. + /// + /// For example, an `OtherError` could be produced by a custom crypto provider + /// exposing a provider specific error. + /// + /// Enums holding this type will never compare equal to each other. + #[derive(Debug, Clone)] + pub struct OtherError(#[cfg(feature = "std")] pub Arc); + + impl PartialEq for OtherError { + fn eq(&self, _other: &Self) -> bool { + false + } } -} -impl From for Error { - fn from(value: OtherError) -> Self { - Self::Other(value) + impl From for Error { + fn from(value: OtherError) -> Self { + Self::Other(value) + } } -} -impl fmt::Display for OtherError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) + impl fmt::Display for OtherError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + #[cfg(feature = "std")] + { + write!(f, "{}", self.0) + } + #[cfg(not(feature = "std"))] + { + f.write_str("no further information available") + } + } } -} -impl StdError for OtherError { - fn source(&self) -> Option<&(dyn StdError + 'static)> { - Some(self.0.as_ref()) + #[cfg(feature = "std")] + impl StdError for OtherError { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + Some(self.0.as_ref()) + } } } +pub use other_error::OtherError; + #[cfg(test)] mod tests { use super::{Error, InvalidMessage}; - use crate::error::{CertRevocationListError, OtherError}; + use crate::error::CertRevocationListError; + use crate::error::OtherError; #[test] fn certificate_error_equality() { @@ -598,7 +618,10 @@ mod tests { ApplicationVerificationFailure, ApplicationVerificationFailure ); - let other = Other(OtherError(alloc::sync::Arc::from(Box::from("")))); + let other = Other(OtherError( + #[cfg(feature = "std")] + alloc::sync::Arc::from(Box::from("")), + )); assert_ne!(other, other); assert_ne!(BadEncoding, Expired); } @@ -619,12 +642,16 @@ mod tests { assert_eq!(UnsupportedDeltaCrl, UnsupportedDeltaCrl); assert_eq!(UnsupportedIndirectCrl, UnsupportedIndirectCrl); assert_eq!(UnsupportedRevocationReason, UnsupportedRevocationReason); - let other = Other(OtherError(alloc::sync::Arc::from(Box::from("")))); + let other = Other(OtherError( + #[cfg(feature = "std")] + alloc::sync::Arc::from(Box::from("")), + )); assert_ne!(other, other); assert_ne!(BadSignature, InvalidCrlNumber); } #[test] + #[cfg(feature = "std")] fn other_error_equality() { let other_error = OtherError(alloc::sync::Arc::from(Box::from(""))); assert_ne!(other_error, other_error); @@ -660,7 +687,10 @@ mod tests { Error::NoApplicationProtocol, Error::BadMaxFragmentSize, Error::InvalidCertRevocationList(CertRevocationListError::BadSignature), - Error::Other(OtherError(alloc::sync::Arc::from(Box::from("")))), + Error::Other(OtherError( + #[cfg(feature = "std")] + alloc::sync::Arc::from(Box::from("")), + )), ]; for err in all { diff --git a/rustls/src/webpki/mod.rs b/rustls/src/webpki/mod.rs index f275bd6d53..1491b8d762 100644 --- a/rustls/src/webpki/mod.rs +++ b/rustls/src/webpki/mod.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "std")] use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt; @@ -75,7 +76,11 @@ fn pki_error(error: webpki::Error) -> Error { CertRevocationListError::BadSignature.into() } - _ => CertificateError::Other(OtherError(Arc::new(error))).into(), + _ => CertificateError::Other(OtherError( + #[cfg(feature = "std")] + Arc::new(error), + )) + .into(), } } @@ -95,7 +100,10 @@ fn crl_error(e: webpki::Error) -> CertRevocationListError { UnsupportedIndirectCrl => CertRevocationListError::UnsupportedIndirectCrl, UnsupportedRevocationReason => CertRevocationListError::UnsupportedRevocationReason, - _ => CertRevocationListError::Other(OtherError(Arc::new(e))), + _ => CertRevocationListError::Other(OtherError( + #[cfg(feature = "std")] + Arc::new(e), + )), } } @@ -184,7 +192,7 @@ mod tests { assert!(matches!( crl_error(webpki::Error::NameConstraintViolation), - Other(_) + Other(..) )); } } From 388396c4b219ef1a1823968d1899bf4d894c0efb Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 5 Oct 2023 19:19:11 +0200 Subject: [PATCH 05/27] no-std: rm TicketSwitcher will be back in phase II --- rustls/src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rustls/src/lib.rs b/rustls/src/lib.rs index 9d8cbfc6e8..04841297be 100644 --- a/rustls/src/lib.rs +++ b/rustls/src/lib.rs @@ -507,7 +507,11 @@ pub use crate::msgs::enums::NamedGroup; pub use crate::msgs::ffdhe_groups; pub use crate::msgs::handshake::DistinguishedName; pub use crate::stream::{Stream, StreamOwned}; -pub use crate::suites::{ConnectionTrafficSecrets, ExtractedSecrets, SupportedCipherSuite}; +pub use crate::suites::{ + CipherSuiteCommon, ConnectionTrafficSecrets, ExtractedSecrets, SupportedCipherSuite, +}; +#[cfg(feature = "std")] +pub use crate::ticketer::TicketSwitcher; #[cfg(feature = "tls12")] pub use crate::tls12::Tls12CipherSuite; pub use crate::tls13::Tls13CipherSuite; @@ -607,6 +611,7 @@ pub mod sign { /// APIs for implementing QUIC TLS pub mod quic; +#[cfg(feature = "std")] /// APIs for implementing TLS tickets pub mod ticketer; From 6b6042a14ee573f306272ce2cb42172a633283f7 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 5 Oct 2023 19:28:19 +0200 Subject: [PATCH 06/27] no-std: add TimeProvider to ServerConfig no-std users must use the new ServerConfig::builder_with_details(), which requires a `TimeProvider` to be provided up-front. For std builds, the `ServerConfig` uses the `DefaultTimeProvider` for existing, other `ServerConfig::builder*` functions. --- rustls/src/msgs/persist.rs | 2 +- rustls/src/server/builder.rs | 4 +++ rustls/src/server/server_conn.rs | 52 +++++++++++++++++++++++++++++--- rustls/src/server/tls12.rs | 22 ++++++++------ rustls/src/server/tls13.rs | 24 +++++++-------- rustls/tests/api.rs | 12 +++++++- 6 files changed, 89 insertions(+), 27 deletions(-) diff --git a/rustls/src/msgs/persist.rs b/rustls/src/msgs/persist.rs index 2a9e6b555f..f5d41f4ae2 100644 --- a/rustls/src/msgs/persist.rs +++ b/rustls/src/msgs/persist.rs @@ -406,9 +406,9 @@ impl ServerSessionValue { #[cfg(test)] mod tests { use super::*; - use crate::enums::*; use crate::msgs::codec::{Codec, Reader}; + #[cfg(feature = "std")] #[test] fn serversessionvalue_is_debug() { let ssv = ServerSessionValue::new( diff --git a/rustls/src/server/builder.rs b/rustls/src/server/builder.rs index bbf85880eb..2165f5c539 100644 --- a/rustls/src/server/builder.rs +++ b/rustls/src/server/builder.rs @@ -4,6 +4,7 @@ use crate::error::Error; use crate::msgs::handshake::CertificateChain; use crate::server::handy; use crate::server::{ResolvesServerCert, ServerConfig}; +use crate::time_provider::TimeProvider; use crate::verify::{ClientCertVerifier, NoClientAuth}; use crate::versions; use crate::NoKeyLog; @@ -25,6 +26,7 @@ impl ConfigBuilder { provider: self.state.provider, versions: self.state.versions, verifier: client_cert_verifier, + time_provider: self.state.time_provider, }, side: PhantomData, } @@ -45,6 +47,7 @@ pub struct WantsServerCert { provider: Arc, versions: versions::EnabledVersions, verifier: Arc, + time_provider: Arc, } impl ConfigBuilder { @@ -126,6 +129,7 @@ impl ConfigBuilder { send_tls13_tickets: 4, #[cfg(feature = "tls12")] require_ems: cfg!(feature = "fips"), + time_provider: self.state.time_provider, } } } diff --git a/rustls/src/server/server_conn.rs b/rustls/src/server/server_conn.rs index 7190144336..eef8e46bab 100644 --- a/rustls/src/server/server_conn.rs +++ b/rustls/src/server/server_conn.rs @@ -10,16 +10,20 @@ use crate::msgs::base::Payload; use crate::msgs::handshake::{ClientHelloPayload, ProtocolName, ServerExtension}; use crate::msgs::message::Message; use crate::suites::ExtractedSecrets; -use crate::time_provider::{DefaultTimeProvider, TimeProvider}; +#[cfg(feature = "std")] +use crate::time_provider::DefaultTimeProvider; +use crate::time_provider::TimeProvider; use crate::vecbuf::ChunkVecBuffer; use crate::verify; use crate::versions; use crate::KeyLog; -use crate::{sign, WantsVerifier, WantsVersions}; +#[cfg(feature = "std")] +use crate::WantsVerifier; +use crate::{sign, WantsVersions}; use super::hs; -use pki_types::DnsName; +use pki_types::{DnsName, UnixTime}; use alloc::boxed::Box; use alloc::sync::Arc; @@ -255,7 +259,7 @@ pub struct ServerConfig { /// Supported protocol versions, in no particular order. /// The default is all supported versions. - pub(super) versions: crate::versions::EnabledVersions, + pub(super) versions: versions::EnabledVersions, /// How to verify client certificates. pub(super) verifier: Arc, @@ -328,6 +332,9 @@ pub struct ServerConfig { /// [FIPS 140-3 IG.pdf]: https://csrc.nist.gov/csrc/media/Projects/cryptographic-module-validation-program/documents/fips%20140-3/FIPS%20140-3%20IG.pdf #[cfg(feature = "tls12")] pub require_ems: bool, + + /// Provides the current system time + pub time_provider: Arc, } // Avoid a `Clone` bound on `C`. @@ -350,6 +357,7 @@ impl Clone for ServerConfig { send_tls13_tickets: self.send_tls13_tickets, #[cfg(feature = "tls12")] require_ems: self.require_ems, + time_provider: Arc::clone(&self.time_provider), } } } @@ -360,6 +368,7 @@ impl ServerConfig { /// and safe protocol version defaults. /// /// For more information, see the [`ConfigBuilder`] documentation. + #[cfg(feature = "std")] pub fn builder() -> ConfigBuilder { Self::builder_with_protocol_versions(versions::DEFAULT_VERSIONS) } @@ -376,6 +385,7 @@ impl ServerConfig { /// the crate features and process default. /// /// For more information, see the [`ConfigBuilder`] documentation. + #[cfg(feature = "std")] pub fn builder_with_protocol_versions( versions: &[&'static versions::SupportedProtocolVersion], ) -> ConfigBuilder { @@ -397,6 +407,7 @@ impl ServerConfig { /// version is not supported by the provider's ciphersuites. /// /// For more information, see the [`ConfigBuilder`] documentation. + #[cfg(feature = "std")] pub fn builder_with_provider( provider: Arc, ) -> ConfigBuilder { @@ -409,6 +420,33 @@ impl ServerConfig { } } + /// Create a builder for a server configuration with no default implementation details. + /// + /// This API must be used by `no_std` users. + /// + /// You must provide a specific [`TimeProvider`]. + /// + /// You must provide a specific [`CryptoProvider`]. + /// + /// This will use the provider's configured ciphersuites. You must additionally choose + /// which protocol versions to enable, using `with_protocol_versions` or + /// `with_safe_default_protocol_versions` and handling the `Result` in case a protocol + /// version is not supported by the provider's ciphersuites. + /// + /// For more information, see the [`ConfigBuilder`] documentation. + pub fn builder_with_details( + provider: Arc, + time_provider: Arc, + ) -> ConfigBuilder { + ConfigBuilder { + state: WantsVersions { + provider, + time_provider, + }, + side: PhantomData, + } + } + /// Return `true` if connections made with this `ServerConfig` will /// operate in FIPS mode. /// @@ -445,6 +483,12 @@ impl ServerConfig { .iter() .any(|cs| cs.usable_for_protocol(proto)) } + + pub(super) fn current_time(&self) -> Result { + self.time_provider + .current_time() + .ok_or(Error::FailedToGetCurrentTime) + } } /// Allows reading of early data in resumed TLS1.3 connections. diff --git a/rustls/src/server/tls12.rs b/rustls/src/server/tls12.rs index 3988a8e59c..8824ab8817 100644 --- a/rustls/src/server/tls12.rs +++ b/rustls/src/server/tls12.rs @@ -298,12 +298,15 @@ mod client_hello { cx.common.peer_certificates = resumedata.client_cert_chain; if self.send_ticket { + let now = self.config.current_time()?; + emit_ticket( &secrets, &mut self.transcript, self.using_ems, cx, &*self.config.ticketer, + now, )?; } emit_ccs(cx.common); @@ -538,9 +541,11 @@ impl State for ExpectCertificate { None } Some((end_entity, intermediates)) => { + let now = self.config.current_time()?; + self.config .verifier - .verify_client_cert(end_entity, intermediates, UnixTime::now()) + .verify_client_cert(end_entity, intermediates, now) .map_err(|err| { cx.common .send_cert_verify_error_alert(err) @@ -832,9 +837,9 @@ fn emit_ticket( using_ems: bool, cx: &mut ServerContext<'_>, ticketer: &dyn ProducesTickets, + now: UnixTime, ) -> Result<(), Error> { - let plain = - get_server_connection_value_tls12(secrets, using_ems, cx, UnixTime::now()).get_encoding(); + let plain = get_server_connection_value_tls12(secrets, using_ems, cx, now).get_encoding(); // If we can't produce a ticket for some reason, we can't // report an error. Send an empty one. @@ -928,12 +933,9 @@ impl State for ExpectFinished { // Save connection, perhaps if !self.resuming && !self.session_id.is_empty() { - let value = get_server_connection_value_tls12( - &self.secrets, - self.using_ems, - cx, - UnixTime::now(), - ); + let now = self.config.current_time()?; + + let value = get_server_connection_value_tls12(&self.secrets, self.using_ems, cx, now); let worked = self .config @@ -950,12 +952,14 @@ impl State for ExpectFinished { self.transcript.add_message(&m); if !self.resuming { if self.send_ticket { + let now = self.config.current_time()?; emit_ticket( &self.secrets, &mut self.transcript, self.using_ems, cx, &*self.config.ticketer, + now, )?; } emit_ccs(cx.common); diff --git a/rustls/src/server/tls13.rs b/rustls/src/server/tls13.rs index c82cc7e71b..e1f73f0bd1 100644 --- a/rustls/src/server/tls13.rs +++ b/rustls/src/server/tls13.rs @@ -279,10 +279,12 @@ mod client_hello { } for (i, psk_id) in psk_offer.identities.iter().enumerate() { + let now = self.config.current_time()?; + let resume = match self .attempt_tls13_ticket_decryption(&psk_id.identity.0) .map(|resumedata| { - resumedata.set_freshness(psk_id.obfuscated_ticket_age, UnixTime::now()) + resumedata.set_freshness(psk_id.obfuscated_ticket_age, now) }) .filter(|resumedata| { hs::can_resume(self.suite.into(), &cx.data.sni, false, resumedata) @@ -903,9 +905,11 @@ impl State for ExpectCertificate { Some(chain) => chain, }; + let now = self.config.current_time()?; + self.config .verifier - .verify_client_cert(end_entity, intermediates, UnixTime::now()) + .verify_client_cert(end_entity, intermediates, now) .map_err(|err| { cx.common .send_cert_verify_error_alert(err) @@ -1098,16 +1102,12 @@ impl ExpectFinished { let secure_random = config.provider.secure_random; let nonce = rand::random_vec(secure_random, 32)?; let age_add = rand::random_u32(secure_random)?; - let plain = get_server_session_value( - transcript, - suite, - key_schedule, - cx, - &nonce, - UnixTime::now(), - age_add, - ) - .get_encoding(); + + let now = config.current_time()?; + + let plain = + get_server_session_value(transcript, suite, key_schedule, cx, &nonce, now, age_add) + .get_encoding(); let stateless = config.ticketer.enabled(); let (ticket, lifetime) = if stateless { diff --git a/rustls/tests/api.rs b/rustls/tests/api.rs index 5040c334d0..9a98dbca77 100644 --- a/rustls/tests/api.rs +++ b/rustls/tests/api.rs @@ -342,6 +342,16 @@ fn config_builder_for_client_with_time() { .unwrap(); } +#[test] +fn config_builder_for_server_with_time() { + ServerConfig::builder_with_details( + provider::default_provider().into(), + Arc::new(rustls::time_provider::DefaultTimeProvider), + ) + .with_safe_default_protocol_versions() + .unwrap(); +} + #[test] fn buffered_client_data_sent() { let server_config = Arc::new(make_server_config(KeyType::Rsa)); @@ -514,7 +524,7 @@ fn test_config_builders_debug() { format!("{:?}", b) ); let b = b.with_no_client_auth(); - assert_eq!("ConfigBuilder { state: WantsServerCert { provider: CryptoProvider { cipher_suites: [TLS13_AES_256_GCM_SHA384, TLS13_AES_128_GCM_SHA256, TLS13_CHACHA20_POLY1305_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256], kx_groups: [X25519, secp256r1, secp384r1], signature_verification_algorithms: WebPkiSupportedAlgorithms { all: [ .. ], mapping: [ECDSA_NISTP384_SHA384, ECDSA_NISTP256_SHA256, ED25519, RSA_PSS_SHA512, RSA_PSS_SHA384, RSA_PSS_SHA256, RSA_PKCS1_SHA512, RSA_PKCS1_SHA384, RSA_PKCS1_SHA256] }, secure_random: Ring, key_provider: Ring }, versions: [TLSv1_3], verifier: NoClientAuth } }", format!("{:?}", b)); + assert_eq!("ConfigBuilder { state: WantsServerCert { provider: CryptoProvider { cipher_suites: [TLS13_AES_256_GCM_SHA384, TLS13_AES_128_GCM_SHA256, TLS13_CHACHA20_POLY1305_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256], kx_groups: [X25519, secp256r1, secp384r1], signature_verification_algorithms: WebPkiSupportedAlgorithms { all: [ .. ], mapping: [ECDSA_NISTP384_SHA384, ECDSA_NISTP256_SHA256, ED25519, RSA_PSS_SHA512, RSA_PSS_SHA384, RSA_PSS_SHA256, RSA_PKCS1_SHA512, RSA_PKCS1_SHA384, RSA_PKCS1_SHA256] }, secure_random: Ring, key_provider: Ring }, versions: [TLSv1_3], verifier: NoClientAuth, time_provider: DefaultTimeProvider } }", format!("{:?}", b)); let b = ClientConfig::builder_with_provider( CryptoProvider { From eb7fb3b7752d702dc69f92a38cf5e678d0a140db Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 5 Oct 2023 17:09:09 +0200 Subject: [PATCH 07/27] no-std: remove ClientSessionMemoryCache default Resumption to disabled --- rustls/src/client/client_conn.rs | 18 ++- rustls/src/client/handy.rs | 240 ++++++++++++++++--------------- rustls/src/lib.rs | 1 + rustls/src/limited_cache.rs | 41 +++--- 4 files changed, 166 insertions(+), 134 deletions(-) diff --git a/rustls/src/client/client_conn.rs b/rustls/src/client/client_conn.rs index a8e6560258..f249166b7d 100644 --- a/rustls/src/client/client_conn.rs +++ b/rustls/src/client/client_conn.rs @@ -21,7 +21,7 @@ use crate::KeyLog; use crate::WantsVerifier; use crate::{verify, WantsVersions}; -use super::handy::{ClientSessionMemoryCache, NoClientSessionStorage}; +use super::handy::NoClientSessionStorage; use super::hs; use pki_types::{ServerName, UnixTime}; @@ -400,7 +400,7 @@ impl Clone for ClientConfig { #[derive(Clone, Debug)] pub struct Resumption { /// How we store session data or tickets. The default is to use an in-memory - /// [ClientSessionMemoryCache]. + /// [super::handy::ClientSessionMemoryCache]. pub(super) store: Arc, /// What mechanism is used for resuming a TLS 1.2 session. @@ -412,9 +412,10 @@ impl Resumption { /// /// This is the default `Resumption` choice, and enables resuming a TLS 1.2 session with /// a session id or RFC 5077 ticket. + #[cfg(feature = "std")] pub fn in_memory_sessions(num: usize) -> Self { Self { - store: Arc::new(ClientSessionMemoryCache::new(num)), + store: Arc::new(super::handy::ClientSessionMemoryCache::new(num)), tls12_resumption: Tls12Resumption::SessionIdOrTickets, } } @@ -439,7 +440,8 @@ impl Resumption { /// Configure whether TLS 1.2 sessions may be resumed, and by what mechanism. /// - /// This is meaningless if you've disabled resumption entirely. + /// This is meaningless if you've disabled resumption entirely, which is the case in `no-std` + /// contexts. pub fn tls12_resumption(mut self, tls12: Tls12Resumption) -> Self { self.tls12_resumption = tls12; self @@ -450,7 +452,13 @@ impl Default for Resumption { /// Create an in-memory session store resumption with up to 256 server names, allowing /// a TLS 1.2 session to resume with a session id or RFC 5077 ticket. fn default() -> Self { - Self::in_memory_sessions(256) + #[cfg(feature = "std")] + let ret = Self::in_memory_sessions(256); + + #[cfg(not(feature = "std"))] + let ret = Self::disabled(); + + ret } } diff --git a/rustls/src/client/handy.rs b/rustls/src/client/handy.rs index 772f18fa51..5fde7a87c1 100644 --- a/rustls/src/client/handy.rs +++ b/rustls/src/client/handy.rs @@ -1,7 +1,6 @@ use crate::client; use crate::enums::SignatureScheme; use crate::error::Error; -use crate::limited_cache; use crate::msgs::handshake::CertificateChain; use crate::msgs::persist; use crate::sign; @@ -9,10 +8,7 @@ use crate::NamedGroup; use pki_types::ServerName; -use alloc::collections::VecDeque; use alloc::sync::Arc; -use core::fmt; -use std::sync::Mutex; /// An implementer of `ClientSessionStore` which does nothing. #[derive(Debug)] @@ -40,138 +36,156 @@ impl client::ClientSessionStore for NoClientSessionStorage { } } -const MAX_TLS13_TICKETS_PER_SERVER: usize = 8; +#[cfg(feature = "std")] +mod cache { + use alloc::collections::VecDeque; + use core::fmt; + use std::sync::Mutex; -struct ServerData { - kx_hint: Option, + use crate::limited_cache; + use crate::msgs::persist; + use crate::NamedGroup; - // Zero or one TLS1.2 sessions. - #[cfg(feature = "tls12")] - tls12: Option, + use pki_types::ServerName; - // Up to MAX_TLS13_TICKETS_PER_SERVER TLS1.3 tickets, oldest first. - tls13: VecDeque, -} + const MAX_TLS13_TICKETS_PER_SERVER: usize = 8; -impl Default for ServerData { - fn default() -> Self { - Self { - kx_hint: None, - #[cfg(feature = "tls12")] - tls12: None, - tls13: VecDeque::with_capacity(MAX_TLS13_TICKETS_PER_SERVER), - } - } -} + struct ServerData { + kx_hint: Option, -/// An implementer of `ClientSessionStore` that stores everything -/// in memory. -/// -/// It enforces a limit on the number of entries to bound memory usage. -pub struct ClientSessionMemoryCache { - servers: Mutex, ServerData>>, -} + // Zero or one TLS1.2 sessions. + #[cfg(feature = "tls12")] + tls12: Option, -impl ClientSessionMemoryCache { - /// Make a new ClientSessionMemoryCache. `size` is the - /// maximum number of stored sessions. - pub fn new(size: usize) -> Self { - let max_servers = - size.saturating_add(MAX_TLS13_TICKETS_PER_SERVER - 1) / MAX_TLS13_TICKETS_PER_SERVER; - Self { - servers: Mutex::new(limited_cache::LimitedCache::new(max_servers)), - } + // Up to MAX_TLS13_TICKETS_PER_SERVER TLS1.3 tickets, oldest first. + tls13: VecDeque, } -} -impl client::ClientSessionStore for ClientSessionMemoryCache { - fn set_kx_hint(&self, server_name: ServerName<'static>, group: NamedGroup) { - self.servers - .lock() - .unwrap() - .get_or_insert_default_and_edit(server_name, |data| data.kx_hint = Some(group)); + impl Default for ServerData { + fn default() -> Self { + Self { + kx_hint: None, + #[cfg(feature = "tls12")] + tls12: None, + tls13: VecDeque::with_capacity(MAX_TLS13_TICKETS_PER_SERVER), + } + } } - fn kx_hint(&self, server_name: &ServerName<'_>) -> Option { - self.servers - .lock() - .unwrap() - .get(server_name) - .and_then(|sd| sd.kx_hint) + /// An implementer of `ClientSessionStore` that stores everything + /// in memory. + /// + /// It enforces a limit on the number of entries to bound memory usage. + pub struct ClientSessionMemoryCache { + servers: Mutex, ServerData>>, } - fn set_tls12_session( - &self, - _server_name: ServerName<'static>, - _value: persist::Tls12ClientSessionValue, - ) { - #[cfg(feature = "tls12")] - self.servers - .lock() - .unwrap() - .get_or_insert_default_and_edit(_server_name.clone(), |data| data.tls12 = Some(_value)); + impl ClientSessionMemoryCache { + /// Make a new ClientSessionMemoryCache. `size` is the + /// maximum number of stored sessions. + pub fn new(size: usize) -> Self { + let max_servers = size.saturating_add(MAX_TLS13_TICKETS_PER_SERVER - 1) + / MAX_TLS13_TICKETS_PER_SERVER; + Self { + servers: Mutex::new(limited_cache::LimitedCache::new(max_servers)), + } + } } - fn tls12_session( - &self, - _server_name: &ServerName<'_>, - ) -> Option { - #[cfg(not(feature = "tls12"))] - return None; + impl super::client::ClientSessionStore for ClientSessionMemoryCache { + fn set_kx_hint(&self, server_name: ServerName<'static>, group: NamedGroup) { + self.servers + .lock() + .unwrap() + .get_or_insert_default_and_edit(server_name, |data| data.kx_hint = Some(group)); + } - #[cfg(feature = "tls12")] - self.servers - .lock() - .unwrap() - .get(_server_name) - .and_then(|sd| sd.tls12.as_ref().cloned()) - } + fn kx_hint(&self, server_name: &ServerName<'_>) -> Option { + self.servers + .lock() + .unwrap() + .get(server_name) + .and_then(|sd| sd.kx_hint) + } - fn remove_tls12_session(&self, _server_name: &ServerName<'static>) { - #[cfg(feature = "tls12")] - self.servers - .lock() - .unwrap() - .get_mut(_server_name) - .and_then(|data| data.tls12.take()); - } + fn set_tls12_session( + &self, + _server_name: ServerName<'static>, + _value: persist::Tls12ClientSessionValue, + ) { + #[cfg(feature = "tls12")] + self.servers + .lock() + .unwrap() + .get_or_insert_default_and_edit(_server_name.clone(), |data| { + data.tls12 = Some(_value) + }); + } - fn insert_tls13_ticket( - &self, - server_name: ServerName<'static>, - value: persist::Tls13ClientSessionValue, - ) { - self.servers - .lock() - .unwrap() - .get_or_insert_default_and_edit(server_name.clone(), |data| { - if data.tls13.len() == data.tls13.capacity() { - data.tls13.pop_front(); - } - data.tls13.push_back(value); - }); - } + fn tls12_session( + &self, + _server_name: &ServerName<'_>, + ) -> Option { + #[cfg(not(feature = "tls12"))] + return None; - fn take_tls13_ticket( - &self, - server_name: &ServerName<'static>, - ) -> Option { - self.servers - .lock() - .unwrap() - .get_mut(server_name) - .and_then(|data| data.tls13.pop_back()) + #[cfg(feature = "tls12")] + self.servers + .lock() + .unwrap() + .get(_server_name) + .and_then(|sd| sd.tls12.as_ref().cloned()) + } + + fn remove_tls12_session(&self, _server_name: &ServerName<'static>) { + #[cfg(feature = "tls12")] + self.servers + .lock() + .unwrap() + .get_mut(_server_name) + .and_then(|data| data.tls12.take()); + } + + fn insert_tls13_ticket( + &self, + server_name: ServerName<'static>, + value: persist::Tls13ClientSessionValue, + ) { + self.servers + .lock() + .unwrap() + .get_or_insert_default_and_edit(server_name.clone(), |data| { + if data.tls13.len() == data.tls13.capacity() { + data.tls13.pop_front(); + } + data.tls13.push_back(value); + }); + } + + fn take_tls13_ticket( + &self, + server_name: &ServerName<'static>, + ) -> Option { + self.servers + .lock() + .unwrap() + .get_mut(server_name) + .and_then(|data| data.tls13.pop_back()) + } } -} -impl fmt::Debug for ClientSessionMemoryCache { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Note: we omit self.servers as it may contain sensitive data. - f.debug_struct("ClientSessionMemoryCache") - .finish() + impl fmt::Debug for ClientSessionMemoryCache { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Note: we omit self.servers as it may contain sensitive data. + f.debug_struct("ClientSessionMemoryCache") + .finish() + } } } +#[cfg(feature = "std")] +pub use cache::ClientSessionMemoryCache; + #[derive(Debug)] pub(super) struct FailResolveClientCert {} diff --git a/rustls/src/lib.rs b/rustls/src/lib.rs index 04841297be..0605e46ed7 100644 --- a/rustls/src/lib.rs +++ b/rustls/src/lib.rs @@ -536,6 +536,7 @@ pub mod client { ResolvesClientCert, Resumption, Tls12Resumption, UnbufferedClientConnection, WriteEarlyData, }; + #[cfg(feature = "std")] pub use handy::ClientSessionMemoryCache; /// Dangerous configuration that should be audited and used with extreme care. diff --git a/rustls/src/limited_cache.rs b/rustls/src/limited_cache.rs index 70b581fc9e..db527be25f 100644 --- a/rustls/src/limited_cache.rs +++ b/rustls/src/limited_cache.rs @@ -19,19 +19,12 @@ pub(crate) struct LimitedCache { oldest: VecDeque, } +#[cfg(feature = "std")] impl LimitedCache where K: Eq + Hash + Clone + core::fmt::Debug, V: Default, { - /// Create a new LimitedCache with the given rough capacity. - pub(crate) fn new(capacity_order_of_magnitude: usize) -> Self { - Self { - map: HashMap::with_capacity(capacity_order_of_magnitude), - oldest: VecDeque::with_capacity(capacity_order_of_magnitude), - } - } - pub(crate) fn get_or_insert_default_and_edit(&mut self, k: K, edit: impl FnOnce(&mut V)) { let inserted_new_item = match self.map.entry(k) { Entry::Occupied(value) => { @@ -54,6 +47,28 @@ where } } + pub(crate) fn get_mut(&mut self, k: &Q) -> Option<&mut V> + where + K: Borrow, + Q: Hash + Eq, + { + self.map.get_mut(k) + } +} + +impl LimitedCache +where + K: Eq + Hash + Clone + core::fmt::Debug, + V: Default, +{ + /// Create a new LimitedCache with the given rough capacity. + pub(crate) fn new(capacity_order_of_magnitude: usize) -> Self { + Self { + map: HashMap::with_capacity(capacity_order_of_magnitude), + oldest: VecDeque::with_capacity(capacity_order_of_magnitude), + } + } + pub(crate) fn insert(&mut self, k: K, v: V) { let inserted_new_item = match self.map.entry(k) { Entry::Occupied(mut old) => { @@ -86,14 +101,6 @@ where self.map.get(k) } - pub(crate) fn get_mut(&mut self, k: &Q) -> Option<&mut V> - where - K: Borrow, - Q: Hash + Eq, - { - self.map.get_mut(k) - } - pub(crate) fn remove(&mut self, k: &Q) -> Option where K: Borrow, @@ -205,6 +212,7 @@ mod tests { } } + #[cfg(feature = "std")] #[test] fn test_get_or_insert_default_and_edit_evicts_old_items_to_meet_capacity() { let mut t = Test::new(3); @@ -233,6 +241,7 @@ mod tests { assert_eq!(t.get("jkl"), None); } + #[cfg(feature = "std")] #[test] fn test_get_or_insert_default_and_edit_edits_existing_item() { let mut t = Test::new(3); From 8504fa6f9612ce521435659c15830ac97b57c8b4 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 5 Oct 2023 18:45:03 +0200 Subject: [PATCH 08/27] no-std: rm KeyLogFile --- rustls/src/key_log.rs | 2 +- rustls/src/lib.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/rustls/src/key_log.rs b/rustls/src/key_log.rs index bca28b6697..2bf1c34412 100644 --- a/rustls/src/key_log.rs +++ b/rustls/src/key_log.rs @@ -1,6 +1,6 @@ use core::fmt::Debug; -#[cfg(doc)] +#[cfg(all(doc, feature = "std"))] use crate::KeyLogFile; /// This trait represents the ability to do something useful diff --git a/rustls/src/lib.rs b/rustls/src/lib.rs index 0605e46ed7..85483cb12e 100644 --- a/rustls/src/lib.rs +++ b/rustls/src/lib.rs @@ -404,6 +404,7 @@ mod bs_debug; mod builder; mod enums; mod key_log; +#[cfg(feature = "std")] mod key_log_file; mod suites; mod versions; @@ -502,6 +503,7 @@ pub use crate::error::{ PeerMisbehaved, }; pub use crate::key_log::{KeyLog, NoKeyLog}; +#[cfg(feature = "std")] pub use crate::key_log_file::KeyLogFile; pub use crate::msgs::enums::NamedGroup; pub use crate::msgs::ffdhe_groups; From dd7f37d92ca60981294256338f185423a413943d Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 5 Oct 2023 18:45:33 +0200 Subject: [PATCH 09/27] no-std: rm Stream* --- rustls/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rustls/src/lib.rs b/rustls/src/lib.rs index 85483cb12e..7b6e8b5dda 100644 --- a/rustls/src/lib.rs +++ b/rustls/src/lib.rs @@ -389,6 +389,7 @@ mod hash_hs; mod limited_cache; mod rand; mod record_layer; +#[cfg(feature = "std")] mod stream; #[cfg(feature = "tls12")] mod tls12; @@ -508,6 +509,7 @@ pub use crate::key_log_file::KeyLogFile; pub use crate::msgs::enums::NamedGroup; pub use crate::msgs::ffdhe_groups; pub use crate::msgs::handshake::DistinguishedName; +#[cfg(feature = "std")] pub use crate::stream::{Stream, StreamOwned}; pub use crate::suites::{ CipherSuiteCommon, ConnectionTrafficSecrets, ExtractedSecrets, SupportedCipherSuite, From b1bb5f2a3292c3739ddaa9f1aee803c716ebdbbd Mon Sep 17 00:00:00 2001 From: Christian Poveda Date: Tue, 19 Dec 2023 11:32:57 -0500 Subject: [PATCH 10/27] no-std: rm Connection --- rustls/src/client/client_conn.rs | 2 + rustls/src/conn.rs | 210 +++++++++++++++++-------------- rustls/src/lib.rs | 4 +- rustls/src/server/server_conn.rs | 1 + 4 files changed, 120 insertions(+), 97 deletions(-) diff --git a/rustls/src/client/client_conn.rs b/rustls/src/client/client_conn.rs index f249166b7d..102b9e4655 100644 --- a/rustls/src/client/client_conn.rs +++ b/rustls/src/client/client_conn.rs @@ -714,6 +714,7 @@ impl DerefMut for ClientConnection { } } +#[cfg(feature = "std")] #[doc(hidden)] impl<'a> TryFrom<&'a mut crate::Connection> for &'a mut ClientConnection { type Error = (); @@ -727,6 +728,7 @@ impl<'a> TryFrom<&'a mut crate::Connection> for &'a mut ClientConnection { } } +#[cfg(feature = "std")] impl From for crate::Connection { fn from(conn: ClientConnection) -> Self { Self::Client(conn) diff --git a/rustls/src/conn.rs b/rustls/src/conn.rs index 1d29710a09..eea7c2a629 100644 --- a/rustls/src/conn.rs +++ b/rustls/src/conn.rs @@ -18,128 +18,146 @@ use std::io; pub(crate) mod unbuffered; -/// A client or server connection. -#[derive(Debug)] -pub enum Connection { - /// A client connection - Client(crate::client::ClientConnection), - /// A server connection - Server(crate::server::ServerConnection), -} - -impl Connection { - /// Read TLS content from `rd`. - /// - /// See [`ConnectionCommon::read_tls()`] for more information. - pub fn read_tls(&mut self, rd: &mut dyn io::Read) -> Result { - match self { - Self::Client(conn) => conn.read_tls(rd), - Self::Server(conn) => conn.read_tls(rd), +#[cfg(feature = "std")] +mod connection { + use crate::common_state::{CommonState, IoState}; + use crate::error::Error; + use crate::suites::ExtractedSecrets; + + use core::fmt::Debug; + use core::ops::{Deref, DerefMut}; + use std::io; + + #[cfg(doc)] + use super::ConnectionCommon; + use super::{Reader, Writer}; + + /// A client or server connection. + #[derive(Debug)] + pub enum Connection { + /// A client connection + Client(crate::client::ClientConnection), + /// A server connection + Server(crate::server::ServerConnection), + } + + impl Connection { + /// Read TLS content from `rd`. + /// + /// See [`ConnectionCommon::read_tls()`] for more information. + pub fn read_tls(&mut self, rd: &mut dyn io::Read) -> Result { + match self { + Self::Client(conn) => conn.read_tls(rd), + Self::Server(conn) => conn.read_tls(rd), + } } - } - /// Writes TLS messages to `wr`. - /// - /// See [`ConnectionCommon::write_tls()`] for more information. - pub fn write_tls(&mut self, wr: &mut dyn io::Write) -> Result { - self.sendable_tls.write_to(wr) - } + /// Writes TLS messages to `wr`. + /// + /// See [`ConnectionCommon::write_tls()`] for more information. + pub fn write_tls(&mut self, wr: &mut dyn io::Write) -> Result { + self.sendable_tls.write_to(wr) + } - /// Returns an object that allows reading plaintext. - pub fn reader(&mut self) -> Reader { - match self { - Self::Client(conn) => conn.reader(), - Self::Server(conn) => conn.reader(), + /// Returns an object that allows reading plaintext. + pub fn reader(&mut self) -> Reader { + match self { + Self::Client(conn) => conn.reader(), + Self::Server(conn) => conn.reader(), + } } - } - /// Returns an object that allows writing plaintext. - pub fn writer(&mut self) -> Writer { - match self { - Self::Client(conn) => Writer::new(&mut **conn), - Self::Server(conn) => Writer::new(&mut **conn), + /// Returns an object that allows writing plaintext. + pub fn writer(&mut self) -> Writer { + match self { + Self::Client(conn) => Writer::new(&mut **conn), + Self::Server(conn) => Writer::new(&mut **conn), + } } - } - /// Processes any new packets read by a previous call to [`Connection::read_tls`]. - /// - /// See [`ConnectionCommon::process_new_packets()`] for more information. - pub fn process_new_packets(&mut self) -> Result { - match self { - Self::Client(conn) => conn.process_new_packets(), - Self::Server(conn) => conn.process_new_packets(), + /// Processes any new packets read by a previous call to [`Connection::read_tls`]. + /// + /// See [`ConnectionCommon::process_new_packets()`] for more information. + pub fn process_new_packets(&mut self) -> Result { + match self { + Self::Client(conn) => conn.process_new_packets(), + Self::Server(conn) => conn.process_new_packets(), + } } - } - /// Derives key material from the agreed connection secrets. - /// - /// See [`ConnectionCommon::export_keying_material()`] for more information. - pub fn export_keying_material>( - &self, - output: T, - label: &[u8], - context: Option<&[u8]>, - ) -> Result { - match self { - Self::Client(conn) => conn.export_keying_material(output, label, context), - Self::Server(conn) => conn.export_keying_material(output, label, context), + /// Derives key material from the agreed connection secrets. + /// + /// See [`ConnectionCommon::export_keying_material()`] for more information. + pub fn export_keying_material>( + &self, + output: T, + label: &[u8], + context: Option<&[u8]>, + ) -> Result { + match self { + Self::Client(conn) => conn.export_keying_material(output, label, context), + Self::Server(conn) => conn.export_keying_material(output, label, context), + } } - } - /// This function uses `io` to complete any outstanding IO for this connection. - /// - /// See [`ConnectionCommon::complete_io()`] for more information. - pub fn complete_io(&mut self, io: &mut T) -> Result<(usize, usize), io::Error> - where - Self: Sized, - T: io::Read + io::Write, - { - match self { - Self::Client(conn) => conn.complete_io(io), - Self::Server(conn) => conn.complete_io(io), + /// This function uses `io` to complete any outstanding IO for this connection. + /// + /// See [`ConnectionCommon::complete_io()`] for more information. + pub fn complete_io(&mut self, io: &mut T) -> Result<(usize, usize), io::Error> + where + Self: Sized, + T: io::Read + io::Write, + { + match self { + Self::Client(conn) => conn.complete_io(io), + Self::Server(conn) => conn.complete_io(io), + } } - } - /// Extract secrets, so they can be used when configuring kTLS, for example. - /// Should be used with care as it exposes secret key material. - pub fn dangerous_extract_secrets(self) -> Result { - match self { - Self::Client(client) => client.dangerous_extract_secrets(), - Self::Server(server) => server.dangerous_extract_secrets(), + /// Extract secrets, so they can be used when configuring kTLS, for example. + /// Should be used with care as it exposes secret key material. + pub fn dangerous_extract_secrets(self) -> Result { + match self { + Self::Client(client) => client.dangerous_extract_secrets(), + Self::Server(server) => server.dangerous_extract_secrets(), + } } - } - /// Sets a limit on the internal buffers - /// - /// See [`ConnectionCommon::set_buffer_limit()`] for more information. - pub fn set_buffer_limit(&mut self, limit: Option) { - match self { - Self::Client(client) => client.set_buffer_limit(limit), - Self::Server(server) => server.set_buffer_limit(limit), + /// Sets a limit on the internal buffers + /// + /// See [`ConnectionCommon::set_buffer_limit()`] for more information. + pub fn set_buffer_limit(&mut self, limit: Option) { + match self { + Self::Client(client) => client.set_buffer_limit(limit), + Self::Server(server) => server.set_buffer_limit(limit), + } } } -} -impl Deref for Connection { - type Target = CommonState; + impl Deref for Connection { + type Target = CommonState; - fn deref(&self) -> &Self::Target { - match self { - Self::Client(conn) => &conn.core.common_state, - Self::Server(conn) => &conn.core.common_state, + fn deref(&self) -> &Self::Target { + match self { + Self::Client(conn) => &conn.core.common_state, + Self::Server(conn) => &conn.core.common_state, + } } } -} -impl DerefMut for Connection { - fn deref_mut(&mut self) -> &mut Self::Target { - match self { - Self::Client(conn) => &mut conn.core.common_state, - Self::Server(conn) => &mut conn.core.common_state, + impl DerefMut for Connection { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + Self::Client(conn) => &mut conn.core.common_state, + Self::Server(conn) => &mut conn.core.common_state, + } } } } +#[cfg(feature = "std")] +pub use connection::Connection; + /// A structure that implements [`std::io::Read`] for reading plaintext. pub struct Reader<'a> { received_plaintext: &'a mut ChunkVecBuffer, diff --git a/rustls/src/lib.rs b/rustls/src/lib.rs index 7b6e8b5dda..84d5f04fc5 100644 --- a/rustls/src/lib.rs +++ b/rustls/src/lib.rs @@ -494,7 +494,9 @@ pub mod unbuffered { // The public interface is: pub use crate::builder::{ConfigBuilder, ConfigSide, WantsVerifier, WantsVersions}; pub use crate::common_state::{CommonState, IoState, Side}; -pub use crate::conn::{Connection, ConnectionCommon, Reader, SideData, Writer}; +#[cfg(feature = "std")] +pub use crate::conn::Connection; +pub use crate::conn::{ConnectionCommon, Reader, SideData, Writer}; pub use crate::enums::{ AlertDescription, CipherSuite, ContentType, HandshakeType, ProtocolVersion, SignatureAlgorithm, SignatureScheme, diff --git a/rustls/src/server/server_conn.rs b/rustls/src/server/server_conn.rs index eef8e46bab..fda8400f39 100644 --- a/rustls/src/server/server_conn.rs +++ b/rustls/src/server/server_conn.rs @@ -639,6 +639,7 @@ impl DerefMut for ServerConnection { } } +#[cfg(feature = "std")] impl From for crate::Connection { fn from(conn: ServerConnection) -> Self { Self::Server(conn) From 91fccea34fd4d59635b8154778c457109e44e0b1 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 5 Oct 2023 18:50:41 +0200 Subject: [PATCH 11/27] no-std: rm ServerConnection --- rustls/src/conn.rs | 1 + rustls/src/lib.rs | 9 +- rustls/src/server/server_conn.rs | 279 +++++++++++++++++-------------- 3 files changed, 157 insertions(+), 132 deletions(-) diff --git a/rustls/src/conn.rs b/rustls/src/conn.rs index eea7c2a629..14095c32b7 100644 --- a/rustls/src/conn.rs +++ b/rustls/src/conn.rs @@ -505,6 +505,7 @@ impl ConnectionCommon { } } + #[cfg(feature = "std")] pub(crate) fn replace_state(&mut self, new: Box>) { self.core.state = Ok(new); } diff --git a/rustls/src/lib.rs b/rustls/src/lib.rs index 84d5f04fc5..9715592768 100644 --- a/rustls/src/lib.rs +++ b/rustls/src/lib.rs @@ -583,10 +583,11 @@ pub mod server { pub use handy::{NoServerSessionStorage, ServerSessionMemoryCache}; pub use server_conn::StoresServerSessions; pub use server_conn::{ - Accepted, Acceptor, ReadEarlyData, ServerConfig, ServerConnection, ServerConnectionData, - UnbufferedServerConnection, + Accepted, Acceptor, ServerConfig, ServerConnectionData, UnbufferedServerConnection, }; pub use server_conn::{ClientHello, ProducesTickets, ResolvesServerCert}; + #[cfg(feature = "std")] + pub use server_conn::{ReadEarlyData, ServerConnection}; /// Dangerous configuration that should be audited and used with extreme care. pub mod danger { @@ -594,7 +595,9 @@ pub mod server { } } -pub use server::{ServerConfig, ServerConnection}; +pub use server::ServerConfig; +#[cfg(feature = "std")] +pub use server::ServerConnection; /// All defined protocol versions appear in this module. /// diff --git a/rustls/src/server/server_conn.rs b/rustls/src/server/server_conn.rs index fda8400f39..c6028dd807 100644 --- a/rustls/src/server/server_conn.rs +++ b/rustls/src/server/server_conn.rs @@ -9,7 +9,6 @@ use crate::log::trace; use crate::msgs::base::Payload; use crate::msgs::handshake::{ClientHelloPayload, ProtocolName, ServerExtension}; use crate::msgs::message::Message; -use crate::suites::ExtractedSecrets; #[cfg(feature = "std")] use crate::time_provider::DefaultTimeProvider; use crate::time_provider::TimeProvider; @@ -491,160 +490,178 @@ impl ServerConfig { } } -/// Allows reading of early data in resumed TLS1.3 connections. -/// -/// "Early data" is also known as "0-RTT data". -/// -/// This structure implements [`std::io::Read`]. -pub struct ReadEarlyData<'a> { - early_data: &'a mut EarlyDataState, -} - -impl<'a> ReadEarlyData<'a> { - fn new(early_data: &'a mut EarlyDataState) -> Self { - ReadEarlyData { early_data } - } -} - -impl<'a> std::io::Read for ReadEarlyData<'a> { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.early_data.read(buf) +#[cfg(feature = "std")] +mod connection { + use crate::common_state::{CommonState, Side}; + use crate::conn::{ConnectionCommon, ConnectionCore}; + use crate::error::Error; + use crate::suites::ExtractedSecrets; + + use alloc::sync::Arc; + use alloc::vec::Vec; + use core::fmt; + use core::fmt::{Debug, Formatter}; + use core::ops::{Deref, DerefMut}; + use std::io; + + use super::{EarlyDataState, ServerConfig, ServerConnectionData}; + + /// Allows reading of early data in resumed TLS1.3 connections. + /// + /// "Early data" is also known as "0-RTT data". + /// + /// This structure implements [`std::io::Read`]. + pub struct ReadEarlyData<'a> { + early_data: &'a mut EarlyDataState, } - #[cfg(read_buf)] - fn read_buf(&mut self, cursor: core::io::BorrowedCursor<'_>) -> io::Result<()> { - self.early_data.read_buf(cursor) + impl<'a> ReadEarlyData<'a> { + fn new(early_data: &'a mut EarlyDataState) -> Self { + ReadEarlyData { early_data } + } } -} -/// This represents a single TLS server connection. -/// -/// Send TLS-protected data to the peer using the `io::Write` trait implementation. -/// Read data from the peer using the `io::Read` trait implementation. -pub struct ServerConnection { - inner: ConnectionCommon, -} + impl<'a> std::io::Read for ReadEarlyData<'a> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.early_data.read(buf) + } -impl ServerConnection { - /// Make a new ServerConnection. `config` controls how - /// we behave in the TLS protocol. - pub fn new(config: Arc) -> Result { - let mut common = CommonState::new(Side::Server); - common.set_max_fragment_size(config.max_fragment_size)?; - common.enable_secret_extraction = config.enable_secret_extraction; - Ok(Self { - inner: ConnectionCommon::from(ConnectionCore::for_server(config, Vec::new())?), - }) + #[cfg(read_buf)] + fn read_buf(&mut self, cursor: core::io::BorrowedCursor<'_>) -> io::Result<()> { + self.early_data.read_buf(cursor) + } } - /// Retrieves the server name, if any, used to select the certificate and - /// private key. - /// - /// This returns `None` until some time after the client's server name indication - /// (SNI) extension value is processed during the handshake. It will never be - /// `None` when the connection is ready to send or process application data, - /// unless the client does not support SNI. + /// This represents a single TLS server connection. /// - /// This is useful for application protocols that need to enforce that the - /// server name matches an application layer protocol hostname. For - /// example, HTTP/1.1 servers commonly expect the `Host:` header field of - /// every request on a connection to match the hostname in the SNI extension - /// when the client provides the SNI extension. - /// - /// The server name is also used to match sessions during session resumption. - pub fn server_name(&self) -> Option<&str> { - self.inner.core.get_sni_str() + /// Send TLS-protected data to the peer using the `io::Write` trait implementation. + /// Read data from the peer using the `io::Read` trait implementation. + pub struct ServerConnection { + pub(super) inner: ConnectionCommon, } - /// Application-controlled portion of the resumption ticket supplied by the client, if any. - /// - /// Recovered from the prior session's `set_resumption_data`. Integrity is guaranteed by rustls. - /// - /// Returns `Some` iff a valid resumption ticket has been received from the client. - pub fn received_resumption_data(&self) -> Option<&[u8]> { - self.inner - .core - .data - .received_resumption_data - .as_ref() - .map(|x| &x[..]) - } + impl ServerConnection { + /// Make a new ServerConnection. `config` controls how + /// we behave in the TLS protocol. + pub fn new(config: Arc) -> Result { + let mut common = CommonState::new(Side::Server); + common.set_max_fragment_size(config.max_fragment_size)?; + common.enable_secret_extraction = config.enable_secret_extraction; + Ok(Self { + inner: ConnectionCommon::from(ConnectionCore::for_server(config, Vec::new())?), + }) + } - /// Set the resumption data to embed in future resumption tickets supplied to the client. - /// - /// Defaults to the empty byte string. Must be less than 2^15 bytes to allow room for other - /// data. Should be called while `is_handshaking` returns true to ensure all transmitted - /// resumption tickets are affected. - /// - /// Integrity will be assured by rustls, but the data will be visible to the client. If secrecy - /// from the client is desired, encrypt the data separately. - pub fn set_resumption_data(&mut self, data: &[u8]) { - assert!(data.len() < 2usize.pow(15)); - self.inner.core.data.resumption_data = data.into(); - } + /// Retrieves the server name, if any, used to select the certificate and + /// private key. + /// + /// This returns `None` until some time after the client's server name indication + /// (SNI) extension value is processed during the handshake. It will never be + /// `None` when the connection is ready to send or process application data, + /// unless the client does not support SNI. + /// + /// This is useful for application protocols that need to enforce that the + /// server name matches an application layer protocol hostname. For + /// example, HTTP/1.1 servers commonly expect the `Host:` header field of + /// every request on a connection to match the hostname in the SNI extension + /// when the client provides the SNI extension. + /// + /// The server name is also used to match sessions during session resumption. + pub fn server_name(&self) -> Option<&str> { + self.inner.core.get_sni_str() + } - /// Explicitly discard early data, notifying the client - /// - /// Useful if invariants encoded in `received_resumption_data()` cannot be respected. - /// - /// Must be called while `is_handshaking` is true. - pub fn reject_early_data(&mut self) { - self.inner.core.reject_early_data() - } + /// Application-controlled portion of the resumption ticket supplied by the client, if any. + /// + /// Recovered from the prior session's `set_resumption_data`. Integrity is guaranteed by rustls. + /// + /// Returns `Some` iff a valid resumption ticket has been received from the client. + pub fn received_resumption_data(&self) -> Option<&[u8]> { + self.inner + .core + .data + .received_resumption_data + .as_ref() + .map(|x| &x[..]) + } - /// Returns an `io::Read` implementer you can read bytes from that are - /// received from a client as TLS1.3 0RTT/"early" data, during the handshake. - /// - /// This returns `None` in many circumstances, such as : - /// - /// - Early data is disabled if [`ServerConfig::max_early_data_size`] is zero (the default). - /// - The session negotiated with the client is not TLS1.3. - /// - The client just doesn't support early data. - /// - The connection doesn't resume an existing session. - /// - The client hasn't sent a full ClientHello yet. - pub fn early_data(&mut self) -> Option { - let data = &mut self.inner.core.data; - if data.early_data.was_accepted() { - Some(ReadEarlyData::new(&mut data.early_data)) - } else { - None + /// Set the resumption data to embed in future resumption tickets supplied to the client. + /// + /// Defaults to the empty byte string. Must be less than 2^15 bytes to allow room for other + /// data. Should be called while `is_handshaking` returns true to ensure all transmitted + /// resumption tickets are affected. + /// + /// Integrity will be assured by rustls, but the data will be visible to the client. If secrecy + /// from the client is desired, encrypt the data separately. + pub fn set_resumption_data(&mut self, data: &[u8]) { + assert!(data.len() < 2usize.pow(15)); + self.inner.core.data.resumption_data = data.into(); + } + + /// Explicitly discard early data, notifying the client + /// + /// Useful if invariants encoded in `received_resumption_data()` cannot be respected. + /// + /// Must be called while `is_handshaking` is true. + pub fn reject_early_data(&mut self) { + self.inner.core.reject_early_data() + } + + /// Returns an `io::Read` implementer you can read bytes from that are + /// received from a client as TLS1.3 0RTT/"early" data, during the handshake. + /// + /// This returns `None` in many circumstances, such as : + /// + /// - Early data is disabled if [`ServerConfig::max_early_data_size`] is zero (the default). + /// - The session negotiated with the client is not TLS1.3. + /// - The client just doesn't support early data. + /// - The connection doesn't resume an existing session. + /// - The client hasn't sent a full ClientHello yet. + pub fn early_data(&mut self) -> Option { + let data = &mut self.inner.core.data; + if data.early_data.was_accepted() { + Some(ReadEarlyData::new(&mut data.early_data)) + } else { + None + } } - } - /// Extract secrets, so they can be used when configuring kTLS, for example. - /// Should be used with care as it exposes secret key material. - pub fn dangerous_extract_secrets(self) -> Result { - self.inner.dangerous_extract_secrets() + /// Extract secrets, so they can be used when configuring kTLS, for example. + /// Should be used with care as it exposes secret key material. + pub fn dangerous_extract_secrets(self) -> Result { + self.inner.dangerous_extract_secrets() + } } -} -impl Debug for ServerConnection { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_struct("ServerConnection") - .finish() + impl Debug for ServerConnection { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.debug_struct("ServerConnection") + .finish() + } } -} -impl Deref for ServerConnection { - type Target = ConnectionCommon; + impl Deref for ServerConnection { + type Target = ConnectionCommon; - fn deref(&self) -> &Self::Target { - &self.inner + fn deref(&self) -> &Self::Target { + &self.inner + } } -} -impl DerefMut for ServerConnection { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner + impl DerefMut for ServerConnection { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } } -} -#[cfg(feature = "std")] -impl From for crate::Connection { - fn from(conn: ServerConnection) -> Self { - Self::Server(conn) + impl From for crate::Connection { + fn from(conn: ServerConnection) -> Self { + Self::Server(conn) + } } } +#[cfg(feature = "std")] +pub use connection::{ReadEarlyData, ServerConnection}; /// Unbuffered version of `ServerConnection` /// @@ -833,6 +850,7 @@ impl Accepted { /// Takes the state returned from [`Acceptor::accept()`] as well as the [`ServerConfig`] and /// [`sign::CertifiedKey`] that should be used for the session. Returns an error if /// configuration-dependent validation of the received `ClientHello` message fails. + #[cfg(feature = "std")] pub fn into_connection(mut self, config: Arc) -> Result { self.connection .set_max_fragment_size(config.max_fragment_size)?; @@ -907,6 +925,7 @@ impl EarlyDataState { *self = Self::Accepted(ChunkVecBuffer::new(Some(max_size))); } + #[cfg(feature = "std")] fn was_accepted(&self) -> bool { matches!(self, Self::Accepted(_)) } @@ -922,6 +941,7 @@ impl EarlyDataState { } } + #[cfg(feature = "std")] fn read(&mut self, buf: &mut [u8]) -> io::Result { match self { Self::Accepted(ref mut received) => received.read(buf), @@ -1004,6 +1024,7 @@ impl ServerConnectionData { impl crate::conn::SideData for ServerConnectionData {} +#[cfg(feature = "std")] #[cfg(test)] mod tests { use super::*; From d7853a75225f9573ae3b8158e371be028f524977 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 5 Oct 2023 18:53:21 +0200 Subject: [PATCH 12/27] no-std: rm ClientConnection --- rustls/src/client/client_conn.rs | 279 +++++++++++++++++-------------- rustls/src/common_state.rs | 1 + rustls/src/lib.rs | 11 +- 3 files changed, 158 insertions(+), 133 deletions(-) diff --git a/rustls/src/client/client_conn.rs b/rustls/src/client/client_conn.rs index 102b9e4655..5c641e873e 100644 --- a/rustls/src/client/client_conn.rs +++ b/rustls/src/client/client_conn.rs @@ -1,6 +1,6 @@ use crate::builder::ConfigBuilder; use crate::common_state::{CommonState, Protocol, Side}; -use crate::conn::{ConnectionCommon, ConnectionCore, UnbufferedConnectionCommon}; +use crate::conn::{ConnectionCore, UnbufferedConnectionCommon}; use crate::crypto::{CryptoProvider, SupportedKxGroup}; use crate::enums::{CipherSuite, ProtocolVersion, SignatureScheme}; use crate::error::Error; @@ -10,7 +10,7 @@ use crate::msgs::enums::NamedGroup; use crate::msgs::handshake::ClientExtension; use crate::msgs::persist; use crate::sign; -use crate::suites::{ExtractedSecrets, SupportedCipherSuite}; +use crate::suites::SupportedCipherSuite; #[cfg(feature = "std")] use crate::time_provider::DefaultTimeProvider; use crate::time_provider::TimeProvider; @@ -33,7 +33,6 @@ use core::marker::PhantomData; use core::mem; use core::ops::{Deref, DerefMut}; use std::error::Error as StdError; -use std::io; #[cfg(doc)] use crate::{crypto, DistinguishedName}; @@ -559,11 +558,6 @@ impl EarlyData { } } - fn check_write(&mut self, sz: usize) -> io::Result { - self.check_write_opt(sz) - .ok_or_else(|| io::Error::from(io::ErrorKind::InvalidInput)) - } - fn check_write_opt(&mut self, sz: usize) -> Option { match self.state { EarlyDataState::Disabled => unreachable!(), @@ -580,160 +574,187 @@ impl EarlyData { EarlyDataState::Rejected | EarlyDataState::AcceptedFinished => None, } } - - fn bytes_left(&self) -> usize { - self.left - } } -/// Stub that implements io::Write and dispatches to `write_early_data`. -pub struct WriteEarlyData<'a> { - sess: &'a mut ClientConnection, -} +#[cfg(feature = "std")] +mod connection { + use crate::common_state::Protocol; + use crate::conn::ConnectionCommon; + use crate::conn::ConnectionCore; + use crate::error::Error; + use crate::suites::ExtractedSecrets; + use crate::ClientConfig; -impl<'a> WriteEarlyData<'a> { - fn new(sess: &'a mut ClientConnection) -> WriteEarlyData<'a> { - WriteEarlyData { sess } - } + use pki_types::ServerName; - /// How many bytes you may send. Writes will become short - /// once this reaches zero. - pub fn bytes_left(&self) -> usize { - self.sess - .inner - .core - .data - .early_data - .bytes_left() - } -} + use alloc::sync::Arc; + use alloc::vec::Vec; + use core::fmt; + use core::ops::{Deref, DerefMut}; + use std::io; -impl<'a> io::Write for WriteEarlyData<'a> { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.sess.write_early_data(buf) - } + use super::ClientConnectionData; - fn flush(&mut self) -> io::Result<()> { - Ok(()) + /// Stub that implements io::Write and dispatches to `write_early_data`. + pub struct WriteEarlyData<'a> { + sess: &'a mut ClientConnection, } -} -/// This represents a single TLS client connection. -pub struct ClientConnection { - inner: ConnectionCommon, -} + impl<'a> WriteEarlyData<'a> { + fn new(sess: &'a mut ClientConnection) -> WriteEarlyData<'a> { + WriteEarlyData { sess } + } -impl fmt::Debug for ClientConnection { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("ClientConnection") - .finish() + /// How many bytes you may send. Writes will become short + /// once this reaches zero. + pub fn bytes_left(&self) -> usize { + self.sess + .inner + .core + .data + .early_data + .bytes_left() + } } -} -impl ClientConnection { - /// Make a new ClientConnection. `config` controls how - /// we behave in the TLS protocol, `name` is the - /// name of the server we want to talk to. - pub fn new(config: Arc, name: ServerName<'static>) -> Result { - Ok(Self { - inner: ConnectionCore::for_client(config, name, Vec::new(), Protocol::Tcp)?.into(), - }) + impl<'a> io::Write for WriteEarlyData<'a> { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.sess.write_early_data(buf) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } } - /// Returns an `io::Write` implementer you can write bytes to - /// to send TLS1.3 early data (a.k.a. "0-RTT data") to the server. - /// - /// This returns None in many circumstances when the capability to - /// send early data is not available, including but not limited to: - /// - /// - The server hasn't been talked to previously. - /// - The server does not support resumption. - /// - The server does not support early data. - /// - The resumption data for the server has expired. - /// - /// The server specifies a maximum amount of early data. You can - /// learn this limit through the returned object, and writes through - /// it will process only this many bytes. - /// - /// The server can choose not to accept any sent early data -- - /// in this case the data is lost but the connection continues. You - /// can tell this happened using `is_early_data_accepted`. - pub fn early_data(&mut self) -> Option { - if self - .inner - .core - .data - .early_data - .is_enabled() - { - Some(WriteEarlyData::new(self)) - } else { - None + impl super::EarlyData { + fn check_write(&mut self, sz: usize) -> io::Result { + self.check_write_opt(sz) + .ok_or_else(|| io::Error::from(io::ErrorKind::InvalidInput)) + } + + fn bytes_left(&self) -> usize { + self.left } } - /// Returns True if the server signalled it will process early data. - /// - /// If you sent early data and this returns false at the end of the - /// handshake then the server will not process the data. This - /// is not an error, but you may wish to resend the data. - pub fn is_early_data_accepted(&self) -> bool { - self.inner.core.is_early_data_accepted() + /// This represents a single TLS client connection. + pub struct ClientConnection { + inner: ConnectionCommon, } - /// Extract secrets, so they can be used when configuring kTLS, for example. - /// Should be used with care as it exposes secret key material. - pub fn dangerous_extract_secrets(self) -> Result { - self.inner.dangerous_extract_secrets() + impl fmt::Debug for ClientConnection { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("ClientConnection") + .finish() + } } - fn write_early_data(&mut self, data: &[u8]) -> io::Result { - self.inner - .core - .data - .early_data - .check_write(data.len()) - .map(|sz| { - self.inner - .send_early_plaintext(&data[..sz]) + impl ClientConnection { + /// Make a new ClientConnection. `config` controls how + /// we behave in the TLS protocol, `name` is the + /// name of the server we want to talk to. + pub fn new(config: Arc, name: ServerName<'static>) -> Result { + Ok(Self { + inner: ConnectionCore::for_client(config, name, Vec::new(), Protocol::Tcp)?.into(), }) + } + + /// Returns an `io::Write` implementer you can write bytes to + /// to send TLS1.3 early data (a.k.a. "0-RTT data") to the server. + /// + /// This returns None in many circumstances when the capability to + /// send early data is not available, including but not limited to: + /// + /// - The server hasn't been talked to previously. + /// - The server does not support resumption. + /// - The server does not support early data. + /// - The resumption data for the server has expired. + /// + /// The server specifies a maximum amount of early data. You can + /// learn this limit through the returned object, and writes through + /// it will process only this many bytes. + /// + /// The server can choose not to accept any sent early data -- + /// in this case the data is lost but the connection continues. You + /// can tell this happened using `is_early_data_accepted`. + pub fn early_data(&mut self) -> Option { + if self + .inner + .core + .data + .early_data + .is_enabled() + { + Some(WriteEarlyData::new(self)) + } else { + None + } + } + + /// Returns True if the server signalled it will process early data. + /// + /// If you sent early data and this returns false at the end of the + /// handshake then the server will not process the data. This + /// is not an error, but you may wish to resend the data. + pub fn is_early_data_accepted(&self) -> bool { + self.inner.core.is_early_data_accepted() + } + + /// Extract secrets, so they can be used when configuring kTLS, for example. + /// Should be used with care as it exposes secret key material. + pub fn dangerous_extract_secrets(self) -> Result { + self.inner.dangerous_extract_secrets() + } + + fn write_early_data(&mut self, data: &[u8]) -> io::Result { + self.inner + .core + .data + .early_data + .check_write(data.len()) + .map(|sz| { + self.inner + .send_early_plaintext(&data[..sz]) + }) + } } -} -impl Deref for ClientConnection { - type Target = ConnectionCommon; + impl Deref for ClientConnection { + type Target = ConnectionCommon; - fn deref(&self) -> &Self::Target { - &self.inner + fn deref(&self) -> &Self::Target { + &self.inner + } } -} -impl DerefMut for ClientConnection { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner + impl DerefMut for ClientConnection { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } } -} -#[cfg(feature = "std")] -#[doc(hidden)] -impl<'a> TryFrom<&'a mut crate::Connection> for &'a mut ClientConnection { - type Error = (); - - fn try_from(value: &'a mut crate::Connection) -> Result { - use crate::Connection::*; - match value { - Client(conn) => Ok(conn), - Server(_) => Err(()), + #[doc(hidden)] + impl<'a> TryFrom<&'a mut crate::Connection> for &'a mut ClientConnection { + type Error = (); + + fn try_from(value: &'a mut crate::Connection) -> Result { + use crate::Connection::*; + match value { + Client(conn) => Ok(conn), + Server(_) => Err(()), + } } } -} -#[cfg(feature = "std")] -impl From for crate::Connection { - fn from(conn: ClientConnection) -> Self { - Self::Client(conn) + impl From for crate::Connection { + fn from(conn: ClientConnection) -> Self { + Self::Client(conn) + } } } +#[cfg(feature = "std")] +pub use connection::{ClientConnection, WriteEarlyData}; impl ConnectionCore { pub(crate) fn for_client( diff --git a/rustls/src/common_state.rs b/rustls/src/common_state.rs index 9461024099..70567dcd21 100644 --- a/rustls/src/common_state.rs +++ b/rustls/src/common_state.rs @@ -243,6 +243,7 @@ impl CommonState { Ok(written) } + #[cfg(feature = "std")] pub(crate) fn send_early_plaintext(&mut self, data: &[u8]) -> usize { debug_assert!(self.early_traffic); debug_assert!(self.record_layer.is_encrypting()); diff --git a/rustls/src/lib.rs b/rustls/src/lib.rs index 9715592768..35f5122d02 100644 --- a/rustls/src/lib.rs +++ b/rustls/src/lib.rs @@ -538,11 +538,12 @@ pub mod client { pub use builder::WantsClientCert; pub use client_conn::{ - ClientConfig, ClientConnection, ClientConnectionData, ClientSessionStore, EarlyDataError, - ResolvesClientCert, Resumption, Tls12Resumption, UnbufferedClientConnection, - WriteEarlyData, + ClientConfig, ClientConnectionData, ClientSessionStore, EarlyDataError, ResolvesClientCert, + Resumption, Tls12Resumption, UnbufferedClientConnection, }; #[cfg(feature = "std")] + pub use client_conn::{ClientConnection, WriteEarlyData}; + #[cfg(feature = "std")] pub use handy::ClientSessionMemoryCache; /// Dangerous configuration that should be audited and used with extreme care. @@ -561,7 +562,9 @@ pub mod client { pub use crate::msgs::persist::Tls13ClientSessionValue; } -pub use client::{ClientConfig, ClientConnection}; +pub use client::ClientConfig; +#[cfg(feature = "std")] +pub use client::ClientConnection; /// Items for use in a server. pub mod server { From bb8b92c67a2a467b43bd571c05b0aaff95dea13e Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 5 Oct 2023 18:57:11 +0200 Subject: [PATCH 13/27] no-std: rm Acceptor --- rustls/src/conn.rs | 1 + rustls/src/lib.rs | 6 +- rustls/src/server/server_conn.rs | 249 ++++++++++++++++--------------- 3 files changed, 130 insertions(+), 126 deletions(-) diff --git a/rustls/src/conn.rs b/rustls/src/conn.rs index 14095c32b7..e61292b5c2 100644 --- a/rustls/src/conn.rs +++ b/rustls/src/conn.rs @@ -489,6 +489,7 @@ impl ConnectionCommon { /// /// This is a shortcut to the `process_new_packets()` -> `process_msg()` -> /// `process_handshake_messages()` path, specialized for the first handshake message. + #[cfg(feature = "std")] pub(crate) fn first_handshake_message(&mut self) -> Result>, Error> { let mut deframer_buffer = self.deframer_buffer.borrow(); let res = self diff --git a/rustls/src/lib.rs b/rustls/src/lib.rs index 35f5122d02..3e976342ab 100644 --- a/rustls/src/lib.rs +++ b/rustls/src/lib.rs @@ -586,11 +586,11 @@ pub mod server { pub use handy::{NoServerSessionStorage, ServerSessionMemoryCache}; pub use server_conn::StoresServerSessions; pub use server_conn::{ - Accepted, Acceptor, ServerConfig, ServerConnectionData, UnbufferedServerConnection, + Accepted, ServerConfig, ServerConnectionData, UnbufferedServerConnection, }; - pub use server_conn::{ClientHello, ProducesTickets, ResolvesServerCert}; #[cfg(feature = "std")] - pub use server_conn::{ReadEarlyData, ServerConnection}; + pub use server_conn::{Acceptor, ReadEarlyData, ServerConnection}; + pub use server_conn::{ClientHello, ProducesTickets, ResolvesServerCert}; /// Dangerous configuration that should be audited and used with extreme care. pub mod danger { diff --git a/rustls/src/server/server_conn.rs b/rustls/src/server/server_conn.rs index c6028dd807..d93fff21da 100644 --- a/rustls/src/server/server_conn.rs +++ b/rustls/src/server/server_conn.rs @@ -1,5 +1,5 @@ use crate::builder::ConfigBuilder; -use crate::common_state::{CommonState, Context, Protocol, Side, State}; +use crate::common_state::{CommonState, Protocol, Side, State}; use crate::conn::{ConnectionCommon, ConnectionCore, UnbufferedConnectionCommon}; use crate::crypto::CryptoProvider; use crate::enums::{CipherSuite, ProtocolVersion, SignatureScheme}; @@ -31,6 +31,7 @@ use core::fmt; use core::fmt::{Debug, Formatter}; use core::marker::PhantomData; use core::ops::{Deref, DerefMut}; +#[cfg(feature = "std")] use std::io; #[cfg(doc)] @@ -492,11 +493,13 @@ impl ServerConfig { #[cfg(feature = "std")] mod connection { - use crate::common_state::{CommonState, Side}; + use crate::common_state::{CommonState, Context, Side}; use crate::conn::{ConnectionCommon, ConnectionCore}; use crate::error::Error; + use crate::server::hs; use crate::suites::ExtractedSecrets; + use alloc::boxed::Box; use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt; @@ -504,7 +507,7 @@ mod connection { use core::ops::{Deref, DerefMut}; use std::io; - use super::{EarlyDataState, ServerConfig, ServerConnectionData}; + use super::{Accepted, Accepting, EarlyDataState, ServerConfig, ServerConnectionData}; /// Allows reading of early data in resumed TLS1.3 connections. /// @@ -659,9 +662,128 @@ mod connection { Self::Server(conn) } } + + /// Handle a server-side connection before configuration is available. + /// + /// `Acceptor` allows the caller to choose a [`ServerConfig`] after reading + /// the [`super::ClientHello`] of an incoming connection. This is useful for servers + /// that choose different certificates or cipher suites based on the + /// characteristics of the `ClientHello`. In particular it is useful for + /// servers that need to do some I/O to load a certificate and its private key + /// and don't want to use the blocking interface provided by + /// [`super::ResolvesServerCert`]. + /// + /// Create an Acceptor with [`Acceptor::default()`]. + /// + /// # Example + /// + /// ```no_run + /// # #[cfg(feature = "aws_lc_rs")] { + /// # fn choose_server_config( + /// # _: rustls::server::ClientHello, + /// # ) -> std::sync::Arc { + /// # unimplemented!(); + /// # } + /// # #[allow(unused_variables)] + /// # fn main() { + /// use rustls::server::{Acceptor, ServerConfig}; + /// let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); + /// for stream in listener.incoming() { + /// let mut stream = stream.unwrap(); + /// let mut acceptor = Acceptor::default(); + /// let accepted = loop { + /// acceptor.read_tls(&mut stream).unwrap(); + /// if let Some(accepted) = acceptor.accept().unwrap() { + /// break accepted; + /// } + /// }; + /// + /// // For some user-defined choose_server_config: + /// let config = choose_server_config(accepted.client_hello()); + /// let conn = accepted + /// .into_connection(config) + /// .unwrap(); + + /// // Proceed with handling the ServerConnection. + /// } + /// # } + /// # } + /// ``` + pub struct Acceptor { + inner: Option>, + } + + impl Default for Acceptor { + /// Return an empty Acceptor, ready to receive bytes from a new client connection. + fn default() -> Self { + Self { + inner: Some( + ConnectionCore::new( + Box::new(Accepting), + ServerConnectionData::default(), + CommonState::new(Side::Server), + ) + .into(), + ), + } + } + } + + impl Acceptor { + /// Read TLS content from `rd`. + /// + /// Returns an error if this `Acceptor` has already yielded an [`Accepted`]. For more details, + /// refer to [`Connection::read_tls()`]. + /// + /// [`Connection::read_tls()`]: crate::Connection::read_tls + pub fn read_tls(&mut self, rd: &mut dyn io::Read) -> Result { + match &mut self.inner { + Some(conn) => conn.read_tls(rd), + None => Err(io::Error::new( + io::ErrorKind::Other, + "acceptor cannot read after successful acceptance", + )), + } + } + + /// Check if a `ClientHello` message has been received. + /// + /// Returns `Ok(None)` if the complete `ClientHello` has not yet been received. + /// Do more I/O and then call this function again. + /// + /// Returns `Ok(Some(accepted))` if the connection has been accepted. Call + /// `accepted.into_connection()` to continue. Do not call this function again. + /// + /// Returns `Err(err)` if an error occurred. Do not call this function again. + pub fn accept(&mut self) -> Result, Error> { + let mut connection = match self.inner.take() { + Some(conn) => conn, + None => { + return Err(Error::General("Acceptor polled after completion".into())); + } + }; + + let message = match connection.first_handshake_message()? { + Some(msg) => msg, + None => { + self.inner = Some(connection); + return Ok(None); + } + }; + + let (_, sig_schemes) = + hs::process_client_hello(&message, false, &mut Context::from(&mut connection))?; + + Ok(Some(Accepted { + connection, + message, + sig_schemes, + })) + } + } } #[cfg(feature = "std")] -pub use connection::{ReadEarlyData, ServerConnection}; +pub use connection::{Acceptor, ReadEarlyData, ServerConnection}; /// Unbuffered version of `ServerConnection` /// @@ -705,125 +827,6 @@ impl UnbufferedConnectionCommon { } } -/// Handle a server-side connection before configuration is available. -/// -/// `Acceptor` allows the caller to choose a [`ServerConfig`] after reading -/// the [`ClientHello`] of an incoming connection. This is useful for servers -/// that choose different certificates or cipher suites based on the -/// characteristics of the `ClientHello`. In particular it is useful for -/// servers that need to do some I/O to load a certificate and its private key -/// and don't want to use the blocking interface provided by -/// [`ResolvesServerCert`]. -/// -/// Create an Acceptor with [`Acceptor::default()`]. -/// -/// # Example -/// -/// ```no_run -/// # #[cfg(feature = "aws_lc_rs")] { -/// # fn choose_server_config( -/// # _: rustls::server::ClientHello, -/// # ) -> std::sync::Arc { -/// # unimplemented!(); -/// # } -/// # #[allow(unused_variables)] -/// # fn main() { -/// use rustls::server::{Acceptor, ServerConfig}; -/// let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); -/// for stream in listener.incoming() { -/// let mut stream = stream.unwrap(); -/// let mut acceptor = Acceptor::default(); -/// let accepted = loop { -/// acceptor.read_tls(&mut stream).unwrap(); -/// if let Some(accepted) = acceptor.accept().unwrap() { -/// break accepted; -/// } -/// }; -/// -/// // For some user-defined choose_server_config: -/// let config = choose_server_config(accepted.client_hello()); -/// let conn = accepted -/// .into_connection(config) -/// .unwrap(); - -/// // Proceed with handling the ServerConnection. -/// } -/// # } -/// # } -/// ``` -pub struct Acceptor { - inner: Option>, -} - -impl Default for Acceptor { - /// Return an empty Acceptor, ready to receive bytes from a new client connection. - fn default() -> Self { - Self { - inner: Some( - ConnectionCore::new( - Box::new(Accepting), - ServerConnectionData::default(), - CommonState::new(Side::Server), - ) - .into(), - ), - } - } -} - -impl Acceptor { - /// Read TLS content from `rd`. - /// - /// Returns an error if this `Acceptor` has already yielded an [`Accepted`]. For more details, - /// refer to [`Connection::read_tls()`]. - /// - /// [`Connection::read_tls()`]: crate::Connection::read_tls - pub fn read_tls(&mut self, rd: &mut dyn io::Read) -> Result { - match &mut self.inner { - Some(conn) => conn.read_tls(rd), - None => Err(io::Error::new( - io::ErrorKind::Other, - "acceptor cannot read after successful acceptance", - )), - } - } - - /// Check if a `ClientHello` message has been received. - /// - /// Returns `Ok(None)` if the complete `ClientHello` has not yet been received. - /// Do more I/O and then call this function again. - /// - /// Returns `Ok(Some(accepted))` if the connection has been accepted. Call - /// `accepted.into_connection()` to continue. Do not call this function again. - /// - /// Returns `Err(err)` if an error occurred. Do not call this function again. - pub fn accept(&mut self) -> Result, Error> { - let mut connection = match self.inner.take() { - Some(conn) => conn, - None => { - return Err(Error::General("Acceptor polled after completion".into())); - } - }; - - let message = match connection.first_handshake_message()? { - Some(msg) => msg, - None => { - self.inner = Some(connection); - return Ok(None); - } - }; - - let (_, sig_schemes) = - hs::process_client_hello(&message, false, &mut Context::from(&mut connection))?; - - Ok(Some(Accepted { - connection, - message, - sig_schemes, - })) - } -} - /// Represents a `ClientHello` message received through the [`Acceptor`]. /// /// Contains the state required to resume the connection through [`Accepted::into_connection()`]. From 79826baa523c613dd3ef68cca228f7a556ae15b0 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 5 Oct 2023 19:03:36 +0200 Subject: [PATCH 14/27] no-std: rm Reader --- rustls/src/conn.rs | 201 ++++++++++++++++++++++--------------------- rustls/src/lib.rs | 4 +- rustls/src/vecbuf.rs | 4 + 3 files changed, 108 insertions(+), 101 deletions(-) diff --git a/rustls/src/conn.rs b/rustls/src/conn.rs index e61292b5c2..f334123741 100644 --- a/rustls/src/conn.rs +++ b/rustls/src/conn.rs @@ -23,6 +23,7 @@ mod connection { use crate::common_state::{CommonState, IoState}; use crate::error::Error; use crate::suites::ExtractedSecrets; + use crate::vecbuf::ChunkVecBuffer; use core::fmt::Debug; use core::ops::{Deref, DerefMut}; @@ -30,7 +31,7 @@ mod connection { #[cfg(doc)] use super::ConnectionCommon; - use super::{Reader, Writer}; + use super::Writer; /// A client or server connection. #[derive(Debug)] @@ -153,113 +154,117 @@ mod connection { } } } -} - -#[cfg(feature = "std")] -pub use connection::Connection; -/// A structure that implements [`std::io::Read`] for reading plaintext. -pub struct Reader<'a> { - received_plaintext: &'a mut ChunkVecBuffer, - peer_cleanly_closed: bool, - has_seen_eof: bool, -} + /// A structure that implements [`std::io::Read`] for reading plaintext. + pub struct Reader<'a> { + pub(super) received_plaintext: &'a mut ChunkVecBuffer, + pub(super) peer_cleanly_closed: bool, + pub(super) has_seen_eof: bool, + } -impl<'a> io::Read for Reader<'a> { - /// Obtain plaintext data received from the peer over this TLS connection. - /// - /// If the peer closes the TLS session cleanly, this returns `Ok(0)` once all - /// the pending data has been read. No further data can be received on that - /// connection, so the underlying TCP connection should be half-closed too. - /// - /// If the peer closes the TLS session uncleanly (a TCP EOF without sending a - /// `close_notify` alert) this function returns a `std::io::Error` of type - /// `ErrorKind::UnexpectedEof` once any pending data has been read. - /// - /// Note that support for `close_notify` varies in peer TLS libraries: many do not - /// support it and uncleanly close the TCP connection (this might be - /// vulnerable to truncation attacks depending on the application protocol). - /// This means applications using rustls must both handle EOF - /// from this function, *and* unexpected EOF of the underlying TCP connection. - /// - /// If there are no bytes to read, this returns `Err(ErrorKind::WouldBlock.into())`. - /// - /// You may learn the number of bytes available at any time by inspecting - /// the return of [`Connection::process_new_packets`]. - fn read(&mut self, buf: &mut [u8]) -> io::Result { - let len = self.received_plaintext.read(buf)?; - - if len == 0 && !buf.is_empty() { - // No bytes available: - match (self.peer_cleanly_closed, self.has_seen_eof) { - // cleanly closed; don't care about TCP EOF: express this as Ok(0) - (true, _) => {} - // unclean closure - (false, true) => { - return Err(io::Error::new( - io::ErrorKind::UnexpectedEof, - UNEXPECTED_EOF_MESSAGE, - )) + impl<'a> io::Read for Reader<'a> { + /// Obtain plaintext data received from the peer over this TLS connection. + /// + /// If the peer closes the TLS session cleanly, this returns `Ok(0)` once all + /// the pending data has been read. No further data can be received on that + /// connection, so the underlying TCP connection should be half-closed too. + /// + /// If the peer closes the TLS session uncleanly (a TCP EOF without sending a + /// `close_notify` alert) this function returns a `std::io::Error` of type + /// `ErrorKind::UnexpectedEof` once any pending data has been read. + /// + /// Note that support for `close_notify` varies in peer TLS libraries: many do not + /// support it and uncleanly close the TCP connection (this might be + /// vulnerable to truncation attacks depending on the application protocol). + /// This means applications using rustls must both handle EOF + /// from this function, *and* unexpected EOF of the underlying TCP connection. + /// + /// If there are no bytes to read, this returns `Err(ErrorKind::WouldBlock.into())`. + /// + /// You may learn the number of bytes available at any time by inspecting + /// the return of [`Connection::process_new_packets`]. + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let len = self.received_plaintext.read(buf)?; + + if len == 0 && !buf.is_empty() { + // No bytes available: + match (self.peer_cleanly_closed, self.has_seen_eof) { + // cleanly closed; don't care about TCP EOF: express this as Ok(0) + (true, _) => {} + // unclean closure + (false, true) => { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + UNEXPECTED_EOF_MESSAGE, + )) + } + // connection still going, but needs more data: signal `WouldBlock` so that + // the caller knows this + (false, false) => return Err(io::ErrorKind::WouldBlock.into()), } - // connection still going, but needs more data: signal `WouldBlock` so that - // the caller knows this - (false, false) => return Err(io::ErrorKind::WouldBlock.into()), } + + Ok(len) } - Ok(len) - } - - /// Obtain plaintext data received from the peer over this TLS connection. - /// - /// If the peer closes the TLS session, this returns `Ok(())` without filling - /// any more of the buffer once all the pending data has been read. No further - /// data can be received on that connection, so the underlying TCP connection - /// should be half-closed too. - /// - /// If the peer closes the TLS session uncleanly (a TCP EOF without sending a - /// `close_notify` alert) this function returns a `std::io::Error` of type - /// `ErrorKind::UnexpectedEof` once any pending data has been read. - /// - /// Note that support for `close_notify` varies in peer TLS libraries: many do not - /// support it and uncleanly close the TCP connection (this might be - /// vulnerable to truncation attacks depending on the application protocol). - /// This means applications using rustls must both handle EOF - /// from this function, *and* unexpected EOF of the underlying TCP connection. - /// - /// If there are no bytes to read, this returns `Err(ErrorKind::WouldBlock.into())`. - /// - /// You may learn the number of bytes available at any time by inspecting - /// the return of [`Connection::process_new_packets`]. - #[cfg(read_buf)] - fn read_buf(&mut self, mut cursor: core::io::BorrowedCursor<'_>) -> io::Result<()> { - let before = cursor.written(); - self.received_plaintext - .read_buf(cursor.reborrow())?; - let len = cursor.written() - before; - - if len == 0 && cursor.capacity() > 0 { - // No bytes available: - match (self.peer_cleanly_closed, self.has_seen_eof) { - // cleanly closed; don't care about TCP EOF: express this as Ok(0) - (true, _) => {} - // unclean closure - (false, true) => { - return Err(io::Error::new( - io::ErrorKind::UnexpectedEof, - UNEXPECTED_EOF_MESSAGE, - )); + /// Obtain plaintext data received from the peer over this TLS connection. + /// + /// If the peer closes the TLS session, this returns `Ok(())` without filling + /// any more of the buffer once all the pending data has been read. No further + /// data can be received on that connection, so the underlying TCP connection + /// should be half-closed too. + /// + /// If the peer closes the TLS session uncleanly (a TCP EOF without sending a + /// `close_notify` alert) this function returns a `std::io::Error` of type + /// `ErrorKind::UnexpectedEof` once any pending data has been read. + /// + /// Note that support for `close_notify` varies in peer TLS libraries: many do not + /// support it and uncleanly close the TCP connection (this might be + /// vulnerable to truncation attacks depending on the application protocol). + /// This means applications using rustls must both handle EOF + /// from this function, *and* unexpected EOF of the underlying TCP connection. + /// + /// If there are no bytes to read, this returns `Err(ErrorKind::WouldBlock.into())`. + /// + /// You may learn the number of bytes available at any time by inspecting + /// the return of [`Connection::process_new_packets`]. + #[cfg(read_buf)] + fn read_buf(&mut self, mut cursor: core::io::BorrowedCursor<'_>) -> io::Result<()> { + let before = cursor.written(); + self.received_plaintext + .read_buf(cursor.reborrow())?; + let len = cursor.written() - before; + + if len == 0 && cursor.capacity() > 0 { + // No bytes available: + match (self.peer_cleanly_closed, self.has_seen_eof) { + // cleanly closed; don't care about TCP EOF: express this as Ok(0) + (true, _) => {} + // unclean closure + (false, true) => { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + UNEXPECTED_EOF_MESSAGE, + )); + } + // connection still going, but need more data: signal `WouldBlock` so that + // the caller knows this + (false, false) => return Err(io::ErrorKind::WouldBlock.into()), } - // connection still going, but need more data: signal `WouldBlock` so that - // the caller knows this - (false, false) => return Err(io::ErrorKind::WouldBlock.into()), } - } - Ok(()) + Ok(()) + } } + + const UNEXPECTED_EOF_MESSAGE: &str = + "peer closed connection without sending TLS close_notify: \ +https://docs.rs/rustls/latest/rustls/manual/_03_howto/index.html#unexpected-eof"; } +#[cfg(feature = "std")] +pub use connection::{Connection, Reader}; + /// Internal trait implemented by the [`ServerConnection`]/[`ClientConnection`] /// allowing them to be the subject of a [`Writer`]. /// @@ -371,6 +376,7 @@ pub struct ConnectionCommon { impl ConnectionCommon { /// Returns an object that allows reading plaintext. + #[cfg(feature = "std")] pub fn reader(&mut self) -> Reader { let common = &mut self.core.common_state; Reader { @@ -924,6 +930,3 @@ impl ConnectionCore { /// Data specific to the peer's side (client or server). pub trait SideData: Debug {} - -const UNEXPECTED_EOF_MESSAGE: &str = "peer closed connection without sending TLS close_notify: \ -https://docs.rs/rustls/latest/rustls/manual/_03_howto/index.html#unexpected-eof"; diff --git a/rustls/src/lib.rs b/rustls/src/lib.rs index 3e976342ab..0e9721c090 100644 --- a/rustls/src/lib.rs +++ b/rustls/src/lib.rs @@ -495,8 +495,8 @@ pub mod unbuffered { pub use crate::builder::{ConfigBuilder, ConfigSide, WantsVerifier, WantsVersions}; pub use crate::common_state::{CommonState, IoState, Side}; #[cfg(feature = "std")] -pub use crate::conn::Connection; -pub use crate::conn::{ConnectionCommon, Reader, SideData, Writer}; +pub use crate::conn::{Connection, Reader}; +pub use crate::conn::{ConnectionCommon, SideData, Writer}; pub use crate::enums::{ AlertDescription, CipherSuite, ContentType, HandshakeType, ProtocolVersion, SignatureAlgorithm, SignatureScheme, diff --git a/rustls/src/vecbuf.rs b/rustls/src/vecbuf.rs index 892e852f73..7989e208c5 100644 --- a/rustls/src/vecbuf.rs +++ b/rustls/src/vecbuf.rs @@ -2,8 +2,10 @@ use alloc::collections::VecDeque; use alloc::vec::Vec; use core::cmp; use std::io; +#[cfg(feature = "std")] use std::io::Read; +#[cfg(feature = "std")] use crate::msgs::message::OutboundChunks; /// This is a byte buffer that is built from a vector @@ -93,6 +95,7 @@ impl ChunkVecBuffer { /// Read data out of this object, writing it into `buf` /// and returning how many bytes were written there. + #[cfg(feature = "std")] pub(crate) fn read(&mut self, buf: &mut [u8]) -> io::Result { let mut offs = 0; @@ -150,6 +153,7 @@ impl ChunkVecBuffer { } } +#[cfg(feature = "std")] #[cfg(test)] mod tests { use super::ChunkVecBuffer; From e5543dccff273a54daa774b0211693df5db2b294 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 5 Oct 2023 19:04:24 +0200 Subject: [PATCH 15/27] no-std: rm Writer --- rustls/src/conn.rs | 81 +++++++++++++++++++++++----------------------- rustls/src/lib.rs | 4 +-- 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/rustls/src/conn.rs b/rustls/src/conn.rs index f334123741..f6744b4d54 100644 --- a/rustls/src/conn.rs +++ b/rustls/src/conn.rs @@ -31,7 +31,7 @@ mod connection { #[cfg(doc)] use super::ConnectionCommon; - use super::Writer; + use super::PlaintextSink; /// A client or server connection. #[derive(Debug)] @@ -260,10 +260,48 @@ mod connection { const UNEXPECTED_EOF_MESSAGE: &str = "peer closed connection without sending TLS close_notify: \ https://docs.rs/rustls/latest/rustls/manual/_03_howto/index.html#unexpected-eof"; + + /// A structure that implements [`std::io::Write`] for writing plaintext. + pub struct Writer<'a> { + sink: &'a mut dyn PlaintextSink, + } + + impl<'a> Writer<'a> { + /// Create a new Writer. + /// + /// This is not an external interface. Get one of these objects + /// from [`Connection::writer`]. + pub(crate) fn new(sink: &'a mut dyn PlaintextSink) -> Writer<'a> { + Writer { sink } + } + } + + impl<'a> io::Write for Writer<'a> { + /// Send the plaintext `buf` to the peer, encrypting + /// and authenticating it. Once this function succeeds + /// you should call [`Connection::write_tls`] which will output the + /// corresponding TLS records. + /// + /// This function buffers plaintext sent before the + /// TLS handshake completes, and sends it as soon + /// as it can. See [`ConnectionCommon::set_buffer_limit`] to control + /// the size of this buffer. + fn write(&mut self, buf: &[u8]) -> io::Result { + self.sink.write(buf) + } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + self.sink.write_vectored(bufs) + } + + fn flush(&mut self) -> io::Result<()> { + self.sink.flush() + } + } } #[cfg(feature = "std")] -pub use connection::{Connection, Reader}; +pub use connection::{Connection, Reader, Writer}; /// Internal trait implemented by the [`ServerConnection`]/[`ClientConnection`] /// allowing them to be the subject of a [`Writer`]. @@ -309,44 +347,6 @@ impl PlaintextSink for ConnectionCommon { } } -/// A structure that implements [`std::io::Write`] for writing plaintext. -pub struct Writer<'a> { - sink: &'a mut dyn PlaintextSink, -} - -impl<'a> Writer<'a> { - /// Create a new Writer. - /// - /// This is not an external interface. Get one of these objects - /// from [`Connection::writer`]. - pub(crate) fn new(sink: &'a mut dyn PlaintextSink) -> Writer<'a> { - Writer { sink } - } -} - -impl<'a> io::Write for Writer<'a> { - /// Send the plaintext `buf` to the peer, encrypting - /// and authenticating it. Once this function succeeds - /// you should call [`Connection::write_tls`] which will output the - /// corresponding TLS records. - /// - /// This function buffers plaintext sent before the - /// TLS handshake completes, and sends it as soon - /// as it can. See [`ConnectionCommon::set_buffer_limit`] to control - /// the size of this buffer. - fn write(&mut self, buf: &[u8]) -> io::Result { - self.sink.write(buf) - } - - fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { - self.sink.write_vectored(bufs) - } - - fn flush(&mut self) -> io::Result<()> { - self.sink.flush() - } -} - #[derive(Debug)] pub(crate) struct ConnectionRandoms { pub(crate) client: [u8; 32], @@ -390,6 +390,7 @@ impl ConnectionCommon { } /// Returns an object that allows writing plaintext. + #[cfg(feature = "std")] pub fn writer(&mut self) -> Writer { Writer::new(self) } diff --git a/rustls/src/lib.rs b/rustls/src/lib.rs index 0e9721c090..efde031b1a 100644 --- a/rustls/src/lib.rs +++ b/rustls/src/lib.rs @@ -495,8 +495,8 @@ pub mod unbuffered { pub use crate::builder::{ConfigBuilder, ConfigSide, WantsVerifier, WantsVersions}; pub use crate::common_state::{CommonState, IoState, Side}; #[cfg(feature = "std")] -pub use crate::conn::{Connection, Reader}; -pub use crate::conn::{ConnectionCommon, SideData, Writer}; +pub use crate::conn::{Connection, Reader, Writer}; +pub use crate::conn::{ConnectionCommon, SideData}; pub use crate::enums::{ AlertDescription, CipherSuite, ContentType, HandshakeType, ProtocolVersion, SignatureAlgorithm, SignatureScheme, From 3b2aec7bfbb0cf97ff25df2ac29be1ef44f43e8f Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 5 Oct 2023 19:05:24 +0200 Subject: [PATCH 16/27] no-std: rm PlaintextSink --- rustls/src/common_state.rs | 106 +++++++++++++++++++------------------ rustls/src/conn.rs | 94 ++++++++++++++++---------------- rustls/src/vecbuf.rs | 1 + 3 files changed, 102 insertions(+), 99 deletions(-) diff --git a/rustls/src/common_state.rs b/rustls/src/common_state.rs index 70567dcd21..2d3fe37421 100644 --- a/rustls/src/common_state.rs +++ b/rustls/src/common_state.rs @@ -182,20 +182,6 @@ impl CommonState { } } - /// Send plaintext application data, fragmenting and - /// encrypting it as it goes out. - /// - /// If internal buffers are too small, this function will not accept - /// all the data. - pub(crate) fn buffer_plaintext( - &mut self, - payload: OutboundChunks<'_>, - sendable_plaintext: &mut ChunkVecBuffer, - ) -> usize { - self.perhaps_write_key_update(); - self.send_plain(payload, Limit::Yes, sendable_plaintext) - } - pub(crate) fn write_plaintext( &mut self, payload: OutboundChunks<'_>, @@ -243,19 +229,6 @@ impl CommonState { Ok(written) } - #[cfg(feature = "std")] - pub(crate) fn send_early_plaintext(&mut self, data: &[u8]) -> usize { - debug_assert!(self.early_traffic); - debug_assert!(self.record_layer.is_encrypting()); - - if data.is_empty() { - // Don't send empty fragments. - return 0; - } - - self.send_appdata_encrypt(data.into(), Limit::Yes) - } - // Changing the keys must not span any fragmented handshake // messages. Otherwise the defragmented messages will have // been protected with two different record layer protections, @@ -289,6 +262,7 @@ impl CommonState { // be out by whatever the cipher+record overhead is. That's a // constant and predictable amount, so it's not a terrible issue. let len = match limit { + #[cfg(feature = "std")] Limit::Yes => self .sendable_tls .apply_limit(payload.len()), @@ -329,30 +303,6 @@ impl CommonState { self.queue_tls_message(em); } - /// Encrypt and send some plaintext `data`. `limit` controls - /// whether the per-connection buffer limits apply. - /// - /// Returns the number of bytes written from `data`: this might - /// be less than `data.len()` if buffer limits were exceeded. - fn send_plain( - &mut self, - payload: OutboundChunks<'_>, - limit: Limit, - sendable_plaintext: &mut ChunkVecBuffer, - ) -> usize { - if !self.may_send_application_data { - // If we haven't completed handshaking, buffer - // plaintext to send once we do. - let len = match limit { - Limit::Yes => sendable_plaintext.append_limited_copy(payload), - Limit::No => sendable_plaintext.append(payload.to_vec()), - }; - return len; - } - - self.send_plain_non_buffering(payload, limit) - } - fn send_plain_non_buffering(&mut self, payload: OutboundChunks<'_>, limit: Limit) -> usize { debug_assert!(self.may_send_application_data); debug_assert!(self.record_layer.is_encrypting()); @@ -671,6 +621,59 @@ impl CommonState { .encode(), ); } +} + +#[cfg(feature = "std")] +impl CommonState { + /// Send plaintext application data, fragmenting and + /// encrypting it as it goes out. + /// + /// If internal buffers are too small, this function will not accept + /// all the data. + pub(crate) fn buffer_plaintext( + &mut self, + payload: OutboundChunks<'_>, + sendable_plaintext: &mut ChunkVecBuffer, + ) -> usize { + self.perhaps_write_key_update(); + self.send_plain(payload, Limit::Yes, sendable_plaintext) + } + + pub(crate) fn send_early_plaintext(&mut self, data: &[u8]) -> usize { + debug_assert!(self.early_traffic); + debug_assert!(self.record_layer.is_encrypting()); + + if data.is_empty() { + // Don't send empty fragments. + return 0; + } + + self.send_appdata_encrypt(data.into(), Limit::Yes) + } + + /// Encrypt and send some plaintext `data`. `limit` controls + /// whether the per-connection buffer limits apply. + /// + /// Returns the number of bytes written from `data`: this might + /// be less than `data.len()` if buffer limits were exceeded. + fn send_plain( + &mut self, + payload: OutboundChunks<'_>, + limit: Limit, + sendable_plaintext: &mut ChunkVecBuffer, + ) -> usize { + if !self.may_send_application_data { + // If we haven't completed handshaking, buffer + // plaintext to send once we do. + let len = match limit { + Limit::Yes => sendable_plaintext.append_limited_copy(payload), + Limit::No => sendable_plaintext.append(payload.to_vec()), + }; + return len; + } + + self.send_plain_non_buffering(payload, limit) + } pub(crate) fn perhaps_write_key_update(&mut self) { if let Some(message) = self.queued_key_update_message.take() { @@ -778,6 +781,7 @@ pub(crate) enum Protocol { } enum Limit { + #[cfg(feature = "std")] Yes, No, } diff --git a/rustls/src/conn.rs b/rustls/src/conn.rs index f6744b4d54..959c45235e 100644 --- a/rustls/src/conn.rs +++ b/rustls/src/conn.rs @@ -5,12 +5,11 @@ use crate::error::{Error, PeerMisbehaved}; use crate::log::trace; use crate::msgs::deframer::{Deframed, DeframerSliceBuffer, DeframerVecBuffer, MessageDeframer}; use crate::msgs::handshake::Random; -use crate::msgs::message::{InboundPlainMessage, Message, MessagePayload, OutboundChunks}; +use crate::msgs::message::{InboundPlainMessage, Message, MessagePayload}; use crate::suites::{ExtractedSecrets, PartiallyExtractedSecrets}; use crate::vecbuf::ChunkVecBuffer; use alloc::boxed::Box; -use alloc::vec::Vec; use core::fmt::Debug; use core::mem; use core::ops::{Deref, DerefMut}; @@ -22,17 +21,16 @@ pub(crate) mod unbuffered; mod connection { use crate::common_state::{CommonState, IoState}; use crate::error::Error; + use crate::msgs::message::OutboundChunks; use crate::suites::ExtractedSecrets; use crate::vecbuf::ChunkVecBuffer; + use crate::ConnectionCommon; + use alloc::vec::Vec; use core::fmt::Debug; use core::ops::{Deref, DerefMut}; use std::io; - #[cfg(doc)] - use super::ConnectionCommon; - use super::PlaintextSink; - /// A client or server connection. #[derive(Debug)] pub enum Connection { @@ -298,55 +296,55 @@ https://docs.rs/rustls/latest/rustls/manual/_03_howto/index.html#unexpected-eof" self.sink.flush() } } -} -#[cfg(feature = "std")] -pub use connection::{Connection, Reader, Writer}; + /// Internal trait implemented by the [`ServerConnection`]/[`ClientConnection`] + /// allowing them to be the subject of a [`Writer`]. + /// + /// [`ServerConnection`]: crate::ServerConnection + /// [`ClientConnection`]: crate::ClientConnection + pub(crate) trait PlaintextSink { + fn write(&mut self, buf: &[u8]) -> io::Result; + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result; + fn flush(&mut self) -> io::Result<()>; + } -/// Internal trait implemented by the [`ServerConnection`]/[`ClientConnection`] -/// allowing them to be the subject of a [`Writer`]. -/// -/// [`ServerConnection`]: crate::ServerConnection -/// [`ClientConnection`]: crate::ClientConnection -pub(crate) trait PlaintextSink { - fn write(&mut self, buf: &[u8]) -> io::Result; - fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result; - fn flush(&mut self) -> io::Result<()>; -} + impl PlaintextSink for ConnectionCommon { + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(self + .core + .common_state + .buffer_plaintext(buf.into(), &mut self.sendable_plaintext)) + } -impl PlaintextSink for ConnectionCommon { - fn write(&mut self, buf: &[u8]) -> io::Result { - Ok(self - .core - .common_state - .buffer_plaintext(buf.into(), &mut self.sendable_plaintext)) - } - - fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { - let payload_owner: Vec<&[u8]>; - let payload = match bufs.len() { - 0 => return Ok(0), - 1 => OutboundChunks::Single(bufs[0].deref()), - _ => { - payload_owner = bufs - .iter() - .map(|io_slice| io_slice.deref()) - .collect(); - - OutboundChunks::new(&payload_owner) - } - }; - Ok(self - .core - .common_state - .buffer_plaintext(payload, &mut self.sendable_plaintext)) - } + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + let payload_owner: Vec<&[u8]>; + let payload = match bufs.len() { + 0 => return Ok(0), + 1 => OutboundChunks::Single(bufs[0].deref()), + _ => { + payload_owner = bufs + .iter() + .map(|io_slice| io_slice.deref()) + .collect(); + + OutboundChunks::new(&payload_owner) + } + }; + Ok(self + .core + .common_state + .buffer_plaintext(payload, &mut self.sendable_plaintext)) + } - fn flush(&mut self) -> io::Result<()> { - Ok(()) + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } } } +#[cfg(feature = "std")] +pub use connection::{Connection, Reader, Writer}; + #[derive(Debug)] pub(crate) struct ConnectionRandoms { pub(crate) client: [u8; 32], diff --git a/rustls/src/vecbuf.rs b/rustls/src/vecbuf.rs index 7989e208c5..ea0c19ce45 100644 --- a/rustls/src/vecbuf.rs +++ b/rustls/src/vecbuf.rs @@ -70,6 +70,7 @@ impl ChunkVecBuffer { /// Append a copy of `bytes`, perhaps a prefix if /// we're near the limit. + #[cfg(feature = "std")] pub(crate) fn append_limited_copy(&mut self, payload: OutboundChunks<'_>) -> usize { let take = self.apply_limit(payload.len()); self.append(payload.split_at(take).0.to_vec()); From 73d7780e90ca3dd10e01ccb18aac5b10a8349de7 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 5 Oct 2023 19:07:15 +0200 Subject: [PATCH 17/27] no-std: rm ConnectionCommon IO methods --- rustls/src/common_state.rs | 2 + rustls/src/conn.rs | 242 +++++++++++------------ rustls/src/msgs/message/inbound_plain.rs | 2 +- rustls/src/msgs/message/mod.rs | 1 + rustls/src/vecbuf.rs | 63 +++--- 5 files changed, 157 insertions(+), 153 deletions(-) diff --git a/rustls/src/common_state.rs b/rustls/src/common_state.rs index 2d3fe37421..015c345cef 100644 --- a/rustls/src/common_state.rs +++ b/rustls/src/common_state.rs @@ -39,6 +39,7 @@ pub struct CommonState { sent_fatal_alert: bool, /// If the peer has signaled end of stream. pub(crate) has_received_close_notify: bool, + #[cfg(feature = "std")] pub(crate) has_seen_eof: bool, pub(crate) received_middlebox_ccs: u8, pub(crate) peer_certificates: Option>, @@ -67,6 +68,7 @@ impl CommonState { early_traffic: false, sent_fatal_alert: false, has_received_close_notify: false, + #[cfg(feature = "std")] has_seen_eof: false, received_middlebox_ccs: 0, peer_certificates: None, diff --git a/rustls/src/conn.rs b/rustls/src/conn.rs index 959c45235e..42d158943d 100644 --- a/rustls/src/conn.rs +++ b/rustls/src/conn.rs @@ -13,6 +13,7 @@ use alloc::boxed::Box; use core::fmt::Debug; use core::mem; use core::ops::{Deref, DerefMut}; +#[cfg(feature = "std")] use std::io; pub(crate) mod unbuffered; @@ -372,9 +373,128 @@ pub struct ConnectionCommon { sendable_plaintext: ChunkVecBuffer, } +impl ConnectionCommon { + /// Processes any new packets read by a previous call to + /// [`Connection::read_tls`]. + /// + /// Errors from this function relate to TLS protocol errors, and + /// are fatal to the connection. Future calls after an error will do + /// no new work and will return the same error. After an error is + /// received from [`process_new_packets`], you should not call [`read_tls`] + /// any more (it will fill up buffers to no purpose). However, you + /// may call the other methods on the connection, including `write`, + /// `send_close_notify`, and `write_tls`. Most likely you will want to + /// call `write_tls` to send any alerts queued by the error and then + /// close the underlying connection. + /// + /// Success from this function comes with some sundry state data + /// about the connection. + /// + /// [`read_tls`]: Connection::read_tls + /// [`process_new_packets`]: Connection::process_new_packets + #[inline] + pub fn process_new_packets(&mut self) -> Result { + self.core + .process_new_packets(&mut self.deframer_buffer, &mut self.sendable_plaintext) + } + + /// Derives key material from the agreed connection secrets. + /// + /// This function fills in `output` with `output.len()` bytes of key + /// material derived from the master session secret using `label` + /// and `context` for diversification. Ownership of the buffer is taken + /// by the function and returned via the Ok result to ensure no key + /// material leaks if the function fails. + /// + /// See RFC5705 for more details on what this does and is for. + /// + /// For TLS1.3 connections, this function does not use the + /// "early" exporter at any point. + /// + /// This function fails if called prior to the handshake completing; + /// check with [`CommonState::is_handshaking`] first. + /// + /// This function fails if `output.len()` is zero. + #[inline] + pub fn export_keying_material>( + &self, + output: T, + label: &[u8], + context: Option<&[u8]>, + ) -> Result { + self.core + .export_keying_material(output, label, context) + } + + /// Extract secrets, so they can be used when configuring kTLS, for example. + /// Should be used with care as it exposes secret key material. + pub fn dangerous_extract_secrets(self) -> Result { + if !self.enable_secret_extraction { + return Err(Error::General("Secret extraction is disabled".into())); + } + + let st = self.core.state?; + + let record_layer = self.core.common_state.record_layer; + let PartiallyExtractedSecrets { tx, rx } = st.extract_secrets()?; + Ok(ExtractedSecrets { + tx: (record_layer.write_seq(), tx), + rx: (record_layer.read_seq(), rx), + }) + } + + /// Sets a limit on the internal buffers used to buffer + /// unsent plaintext (prior to completing the TLS handshake) + /// and unsent TLS records. This limit acts only on application + /// data written through [`Connection::writer`]. + /// + /// By default the limit is 64KB. The limit can be set + /// at any time, even if the current buffer use is higher. + /// + /// [`None`] means no limit applies, and will mean that written + /// data is buffered without bound -- it is up to the application + /// to appropriately schedule its plaintext and TLS writes to bound + /// memory usage. + /// + /// For illustration: `Some(1)` means a limit of one byte applies: + /// [`Connection::writer`] will accept only one byte, encrypt it and + /// add a TLS header. Once this is sent via [`Connection::write_tls`], + /// another byte may be sent. + /// + /// # Internal write-direction buffering + /// rustls has two buffers whose size are bounded by this setting: + /// + /// ## Buffering of unsent plaintext data prior to handshake completion + /// + /// Calls to [`Connection::writer`] before or during the handshake + /// are buffered (up to the limit specified here). Once the + /// handshake completes this data is encrypted and the resulting + /// TLS records are added to the outgoing buffer. + /// + /// ## Buffering of outgoing TLS records + /// + /// This buffer is used to store TLS records that rustls needs to + /// send to the peer. It is used in these two circumstances: + /// + /// - by [`Connection::process_new_packets`] when a handshake or alert + /// TLS record needs to be sent. + /// - by [`Connection::writer`] post-handshake: the plaintext is + /// encrypted and the resulting TLS record is buffered. + /// + /// This buffer is emptied by [`Connection::write_tls`]. + /// + /// [`Connection::writer`]: crate::Connection::writer + /// [`Connection::write_tls`]: crate::Connection::write_tls + /// [`Connection::process_new_packets`]: crate::Connection::process_new_packets + pub fn set_buffer_limit(&mut self, limit: Option) { + self.sendable_plaintext.set_limit(limit); + self.sendable_tls.set_limit(limit); + } +} + +#[cfg(feature = "std")] impl ConnectionCommon { /// Returns an object that allows reading plaintext. - #[cfg(feature = "std")] pub fn reader(&mut self) -> Reader { let common = &mut self.core.common_state; Reader { @@ -388,7 +508,6 @@ impl ConnectionCommon { } /// Returns an object that allows writing plaintext. - #[cfg(feature = "std")] pub fn writer(&mut self) -> Writer { Writer::new(self) } @@ -494,7 +613,6 @@ impl ConnectionCommon { /// /// This is a shortcut to the `process_new_packets()` -> `process_msg()` -> /// `process_handshake_messages()` path, specialized for the first handshake message. - #[cfg(feature = "std")] pub(crate) fn first_handshake_message(&mut self) -> Result>, Error> { let mut deframer_buffer = self.deframer_buffer.borrow(); let res = self @@ -511,35 +629,10 @@ impl ConnectionCommon { } } - #[cfg(feature = "std")] pub(crate) fn replace_state(&mut self, new: Box>) { self.core.state = Ok(new); } - /// Processes any new packets read by a previous call to - /// [`Connection::read_tls`]. - /// - /// Errors from this function relate to TLS protocol errors, and - /// are fatal to the connection. Future calls after an error will do - /// no new work and will return the same error. After an error is - /// received from [`process_new_packets`], you should not call [`read_tls`] - /// any more (it will fill up buffers to no purpose). However, you - /// may call the other methods on the connection, including `write`, - /// `send_close_notify`, and `write_tls`. Most likely you will want to - /// call `write_tls` to send any alerts queued by the error and then - /// close the underlying connection. - /// - /// Success from this function comes with some sundry state data - /// about the connection. - /// - /// [`read_tls`]: Connection::read_tls - /// [`process_new_packets`]: Connection::process_new_packets - #[inline] - pub fn process_new_packets(&mut self) -> Result { - self.core - .process_new_packets(&mut self.deframer_buffer, &mut self.sendable_plaintext) - } - /// Read TLS content from `rd` into the internal buffer. /// /// Due to the internal buffering, `rd` can supply TLS messages in arbitrary-sized chunks (like @@ -587,99 +680,6 @@ impl ConnectionCommon { pub fn write_tls(&mut self, wr: &mut dyn io::Write) -> Result { self.sendable_tls.write_to(wr) } - - /// Derives key material from the agreed connection secrets. - /// - /// This function fills in `output` with `output.len()` bytes of key - /// material derived from the master session secret using `label` - /// and `context` for diversification. Ownership of the buffer is taken - /// by the function and returned via the Ok result to ensure no key - /// material leaks if the function fails. - /// - /// See RFC5705 for more details on what this does and is for. - /// - /// For TLS1.3 connections, this function does not use the - /// "early" exporter at any point. - /// - /// This function fails if called prior to the handshake completing; - /// check with [`CommonState::is_handshaking`] first. - /// - /// This function fails if `output.len()` is zero. - #[inline] - pub fn export_keying_material>( - &self, - output: T, - label: &[u8], - context: Option<&[u8]>, - ) -> Result { - self.core - .export_keying_material(output, label, context) - } - - /// Extract secrets, so they can be used when configuring kTLS, for example. - /// Should be used with care as it exposes secret key material. - pub fn dangerous_extract_secrets(self) -> Result { - if !self.enable_secret_extraction { - return Err(Error::General("Secret extraction is disabled".into())); - } - - let st = self.core.state?; - - let record_layer = self.core.common_state.record_layer; - let PartiallyExtractedSecrets { tx, rx } = st.extract_secrets()?; - Ok(ExtractedSecrets { - tx: (record_layer.write_seq(), tx), - rx: (record_layer.read_seq(), rx), - }) - } - - /// Sets a limit on the internal buffers used to buffer - /// unsent plaintext (prior to completing the TLS handshake) - /// and unsent TLS records. This limit acts only on application - /// data written through [`Connection::writer`]. - /// - /// By default the limit is 64KB. The limit can be set - /// at any time, even if the current buffer use is higher. - /// - /// [`None`] means no limit applies, and will mean that written - /// data is buffered without bound -- it is up to the application - /// to appropriately schedule its plaintext and TLS writes to bound - /// memory usage. - /// - /// For illustration: `Some(1)` means a limit of one byte applies: - /// [`Connection::writer`] will accept only one byte, encrypt it and - /// add a TLS header. Once this is sent via [`Connection::write_tls`], - /// another byte may be sent. - /// - /// # Internal write-direction buffering - /// rustls has two buffers whose size are bounded by this setting: - /// - /// ## Buffering of unsent plaintext data prior to handshake completion - /// - /// Calls to [`Connection::writer`] before or during the handshake - /// are buffered (up to the limit specified here). Once the - /// handshake completes this data is encrypted and the resulting - /// TLS records are added to the outgoing buffer. - /// - /// ## Buffering of outgoing TLS records - /// - /// This buffer is used to store TLS records that rustls needs to - /// send to the peer. It is used in these two circumstances: - /// - /// - by [`Connection::process_new_packets`] when a handshake or alert - /// TLS record needs to be sent. - /// - by [`Connection::writer`] post-handshake: the plaintext is - /// encrypted and the resulting TLS record is buffered. - /// - /// This buffer is emptied by [`Connection::write_tls`]. - /// - /// [`Connection::writer`]: crate::Connection::writer - /// [`Connection::write_tls`]: crate::Connection::write_tls - /// [`Connection::process_new_packets`]: crate::Connection::process_new_packets - pub fn set_buffer_limit(&mut self, limit: Option) { - self.sendable_plaintext.set_limit(limit); - self.sendable_tls.set_limit(limit); - } } impl<'a, Data> From<&'a mut ConnectionCommon> for Context<'a, Data> { diff --git a/rustls/src/msgs/message/inbound_plain.rs b/rustls/src/msgs/message/inbound_plain.rs index 36fca08b00..398bf56f00 100644 --- a/rustls/src/msgs/message/inbound_plain.rs +++ b/rustls/src/msgs/message/inbound_plain.rs @@ -23,7 +23,7 @@ impl InboundPlainMessage<'_> { self.typ == ContentType::ChangeCipherSpec && self.payload == [0x01] } - #[cfg(test)] + #[cfg(all(test, feature = "std"))] pub(crate) fn into_owned(self) -> super::PlainMessage { super::PlainMessage { version: self.version, diff --git a/rustls/src/msgs/message/mod.rs b/rustls/src/msgs/message/mod.rs index 052bf93b36..435715a4d3 100644 --- a/rustls/src/msgs/message/mod.rs +++ b/rustls/src/msgs/message/mod.rs @@ -183,6 +183,7 @@ impl Message<'_> { } } + #[cfg(feature = "std")] pub(crate) fn into_owned(self) -> Message<'static> { let Self { version, payload } = self; Message { diff --git a/rustls/src/vecbuf.rs b/rustls/src/vecbuf.rs index ea0c19ce45..8637bc0666 100644 --- a/rustls/src/vecbuf.rs +++ b/rustls/src/vecbuf.rs @@ -1,6 +1,7 @@ use alloc::collections::VecDeque; use alloc::vec::Vec; use core::cmp; +#[cfg(feature = "std")] use std::io; #[cfg(feature = "std")] use std::io::Read; @@ -41,12 +42,6 @@ impl ChunkVecBuffer { self.chunks.is_empty() } - pub(crate) fn is_full(&self) -> bool { - self.limit - .map(|limit| self.len() > limit) - .unwrap_or_default() - } - /// How many bytes we're storing pub(crate) fn len(&self) -> usize { let mut len = 0; @@ -68,15 +63,6 @@ impl ChunkVecBuffer { } } - /// Append a copy of `bytes`, perhaps a prefix if - /// we're near the limit. - #[cfg(feature = "std")] - pub(crate) fn append_limited_copy(&mut self, payload: OutboundChunks<'_>) -> usize { - let take = self.apply_limit(payload.len()); - self.append(payload.split_at(take).0.to_vec()); - take - } - /// Take and append the given `bytes`. pub(crate) fn append(&mut self, bytes: Vec) -> usize { let len = bytes.len(); @@ -94,9 +80,38 @@ impl ChunkVecBuffer { self.chunks.pop_front() } + #[cfg(read_buf)] + /// Read data out of this object, writing it into `cursor`. + pub(crate) fn read_buf(&mut self, mut cursor: core::io::BorrowedCursor<'_>) -> io::Result<()> { + while !self.is_empty() && cursor.capacity() > 0 { + let chunk = self.chunks[0].as_slice(); + let used = core::cmp::min(chunk.len(), cursor.capacity()); + cursor.append(&chunk[..used]); + self.consume(used); + } + + Ok(()) + } +} + +#[cfg(feature = "std")] +impl ChunkVecBuffer { + pub(crate) fn is_full(&self) -> bool { + self.limit + .map(|limit| self.len() > limit) + .unwrap_or_default() + } + + /// Append a copy of `bytes`, perhaps a prefix if + /// we're near the limit. + pub(crate) fn append_limited_copy(&mut self, payload: OutboundChunks<'_>) -> usize { + let take = self.apply_limit(payload.len()); + self.append(payload.split_at(take).0.to_vec()); + take + } + /// Read data out of this object, writing it into `buf` /// and returning how many bytes were written there. - #[cfg(feature = "std")] pub(crate) fn read(&mut self, buf: &mut [u8]) -> io::Result { let mut offs = 0; @@ -112,19 +127,6 @@ impl ChunkVecBuffer { Ok(offs) } - #[cfg(read_buf)] - /// Read data out of this object, writing it into `cursor`. - pub(crate) fn read_buf(&mut self, mut cursor: core::io::BorrowedCursor<'_>) -> io::Result<()> { - while !self.is_empty() && cursor.capacity() > 0 { - let chunk = self.chunks[0].as_slice(); - let used = core::cmp::min(chunk.len(), cursor.capacity()); - cursor.append(&chunk[..used]); - self.consume(used); - } - - Ok(()) - } - fn consume(&mut self, mut used: usize) { while let Some(mut buf) = self.chunks.pop_front() { if used < buf.len() { @@ -154,8 +156,7 @@ impl ChunkVecBuffer { } } -#[cfg(feature = "std")] -#[cfg(test)] +#[cfg(all(test, feature = "std"))] mod tests { use super::ChunkVecBuffer; From fe8ca37bc5d8c055b96348e16abe2f4528afadc5 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 5 Oct 2023 19:10:05 +0200 Subject: [PATCH 18/27] no-std: rm StdError implementations --- rustls/src/client/client_conn.rs | 4 ++-- rustls/src/conn/unbuffered.rs | 3 +++ rustls/src/crypto/cipher.rs | 4 ++-- rustls/src/error.rs | 4 ++-- rustls/src/webpki/mod.rs | 4 ++-- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/rustls/src/client/client_conn.rs b/rustls/src/client/client_conn.rs index 5c641e873e..d57a5a18b8 100644 --- a/rustls/src/client/client_conn.rs +++ b/rustls/src/client/client_conn.rs @@ -32,7 +32,6 @@ use core::fmt; use core::marker::PhantomData; use core::mem; use core::ops::{Deref, DerefMut}; -use std::error::Error as StdError; #[cfg(doc)] use crate::{crypto, DistinguishedName}; @@ -894,7 +893,8 @@ impl fmt::Display for EarlyDataError { } } -impl StdError for EarlyDataError {} +#[cfg(feature = "std")] +impl std::error::Error for EarlyDataError {} /// State associated with a client connection. #[derive(Debug)] diff --git a/rustls/src/conn/unbuffered.rs b/rustls/src/conn/unbuffered.rs index b4946dfd5b..eef4066343 100644 --- a/rustls/src/conn/unbuffered.rs +++ b/rustls/src/conn/unbuffered.rs @@ -3,6 +3,7 @@ use alloc::vec::Vec; use core::num::NonZeroUsize; use core::{fmt, mem}; +#[cfg(feature = "std")] use std::error::Error as StdError; use super::UnbufferedConnectionCommon; @@ -512,6 +513,7 @@ impl fmt::Display for EncodeError { } } +#[cfg(feature = "std")] impl StdError for EncodeError {} /// Errors that may arise when encrypting application data @@ -542,6 +544,7 @@ impl fmt::Display for EncryptError { } } +#[cfg(feature = "std")] impl StdError for EncryptError {} /// Provided buffer was too small diff --git a/rustls/src/crypto/cipher.rs b/rustls/src/crypto/cipher.rs index 3c4b42dd2d..326b0351ee 100644 --- a/rustls/src/crypto/cipher.rs +++ b/rustls/src/crypto/cipher.rs @@ -1,7 +1,6 @@ use alloc::boxed::Box; use alloc::string::ToString; use core::fmt; -use std::error::Error as StdError; use crate::enums::{ContentType, ProtocolVersion}; use crate::error::Error; @@ -103,7 +102,8 @@ impl fmt::Display for UnsupportedOperationError { } } -impl StdError for UnsupportedOperationError {} +#[cfg(feature = "std")] +impl std::error::Error for UnsupportedOperationError {} /// How a TLS1.2 `key_block` is partitioned. /// diff --git a/rustls/src/error.rs b/rustls/src/error.rs index f9d4ddd9d3..510d96c37f 100644 --- a/rustls/src/error.rs +++ b/rustls/src/error.rs @@ -6,7 +6,6 @@ use alloc::format; use alloc::string::String; use alloc::vec::Vec; use core::fmt; -use std::error::Error as StdError; use std::time::SystemTimeError; /// rustls reports protocol errors using this type. @@ -535,7 +534,8 @@ impl From for Error { } } -impl StdError for Error {} +#[cfg(feature = "std")] +impl std::error::Error for Error {} impl From for Error { fn from(_: rand::GetRandomFailed) -> Self { diff --git a/rustls/src/webpki/mod.rs b/rustls/src/webpki/mod.rs index 1491b8d762..0b64c2ce59 100644 --- a/rustls/src/webpki/mod.rs +++ b/rustls/src/webpki/mod.rs @@ -4,7 +4,6 @@ use alloc::vec::Vec; use core::fmt; use pki_types::CertificateRevocationListDer; -use std::error::Error as StdError; use webpki::{CertRevocationList, OwnedCertRevocationList}; use crate::error::{CertRevocationListError, CertificateError, Error, OtherError}; @@ -52,7 +51,8 @@ impl fmt::Display for VerifierBuilderError { } } -impl StdError for VerifierBuilderError {} +#[cfg(feature = "std")] +impl std::error::Error for VerifierBuilderError {} fn pki_error(error: webpki::Error) -> Error { use webpki::Error::*; From b29cc294928d76da55782c217fe72e3f3290cfac Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 5 Oct 2023 19:12:54 +0200 Subject: [PATCH 19/27] no-std: rm ResolvesServerCertUsingSni --- rustls/src/lib.rs | 1 + rustls/src/server/handy.rs | 168 ++++++++++++++++++++----------------- 2 files changed, 94 insertions(+), 75 deletions(-) diff --git a/rustls/src/lib.rs b/rustls/src/lib.rs index efde031b1a..26874c4047 100644 --- a/rustls/src/lib.rs +++ b/rustls/src/lib.rs @@ -582,6 +582,7 @@ pub mod server { ClientCertVerifierBuilder, ParsedCertificate, VerifierBuilderError, WebPkiClientVerifier, }; pub use builder::WantsServerCert; + #[cfg(feature = "std")] pub use handy::ResolvesServerCertUsingSni; pub use handy::{NoServerSessionStorage, ServerSessionMemoryCache}; pub use server_conn::StoresServerSessions; diff --git a/rustls/src/server/handy.rs b/rustls/src/server/handy.rs index 68a9a47682..0ac343dcf8 100644 --- a/rustls/src/server/handy.rs +++ b/rustls/src/server/handy.rs @@ -1,18 +1,12 @@ -use crate::error::Error; use crate::limited_cache; use crate::msgs::handshake::CertificateChain; use crate::server; use crate::server::ClientHello; use crate::sign; -use crate::webpki::{verify_server_name, ParsedCertificate}; -use pki_types::{DnsName, ServerName}; - -use alloc::string::{String, ToString}; use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt::{Debug, Formatter}; -use std::collections::HashMap; use std::sync::Mutex; /// Something which never stores sessions. @@ -144,71 +138,114 @@ impl server::ResolvesServerCert for AlwaysResolvesChain { } } -/// Something that resolves do different cert chains/keys based -/// on client-supplied server name (via SNI). -#[derive(Debug)] -pub struct ResolvesServerCertUsingSni { - by_name: HashMap>, -} +#[cfg(feature = "std")] +mod sni_resolver { + use crate::error::Error; + use crate::server; + use crate::server::ClientHello; + use crate::sign; + use crate::webpki::{verify_server_name, ParsedCertificate}; + + use pki_types::{DnsName, ServerName}; + + use alloc::string::{String, ToString}; + use alloc::sync::Arc; + use core::fmt::Debug; + use std::collections::HashMap; + + /// Something that resolves do different cert chains/keys based + /// on client-supplied server name (via SNI). + #[derive(Debug)] + pub struct ResolvesServerCertUsingSni { + by_name: HashMap>, + } -impl ResolvesServerCertUsingSni { - /// Create a new and empty (i.e., knows no certificates) resolver. - pub fn new() -> Self { - Self { - by_name: HashMap::new(), + impl ResolvesServerCertUsingSni { + /// Create a new and empty (i.e., knows no certificates) resolver. + pub fn new() -> Self { + Self { + by_name: HashMap::new(), + } + } + + /// Add a new `sign::CertifiedKey` to be used for the given SNI `name`. + /// + /// This function fails if `name` is not a valid DNS name, or if + /// it's not valid for the supplied certificate, or if the certificate + /// chain is syntactically faulty. + pub fn add(&mut self, name: &str, ck: sign::CertifiedKey) -> Result<(), Error> { + let server_name = { + let checked_name = DnsName::try_from(name) + .map_err(|_| Error::General("Bad DNS name".into())) + .map(|name| name.to_lowercase_owned())?; + ServerName::DnsName(checked_name) + }; + + // Check the certificate chain for validity: + // - it should be non-empty list + // - the first certificate should be parsable as a x509v3, + // - the first certificate should quote the given server name + // (if provided) + // + // These checks are not security-sensitive. They are the + // *server* attempting to detect accidental misconfiguration. + + ck.end_entity_cert() + .and_then(ParsedCertificate::try_from) + .and_then(|cert| verify_server_name(&cert, &server_name))?; + + if let ServerName::DnsName(name) = server_name { + self.by_name + .insert(name.as_ref().to_string(), Arc::new(ck)); + } + Ok(()) } } - /// Add a new `sign::CertifiedKey` to be used for the given SNI `name`. - /// - /// This function fails if `name` is not a valid DNS name, or if - /// it's not valid for the supplied certificate, or if the certificate - /// chain is syntactically faulty. - pub fn add(&mut self, name: &str, ck: sign::CertifiedKey) -> Result<(), Error> { - let server_name = { - let checked_name = DnsName::try_from(name) - .map_err(|_| Error::General("Bad DNS name".into())) - .map(|name| name.to_lowercase_owned())?; - ServerName::DnsName(checked_name) - }; - - // Check the certificate chain for validity: - // - it should be non-empty list - // - the first certificate should be parsable as a x509v3, - // - the first certificate should quote the given server name - // (if provided) - // - // These checks are not security-sensitive. They are the - // *server* attempting to detect accidental misconfiguration. - - ck.end_entity_cert() - .and_then(ParsedCertificate::try_from) - .and_then(|cert| verify_server_name(&cert, &server_name))?; - - if let ServerName::DnsName(name) = server_name { - self.by_name - .insert(name.as_ref().to_string(), Arc::new(ck)); + impl server::ResolvesServerCert for ResolvesServerCertUsingSni { + fn resolve(&self, client_hello: ClientHello) -> Option> { + if let Some(name) = client_hello.server_name() { + self.by_name.get(name).map(Arc::clone) + } else { + // This kind of resolver requires SNI + None + } } - Ok(()) } -} -impl server::ResolvesServerCert for ResolvesServerCertUsingSni { - fn resolve(&self, client_hello: ClientHello) -> Option> { - if let Some(name) = client_hello.server_name() { - self.by_name.get(name).map(Arc::clone) - } else { - // This kind of resolver requires SNI - None + #[cfg(test)] + mod tests { + use super::*; + use crate::server::ResolvesServerCert; + + #[test] + fn test_resolvesservercertusingsni_requires_sni() { + let rscsni = ResolvesServerCertUsingSni::new(); + assert!(rscsni + .resolve(ClientHello::new(&None, &[], None, &[])) + .is_none()); + } + + #[test] + fn test_resolvesservercertusingsni_handles_unknown_name() { + let rscsni = ResolvesServerCertUsingSni::new(); + let name = DnsName::try_from("hello.com") + .unwrap() + .to_owned(); + assert!(rscsni + .resolve(ClientHello::new(&Some(name), &[], None, &[])) + .is_none()); } } } +#[cfg(feature = "std")] +pub use sni_resolver::ResolvesServerCertUsingSni; + #[cfg(test)] mod tests { use super::*; use crate::server::ProducesTickets; - use crate::server::ResolvesServerCert; use crate::server::StoresServerSessions; #[test] @@ -282,23 +319,4 @@ mod tests { assert_eq!(None, npt.encrypt(&[])); assert_eq!(None, npt.decrypt(&[])); } - - #[test] - fn test_resolvesservercertusingsni_requires_sni() { - let rscsni = ResolvesServerCertUsingSni::new(); - assert!(rscsni - .resolve(ClientHello::new(&None, &[], None, &[])) - .is_none()); - } - - #[test] - fn test_resolvesservercertusingsni_handles_unknown_name() { - let rscsni = ResolvesServerCertUsingSni::new(); - let name = DnsName::try_from("hello.com") - .unwrap() - .to_owned(); - assert!(rscsni - .resolve(ClientHello::new(&Some(name), &[], None, &[])) - .is_none()); - } } From 63c788a874129a1b901aba40c8e6f616581a1e2d Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 5 Oct 2023 19:13:49 +0200 Subject: [PATCH 20/27] no-std: rm SystemTimeError -> Error conversion --- rustls/src/error.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rustls/src/error.rs b/rustls/src/error.rs index 510d96c37f..cf6bdf4d7f 100644 --- a/rustls/src/error.rs +++ b/rustls/src/error.rs @@ -6,6 +6,7 @@ use alloc::format; use alloc::string::String; use alloc::vec::Vec; use core::fmt; +#[cfg(feature = "std")] use std::time::SystemTimeError; /// rustls reports protocol errors using this type. @@ -527,6 +528,7 @@ impl fmt::Display for Error { } } +#[cfg(feature = "std")] impl From for Error { #[inline] fn from(_: SystemTimeError) -> Self { @@ -706,6 +708,7 @@ mod tests { assert_eq!(err, Error::FailedToGetRandomBytes); } + #[cfg(feature = "std")] #[test] fn time_error_mapping() { use std::time::SystemTime; From fb4e967b21c4e6c6a23243c8a157b053da7a49cd Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 5 Oct 2023 19:17:10 +0200 Subject: [PATCH 21/27] no-std: rm ServerSessionMemoryCache will be back in phase II --- rustls/src/lib.rs | 5 +- rustls/src/server/builder.rs | 3 + rustls/src/server/handy.rs | 179 +++++++++++++++++-------------- rustls/src/server/server_conn.rs | 4 +- 4 files changed, 108 insertions(+), 83 deletions(-) diff --git a/rustls/src/lib.rs b/rustls/src/lib.rs index 26874c4047..11e5b76a1d 100644 --- a/rustls/src/lib.rs +++ b/rustls/src/lib.rs @@ -386,6 +386,7 @@ mod conn; pub mod crypto; mod error; mod hash_hs; +#[cfg(feature = "std")] mod limited_cache; mod rand; mod record_layer; @@ -582,9 +583,11 @@ pub mod server { ClientCertVerifierBuilder, ParsedCertificate, VerifierBuilderError, WebPkiClientVerifier, }; pub use builder::WantsServerCert; + pub use handy::NoServerSessionStorage; #[cfg(feature = "std")] pub use handy::ResolvesServerCertUsingSni; - pub use handy::{NoServerSessionStorage, ServerSessionMemoryCache}; + #[cfg(feature = "std")] + pub use handy::ServerSessionMemoryCache; pub use server_conn::StoresServerSessions; pub use server_conn::{ Accepted, ServerConfig, ServerConnectionData, UnbufferedServerConnection, diff --git a/rustls/src/server/builder.rs b/rustls/src/server/builder.rs index 2165f5c539..11ea41ab9d 100644 --- a/rustls/src/server/builder.rs +++ b/rustls/src/server/builder.rs @@ -118,7 +118,10 @@ impl ConfigBuilder { cert_resolver, ignore_client_order: false, max_fragment_size: None, + #[cfg(feature = "std")] session_storage: handy::ServerSessionMemoryCache::new(256), + #[cfg(not(feature = "std"))] + session_storage: Arc::new(handy::NoServerSessionStorage {}), ticketer: Arc::new(handy::NeverProducesTickets {}), alpn_protocols: Vec::new(), versions: self.state.versions, diff --git a/rustls/src/server/handy.rs b/rustls/src/server/handy.rs index 0ac343dcf8..c4524e629e 100644 --- a/rustls/src/server/handy.rs +++ b/rustls/src/server/handy.rs @@ -1,4 +1,3 @@ -use crate::limited_cache; use crate::msgs::handshake::CertificateChain; use crate::server; use crate::server::ClientHello; @@ -6,8 +5,7 @@ use crate::sign; use alloc::sync::Arc; use alloc::vec::Vec; -use core::fmt::{Debug, Formatter}; -use std::sync::Mutex; +use core::fmt::Debug; /// Something which never stores sessions. #[derive(Debug)] @@ -28,56 +26,115 @@ impl server::StoresServerSessions for NoServerSessionStorage { } } -/// An implementer of `StoresServerSessions` that stores everything -/// in memory. If enforces a limit on the number of stored sessions -/// to bound memory usage. -pub struct ServerSessionMemoryCache { - cache: Mutex, Vec>>, -} +#[cfg(feature = "std")] +mod cache { + use crate::limited_cache; + use crate::server; -impl ServerSessionMemoryCache { - /// Make a new ServerSessionMemoryCache. `size` is the maximum - /// number of stored sessions, and may be rounded-up for - /// efficiency. - pub fn new(size: usize) -> Arc { - Arc::new(Self { - cache: Mutex::new(limited_cache::LimitedCache::new(size)), - }) + use alloc::sync::Arc; + use alloc::vec::Vec; + use core::fmt::{Debug, Formatter}; + use std::sync::Mutex; + + /// An implementer of `StoresServerSessions` that stores everything + /// in memory. If enforces a limit on the number of stored sessions + /// to bound memory usage. + pub struct ServerSessionMemoryCache { + cache: Mutex, Vec>>, } -} -impl server::StoresServerSessions for ServerSessionMemoryCache { - fn put(&self, key: Vec, value: Vec) -> bool { - self.cache - .lock() - .unwrap() - .insert(key, value); - true + impl ServerSessionMemoryCache { + /// Make a new ServerSessionMemoryCache. `size` is the maximum + /// number of stored sessions, and may be rounded-up for + /// efficiency. + pub fn new(size: usize) -> Arc { + Arc::new(Self { + cache: Mutex::new(limited_cache::LimitedCache::new(size)), + }) + } } - fn get(&self, key: &[u8]) -> Option> { - self.cache - .lock() - .unwrap() - .get(key) - .cloned() - } + impl server::StoresServerSessions for ServerSessionMemoryCache { + fn put(&self, key: Vec, value: Vec) -> bool { + self.cache + .lock() + .unwrap() + .insert(key, value); + true + } + + fn get(&self, key: &[u8]) -> Option> { + self.cache + .lock() + .unwrap() + .get(key) + .cloned() + } - fn take(&self, key: &[u8]) -> Option> { - self.cache.lock().unwrap().remove(key) + fn take(&self, key: &[u8]) -> Option> { + self.cache.lock().unwrap().remove(key) + } + + fn can_cache(&self) -> bool { + true + } } - fn can_cache(&self) -> bool { - true + impl Debug for ServerSessionMemoryCache { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_struct("ServerSessionMemoryCache") + .finish() + } } -} -impl Debug for ServerSessionMemoryCache { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - f.debug_struct("ServerSessionMemoryCache") - .finish() + #[cfg(test)] + mod tests { + use super::*; + use crate::server::StoresServerSessions; + + #[test] + fn test_serversessionmemorycache_accepts_put() { + let c = ServerSessionMemoryCache::new(4); + assert!(c.put(vec![0x01], vec![0x02])); + } + + #[test] + fn test_serversessionmemorycache_persists_put() { + let c = ServerSessionMemoryCache::new(4); + assert!(c.put(vec![0x01], vec![0x02])); + assert_eq!(c.get(&[0x01]), Some(vec![0x02])); + assert_eq!(c.get(&[0x01]), Some(vec![0x02])); + } + + #[test] + fn test_serversessionmemorycache_overwrites_put() { + let c = ServerSessionMemoryCache::new(4); + assert!(c.put(vec![0x01], vec![0x02])); + assert!(c.put(vec![0x01], vec![0x04])); + assert_eq!(c.get(&[0x01]), Some(vec![0x04])); + } + + #[test] + fn test_serversessionmemorycache_drops_to_maintain_size_invariant() { + let c = ServerSessionMemoryCache::new(2); + assert!(c.put(vec![0x01], vec![0x02])); + assert!(c.put(vec![0x03], vec![0x04])); + assert!(c.put(vec![0x05], vec![0x06])); + assert!(c.put(vec![0x07], vec![0x08])); + assert!(c.put(vec![0x09], vec![0x0a])); + + let count = c.get(&[0x01]).iter().count() + + c.get(&[0x03]).iter().count() + + c.get(&[0x05]).iter().count() + + c.get(&[0x07]).iter().count() + + c.get(&[0x09]).iter().count(); + + assert!(count < 5); + } } } +#[cfg(feature = "std")] +pub use cache::ServerSessionMemoryCache; /// Something which never produces tickets. #[derive(Debug)] @@ -271,46 +328,6 @@ mod tests { assert_eq!(c.take(&[0x02]), None); } - #[test] - fn test_serversessionmemorycache_accepts_put() { - let c = ServerSessionMemoryCache::new(4); - assert!(c.put(vec![0x01], vec![0x02])); - } - - #[test] - fn test_serversessionmemorycache_persists_put() { - let c = ServerSessionMemoryCache::new(4); - assert!(c.put(vec![0x01], vec![0x02])); - assert_eq!(c.get(&[0x01]), Some(vec![0x02])); - assert_eq!(c.get(&[0x01]), Some(vec![0x02])); - } - - #[test] - fn test_serversessionmemorycache_overwrites_put() { - let c = ServerSessionMemoryCache::new(4); - assert!(c.put(vec![0x01], vec![0x02])); - assert!(c.put(vec![0x01], vec![0x04])); - assert_eq!(c.get(&[0x01]), Some(vec![0x04])); - } - - #[test] - fn test_serversessionmemorycache_drops_to_maintain_size_invariant() { - let c = ServerSessionMemoryCache::new(2); - assert!(c.put(vec![0x01], vec![0x02])); - assert!(c.put(vec![0x03], vec![0x04])); - assert!(c.put(vec![0x05], vec![0x06])); - assert!(c.put(vec![0x07], vec![0x08])); - assert!(c.put(vec![0x09], vec![0x0a])); - - let count = c.get(&[0x01]).iter().count() - + c.get(&[0x03]).iter().count() - + c.get(&[0x05]).iter().count() - + c.get(&[0x07]).iter().count() - + c.get(&[0x09]).iter().count(); - - assert!(count < 5); - } - #[test] fn test_neverproducestickets_does_nothing() { let npt = NeverProducesTickets {}; diff --git a/rustls/src/server/server_conn.rs b/rustls/src/server/server_conn.rs index d93fff21da..fda881febf 100644 --- a/rustls/src/server/server_conn.rs +++ b/rustls/src/server/server_conn.rs @@ -213,7 +213,9 @@ impl<'a> ClientHello<'a> { /// # Defaults /// /// * [`ServerConfig::max_fragment_size`]: the default is `None` (meaning 16kB). -/// * [`ServerConfig::session_storage`]: the default stores 256 sessions in memory. +/// * [`ServerConfig::session_storage`]: if the `std` feature is enabled, the default stores 256 +/// sessions in memory. If the `std` feature is not enabled, the default is to not store any +/// sessions. /// * [`ServerConfig::alpn_protocols`]: the default is empty -- no ALPN protocol is negotiated. /// * [`ServerConfig::key_log`]: key material is not logged. /// * [`ServerConfig::send_tls13_tickets`]: 4 tickets are sent. From f5ac27762469f859fe0e865ee49964e5a58144a1 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Fri, 24 Nov 2023 15:33:27 +0100 Subject: [PATCH 22/27] no-std: rm quic::*Connection API --- rustls/src/client/client_conn.rs | 3 + rustls/src/msgs/deframer.rs | 1 + rustls/src/quic.rs | 690 ++++++++++++++++--------------- rustls/src/server/server_conn.rs | 8 +- 4 files changed, 365 insertions(+), 337 deletions(-) diff --git a/rustls/src/client/client_conn.rs b/rustls/src/client/client_conn.rs index d57a5a18b8..0e184329bf 100644 --- a/rustls/src/client/client_conn.rs +++ b/rustls/src/client/client_conn.rs @@ -337,6 +337,7 @@ impl ClientConfig { .any(|cs| cs.version().version == v) } + #[cfg(feature = "std")] pub(crate) fn supports_protocol(&self, proto: Protocol) -> bool { self.provider .cipher_suites @@ -525,6 +526,7 @@ impl EarlyData { matches!(self.state, EarlyDataState::Ready | EarlyDataState::Accepted) } + #[cfg(feature = "std")] fn is_accepted(&self) -> bool { matches!( self.state, @@ -779,6 +781,7 @@ impl ConnectionCore { Ok(Self::new(state, data, common_state)) } + #[cfg(feature = "std")] pub(crate) fn is_early_data_accepted(&self) -> bool { self.data.early_data.is_accepted() } diff --git a/rustls/src/msgs/deframer.rs b/rustls/src/msgs/deframer.rs index d0ea3dee55..7f897de7d4 100644 --- a/rustls/src/msgs/deframer.rs +++ b/rustls/src/msgs/deframer.rs @@ -242,6 +242,7 @@ impl MessageDeframer { } /// Allow pushing handshake messages directly into the buffer. + #[cfg(feature = "std")] pub(crate) fn push( &mut self, version: ProtocolVersion, diff --git a/rustls/src/quic.rs b/rustls/src/quic.rs index 10fe7c0763..e7baa20316 100644 --- a/rustls/src/quic.rs +++ b/rustls/src/quic.rs @@ -1,417 +1,433 @@ /// This module contains optional APIs for implementing QUIC TLS. -use crate::client::{ClientConfig, ClientConnectionData}; -use crate::common_state::{CommonState, Protocol, Side, DEFAULT_BUFFER_LIMIT}; -use crate::conn::{ConnectionCore, SideData}; +use crate::common_state::Side; use crate::crypto::cipher::{AeadKey, Iv}; use crate::crypto::tls13::{Hkdf, HkdfExpander, OkmBlock}; -use crate::enums::{AlertDescription, ProtocolVersion}; +use crate::enums::AlertDescription; use crate::error::Error; -use crate::msgs::deframer::DeframerVecBuffer; -use crate::msgs::handshake::{ClientExtension, ServerExtension}; -use crate::server::{ServerConfig, ServerConnectionData}; use crate::tls13::key_schedule::{ hkdf_expand_label, hkdf_expand_label_aead_key, hkdf_expand_label_block, }; use crate::tls13::Tls13CipherSuite; -use crate::vecbuf::ChunkVecBuffer; - -use pki_types::ServerName; use alloc::boxed::Box; use alloc::collections::VecDeque; -use alloc::sync::Arc; -use alloc::vec; use alloc::vec::Vec; -use core::fmt::{self, Debug}; -use core::ops::{Deref, DerefMut}; - -/// A QUIC client or server connection. -#[derive(Debug)] -pub enum Connection { - /// A client connection - Client(ClientConnection), - /// A server connection - Server(ServerConnection), -} - -impl Connection { - /// Return the TLS-encoded transport parameters for the session's peer. - /// - /// See [`ConnectionCommon::quic_transport_parameters()`] for more details. - pub fn quic_transport_parameters(&self) -> Option<&[u8]> { - match self { - Self::Client(conn) => conn.quic_transport_parameters(), - Self::Server(conn) => conn.quic_transport_parameters(), +#[cfg(feature = "std")] +use core::fmt::Debug; + +#[cfg(feature = "std")] +mod connection { + use crate::client::{ClientConfig, ClientConnectionData}; + use crate::common_state::{CommonState, Protocol, DEFAULT_BUFFER_LIMIT}; + use crate::conn::{ConnectionCore, SideData}; + use crate::enums::{AlertDescription, ProtocolVersion}; + use crate::error::Error; + use crate::msgs::deframer::DeframerVecBuffer; + use crate::msgs::handshake::{ClientExtension, ServerExtension}; + use crate::server::{ServerConfig, ServerConnectionData}; + use crate::vecbuf::ChunkVecBuffer; + + use pki_types::ServerName; + + use alloc::sync::Arc; + use alloc::vec; + use alloc::vec::Vec; + use core::fmt::{self, Debug}; + use core::ops::{Deref, DerefMut}; + + use super::{DirectionalKeys, KeyChange, Version}; + + /// A QUIC client or server connection. + #[derive(Debug)] + pub enum Connection { + /// A client connection + Client(ClientConnection), + /// A server connection + Server(ServerConnection), + } + + impl Connection { + /// Return the TLS-encoded transport parameters for the session's peer. + /// + /// See [`ConnectionCommon::quic_transport_parameters()`] for more details. + pub fn quic_transport_parameters(&self) -> Option<&[u8]> { + match self { + Self::Client(conn) => conn.quic_transport_parameters(), + Self::Server(conn) => conn.quic_transport_parameters(), + } } - } - /// Compute the keys for encrypting/decrypting 0-RTT packets, if available - pub fn zero_rtt_keys(&self) -> Option { - match self { - Self::Client(conn) => conn.zero_rtt_keys(), - Self::Server(conn) => conn.zero_rtt_keys(), + /// Compute the keys for encrypting/decrypting 0-RTT packets, if available + pub fn zero_rtt_keys(&self) -> Option { + match self { + Self::Client(conn) => conn.zero_rtt_keys(), + Self::Server(conn) => conn.zero_rtt_keys(), + } } - } - /// Consume unencrypted TLS handshake data. - /// - /// Handshake data obtained from separate encryption levels should be supplied in separate calls. - pub fn read_hs(&mut self, plaintext: &[u8]) -> Result<(), Error> { - match self { - Self::Client(conn) => conn.read_hs(plaintext), - Self::Server(conn) => conn.read_hs(plaintext), + /// Consume unencrypted TLS handshake data. + /// + /// Handshake data obtained from separate encryption levels should be supplied in separate calls. + pub fn read_hs(&mut self, plaintext: &[u8]) -> Result<(), Error> { + match self { + Self::Client(conn) => conn.read_hs(plaintext), + Self::Server(conn) => conn.read_hs(plaintext), + } } - } - /// Emit unencrypted TLS handshake data. - /// - /// When this returns `Some(_)`, the new keys must be used for future handshake data. - pub fn write_hs(&mut self, buf: &mut Vec) -> Option { - match self { - Self::Client(conn) => conn.write_hs(buf), - Self::Server(conn) => conn.write_hs(buf), + /// Emit unencrypted TLS handshake data. + /// + /// When this returns `Some(_)`, the new keys must be used for future handshake data. + pub fn write_hs(&mut self, buf: &mut Vec) -> Option { + match self { + Self::Client(conn) => conn.write_hs(buf), + Self::Server(conn) => conn.write_hs(buf), + } } - } - /// Emit the TLS description code of a fatal alert, if one has arisen. - /// - /// Check after `read_hs` returns `Err(_)`. - pub fn alert(&self) -> Option { - match self { - Self::Client(conn) => conn.alert(), - Self::Server(conn) => conn.alert(), + /// Emit the TLS description code of a fatal alert, if one has arisen. + /// + /// Check after `read_hs` returns `Err(_)`. + pub fn alert(&self) -> Option { + match self { + Self::Client(conn) => conn.alert(), + Self::Server(conn) => conn.alert(), + } } - } - /// Derives key material from the agreed connection secrets. - /// - /// This function fills in `output` with `output.len()` bytes of key - /// material derived from the master session secret using `label` - /// and `context` for diversification. Ownership of the buffer is taken - /// by the function and returned via the Ok result to ensure no key - /// material leaks if the function fails. - /// - /// See RFC5705 for more details on what this does and is for. - /// - /// For TLS1.3 connections, this function does not use the - /// "early" exporter at any point. - /// - /// This function fails if called prior to the handshake completing; - /// check with [`CommonState::is_handshaking`] first. - #[inline] - pub fn export_keying_material>( - &self, - output: T, - label: &[u8], - context: Option<&[u8]>, - ) -> Result { - match self { - Self::Client(conn) => conn - .core - .export_keying_material(output, label, context), - Self::Server(conn) => conn - .core - .export_keying_material(output, label, context), + /// Derives key material from the agreed connection secrets. + /// + /// This function fills in `output` with `output.len()` bytes of key + /// material derived from the master session secret using `label` + /// and `context` for diversification. Ownership of the buffer is taken + /// by the function and returned via the Ok result to ensure no key + /// material leaks if the function fails. + /// + /// See RFC5705 for more details on what this does and is for. + /// + /// For TLS1.3 connections, this function does not use the + /// "early" exporter at any point. + /// + /// This function fails if called prior to the handshake completing; + /// check with [`CommonState::is_handshaking`] first. + #[inline] + pub fn export_keying_material>( + &self, + output: T, + label: &[u8], + context: Option<&[u8]>, + ) -> Result { + match self { + Self::Client(conn) => conn + .core + .export_keying_material(output, label, context), + Self::Server(conn) => conn + .core + .export_keying_material(output, label, context), + } } } -} -impl Deref for Connection { - type Target = CommonState; + impl Deref for Connection { + type Target = CommonState; - fn deref(&self) -> &Self::Target { - match self { - Self::Client(conn) => &conn.core.common_state, - Self::Server(conn) => &conn.core.common_state, + fn deref(&self) -> &Self::Target { + match self { + Self::Client(conn) => &conn.core.common_state, + Self::Server(conn) => &conn.core.common_state, + } } } -} -impl DerefMut for Connection { - fn deref_mut(&mut self) -> &mut Self::Target { - match self { - Self::Client(conn) => &mut conn.core.common_state, - Self::Server(conn) => &mut conn.core.common_state, + impl DerefMut for Connection { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + Self::Client(conn) => &mut conn.core.common_state, + Self::Server(conn) => &mut conn.core.common_state, + } } } -} -/// A QUIC client connection. -pub struct ClientConnection { - inner: ConnectionCommon, -} + /// A QUIC client connection. + pub struct ClientConnection { + inner: ConnectionCommon, + } + + impl ClientConnection { + /// Make a new QUIC ClientConnection. + /// + /// This differs from `ClientConnection::new()` in that it takes an extra `params` argument, + /// which contains the TLS-encoded transport parameters to send. + pub fn new( + config: Arc, + quic_version: Version, + name: ServerName<'static>, + params: Vec, + ) -> Result { + if !config.supports_version(ProtocolVersion::TLSv1_3) { + return Err(Error::General( + "TLS 1.3 support is required for QUIC".into(), + )); + } -impl ClientConnection { - /// Make a new QUIC ClientConnection. - /// - /// This differs from `ClientConnection::new()` in that it takes an extra `params` argument, - /// which contains the TLS-encoded transport parameters to send. - pub fn new( - config: Arc, - quic_version: Version, - name: ServerName<'static>, - params: Vec, - ) -> Result { - if !config.supports_version(ProtocolVersion::TLSv1_3) { - return Err(Error::General( - "TLS 1.3 support is required for QUIC".into(), - )); - } - - if !config.supports_protocol(Protocol::Quic) { - return Err(Error::General( - "at least one ciphersuite must support QUIC".into(), - )); - } - - let ext = match quic_version { - Version::V1Draft => ClientExtension::TransportParametersDraft(params), - Version::V1 | Version::V2 => ClientExtension::TransportParameters(params), - }; + if !config.supports_protocol(Protocol::Quic) { + return Err(Error::General( + "at least one ciphersuite must support QUIC".into(), + )); + } - let mut inner = ConnectionCore::for_client(config, name, vec![ext], Protocol::Quic)?; - inner.common_state.quic.version = quic_version; - Ok(Self { - inner: inner.into(), - }) - } + let ext = match quic_version { + Version::V1Draft => ClientExtension::TransportParametersDraft(params), + Version::V1 | Version::V2 => ClientExtension::TransportParameters(params), + }; - /// Returns True if the server signalled it will process early data. - /// - /// If you sent early data and this returns false at the end of the - /// handshake then the server will not process the data. This - /// is not an error, but you may wish to resend the data. - pub fn is_early_data_accepted(&self) -> bool { - self.inner.core.is_early_data_accepted() + let mut inner = ConnectionCore::for_client(config, name, vec![ext], Protocol::Quic)?; + inner.common_state.quic.version = quic_version; + Ok(Self { + inner: inner.into(), + }) + } + + /// Returns True if the server signalled it will process early data. + /// + /// If you sent early data and this returns false at the end of the + /// handshake then the server will not process the data. This + /// is not an error, but you may wish to resend the data. + pub fn is_early_data_accepted(&self) -> bool { + self.inner.core.is_early_data_accepted() + } } -} -impl Deref for ClientConnection { - type Target = ConnectionCommon; + impl Deref for ClientConnection { + type Target = ConnectionCommon; - fn deref(&self) -> &Self::Target { - &self.inner + fn deref(&self) -> &Self::Target { + &self.inner + } } -} -impl DerefMut for ClientConnection { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner + impl DerefMut for ClientConnection { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } } -} -impl Debug for ClientConnection { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("quic::ClientConnection") - .finish() + impl Debug for ClientConnection { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("quic::ClientConnection") + .finish() + } } -} -impl From for Connection { - fn from(c: ClientConnection) -> Self { - Self::Client(c) + impl From for Connection { + fn from(c: ClientConnection) -> Self { + Self::Client(c) + } } -} -/// A QUIC server connection. -pub struct ServerConnection { - inner: ConnectionCommon, -} + /// A QUIC server connection. + pub struct ServerConnection { + inner: ConnectionCommon, + } + + impl ServerConnection { + /// Make a new QUIC ServerConnection. + /// + /// This differs from `ServerConnection::new()` in that it takes an extra `params` argument, + /// which contains the TLS-encoded transport parameters to send. + pub fn new( + config: Arc, + quic_version: Version, + params: Vec, + ) -> Result { + if !config.supports_version(ProtocolVersion::TLSv1_3) { + return Err(Error::General( + "TLS 1.3 support is required for QUIC".into(), + )); + } -impl ServerConnection { - /// Make a new QUIC ServerConnection. - /// - /// This differs from `ServerConnection::new()` in that it takes an extra `params` argument, - /// which contains the TLS-encoded transport parameters to send. - pub fn new( - config: Arc, - quic_version: Version, - params: Vec, - ) -> Result { - if !config.supports_version(ProtocolVersion::TLSv1_3) { - return Err(Error::General( - "TLS 1.3 support is required for QUIC".into(), - )); - } - - if !config.supports_protocol(Protocol::Quic) { - return Err(Error::General( - "at least one ciphersuite must support QUIC".into(), - )); - } - - if config.max_early_data_size != 0 && config.max_early_data_size != 0xffff_ffff { - return Err(Error::General( - "QUIC sessions must set a max early data of 0 or 2^32-1".into(), - )); - } - - let ext = match quic_version { - Version::V1Draft => ServerExtension::TransportParametersDraft(params), - Version::V1 | Version::V2 => ServerExtension::TransportParameters(params), - }; + if !config.supports_protocol(Protocol::Quic) { + return Err(Error::General( + "at least one ciphersuite must support QUIC".into(), + )); + } - let mut core = ConnectionCore::for_server(config, vec![ext])?; - core.common_state.protocol = Protocol::Quic; - core.common_state.quic.version = quic_version; - Ok(Self { inner: core.into() }) - } + if config.max_early_data_size != 0 && config.max_early_data_size != 0xffff_ffff { + return Err(Error::General( + "QUIC sessions must set a max early data of 0 or 2^32-1".into(), + )); + } - /// Explicitly discard early data, notifying the client - /// - /// Useful if invariants encoded in `received_resumption_data()` cannot be respected. - /// - /// Must be called while `is_handshaking` is true. - pub fn reject_early_data(&mut self) { - self.inner.core.reject_early_data() - } + let ext = match quic_version { + Version::V1Draft => ServerExtension::TransportParametersDraft(params), + Version::V1 | Version::V2 => ServerExtension::TransportParameters(params), + }; - /// Retrieves the server name, if any, used to select the certificate and - /// private key. - /// - /// This returns `None` until some time after the client's server name indication - /// (SNI) extension value is processed during the handshake. It will never be - /// `None` when the connection is ready to send or process application data, - /// unless the client does not support SNI. - /// - /// This is useful for application protocols that need to enforce that the - /// server name matches an application layer protocol hostname. For - /// example, HTTP/1.1 servers commonly expect the `Host:` header field of - /// every request on a connection to match the hostname in the SNI extension - /// when the client provides the SNI extension. - /// - /// The server name is also used to match sessions during session resumption. - pub fn server_name(&self) -> Option<&str> { - self.inner.core.get_sni_str() + let mut core = ConnectionCore::for_server(config, vec![ext])?; + core.common_state.protocol = Protocol::Quic; + core.common_state.quic.version = quic_version; + Ok(Self { inner: core.into() }) + } + + /// Explicitly discard early data, notifying the client + /// + /// Useful if invariants encoded in `received_resumption_data()` cannot be respected. + /// + /// Must be called while `is_handshaking` is true. + pub fn reject_early_data(&mut self) { + self.inner.core.reject_early_data() + } + + /// Retrieves the server name, if any, used to select the certificate and + /// private key. + /// + /// This returns `None` until some time after the client's server name indication + /// (SNI) extension value is processed during the handshake. It will never be + /// `None` when the connection is ready to send or process application data, + /// unless the client does not support SNI. + /// + /// This is useful for application protocols that need to enforce that the + /// server name matches an application layer protocol hostname. For + /// example, HTTP/1.1 servers commonly expect the `Host:` header field of + /// every request on a connection to match the hostname in the SNI extension + /// when the client provides the SNI extension. + /// + /// The server name is also used to match sessions during session resumption. + pub fn server_name(&self) -> Option<&str> { + self.inner.core.get_sni_str() + } } -} -impl Deref for ServerConnection { - type Target = ConnectionCommon; + impl Deref for ServerConnection { + type Target = ConnectionCommon; - fn deref(&self) -> &Self::Target { - &self.inner + fn deref(&self) -> &Self::Target { + &self.inner + } } -} -impl DerefMut for ServerConnection { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner + impl DerefMut for ServerConnection { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } } -} -impl Debug for ServerConnection { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("quic::ServerConnection") - .finish() + impl Debug for ServerConnection { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("quic::ServerConnection") + .finish() + } } -} -impl From for Connection { - fn from(c: ServerConnection) -> Self { - Self::Server(c) + impl From for Connection { + fn from(c: ServerConnection) -> Self { + Self::Server(c) + } } -} -/// A shared interface for QUIC connections. -pub struct ConnectionCommon { - core: ConnectionCore, - deframer_buffer: DeframerVecBuffer, - sendable_plaintext: ChunkVecBuffer, -} + /// A shared interface for QUIC connections. + pub struct ConnectionCommon { + core: ConnectionCore, + deframer_buffer: DeframerVecBuffer, + sendable_plaintext: ChunkVecBuffer, + } -impl ConnectionCommon { - /// Return the TLS-encoded transport parameters for the session's peer. - /// - /// While the transport parameters are technically available prior to the - /// completion of the handshake, they cannot be fully trusted until the - /// handshake completes, and reliance on them should be minimized. - /// However, any tampering with the parameters will cause the handshake - /// to fail. - pub fn quic_transport_parameters(&self) -> Option<&[u8]> { - self.core - .common_state - .quic - .params - .as_ref() - .map(|v| v.as_ref()) - } - - /// Compute the keys for encrypting/decrypting 0-RTT packets, if available - pub fn zero_rtt_keys(&self) -> Option { - let suite = self - .core - .common_state - .suite - .and_then(|suite| suite.tls13())?; - Some(DirectionalKeys::new( - suite, - suite.quic?, + impl ConnectionCommon { + /// Return the TLS-encoded transport parameters for the session's peer. + /// + /// While the transport parameters are technically available prior to the + /// completion of the handshake, they cannot be fully trusted until the + /// handshake completes, and reliance on them should be minimized. + /// However, any tampering with the parameters will cause the handshake + /// to fail. + pub fn quic_transport_parameters(&self) -> Option<&[u8]> { self.core .common_state .quic - .early_secret - .as_ref()?, - self.core.common_state.quic.version, - )) - } + .params + .as_ref() + .map(|v| v.as_ref()) + } - /// Consume unencrypted TLS handshake data. - /// - /// Handshake data obtained from separate encryption levels should be supplied in separate calls. - pub fn read_hs(&mut self, plaintext: &[u8]) -> Result<(), Error> { - self.core.message_deframer.push( - ProtocolVersion::TLSv1_3, - plaintext, - &mut self.deframer_buffer, - )?; - self.core - .process_new_packets(&mut self.deframer_buffer, &mut self.sendable_plaintext)?; - Ok(()) - } - - /// Emit unencrypted TLS handshake data. - /// - /// When this returns `Some(_)`, the new keys must be used for future handshake data. - pub fn write_hs(&mut self, buf: &mut Vec) -> Option { - self.core - .common_state - .quic - .write_hs(buf) - } + /// Compute the keys for encrypting/decrypting 0-RTT packets, if available + pub fn zero_rtt_keys(&self) -> Option { + let suite = self + .core + .common_state + .suite + .and_then(|suite| suite.tls13())?; + Some(DirectionalKeys::new( + suite, + suite.quic?, + self.core + .common_state + .quic + .early_secret + .as_ref()?, + self.core.common_state.quic.version, + )) + } - /// Emit the TLS description code of a fatal alert, if one has arisen. - /// - /// Check after `read_hs` returns `Err(_)`. - pub fn alert(&self) -> Option { - self.core.common_state.quic.alert + /// Consume unencrypted TLS handshake data. + /// + /// Handshake data obtained from separate encryption levels should be supplied in separate calls. + pub fn read_hs(&mut self, plaintext: &[u8]) -> Result<(), Error> { + self.core.message_deframer.push( + ProtocolVersion::TLSv1_3, + plaintext, + &mut self.deframer_buffer, + )?; + self.core + .process_new_packets(&mut self.deframer_buffer, &mut self.sendable_plaintext)?; + Ok(()) + } + + /// Emit unencrypted TLS handshake data. + /// + /// When this returns `Some(_)`, the new keys must be used for future handshake data. + pub fn write_hs(&mut self, buf: &mut Vec) -> Option { + self.core + .common_state + .quic + .write_hs(buf) + } + + /// Emit the TLS description code of a fatal alert, if one has arisen. + /// + /// Check after `read_hs` returns `Err(_)`. + pub fn alert(&self) -> Option { + self.core.common_state.quic.alert + } } -} -impl Deref for ConnectionCommon { - type Target = CommonState; + impl Deref for ConnectionCommon { + type Target = CommonState; - fn deref(&self) -> &Self::Target { - &self.core.common_state + fn deref(&self) -> &Self::Target { + &self.core.common_state + } } -} -impl DerefMut for ConnectionCommon { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.core.common_state + impl DerefMut for ConnectionCommon { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.core.common_state + } } -} -impl From> for ConnectionCommon { - fn from(core: ConnectionCore) -> Self { - Self { - core, - deframer_buffer: DeframerVecBuffer::default(), - sendable_plaintext: ChunkVecBuffer::new(Some(DEFAULT_BUFFER_LIMIT)), + impl From> for ConnectionCommon { + fn from(core: ConnectionCore) -> Self { + Self { + core, + deframer_buffer: DeframerVecBuffer::default(), + sendable_plaintext: ChunkVecBuffer::new(Some(DEFAULT_BUFFER_LIMIT)), + } } } } +#[cfg(feature = "std")] +pub use connection::{ClientConnection, Connection, ConnectionCommon, ServerConnection}; + #[derive(Default)] pub(crate) struct Quic { /// QUIC transport parameters received from the peer during the handshake @@ -422,10 +438,12 @@ pub(crate) struct Quic { pub(crate) hs_secrets: Option, pub(crate) traffic_secrets: Option, /// Whether keys derived from traffic_secrets have been passed to the QUIC implementation + #[cfg(feature = "std")] pub(crate) returned_traffic_keys: bool, pub(crate) version: Version, } +#[cfg(feature = "std")] impl Quic { pub(crate) fn write_hs(&mut self, buf: &mut Vec) -> Option { while let Some((_, msg)) = self.hs_queue.pop_front() { diff --git a/rustls/src/server/server_conn.rs b/rustls/src/server/server_conn.rs index fda881febf..f22cef9468 100644 --- a/rustls/src/server/server_conn.rs +++ b/rustls/src/server/server_conn.rs @@ -1,5 +1,7 @@ use crate::builder::ConfigBuilder; -use crate::common_state::{CommonState, Protocol, Side, State}; +#[cfg(feature = "std")] +use crate::common_state::Protocol; +use crate::common_state::{CommonState, Side, State}; use crate::conn::{ConnectionCommon, ConnectionCore, UnbufferedConnectionCommon}; use crate::crypto::CryptoProvider; use crate::enums::{CipherSuite, ProtocolVersion, SignatureScheme}; @@ -479,6 +481,7 @@ impl ServerConfig { .any(|cs| cs.version().version == v) } + #[cfg(feature = "std")] pub(crate) fn supports_protocol(&self, proto: Protocol) -> bool { self.provider .cipher_suites @@ -999,6 +1002,7 @@ impl ConnectionCore { )) } + #[cfg(feature = "std")] pub(crate) fn reject_early_data(&mut self) { assert!( self.common_state.is_handshaking(), @@ -1007,6 +1011,7 @@ impl ConnectionCore { self.data.early_data.reject(); } + #[cfg(feature = "std")] pub(crate) fn get_sni_str(&self) -> Option<&str> { self.data.get_sni_str() } @@ -1022,6 +1027,7 @@ pub struct ServerConnectionData { } impl ServerConnectionData { + #[cfg(feature = "std")] pub(super) fn get_sni_str(&self) -> Option<&str> { self.sni.as_ref().map(AsRef::as_ref) } From 49dd8f80cbb086f8873ca9b08b07c693141e68a7 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 5 Oct 2023 19:21:41 +0200 Subject: [PATCH 23/27] no-std: rm MessageDeframer IO methods --- rustls/src/msgs/deframer.rs | 109 ++++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 49 deletions(-) diff --git a/rustls/src/msgs/deframer.rs b/rustls/src/msgs/deframer.rs index 7f897de7d4..c171c9df7c 100644 --- a/rustls/src/msgs/deframer.rs +++ b/rustls/src/msgs/deframer.rs @@ -1,15 +1,16 @@ use alloc::vec::Vec; use core::ops::Range; use core::slice::SliceIndex; +#[cfg(feature = "std")] use std::io; use super::codec::Codec; use crate::enums::{ContentType, ProtocolVersion}; use crate::error::{Error, InvalidMessage, PeerMisbehaved}; use crate::msgs::codec; -use crate::msgs::message::{ - InboundOpaqueMessage, InboundPlainMessage, MessageError, OutboundOpaqueMessage, -}; +#[cfg(feature = "std")] +use crate::msgs::message::OutboundOpaqueMessage; +use crate::msgs::message::{InboundOpaqueMessage, InboundPlainMessage, MessageError}; use crate::record_layer::{Decrypted, RecordLayer}; /// This deframer works to reconstruct TLS messages from a stream of arbitrary-sized reads. @@ -241,27 +242,6 @@ impl MessageDeframer { err } - /// Allow pushing handshake messages directly into the buffer. - #[cfg(feature = "std")] - pub(crate) fn push( - &mut self, - version: ProtocolVersion, - payload: &[u8], - buffer: &mut DeframerVecBuffer, - ) -> Result<(), Error> { - if !buffer.is_empty() && self.joining_hs.is_none() { - return Err(Error::General( - "cannot push QUIC messages into unrelated connection".into(), - )); - } else if let Err(err) = buffer.prepare_read(self.joining_hs.is_some()) { - return Err(Error::General(err.into())); - } - - let end = buffer.len() + payload.len(); - self.append_hs(version, ExternalPayload(payload), end, buffer)?; - Ok(()) - } - /// Write the handshake message contents into the buffer and update the metadata. /// /// Returns true if a complete message is found. @@ -319,6 +299,29 @@ impl MessageDeframer { }, }) } +} + +#[cfg(feature = "std")] +impl MessageDeframer { + /// Allow pushing handshake messages directly into the buffer. + pub(crate) fn push( + &mut self, + version: ProtocolVersion, + payload: &[u8], + buffer: &mut DeframerVecBuffer, + ) -> Result<(), Error> { + if !buffer.is_empty() && self.joining_hs.is_none() { + return Err(Error::General( + "cannot push QUIC messages into unrelated connection".into(), + )); + } else if let Err(err) = buffer.prepare_read(self.joining_hs.is_some()) { + return Err(Error::General(err.into())); + } + + let end = buffer.len() + payload.len(); + self.append_hs(version, ExternalPayload(payload), end, buffer)?; + Ok(()) + } /// Read some bytes from `rd`, and add them to our internal buffer. #[allow(clippy::comparison_chain)] @@ -401,6 +404,34 @@ impl DeframerVecBuffer { DeframerSliceBuffer::new(&mut self.buf[..self.used]) } + /// Discard `taken` bytes from the start of our buffer. + pub fn discard(&mut self, taken: usize) { + #[allow(clippy::comparison_chain)] + if taken < self.used { + /* Before: + * +----------+----------+----------+ + * | taken | pending |xxxxxxxxxx| + * +----------+----------+----------+ + * 0 ^ taken ^ self.used + * + * After: + * +----------+----------+----------+ + * | pending |xxxxxxxxxxxxxxxxxxxxx| + * +----------+----------+----------+ + * 0 ^ self.used + */ + + self.buf + .copy_within(taken..self.used, 0); + self.used -= taken; + } else if taken == self.used { + self.used = 0; + } + } +} + +#[cfg(feature = "std")] +impl DeframerVecBuffer { /// Returns true if there are messages for the caller to process pub fn has_pending(&self) -> bool { !self.is_empty() @@ -440,31 +471,6 @@ impl DeframerVecBuffer { Ok(()) } - /// Discard `taken` bytes from the start of our buffer. - pub fn discard(&mut self, taken: usize) { - #[allow(clippy::comparison_chain)] - if taken < self.used { - /* Before: - * +----------+----------+----------+ - * | taken | pending |xxxxxxxxxx| - * +----------+----------+----------+ - * 0 ^ taken ^ self.used - * - * After: - * +----------+----------+----------+ - * | pending |xxxxxxxxxxxxxxxxxxxxx| - * +----------+----------+----------+ - * 0 ^ self.used - */ - - self.buf - .copy_within(taken..self.used, 0); - self.used -= taken; - } else if taken == self.used { - self.used = 0; - } - } - fn is_empty(&self) -> bool { self.len() == 0 } @@ -478,6 +484,7 @@ impl DeframerVecBuffer { } } +#[cfg(feature = "std")] impl FilledDeframerBuffer for DeframerVecBuffer { fn filled_mut(&mut self) -> &mut [u8] { &mut self.buf[..self.used] @@ -488,12 +495,14 @@ impl FilledDeframerBuffer for DeframerVecBuffer { } } +#[cfg(feature = "std")] impl DeframerBuffer<'_, InternalPayload> for DeframerVecBuffer { fn copy(&mut self, payload: &InternalPayload, at: usize) { self.borrow().copy(payload, at) } } +#[cfg(feature = "std")] impl<'a> DeframerBuffer<'a, ExternalPayload<'a>> for DeframerVecBuffer { fn copy(&mut self, payload: &ExternalPayload<'a>, at: usize) { let len = payload.len(); @@ -694,8 +703,10 @@ const HEADER_SIZE: usize = 1 + 3; /// service. const MAX_HANDSHAKE_SIZE: u32 = 0xffff; +#[cfg(feature = "std")] const READ_SIZE: usize = 4096; +#[cfg(feature = "std")] #[cfg(test)] mod tests { use std::io; From 90748ec43497c64a689571bb63a28ddce6e4ea8a Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 5 Oct 2023 19:23:38 +0200 Subject: [PATCH 24/27] no-std: rm temporary extern crate std --- rustls/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rustls/src/lib.rs b/rustls/src/lib.rs index 11e5b76a1d..6b4ff649f9 100644 --- a/rustls/src/lib.rs +++ b/rustls/src/lib.rs @@ -352,7 +352,7 @@ extern crate alloc; // is in `std::prelude` but not in `core::prelude`. This helps maintain no-std support as even // developers that are not interested in, or aware of, no-std support and / or that never run // `cargo build --no-default-features` locally will get errors when they rely on `std::prelude` API. -#[cfg(not(test))] +#[cfg(all(feature = "std", not(test)))] extern crate std; // Import `test` sysroot crate for `Bencher` definitions. From 0b5a43482c3a77c878751112e2e68807fbeca26e Mon Sep 17 00:00:00 2001 From: Christian Poveda Date: Tue, 19 Dec 2023 11:37:14 -0500 Subject: [PATCH 25/27] make ring's ticketer module std-only --- rustls/src/crypto/ring/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rustls/src/crypto/ring/mod.rs b/rustls/src/crypto/ring/mod.rs index f69c7f72b6..68969ac781 100644 --- a/rustls/src/crypto/ring/mod.rs +++ b/rustls/src/crypto/ring/mod.rs @@ -20,6 +20,7 @@ pub(crate) mod hash; pub(crate) mod hmac; pub(crate) mod kx; pub(crate) mod quic; +#[cfg(feature = "std")] pub(crate) mod ticketer; #[cfg(feature = "tls12")] pub(crate) mod tls12; @@ -172,6 +173,7 @@ pub mod kx_group { } pub use kx::ALL_KX_GROUPS; +#[cfg(feature = "std")] pub use ticketer::Ticketer; /// Compatibility shims between ring 0.16.x and 0.17.x API From 8805236777387cada0f9036230df005e29ce37da Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Mon, 19 Feb 2024 11:01:51 -0500 Subject: [PATCH 26/27] crypto: use race::OnceBox for no-std support When built w/ the `std` feature, the `DEFAULT_CRYPTO_PROVIDER` and assoc. fns use a standard `once_cell::sync::OnceCell`. When built w/o the `std` feature we use `once_cell::race::OnceBox`, requiring some minor adjustments to account for the expectation of storing a `Box>` in place of a `Arc`. --- rustls/src/crypto/mod.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/rustls/src/crypto/mod.rs b/rustls/src/crypto/mod.rs index 13818def7e..cd73454b3d 100644 --- a/rustls/src/crypto/mod.rs +++ b/rustls/src/crypto/mod.rs @@ -7,7 +7,11 @@ use alloc::sync::Arc; use alloc::vec::Vec; use core::fmt::Debug; +#[cfg(not(feature = "std"))] +use once_cell::race::OnceBox; +#[cfg(feature = "std")] use once_cell::sync::OnceCell; + use pki_types::PrivateKeyDer; use zeroize::Zeroize; @@ -224,10 +228,23 @@ impl CryptoProvider { /// Call this early in your process to configure which provider is used for /// the provider. The configuration should happen before any use of /// [`ClientConfig::builder()`] or [`ServerConfig::builder()`]. + #[cfg(feature = "std")] pub fn install_default(self) -> Result<(), Arc> { PROCESS_DEFAULT_PROVIDER.set(Arc::new(self)) } + /// Sets this `CryptoProvider` as the default for this process. + /// + /// This can be called successfully at most once in any process execution. + /// + /// Call this early in your process to configure which provider is used for + /// the provider. The configuration should happen before any use of + /// [`ClientConfig::builder()`] or [`ServerConfig::builder()`]. + #[cfg(not(feature = "std"))] + pub fn install_default(self) -> Result<(), Box>> { + PROCESS_DEFAULT_PROVIDER.set(Box::new(Arc::new(self))) + } + /// Returns the default `CryptoProvider` for this process. /// /// This will be `None` if no default has been set yet. @@ -294,7 +311,10 @@ impl CryptoProvider { } } +#[cfg(feature = "std")] static PROCESS_DEFAULT_PROVIDER: OnceCell> = OnceCell::new(); +#[cfg(not(feature = "std"))] +static PROCESS_DEFAULT_PROVIDER: OnceBox> = OnceBox::new(); /// A source of cryptographically secure randomness. pub trait SecureRandom: Send + Sync + Debug { From 24445581291a4294a3f322752e7af9888e809c7d Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Tue, 14 Nov 2023 17:47:26 +0100 Subject: [PATCH 27/27] CI: check that deps are not using libstd API --- .github/workflows/build.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 601d0f626a..16f18f251d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -108,11 +108,19 @@ jobs: - name: Install stable toolchain uses: dtolnay/rust-toolchain@stable + with: + target: x86_64-unknown-none - name: cargo build (debug; default features) run: cargo build --locked working-directory: rustls + # this target does _not_ include the libstd crate in its sysroot + # it will catch unwanted usage of libstd in _dependencies_ + - name: cargo build (debug; default features; no-std) + run: cargo build --locked --no-default-features --target x86_64-unknown-none + working-directory: rustls + - name: cargo test (debug; default features) run: cargo test --locked working-directory: rustls