diff options
Diffstat (limited to 'vendor/rustls/src/client')
| -rw-r--r-- | vendor/rustls/src/client/builder.rs | 186 | ||||
| -rw-r--r-- | vendor/rustls/src/client/client_conn.rs | 1062 | ||||
| -rw-r--r-- | vendor/rustls/src/client/common.rs | 119 | ||||
| -rw-r--r-- | vendor/rustls/src/client/ech.rs | 899 | ||||
| -rw-r--r-- | vendor/rustls/src/client/handy.rs | 390 | ||||
| -rw-r--r-- | vendor/rustls/src/client/hs.rs | 1178 | ||||
| -rw-r--r-- | vendor/rustls/src/client/test.rs | 712 | ||||
| -rw-r--r-- | vendor/rustls/src/client/tls12.rs | 1372 | ||||
| -rw-r--r-- | vendor/rustls/src/client/tls13.rs | 1700 |
9 files changed, 7618 insertions, 0 deletions
diff --git a/vendor/rustls/src/client/builder.rs b/vendor/rustls/src/client/builder.rs new file mode 100644 index 00000000..b301e750 --- /dev/null +++ b/vendor/rustls/src/client/builder.rs @@ -0,0 +1,186 @@ +use alloc::vec::Vec; +use core::marker::PhantomData; + +use pki_types::{CertificateDer, PrivateKeyDer}; + +use super::client_conn::Resumption; +use crate::builder::{ConfigBuilder, WantsVerifier}; +use crate::client::{ClientConfig, EchMode, ResolvesClientCert, handy}; +use crate::error::Error; +use crate::key_log::NoKeyLog; +use crate::sign::{CertifiedKey, SingleCertAndKey}; +use crate::sync::Arc; +use crate::versions::TLS13; +use crate::webpki::{self, WebPkiServerVerifier}; +use crate::{WantsVersions, compress, verify, versions}; + +impl ConfigBuilder<ClientConfig, WantsVersions> { + /// Enable Encrypted Client Hello (ECH) in the given mode. + /// + /// This implicitly selects TLS 1.3 as the only supported protocol version to meet the + /// requirement to support ECH. + /// + /// The `ClientConfig` that will be produced by this builder will be specific to the provided + /// [`crate::client::EchConfig`] and may not be appropriate for all connections made by the program. + /// In this case the configuration should only be shared by connections intended for domains + /// that offer the provided [`crate::client::EchConfig`] in their DNS zone. + pub fn with_ech( + self, + mode: EchMode, + ) -> Result<ConfigBuilder<ClientConfig, WantsVerifier>, Error> { + let mut res = self.with_protocol_versions(&[&TLS13][..])?; + res.state.client_ech_mode = Some(mode); + Ok(res) + } +} + +impl ConfigBuilder<ClientConfig, WantsVerifier> { + /// Choose how to verify server certificates. + /// + /// Using this function does not configure revocation. If you wish to + /// configure revocation, instead use: + /// + /// ```diff + /// - .with_root_certificates(root_store) + /// + .with_webpki_verifier( + /// + WebPkiServerVerifier::builder_with_provider(root_store, crypto_provider) + /// + .with_crls(...) + /// + .build()? + /// + ) + /// ``` + pub fn with_root_certificates( + self, + root_store: impl Into<Arc<webpki::RootCertStore>>, + ) -> ConfigBuilder<ClientConfig, WantsClientCert> { + let algorithms = self + .provider + .signature_verification_algorithms; + self.with_webpki_verifier( + WebPkiServerVerifier::new_without_revocation(root_store, algorithms).into(), + ) + } + + /// Choose how to verify server certificates using a webpki verifier. + /// + /// See [`webpki::WebPkiServerVerifier::builder`] and + /// [`webpki::WebPkiServerVerifier::builder_with_provider`] for more information. + pub fn with_webpki_verifier( + self, + verifier: Arc<WebPkiServerVerifier>, + ) -> ConfigBuilder<ClientConfig, WantsClientCert> { + ConfigBuilder { + state: WantsClientCert { + versions: self.state.versions, + verifier, + client_ech_mode: self.state.client_ech_mode, + }, + provider: self.provider, + time_provider: self.time_provider, + side: PhantomData, + } + } + + /// Access configuration options whose use is dangerous and requires + /// extra care. + pub fn dangerous(self) -> danger::DangerousClientConfigBuilder { + danger::DangerousClientConfigBuilder { cfg: self } + } +} + +/// Container for unsafe APIs +pub(super) mod danger { + use core::marker::PhantomData; + + use crate::client::WantsClientCert; + use crate::sync::Arc; + use crate::{ClientConfig, ConfigBuilder, WantsVerifier, verify}; + + /// Accessor for dangerous configuration options. + #[derive(Debug)] + pub struct DangerousClientConfigBuilder { + /// The underlying ClientConfigBuilder + pub cfg: ConfigBuilder<ClientConfig, WantsVerifier>, + } + + impl DangerousClientConfigBuilder { + /// Set a custom certificate verifier. + pub fn with_custom_certificate_verifier( + self, + verifier: Arc<dyn verify::ServerCertVerifier>, + ) -> ConfigBuilder<ClientConfig, WantsClientCert> { + ConfigBuilder { + state: WantsClientCert { + versions: self.cfg.state.versions, + verifier, + client_ech_mode: self.cfg.state.client_ech_mode, + }, + provider: self.cfg.provider, + time_provider: self.cfg.time_provider, + side: PhantomData, + } + } + } +} + +/// A config builder state where the caller needs to supply whether and how to provide a client +/// certificate. +/// +/// For more information, see the [`ConfigBuilder`] documentation. +#[derive(Clone)] +pub struct WantsClientCert { + versions: versions::EnabledVersions, + verifier: Arc<dyn verify::ServerCertVerifier>, + client_ech_mode: Option<EchMode>, +} + +impl ConfigBuilder<ClientConfig, WantsClientCert> { + /// Sets a single certificate chain and matching private key for use + /// in client authentication. + /// + /// `cert_chain` is a vector of DER-encoded certificates. + /// `key_der` is a DER-encoded private key as PKCS#1, PKCS#8, or SEC1. The + /// `aws-lc-rs` and `ring` [`CryptoProvider`][crate::CryptoProvider]s support + /// all three encodings, but other `CryptoProviders` may not. + /// + /// This function fails if `key_der` is invalid. + pub fn with_client_auth_cert( + self, + cert_chain: Vec<CertificateDer<'static>>, + key_der: PrivateKeyDer<'static>, + ) -> Result<ClientConfig, Error> { + let certified_key = CertifiedKey::from_der(cert_chain, key_der, &self.provider)?; + Ok(self.with_client_cert_resolver(Arc::new(SingleCertAndKey::from(certified_key)))) + } + + /// Do not support client auth. + pub fn with_no_client_auth(self) -> ClientConfig { + self.with_client_cert_resolver(Arc::new(handy::FailResolveClientCert {})) + } + + /// Sets a custom [`ResolvesClientCert`]. + pub fn with_client_cert_resolver( + self, + client_auth_cert_resolver: Arc<dyn ResolvesClientCert>, + ) -> ClientConfig { + ClientConfig { + provider: self.provider, + alpn_protocols: Vec::new(), + resumption: Resumption::default(), + max_fragment_size: None, + client_auth_cert_resolver, + versions: self.state.versions, + enable_sni: true, + verifier: self.state.verifier, + key_log: Arc::new(NoKeyLog {}), + enable_secret_extraction: false, + enable_early_data: false, + #[cfg(feature = "tls12")] + require_ems: cfg!(feature = "fips"), + time_provider: self.time_provider, + cert_compressors: compress::default_cert_compressors().to_vec(), + cert_compression_cache: Arc::new(compress::CompressionCache::default()), + cert_decompressors: compress::default_cert_decompressors().to_vec(), + ech_mode: self.state.client_ech_mode, + } + } +} diff --git a/vendor/rustls/src/client/client_conn.rs b/vendor/rustls/src/client/client_conn.rs new file mode 100644 index 00000000..d214c2a1 --- /dev/null +++ b/vendor/rustls/src/client/client_conn.rs @@ -0,0 +1,1062 @@ +use alloc::vec::Vec; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; +use core::{fmt, mem}; + +use pki_types::{ServerName, UnixTime}; + +use super::handy::NoClientSessionStorage; +use super::hs::{self, ClientHelloInput}; +#[cfg(feature = "std")] +use crate::WantsVerifier; +use crate::builder::ConfigBuilder; +use crate::client::{EchMode, EchStatus}; +use crate::common_state::{CommonState, Protocol, Side}; +use crate::conn::{ConnectionCore, UnbufferedConnectionCommon}; +use crate::crypto::{CryptoProvider, SupportedKxGroup}; +use crate::enums::{CipherSuite, ProtocolVersion, SignatureScheme}; +use crate::error::Error; +use crate::kernel::KernelConnection; +use crate::log::trace; +use crate::msgs::enums::NamedGroup; +use crate::msgs::handshake::ClientExtensionsInput; +use crate::msgs::persist; +use crate::suites::{ExtractedSecrets, SupportedCipherSuite}; +use crate::sync::Arc; +#[cfg(feature = "std")] +use crate::time_provider::DefaultTimeProvider; +use crate::time_provider::TimeProvider; +use crate::unbuffered::{EncryptError, TransmitTlsData}; +#[cfg(doc)] +use crate::{DistinguishedName, crypto}; +use crate::{KeyLog, WantsVersions, compress, sign, verify, versions}; + +/// A trait for the ability to store client session data, so that sessions +/// can be resumed in future connections. +/// +/// Generally all data in this interface should be treated as +/// **highly sensitive**, containing enough key material to break all security +/// of the corresponding session. +/// +/// `set_`, `insert_`, `remove_` and `take_` operations are mutating; this isn't +/// expressed in the type system to allow implementations freedom in +/// how to achieve interior mutability. `Mutex` is a common choice. +pub trait ClientSessionStore: fmt::Debug + Send + Sync { + /// Remember what `NamedGroup` the given server chose. + fn set_kx_hint(&self, server_name: ServerName<'static>, group: NamedGroup); + + /// This should return the value most recently passed to `set_kx_hint` + /// for the given `server_name`. + /// + /// If `None` is returned, the caller chooses the first configured group, + /// and an extra round trip might happen if that choice is unsatisfactory + /// to the server. + fn kx_hint(&self, server_name: &ServerName<'_>) -> Option<NamedGroup>; + + /// Remember a TLS1.2 session. + /// + /// At most one of these can be remembered at a time, per `server_name`. + fn set_tls12_session( + &self, + server_name: ServerName<'static>, + value: persist::Tls12ClientSessionValue, + ); + + /// Get the most recently saved TLS1.2 session for `server_name` provided to `set_tls12_session`. + fn tls12_session( + &self, + server_name: &ServerName<'_>, + ) -> Option<persist::Tls12ClientSessionValue>; + + /// Remove and forget any saved TLS1.2 session for `server_name`. + fn remove_tls12_session(&self, server_name: &ServerName<'static>); + + /// Remember a TLS1.3 ticket that might be retrieved later from `take_tls13_ticket`, allowing + /// resumption of this session. + /// + /// This can be called multiple times for a given session, allowing multiple independent tickets + /// to be valid at once. The number of times this is called is controlled by the server, so + /// implementations of this trait should apply a reasonable bound of how many items are stored + /// simultaneously. + fn insert_tls13_ticket( + &self, + server_name: ServerName<'static>, + value: persist::Tls13ClientSessionValue, + ); + + /// Return a TLS1.3 ticket previously provided to `add_tls13_ticket`. + /// + /// Implementations of this trait must return each value provided to `add_tls13_ticket` _at most once_. + fn take_tls13_ticket( + &self, + server_name: &ServerName<'static>, + ) -> Option<persist::Tls13ClientSessionValue>; +} + +/// A trait for the ability to choose a certificate chain and +/// private key for the purposes of client authentication. +pub trait ResolvesClientCert: fmt::Debug + Send + Sync { + /// Resolve a client certificate chain/private key to use as the client's + /// identity. + /// + /// `root_hint_subjects` is an optional list of certificate authority + /// subject distinguished names that the client can use to help + /// decide on a client certificate the server is likely to accept. If + /// the list is empty, the client should send whatever certificate it + /// has. The hints are expected to be DER-encoded X.500 distinguished names, + /// per [RFC 5280 A.1]. See [`DistinguishedName`] for more information + /// on decoding with external crates like `x509-parser`. + /// + /// `sigschemes` is the list of the [`SignatureScheme`]s the server + /// supports. + /// + /// Return `None` to continue the handshake without any client + /// authentication. The server may reject the handshake later + /// if it requires authentication. + /// + /// [RFC 5280 A.1]: https://www.rfc-editor.org/rfc/rfc5280#appendix-A.1 + fn resolve( + &self, + root_hint_subjects: &[&[u8]], + sigschemes: &[SignatureScheme], + ) -> Option<Arc<sign::CertifiedKey>>; + + /// Return true if the client only supports raw public keys. + /// + /// See [RFC 7250](https://www.rfc-editor.org/rfc/rfc7250). + fn only_raw_public_keys(&self) -> bool { + false + } + + /// Return true if any certificates at all are available. + fn has_certs(&self) -> bool; +} + +/// Common configuration for (typically) all connections made by a program. +/// +/// Making one of these is cheap, though one of the inputs may be expensive: gathering trust roots +/// from the operating system to add to the [`RootCertStore`] passed to `with_root_certificates()` +/// (the rustls-native-certs crate is often used for this) may take on the order of a few hundred +/// milliseconds. +/// +/// These must be created via the [`ClientConfig::builder()`] or [`ClientConfig::builder_with_provider()`] +/// function. +/// +/// Note that using [`ConfigBuilder<ClientConfig, WantsVersions>::with_ech()`] will produce a common +/// configuration specific to the provided [`crate::client::EchConfig`] that may not be appropriate +/// for all connections made by the program. In this case the configuration should only be shared +/// by connections intended for domains that offer the provided [`crate::client::EchConfig`] in +/// their DNS zone. +/// +/// # Defaults +/// +/// * [`ClientConfig::max_fragment_size`]: the default is `None` (meaning 16kB). +/// * [`ClientConfig::resumption`]: supports resumption with up to 256 server names, using session +/// ids or tickets, with a max of eight tickets per server. +/// * [`ClientConfig::alpn_protocols`]: the default is empty -- no ALPN protocol is negotiated. +/// * [`ClientConfig::key_log`]: key material is not logged. +/// * [`ClientConfig::cert_decompressors`]: depends on the crate features, see [`compress::default_cert_decompressors()`]. +/// * [`ClientConfig::cert_compressors`]: depends on the crate features, see [`compress::default_cert_compressors()`]. +/// * [`ClientConfig::cert_compression_cache`]: caches the most recently used 4 compressions +/// +/// [`RootCertStore`]: crate::RootCertStore +#[derive(Clone, Debug)] +pub struct ClientConfig { + /// Which ALPN protocols we include in our client hello. + /// If empty, no ALPN extension is sent. + pub alpn_protocols: Vec<Vec<u8>>, + + /// How and when the client can resume a previous session. + /// + /// # Sharing `resumption` between `ClientConfig`s + /// In a program using many `ClientConfig`s it may improve resumption rates + /// (which has a significant impact on connection performance) if those + /// configs share a single `Resumption`. + /// + /// However, resumption is only allowed between two `ClientConfig`s if their + /// `client_auth_cert_resolver` (ie, potential client authentication credentials) + /// and `verifier` (ie, server certificate verification settings) are + /// the same (according to `Arc::ptr_eq`). + /// + /// To illustrate, imagine two `ClientConfig`s `A` and `B`. `A` fully validates + /// the server certificate, `B` does not. If `A` and `B` shared a resumption store, + /// it would be possible for a session originated by `B` to be inserted into the + /// store, and then resumed by `A`. This would give a false impression to the user + /// of `A` that the server certificate is fully validated. + pub resumption: Resumption, + + /// The maximum size of plaintext input to be emitted in a single TLS record. + /// A value of None is equivalent to the [TLS maximum] of 16 kB. + /// + /// rustls enforces an arbitrary minimum of 32 bytes for this field. + /// Out of range values are reported as errors from [ClientConnection::new]. + /// + /// Setting this value to a little less than the TCP MSS may improve latency + /// for stream-y workloads. + /// + /// [TLS maximum]: https://datatracker.ietf.org/doc/html/rfc8446#section-5.1 + /// [ClientConnection::new]: crate::client::ClientConnection::new + pub max_fragment_size: Option<usize>, + + /// How to decide what client auth certificate/keys to use. + pub client_auth_cert_resolver: Arc<dyn ResolvesClientCert>, + + /// Whether to send the Server Name Indication (SNI) extension + /// during the client handshake. + /// + /// The default is true. + pub enable_sni: bool, + + /// How to output key material for debugging. The default + /// does nothing. + pub key_log: Arc<dyn KeyLog>, + + /// Allows traffic secrets to be extracted after the handshake, + /// e.g. for kTLS setup. + pub enable_secret_extraction: bool, + + /// Whether to send data on the first flight ("early data") in + /// TLS 1.3 handshakes. + /// + /// The default is false. + pub enable_early_data: bool, + + /// If set to `true`, requires the server to support the extended + /// master secret extraction method defined in [RFC 7627]. + /// + /// The default is `true` if the `fips` crate feature is enabled, + /// `false` otherwise. + /// + /// It must be set to `true` to meet FIPS requirement mentioned in section + /// **D.Q Transition of the TLS 1.2 KDF to Support the Extended Master + /// Secret** from [FIPS 140-3 IG.pdf]. + /// + /// [RFC 7627]: https://datatracker.ietf.org/doc/html/rfc7627 + /// [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<dyn TimeProvider>, + + /// Source of randomness and other crypto. + pub(super) provider: Arc<CryptoProvider>, + + /// Supported versions, in no particular order. The default + /// is all supported versions. + pub(super) versions: versions::EnabledVersions, + + /// How to verify the server certificate chain. + pub(super) verifier: Arc<dyn verify::ServerCertVerifier>, + + /// How to decompress the server's certificate chain. + /// + /// If this is non-empty, the [RFC8779] certificate compression + /// extension is offered, and any compressed certificates are + /// transparently decompressed during the handshake. + /// + /// This only applies to TLS1.3 connections. It is ignored for + /// TLS1.2 connections. + /// + /// [RFC8779]: https://datatracker.ietf.org/doc/rfc8879/ + pub cert_decompressors: Vec<&'static dyn compress::CertDecompressor>, + + /// How to compress the client's certificate chain. + /// + /// If a server supports this extension, and advertises support + /// for one of the compression algorithms included here, the + /// client certificate will be compressed according to [RFC8779]. + /// + /// This only applies to TLS1.3 connections. It is ignored for + /// TLS1.2 connections. + /// + /// [RFC8779]: https://datatracker.ietf.org/doc/rfc8879/ + pub cert_compressors: Vec<&'static dyn compress::CertCompressor>, + + /// Caching for compressed certificates. + /// + /// This is optional: [`compress::CompressionCache::Disabled`] gives + /// a cache that does no caching. + pub cert_compression_cache: Arc<compress::CompressionCache>, + + /// How to offer Encrypted Client Hello (ECH). The default is to not offer ECH. + pub(super) ech_mode: Option<EchMode>, +} + +impl ClientConfig { + /// Create a builder for a client configuration with + /// [the process-default `CryptoProvider`][CryptoProvider#using-the-per-process-default-cryptoprovider] + /// and safe protocol version defaults. + /// + /// For more information, see the [`ConfigBuilder`] documentation. + #[cfg(feature = "std")] + pub fn builder() -> ConfigBuilder<Self, WantsVerifier> { + Self::builder_with_protocol_versions(versions::DEFAULT_VERSIONS) + } + + /// Create a builder for a client configuration with + /// [the process-default `CryptoProvider`][CryptoProvider#using-the-per-process-default-cryptoprovider] + /// and the provided protocol versions. + /// + /// Panics if + /// - the supported versions are not compatible with the provider (eg. + /// the combination of ciphersuites supported by the provider and supported + /// versions lead to zero cipher suites being usable), + /// - if a `CryptoProvider` cannot be resolved using a combination of + /// 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<Self, WantsVerifier> { + // Safety assumptions: + // 1. that the provider has been installed (explicitly or implicitly) + // 2. that the process-level default provider is usable with the supplied protocol versions. + Self::builder_with_provider( + CryptoProvider::get_default_or_install_from_crate_features().clone(), + ) + .with_protocol_versions(versions) + .unwrap() + } + + /// Create a builder for a client configuration with 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. + #[cfg(feature = "std")] + pub fn builder_with_provider( + provider: Arc<CryptoProvider>, + ) -> ConfigBuilder<Self, WantsVersions> { + ConfigBuilder { + 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<CryptoProvider>, + time_provider: Arc<dyn TimeProvider>, + ) -> ConfigBuilder<Self, WantsVersions> { + ConfigBuilder { + state: WantsVersions {}, + provider, + time_provider, + side: PhantomData, + } + } + + /// Return true if connections made with this `ClientConfig` will + /// operate in FIPS mode. + /// + /// This is different from [`CryptoProvider::fips()`]: [`CryptoProvider::fips()`] + /// is concerned only with cryptography, whereas this _also_ covers TLS-level + /// configuration that NIST recommends, as well as ECH HPKE suites if applicable. + pub fn fips(&self) -> bool { + let mut is_fips = self.provider.fips(); + + #[cfg(feature = "tls12")] + { + is_fips = is_fips && self.require_ems + } + + if let Some(ech_mode) = &self.ech_mode { + is_fips = is_fips && ech_mode.fips(); + } + + is_fips + } + + /// Return the crypto provider used to construct this client configuration. + pub fn crypto_provider(&self) -> &Arc<CryptoProvider> { + &self.provider + } + + /// Access configuration options whose use is dangerous and requires + /// extra care. + pub fn dangerous(&mut self) -> danger::DangerousClientConfig<'_> { + danger::DangerousClientConfig { cfg: self } + } + + pub(super) fn needs_key_share(&self) -> bool { + self.supports_version(ProtocolVersion::TLSv1_3) + } + + /// We support a given TLS version if it's quoted in the configured + /// versions *and* at least one ciphersuite for this version is + /// also configured. + pub(crate) fn supports_version(&self, v: ProtocolVersion) -> bool { + self.versions.contains(v) + && self + .provider + .cipher_suites + .iter() + .any(|cs| cs.version().version == v) + } + + #[cfg(feature = "std")] + pub(crate) fn supports_protocol(&self, proto: Protocol) -> bool { + self.provider + .cipher_suites + .iter() + .any(|cs| cs.usable_for_protocol(proto)) + } + + pub(super) fn find_cipher_suite(&self, suite: CipherSuite) -> Option<SupportedCipherSuite> { + self.provider + .cipher_suites + .iter() + .copied() + .find(|&scs| scs.suite() == suite) + } + + pub(super) fn find_kx_group( + &self, + group: NamedGroup, + version: ProtocolVersion, + ) -> Option<&'static dyn SupportedKxGroup> { + self.provider + .kx_groups + .iter() + .copied() + .find(|skxg| skxg.usable_for_version(version) && skxg.name() == group) + } + + pub(super) fn current_time(&self) -> Result<UnixTime, Error> { + self.time_provider + .current_time() + .ok_or(Error::FailedToGetCurrentTime) + } +} + +/// Configuration for how/when a client is allowed to resume a previous session. +#[derive(Clone, Debug)] +pub struct Resumption { + /// How we store session data or tickets. The default is to use an in-memory + /// [super::handy::ClientSessionMemoryCache]. + pub(super) store: Arc<dyn ClientSessionStore>, + + /// What mechanism is used for resuming a TLS 1.2 session. + pub(super) tls12_resumption: Tls12Resumption, +} + +impl Resumption { + /// Create a new `Resumption` that stores data for the given number of sessions in memory. + /// + /// 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(super::handy::ClientSessionMemoryCache::new(num)), + tls12_resumption: Tls12Resumption::SessionIdOrTickets, + } + } + + /// Use a custom [`ClientSessionStore`] implementation to store sessions. + /// + /// By default, enables resuming a TLS 1.2 session with a session id or RFC 5077 ticket. + pub fn store(store: Arc<dyn ClientSessionStore>) -> Self { + Self { + store, + tls12_resumption: Tls12Resumption::SessionIdOrTickets, + } + } + + /// Disable all use of session resumption. + pub fn disabled() -> Self { + Self { + store: Arc::new(NoClientSessionStorage), + tls12_resumption: Tls12Resumption::Disabled, + } + } + + /// Configure whether TLS 1.2 sessions may be resumed, and by what mechanism. + /// + /// 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 + } +} + +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 { + #[cfg(feature = "std")] + let ret = Self::in_memory_sessions(256); + + #[cfg(not(feature = "std"))] + let ret = Self::disabled(); + + ret + } +} + +/// What mechanisms to support for resuming a TLS 1.2 session. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Tls12Resumption { + /// Disable 1.2 resumption. + Disabled, + /// Support 1.2 resumption using session ids only. + SessionIdOnly, + /// Support 1.2 resumption using session ids or RFC 5077 tickets. + /// + /// See[^1] for why you might like to disable RFC 5077 by instead choosing the `SessionIdOnly` + /// option. Note that TLS 1.3 tickets do not have those issues. + /// + /// [^1]: <https://words.filippo.io/we-need-to-talk-about-session-tickets/> + SessionIdOrTickets, +} + +/// Container for unsafe APIs +pub(super) mod danger { + use super::ClientConfig; + use super::verify::ServerCertVerifier; + use crate::sync::Arc; + + /// Accessor for dangerous configuration options. + #[derive(Debug)] + pub struct DangerousClientConfig<'a> { + /// The underlying ClientConfig + pub cfg: &'a mut ClientConfig, + } + + impl DangerousClientConfig<'_> { + /// Overrides the default `ServerCertVerifier` with something else. + pub fn set_certificate_verifier(&mut self, verifier: Arc<dyn ServerCertVerifier>) { + self.cfg.verifier = verifier; + } + } +} + +#[derive(Debug, PartialEq)] +enum EarlyDataState { + Disabled, + Ready, + Accepted, + AcceptedFinished, + Rejected, +} + +#[derive(Debug)] +pub(super) struct EarlyData { + state: EarlyDataState, + left: usize, +} + +impl EarlyData { + fn new() -> Self { + Self { + left: 0, + state: EarlyDataState::Disabled, + } + } + + pub(super) fn is_enabled(&self) -> bool { + matches!(self.state, EarlyDataState::Ready | EarlyDataState::Accepted) + } + + #[cfg(feature = "std")] + fn is_accepted(&self) -> bool { + matches!( + self.state, + EarlyDataState::Accepted | EarlyDataState::AcceptedFinished + ) + } + + pub(super) fn enable(&mut self, max_data: usize) { + assert_eq!(self.state, EarlyDataState::Disabled); + self.state = EarlyDataState::Ready; + self.left = max_data; + } + + pub(super) fn rejected(&mut self) { + trace!("EarlyData rejected"); + self.state = EarlyDataState::Rejected; + } + + pub(super) fn accepted(&mut self) { + trace!("EarlyData accepted"); + assert_eq!(self.state, EarlyDataState::Ready); + self.state = EarlyDataState::Accepted; + } + + pub(super) fn finished(&mut self) { + trace!("EarlyData finished"); + self.state = match self.state { + EarlyDataState::Accepted => EarlyDataState::AcceptedFinished, + _ => panic!("bad EarlyData state"), + } + } + + fn check_write_opt(&mut self, sz: usize) -> Option<usize> { + match self.state { + EarlyDataState::Disabled => unreachable!(), + EarlyDataState::Ready | EarlyDataState::Accepted => { + let take = if self.left < sz { + mem::replace(&mut self.left, 0) + } else { + self.left -= sz; + sz + }; + + Some(take) + } + EarlyDataState::Rejected | EarlyDataState::AcceptedFinished => None, + } + } +} + +#[cfg(feature = "std")] +mod connection { + use alloc::vec::Vec; + use core::fmt; + use core::ops::{Deref, DerefMut}; + use std::io; + + use pki_types::ServerName; + + use super::{ClientConnectionData, ClientExtensionsInput}; + use crate::ClientConfig; + use crate::client::EchStatus; + use crate::common_state::Protocol; + use crate::conn::{ConnectionCommon, ConnectionCore}; + use crate::error::Error; + use crate::suites::ExtractedSecrets; + use crate::sync::Arc; + + /// Stub that implements io::Write and dispatches to `write_early_data`. + pub struct WriteEarlyData<'a> { + sess: &'a mut ClientConnection, + } + + impl<'a> WriteEarlyData<'a> { + fn new(sess: &'a mut ClientConnection) -> Self { + WriteEarlyData { sess } + } + + /// 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 io::Write for WriteEarlyData<'_> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.sess.write_early_data(buf) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + } + + impl super::EarlyData { + fn check_write(&mut self, sz: usize) -> io::Result<usize> { + self.check_write_opt(sz) + .ok_or_else(|| io::Error::from(io::ErrorKind::InvalidInput)) + } + + fn bytes_left(&self) -> usize { + self.left + } + } + + /// This represents a single TLS client connection. + pub struct ClientConnection { + inner: ConnectionCommon<ClientConnectionData>, + } + + impl fmt::Debug for ClientConnection { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ClientConnection") + .finish() + } + } + + 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<ClientConfig>, name: ServerName<'static>) -> Result<Self, Error> { + Self::new_with_alpn(config.clone(), name, config.alpn_protocols.clone()) + } + + /// Make a new ClientConnection with custom ALPN protocols. + pub fn new_with_alpn( + config: Arc<ClientConfig>, + name: ServerName<'static>, + alpn_protocols: Vec<Vec<u8>>, + ) -> Result<Self, Error> { + Ok(Self { + inner: ConnectionCommon::from(ConnectionCore::for_client( + config, + name, + ClientExtensionsInput::from_alpn(alpn_protocols), + Protocol::Tcp, + )?), + }) + } + /// 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<WriteEarlyData<'_>> { + 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<ExtractedSecrets, Error> { + self.inner.dangerous_extract_secrets() + } + + /// Return the connection's Encrypted Client Hello (ECH) status. + pub fn ech_status(&self) -> EchStatus { + self.inner.core.data.ech_status + } + + /// Returns the number of TLS1.3 tickets that have been received. + pub fn tls13_tickets_received(&self) -> u32 { + self.inner.tls13_tickets_received + } + + /// Return true if the connection was made with a `ClientConfig` that is FIPS compatible. + /// + /// This is different from [`crate::crypto::CryptoProvider::fips()`]: + /// it is concerned only with cryptography, whereas this _also_ covers TLS-level + /// configuration that NIST recommends, as well as ECH HPKE suites if applicable. + pub fn fips(&self) -> bool { + self.inner.core.common_state.fips + } + + fn write_early_data(&mut self, data: &[u8]) -> io::Result<usize> { + 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<ClientConnectionData>; + + fn deref(&self) -> &Self::Target { + &self.inner + } + } + + impl DerefMut for ClientConnection { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } + } + + #[doc(hidden)] + impl<'a> TryFrom<&'a mut crate::Connection> for &'a mut ClientConnection { + type Error = (); + + fn try_from(value: &'a mut crate::Connection) -> Result<Self, Self::Error> { + use crate::Connection::*; + match value { + Client(conn) => Ok(conn), + Server(_) => Err(()), + } + } + } + + impl From<ClientConnection> for crate::Connection { + fn from(conn: ClientConnection) -> Self { + Self::Client(conn) + } + } +} +#[cfg(feature = "std")] +pub use connection::{ClientConnection, WriteEarlyData}; + +impl ConnectionCore<ClientConnectionData> { + pub(crate) fn for_client( + config: Arc<ClientConfig>, + name: ServerName<'static>, + extra_exts: ClientExtensionsInput<'static>, + proto: Protocol, + ) -> Result<Self, Error> { + let mut common_state = CommonState::new(Side::Client); + common_state.set_max_fragment_size(config.max_fragment_size)?; + common_state.protocol = proto; + common_state.enable_secret_extraction = config.enable_secret_extraction; + common_state.fips = config.fips(); + let mut data = ClientConnectionData::new(); + + let mut cx = hs::ClientContext { + common: &mut common_state, + data: &mut data, + // `start_handshake` won't produce plaintext + sendable_plaintext: None, + }; + + let input = ClientHelloInput::new(name, &extra_exts, &mut cx, config)?; + let state = input.start_handshake(extra_exts, &mut cx)?; + 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() + } +} + +/// Unbuffered version of `ClientConnection` +/// +/// See the [`crate::unbuffered`] module docs for more details +pub struct UnbufferedClientConnection { + inner: UnbufferedConnectionCommon<ClientConnectionData>, +} + +impl UnbufferedClientConnection { + /// 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<ClientConfig>, name: ServerName<'static>) -> Result<Self, Error> { + Self::new_with_extensions( + config.clone(), + name, + ClientExtensionsInput::from_alpn(config.alpn_protocols.clone()), + ) + } + + /// Make a new UnbufferedClientConnection with custom ALPN protocols. + pub fn new_with_alpn( + config: Arc<ClientConfig>, + name: ServerName<'static>, + alpn_protocols: Vec<Vec<u8>>, + ) -> Result<Self, Error> { + Self::new_with_extensions( + config, + name, + ClientExtensionsInput::from_alpn(alpn_protocols), + ) + } + + fn new_with_extensions( + config: Arc<ClientConfig>, + name: ServerName<'static>, + extensions: ClientExtensionsInput<'static>, + ) -> Result<Self, Error> { + Ok(Self { + inner: UnbufferedConnectionCommon::from(ConnectionCore::for_client( + config, + name, + extensions, + Protocol::Tcp, + )?), + }) + } + + /// Extract secrets, so they can be used when configuring kTLS, for example. + /// Should be used with care as it exposes secret key material. + #[deprecated = "dangerous_extract_secrets() does not support session tickets or \ + key updates, use dangerous_into_kernel_connection() instead"] + pub fn dangerous_extract_secrets(self) -> Result<ExtractedSecrets, Error> { + self.inner.dangerous_extract_secrets() + } + + /// Extract secrets and a [`KernelConnection`] object. + /// + /// This allows you use rustls to manage keys and then manage encryption and + /// decryption yourself (e.g. for kTLS). + /// + /// Should be used with care as it exposes secret key material. + /// + /// See the [`crate::kernel`] documentations for details on prerequisites + /// for calling this method. + pub fn dangerous_into_kernel_connection( + self, + ) -> Result<(ExtractedSecrets, KernelConnection<ClientConnectionData>), Error> { + self.inner + .core + .dangerous_into_kernel_connection() + } + + /// Returns the number of TLS1.3 tickets that have been received. + pub fn tls13_tickets_received(&self) -> u32 { + self.inner.tls13_tickets_received + } +} + +impl Deref for UnbufferedClientConnection { + type Target = UnbufferedConnectionCommon<ClientConnectionData>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for UnbufferedClientConnection { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl TransmitTlsData<'_, ClientConnectionData> { + /// returns an adapter that allows encrypting early (RTT-0) data before transmitting the + /// already encoded TLS data + /// + /// IF allowed by the protocol + pub fn may_encrypt_early_data(&mut self) -> Option<MayEncryptEarlyData<'_>> { + if self + .conn + .core + .data + .early_data + .is_enabled() + { + Some(MayEncryptEarlyData { conn: self.conn }) + } else { + None + } + } +} + +/// Allows encrypting early (RTT-0) data +pub struct MayEncryptEarlyData<'c> { + conn: &'c mut UnbufferedConnectionCommon<ClientConnectionData>, +} + +impl MayEncryptEarlyData<'_> { + /// Encrypts `application_data` into the `outgoing_tls` buffer + /// + /// returns the number of bytes that were written into `outgoing_tls`, or an error if + /// the provided buffer was too small. In the error case, `outgoing_tls` is not modified + pub fn encrypt( + &mut self, + early_data: &[u8], + outgoing_tls: &mut [u8], + ) -> Result<usize, EarlyDataError> { + let Some(allowed) = self + .conn + .core + .data + .early_data + .check_write_opt(early_data.len()) + else { + return Err(EarlyDataError::ExceededAllowedEarlyData); + }; + + self.conn + .core + .common_state + .write_plaintext(early_data[..allowed].into(), outgoing_tls) + .map_err(|e| e.into()) + } +} + +/// Errors that may arise when encrypting early (RTT-0) data +#[derive(Debug)] +pub enum EarlyDataError { + /// Cannot encrypt more early data due to imposed limits + ExceededAllowedEarlyData, + /// Encryption error + Encrypt(EncryptError), +} + +impl From<EncryptError> for EarlyDataError { + fn from(v: EncryptError) -> Self { + Self::Encrypt(v) + } +} + +impl fmt::Display for EarlyDataError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::ExceededAllowedEarlyData => f.write_str("cannot send any more early data"), + Self::Encrypt(e) => fmt::Display::fmt(e, f), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for EarlyDataError {} + +/// State associated with a client connection. +#[derive(Debug)] +pub struct ClientConnectionData { + pub(super) early_data: EarlyData, + pub(super) ech_status: EchStatus, +} + +impl ClientConnectionData { + fn new() -> Self { + Self { + early_data: EarlyData::new(), + ech_status: EchStatus::NotOffered, + } + } +} + +impl crate::conn::SideData for ClientConnectionData {} diff --git a/vendor/rustls/src/client/common.rs b/vendor/rustls/src/client/common.rs new file mode 100644 index 00000000..9afa0c34 --- /dev/null +++ b/vendor/rustls/src/client/common.rs @@ -0,0 +1,119 @@ +use alloc::boxed::Box; +use alloc::vec::Vec; + +use super::ResolvesClientCert; +use crate::log::{debug, trace}; +use crate::msgs::enums::ExtensionType; +use crate::msgs::handshake::{CertificateChain, DistinguishedName, ProtocolName, ServerExtensions}; +use crate::sync::Arc; +use crate::{SignatureScheme, compress, sign}; + +#[derive(Debug)] +pub(super) struct ServerCertDetails<'a> { + pub(super) cert_chain: CertificateChain<'a>, + pub(super) ocsp_response: Vec<u8>, +} + +impl<'a> ServerCertDetails<'a> { + pub(super) fn new(cert_chain: CertificateChain<'a>, ocsp_response: Vec<u8>) -> Self { + Self { + cert_chain, + ocsp_response, + } + } + + pub(super) fn into_owned(self) -> ServerCertDetails<'static> { + let Self { + cert_chain, + ocsp_response, + } = self; + ServerCertDetails { + cert_chain: cert_chain.into_owned(), + ocsp_response, + } + } +} + +pub(super) struct ClientHelloDetails { + pub(super) alpn_protocols: Vec<ProtocolName>, + pub(super) sent_extensions: Vec<ExtensionType>, + pub(super) extension_order_seed: u16, + pub(super) offered_cert_compression: bool, +} + +impl ClientHelloDetails { + pub(super) fn new(alpn_protocols: Vec<ProtocolName>, extension_order_seed: u16) -> Self { + Self { + alpn_protocols, + sent_extensions: Vec::new(), + extension_order_seed, + offered_cert_compression: false, + } + } + + pub(super) fn server_sent_unsolicited_extensions( + &self, + received_exts: &ServerExtensions<'_>, + allowed_unsolicited: &[ExtensionType], + ) -> bool { + let mut extensions = received_exts.collect_used(); + extensions.extend( + received_exts + .unknown_extensions + .iter() + .map(|ext| ExtensionType::from(*ext)), + ); + for ext_type in extensions { + if !self.sent_extensions.contains(&ext_type) && !allowed_unsolicited.contains(&ext_type) + { + trace!("Unsolicited extension {ext_type:?}"); + return true; + } + } + + false + } +} + +pub(super) enum ClientAuthDetails { + /// Send an empty `Certificate` and no `CertificateVerify`. + Empty { auth_context_tls13: Option<Vec<u8>> }, + /// Send a non-empty `Certificate` and a `CertificateVerify`. + Verify { + certkey: Arc<sign::CertifiedKey>, + signer: Box<dyn sign::Signer>, + auth_context_tls13: Option<Vec<u8>>, + compressor: Option<&'static dyn compress::CertCompressor>, + }, +} + +impl ClientAuthDetails { + pub(super) fn resolve( + resolver: &dyn ResolvesClientCert, + canames: Option<&[DistinguishedName]>, + sigschemes: &[SignatureScheme], + auth_context_tls13: Option<Vec<u8>>, + compressor: Option<&'static dyn compress::CertCompressor>, + ) -> Self { + let acceptable_issuers = canames + .unwrap_or_default() + .iter() + .map(|p| p.as_ref()) + .collect::<Vec<&[u8]>>(); + + if let Some(certkey) = resolver.resolve(&acceptable_issuers, sigschemes) { + if let Some(signer) = certkey.key.choose_scheme(sigschemes) { + debug!("Attempting client auth"); + return Self::Verify { + certkey, + signer, + auth_context_tls13, + compressor, + }; + } + } + + debug!("Client auth requested but no cert/sigscheme available"); + Self::Empty { auth_context_tls13 } + } +} diff --git a/vendor/rustls/src/client/ech.rs b/vendor/rustls/src/client/ech.rs new file mode 100644 index 00000000..616ebbfc --- /dev/null +++ b/vendor/rustls/src/client/ech.rs @@ -0,0 +1,899 @@ +use alloc::boxed::Box; +use alloc::vec; +use alloc::vec::Vec; + +use pki_types::{DnsName, EchConfigListBytes, ServerName}; +use subtle::ConstantTimeEq; + +use crate::CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV; +use crate::client::tls13; +use crate::crypto::SecureRandom; +use crate::crypto::hash::Hash; +use crate::crypto::hpke::{EncapsulatedSecret, Hpke, HpkePublicKey, HpkeSealer, HpkeSuite}; +use crate::hash_hs::{HandshakeHash, HandshakeHashBuffer}; +use crate::log::{debug, trace, warn}; +use crate::msgs::base::{Payload, PayloadU16}; +use crate::msgs::codec::{Codec, Reader}; +use crate::msgs::enums::{ExtensionType, HpkeKem}; +use crate::msgs::handshake::{ + ClientExtensions, ClientHelloPayload, EchConfigContents, EchConfigPayload, Encoding, + EncryptedClientHello, EncryptedClientHelloOuter, HandshakeMessagePayload, HandshakePayload, + HelloRetryRequest, HpkeKeyConfig, HpkeSymmetricCipherSuite, PresharedKeyBinder, + PresharedKeyOffer, Random, ServerHelloPayload, ServerNamePayload, +}; +use crate::msgs::message::{Message, MessagePayload}; +use crate::msgs::persist; +use crate::msgs::persist::Retrieved; +use crate::tls13::key_schedule::{ + KeyScheduleEarly, KeyScheduleHandshakeStart, server_ech_hrr_confirmation_secret, +}; +use crate::{ + AlertDescription, ClientConfig, CommonState, EncryptedClientHelloError, Error, + PeerIncompatible, PeerMisbehaved, ProtocolVersion, Tls13CipherSuite, +}; + +/// Controls how Encrypted Client Hello (ECH) is used in a client handshake. +#[derive(Clone, Debug)] +pub enum EchMode { + /// ECH is enabled and the ClientHello will be encrypted based on the provided + /// configuration. + Enable(EchConfig), + + /// No ECH configuration is available but the client should act as though it were. + /// + /// This is an anti-ossification measure, sometimes referred to as "GREASE"[^0]. + /// [^0]: <https://www.rfc-editor.org/rfc/rfc8701> + Grease(EchGreaseConfig), +} + +impl EchMode { + /// Returns true if the ECH mode will use a FIPS approved HPKE suite. + pub fn fips(&self) -> bool { + match self { + Self::Enable(ech_config) => ech_config.suite.fips(), + Self::Grease(grease_config) => grease_config.suite.fips(), + } + } +} + +impl From<EchConfig> for EchMode { + fn from(config: EchConfig) -> Self { + Self::Enable(config) + } +} + +impl From<EchGreaseConfig> for EchMode { + fn from(config: EchGreaseConfig) -> Self { + Self::Grease(config) + } +} + +/// Configuration for performing encrypted client hello. +/// +/// Note: differs from the protocol-encoded EchConfig (`EchConfigMsg`). +#[derive(Clone, Debug)] +pub struct EchConfig { + /// The selected EchConfig. + pub(crate) config: EchConfigPayload, + + /// An HPKE instance corresponding to a suite from the `config` we have selected as + /// a compatible choice. + pub(crate) suite: &'static dyn Hpke, +} + +impl EchConfig { + /// Construct an EchConfig by selecting a ECH config from the provided bytes that is compatible + /// with one of the given HPKE suites. + /// + /// The config list bytes should be sourced from a DNS-over-HTTPS lookup resolving the `HTTPS` + /// resource record for the host name of the server you wish to connect via ECH, + /// and extracting the ECH configuration from the `ech` parameter. The extracted bytes should + /// be base64 decoded to yield the `EchConfigListBytes` you provide to rustls. + /// + /// One of the provided ECH configurations must be compatible with the HPKE provider's supported + /// suites or an error will be returned. + /// + /// See the [`ech-client.rs`] example for a complete example of fetching ECH configs from DNS. + /// + /// [`ech-client.rs`]: https://github.com/rustls/rustls/blob/main/examples/src/bin/ech-client.rs + pub fn new( + ech_config_list: EchConfigListBytes<'_>, + hpke_suites: &[&'static dyn Hpke], + ) -> Result<Self, Error> { + let ech_configs = Vec::<EchConfigPayload>::read(&mut Reader::init(&ech_config_list)) + .map_err(|_| { + Error::InvalidEncryptedClientHello(EncryptedClientHelloError::InvalidConfigList) + })?; + + // Note: we name the index var _i because if the log feature is disabled + // it is unused. + #[cfg_attr(not(feature = "logging"), allow(clippy::unused_enumerate_index))] + for (_i, config) in ech_configs.iter().enumerate() { + let contents = match config { + EchConfigPayload::V18(contents) => contents, + EchConfigPayload::Unknown { + version: _version, .. + } => { + warn!( + "ECH config {} has unsupported version {:?}", + _i + 1, + _version + ); + continue; // Unsupported version. + } + }; + + if contents.has_unknown_mandatory_extension() || contents.has_duplicate_extension() { + warn!("ECH config has duplicate, or unknown mandatory extensions: {contents:?}",); + continue; // Unsupported, or malformed extensions. + } + + let key_config = &contents.key_config; + for cipher_suite in &key_config.symmetric_cipher_suites { + if cipher_suite.aead_id.tag_len().is_none() { + continue; // Unsupported EXPORT_ONLY AEAD cipher suite. + } + + let suite = HpkeSuite { + kem: key_config.kem_id, + sym: *cipher_suite, + }; + if let Some(hpke) = hpke_suites + .iter() + .find(|hpke| hpke.suite() == suite) + { + debug!( + "selected ECH config ID {:?} suite {:?} public_name {:?}", + key_config.config_id, suite, contents.public_name + ); + return Ok(Self { + config: config.clone(), + suite: *hpke, + }); + } + } + } + + Err(EncryptedClientHelloError::NoCompatibleConfig.into()) + } + + pub(super) fn state( + &self, + server_name: ServerName<'static>, + config: &ClientConfig, + ) -> Result<EchState, Error> { + EchState::new( + self, + server_name.clone(), + config + .client_auth_cert_resolver + .has_certs(), + config.provider.secure_random, + config.enable_sni, + ) + } + + /// Compute the HPKE `SetupBaseS` `info` parameter for this ECH configuration. + /// + /// See <https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-17#section-6.1>. + pub(crate) fn hpke_info(&self) -> Vec<u8> { + let mut info = Vec::with_capacity(128); + // "tls ech" || 0x00 || ECHConfig + info.extend_from_slice(b"tls ech\0"); + self.config.encode(&mut info); + info + } +} + +/// Configuration for GREASE Encrypted Client Hello. +#[derive(Clone, Debug)] +pub struct EchGreaseConfig { + pub(crate) suite: &'static dyn Hpke, + pub(crate) placeholder_key: HpkePublicKey, +} + +impl EchGreaseConfig { + /// Construct a GREASE ECH configuration. + /// + /// This configuration is used when the client wishes to offer ECH to prevent ossification, + /// but doesn't have a real ECH configuration to use for the remote server. In this case + /// a placeholder or "GREASE"[^0] extension is used. + /// + /// Returns an error if the HPKE provider does not support the given suite. + /// + /// [^0]: <https://www.rfc-editor.org/rfc/rfc8701> + pub fn new(suite: &'static dyn Hpke, placeholder_key: HpkePublicKey) -> Self { + Self { + suite, + placeholder_key, + } + } + + /// Build a GREASE ECH extension based on the placeholder configuration. + /// + /// See <https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-18#name-grease-ech> for + /// more information. + pub(crate) fn grease_ext( + &self, + secure_random: &'static dyn SecureRandom, + inner_name: ServerName<'static>, + outer_hello: &ClientHelloPayload, + ) -> Result<EncryptedClientHello, Error> { + trace!("Preparing GREASE ECH extension"); + + // Pick a random config id. + let mut config_id: [u8; 1] = [0; 1]; + secure_random.fill(&mut config_id[..])?; + + let suite = self.suite.suite(); + + // Construct a dummy ECH state - we don't have a real ECH config from a server since + // this is for GREASE. + let mut grease_state = EchState::new( + &EchConfig { + config: EchConfigPayload::V18(EchConfigContents { + key_config: HpkeKeyConfig { + config_id: config_id[0], + kem_id: HpkeKem::DHKEM_P256_HKDF_SHA256, + public_key: PayloadU16::new(self.placeholder_key.0.clone()), + symmetric_cipher_suites: vec![suite.sym], + }, + maximum_name_length: 0, + public_name: DnsName::try_from("filler").unwrap(), + extensions: Vec::default(), + }), + suite: self.suite, + }, + inner_name, + false, + secure_random, + false, // Does not matter if we enable/disable SNI here. Inner hello is not used. + )?; + + // Construct an inner hello using the outer hello - this allows us to know the size of + // dummy payload we should use for the GREASE extension. + let encoded_inner_hello = grease_state.encode_inner_hello(outer_hello, None, &None); + + // Generate a payload of random data equivalent in length to a real inner hello. + let payload_len = encoded_inner_hello.len() + + suite + .sym + .aead_id + .tag_len() + // Safety: we have confirmed the AEAD is supported when building the config. All + // supported AEADs have a tag length. + .unwrap(); + let mut payload = vec![0; payload_len]; + secure_random.fill(&mut payload)?; + + // Return the GREASE extension. + Ok(EncryptedClientHello::Outer(EncryptedClientHelloOuter { + cipher_suite: suite.sym, + config_id: config_id[0], + enc: PayloadU16::new(grease_state.enc.0), + payload: PayloadU16::new(payload), + })) + } +} + +/// An enum representing ECH offer status. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum EchStatus { + /// ECH was not offered - it is a normal TLS handshake. + NotOffered, + /// GREASE ECH was sent. This is not considered offering ECH. + Grease, + /// ECH was offered but we do not yet know whether the offer was accepted or rejected. + Offered, + /// ECH was offered and the server accepted. + Accepted, + /// ECH was offered and the server rejected. + Rejected, +} + +/// Contextual data for a TLS client handshake that has offered encrypted client hello (ECH). +pub(crate) struct EchState { + // The public DNS name from the ECH configuration we've chosen - this is included as the SNI + // value for the "outer" client hello. It can only be a DnsName, not an IP address. + pub(crate) outer_name: DnsName<'static>, + // If we're resuming in the inner hello, this is the early key schedule to use for encrypting + // early data if the ECH offer is accepted. + pub(crate) early_data_key_schedule: Option<KeyScheduleEarly>, + // A random value we use for the inner hello. + pub(crate) inner_hello_random: Random, + // A transcript buffer maintained for the inner hello. Once ECH is confirmed we switch to + // using this transcript for the handshake. + pub(crate) inner_hello_transcript: HandshakeHashBuffer, + // A source of secure random data. + secure_random: &'static dyn SecureRandom, + // An HPKE sealer context that can be used for encrypting ECH data. + sender: Box<dyn HpkeSealer>, + // The ID of the ECH configuration we've chosen - this is included in the outer ECH extension. + config_id: u8, + // The private server name we'll use for the inner protected hello. + inner_name: ServerName<'static>, + // The advertised maximum name length from the ECH configuration we've chosen - this is used + // for padding calculations. + maximum_name_length: u8, + // A supported symmetric cipher suite from the ECH configuration we've chosen - this is + // included in the outer ECH extension. + cipher_suite: HpkeSymmetricCipherSuite, + // A secret encapsulated to the public key of the remote server. This is included in the + // outer ECH extension for non-retry outer hello messages. + enc: EncapsulatedSecret, + // Whether the inner client hello should contain a server name indication (SNI) extension. + enable_sni: bool, + // The extensions sent in the inner hello. + sent_extensions: Vec<ExtensionType>, +} + +impl EchState { + pub(crate) fn new( + config: &EchConfig, + inner_name: ServerName<'static>, + client_auth_enabled: bool, + secure_random: &'static dyn SecureRandom, + enable_sni: bool, + ) -> Result<Self, Error> { + let EchConfigPayload::V18(config_contents) = &config.config else { + // the public EchConfig::new() constructor ensures we only have supported + // configurations. + unreachable!("ECH config version mismatch"); + }; + let key_config = &config_contents.key_config; + + // Encapsulate a secret for the server's public key, and set up a sender context + // we can use to seal messages. + let (enc, sender) = config.suite.setup_sealer( + &config.hpke_info(), + &HpkePublicKey(key_config.public_key.0.clone()), + )?; + + // Start a new transcript buffer for the inner hello. + let mut inner_hello_transcript = HandshakeHashBuffer::new(); + if client_auth_enabled { + inner_hello_transcript.set_client_auth_enabled(); + } + + Ok(Self { + secure_random, + sender, + config_id: key_config.config_id, + inner_name, + outer_name: config_contents.public_name.clone(), + maximum_name_length: config_contents.maximum_name_length, + cipher_suite: config.suite.suite().sym, + enc, + inner_hello_random: Random::new(secure_random)?, + inner_hello_transcript, + early_data_key_schedule: None, + enable_sni, + sent_extensions: Vec::new(), + }) + } + + /// Construct a ClientHelloPayload offering ECH. + /// + /// An outer hello, with a protected inner hello for the `inner_name` will be returned, and the + /// ECH context will be updated to reflect the inner hello that was offered. + /// + /// If `retry_req` is `Some`, then the outer hello will be constructed for a hello retry request. + /// + /// If `resuming` is `Some`, then the inner hello will be constructed for a resumption handshake. + pub(crate) fn ech_hello( + &mut self, + mut outer_hello: ClientHelloPayload, + retry_req: Option<&HelloRetryRequest>, + resuming: &Option<Retrieved<&persist::Tls13ClientSessionValue>>, + ) -> Result<ClientHelloPayload, Error> { + trace!( + "Preparing ECH offer {}", + if retry_req.is_some() { "for retry" } else { "" } + ); + + // Construct the encoded inner hello and update the transcript. + let encoded_inner_hello = self.encode_inner_hello(&outer_hello, retry_req, resuming); + + // Complete the ClientHelloOuterAAD with an ech extension, the payload should be a placeholder + // of size L, all zeroes. L == length of encrypting encoded client hello inner w/ the selected + // HPKE AEAD. (sum of plaintext + tag length, typically). + let payload_len = encoded_inner_hello.len() + + self + .cipher_suite + .aead_id + .tag_len() + // Safety: we've already verified this AEAD is supported when loading the config + // that was used to create the ECH context. All supported AEADs have a tag length. + .unwrap(); + + // Outer hello's created in response to a hello retry request omit the enc value. + let enc = match retry_req.is_some() { + true => Vec::default(), + false => self.enc.0.clone(), + }; + + fn outer_hello_ext(ctx: &EchState, enc: Vec<u8>, payload: Vec<u8>) -> EncryptedClientHello { + EncryptedClientHello::Outer(EncryptedClientHelloOuter { + cipher_suite: ctx.cipher_suite, + config_id: ctx.config_id, + enc: PayloadU16::new(enc), + payload: PayloadU16::new(payload), + }) + } + + // The outer handshake is not permitted to resume a session. If we're resuming in the + // inner handshake we remove the PSK extension from the outer hello, replacing it + // with a GREASE PSK to implement the "ClientHello Malleability Mitigation" mentioned + // in 10.12.3. + if let Some(psk_offer) = outer_hello.preshared_key_offer.as_mut() { + self.grease_psk(psk_offer)?; + } + + // To compute the encoded AAD we add a placeholder extension with an empty payload. + outer_hello.encrypted_client_hello = + Some(outer_hello_ext(self, enc.clone(), vec![0; payload_len])); + + // Next we compute the proper extension payload. + let payload = self + .sender + .seal(&outer_hello.get_encoding(), &encoded_inner_hello)?; + + // And then we replace the placeholder extension with the real one. + outer_hello.encrypted_client_hello = Some(outer_hello_ext(self, enc, payload)); + + Ok(outer_hello) + } + + /// Confirm whether an ECH offer was accepted based on examining the server hello. + pub(crate) fn confirm_acceptance( + self, + ks: &mut KeyScheduleHandshakeStart, + server_hello: &ServerHelloPayload, + server_hello_encoded: &Payload<'_>, + hash: &'static dyn Hash, + ) -> Result<Option<EchAccepted>, Error> { + // Start the inner transcript hash now that we know the hash algorithm to use. + let inner_transcript = self + .inner_hello_transcript + .start_hash(hash); + + // Fork the transcript that we've started with the inner hello to use for a confirmation step. + // We need to preserve the original inner_transcript to use if this confirmation succeeds. + let mut confirmation_transcript = inner_transcript.clone(); + + // Add the server hello confirmation - this is computed by altering the received + // encoding rather than reencoding it. + confirmation_transcript + .add_message(&Self::server_hello_conf(server_hello, server_hello_encoded)); + + // Derive a confirmation secret from the inner hello random and the confirmation transcript. + let derived = ks.server_ech_confirmation_secret( + self.inner_hello_random.0.as_ref(), + confirmation_transcript.current_hash(), + ); + + // Check that first 8 digits of the derived secret match the last 8 digits of the original + // server random. This match signals that the server accepted the ECH offer. + // Indexing safety: Random is [0; 32] by construction. + + match ConstantTimeEq::ct_eq(derived.as_ref(), server_hello.random.0[24..].as_ref()).into() { + true => { + trace!("ECH accepted by server"); + Ok(Some(EchAccepted { + transcript: inner_transcript, + random: self.inner_hello_random, + sent_extensions: self.sent_extensions, + })) + } + false => { + trace!("ECH rejected by server"); + Ok(None) + } + } + } + + pub(crate) fn confirm_hrr_acceptance( + &self, + hrr: &HelloRetryRequest, + cs: &Tls13CipherSuite, + common: &mut CommonState, + ) -> Result<bool, Error> { + // The client checks for the "encrypted_client_hello" extension. + let ech_conf = match &hrr.encrypted_client_hello { + // If none is found, the server has implicitly rejected ECH. + None => return Ok(false), + // Otherwise, if it has a length other than 8, the client aborts the + // handshake with a "decode_error" alert. + Some(ech_conf) if ech_conf.bytes().len() != 8 => { + return Err({ + common.send_fatal_alert( + AlertDescription::DecodeError, + PeerMisbehaved::IllegalHelloRetryRequestWithInvalidEch, + ) + }); + } + Some(ech_conf) => ech_conf, + }; + + // Otherwise the client computes hrr_accept_confirmation as described in Section + // 7.2.1 + let confirmation_transcript = self.inner_hello_transcript.clone(); + let mut confirmation_transcript = + confirmation_transcript.start_hash(cs.common.hash_provider); + confirmation_transcript.rollup_for_hrr(); + confirmation_transcript.add_message(&Self::hello_retry_request_conf(hrr)); + + let derived = server_ech_hrr_confirmation_secret( + cs.hkdf_provider, + &self.inner_hello_random.0, + confirmation_transcript.current_hash(), + ); + + match ConstantTimeEq::ct_eq(derived.as_ref(), ech_conf.bytes()).into() { + true => { + trace!("ECH accepted by server in hello retry request"); + Ok(true) + } + false => { + trace!("ECH rejected by server in hello retry request"); + Ok(false) + } + } + } + + /// Update the ECH context inner hello transcript based on a received hello retry request message. + /// + /// This will start the in-progress transcript using the given `hash`, convert it into an HRR + /// buffer, and then add the hello retry message `m`. + pub(crate) fn transcript_hrr_update(&mut self, hash: &'static dyn Hash, m: &Message<'_>) { + trace!("Updating ECH inner transcript for HRR"); + + let inner_transcript = self + .inner_hello_transcript + .clone() + .start_hash(hash); + + let mut inner_transcript_buffer = inner_transcript.into_hrr_buffer(); + inner_transcript_buffer.add_message(m); + self.inner_hello_transcript = inner_transcript_buffer; + } + + // 5.1 "Encoding the ClientHelloInner" + fn encode_inner_hello( + &mut self, + outer_hello: &ClientHelloPayload, + retryreq: Option<&HelloRetryRequest>, + resuming: &Option<Retrieved<&persist::Tls13ClientSessionValue>>, + ) -> Vec<u8> { + // Start building an inner hello using the outer_hello as a template. + let mut inner_hello = ClientHelloPayload { + // Some information is copied over as-is. + client_version: outer_hello.client_version, + session_id: outer_hello.session_id, + compression_methods: outer_hello.compression_methods.clone(), + + // We will build up the included extensions ourselves. + extensions: Box::new(ClientExtensions::default()), + + // Set the inner hello random to the one we generated when creating the ECH state. + // We hold on to the inner_hello_random in the ECH state to use later for confirming + // whether ECH was accepted or not. + random: self.inner_hello_random, + + // We remove the empty renegotiation info SCSV from the outer hello's ciphersuite. + // Similar to the TLS 1.2 specific extensions we will filter out, this is seen as a + // TLS 1.2 only feature by bogo. + cipher_suites: outer_hello + .cipher_suites + .iter() + .filter(|cs| **cs != TLS_EMPTY_RENEGOTIATION_INFO_SCSV) + .cloned() + .collect(), + }; + + inner_hello.order_seed = outer_hello.order_seed; + + // The inner hello will always have an inner variant of the ECH extension added. + // See Section 6.1 rule 4. + inner_hello.encrypted_client_hello = Some(EncryptedClientHello::Inner); + + let inner_sni = match &self.inner_name { + // The inner hello only gets a SNI value if enable_sni is true and the inner name + // is a domain name (not an IP address). + ServerName::DnsName(dns_name) if self.enable_sni => Some(dns_name), + _ => None, + }; + + // Now we consider each of the outer hello's extensions - we can either: + // 1. Omit the extension if it isn't appropriate (e.g. is a TLS 1.2 extension). + // 2. Add the extension to the inner hello as-is. + // 3. Compress the extension, by collecting it into a list of to-be-compressed + // extensions we'll handle separately. + let outer_extensions = outer_hello.used_extensions_in_encoding_order(); + let mut compressed_exts = Vec::with_capacity(outer_extensions.len()); + for ext in outer_extensions { + // Some outer hello extensions are only useful in the context where a TLS 1.3 + // connection allows TLS 1.2. This isn't the case for ECH so we skip adding them + // to the inner hello. + if matches!( + ext, + ExtensionType::ExtendedMasterSecret + | ExtensionType::SessionTicket + | ExtensionType::ECPointFormats + ) { + continue; + } + + if ext == ExtensionType::ServerName { + // We may want to replace the outer hello SNI with our own inner hello specific SNI. + if let Some(sni_value) = inner_sni { + inner_hello.server_name = Some(ServerNamePayload::from(sni_value)); + } + // We don't want to add, or compress, the SNI from the outer hello. + continue; + } + + // Compressed extensions need to be put aside to include in one contiguous block. + // Uncompressed extensions get added directly to the inner hello. + if ext.ech_compress() { + compressed_exts.push(ext); + } + + inner_hello.clone_one(outer_hello, ext); + } + + // We've added all the uncompressed extensions. Now we need to add the contiguous + // block of to-be-compressed extensions. + inner_hello.contiguous_extensions = compressed_exts.clone(); + + // Note which extensions we're sending in the inner hello. This may differ from + // the outer hello (e.g. the inner hello may omit SNI while the outer hello will + // always have the ECH cover name in SNI). + self.sent_extensions = inner_hello.collect_used(); + + // If we're resuming, we need to update the PSK binder in the inner hello. + if let Some(resuming) = resuming.as_ref() { + let mut chp = HandshakeMessagePayload(HandshakePayload::ClientHello(inner_hello)); + + // Retain the early key schedule we get from processing the binder. + self.early_data_key_schedule = Some(tls13::fill_in_psk_binder( + resuming, + &self.inner_hello_transcript, + &mut chp, + )); + + // fill_in_psk_binder works on an owned HandshakeMessagePayload, so we need to + // extract our inner hello back out of it to retain ownership. + inner_hello = match chp.0 { + HandshakePayload::ClientHello(chp) => chp, + // Safety: we construct the HMP above and know its type unconditionally. + _ => unreachable!(), + }; + } + + trace!("ECH Inner Hello: {inner_hello:#?}"); + + // Encode the inner hello according to the rules required for ECH. This differs + // from the standard encoding in several ways. Notably this is where we will + // replace the block of contiguous to-be-compressed extensions with a marker. + let mut encoded_hello = inner_hello.ech_inner_encoding(compressed_exts); + + // Calculate padding + // max_name_len = L + let max_name_len = self.maximum_name_length; + let max_name_len = if max_name_len > 0 { max_name_len } else { 255 }; + + let padding_len = match &self.inner_name { + ServerName::DnsName(name) => { + // name.len() = D + // max(0, L - D) + core::cmp::max( + 0, + max_name_len.saturating_sub(name.as_ref().len() as u8) as usize, + ) + } + _ => { + // L + 9 + // "This is the length of a "server_name" extension with an L-byte name." + // We widen to usize here to avoid overflowing u8 + u8. + max_name_len as usize + 9 + } + }; + + // Let L be the length of the EncodedClientHelloInner with all the padding computed so far + // Let N = 31 - ((L - 1) % 32) and add N bytes of padding. + let padding_len = 31 - ((encoded_hello.len() + padding_len - 1) % 32); + encoded_hello.extend(vec![0; padding_len]); + + // Construct the inner hello message that will be used for the transcript. + let inner_hello_msg = Message { + version: match retryreq { + // <https://datatracker.ietf.org/doc/html/rfc8446#section-5.1>: + // "This value MUST be set to 0x0303 for all records generated + // by a TLS 1.3 implementation ..." + Some(_) => ProtocolVersion::TLSv1_2, + // "... other than an initial ClientHello (i.e., one not + // generated after a HelloRetryRequest), where it MAY also be + // 0x0301 for compatibility purposes" + // + // (retryreq == None means we're in the "initial ClientHello" case) + None => ProtocolVersion::TLSv1_0, + }, + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::ClientHello(inner_hello), + )), + }; + + // Update the inner transcript buffer with the inner hello message. + self.inner_hello_transcript + .add_message(&inner_hello_msg); + + encoded_hello + } + + // See https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-18#name-grease-psk + fn grease_psk(&self, psk_offer: &mut PresharedKeyOffer) -> Result<(), Error> { + for ident in psk_offer.identities.iter_mut() { + // "For each PSK identity advertised in the ClientHelloInner, the + // client generates a random PSK identity with the same length." + self.secure_random + .fill(&mut ident.identity.0)?; + // "It also generates a random, 32-bit, unsigned integer to use as + // the obfuscated_ticket_age." + let mut ticket_age = [0_u8; 4]; + self.secure_random + .fill(&mut ticket_age)?; + ident.obfuscated_ticket_age = u32::from_be_bytes(ticket_age); + } + + // "Likewise, for each inner PSK binder, the client generates a random string + // of the same length." + psk_offer.binders = psk_offer + .binders + .iter() + .map(|old_binder| { + // We can't access the wrapped binder PresharedKeyBinder's PayloadU8 mutably, + // so we construct new PresharedKeyBinder's from scratch with the same length. + let mut new_binder = vec![0; old_binder.as_ref().len()]; + self.secure_random + .fill(&mut new_binder)?; + Ok::<PresharedKeyBinder, Error>(PresharedKeyBinder::from(new_binder)) + }) + .collect::<Result<_, _>>()?; + Ok(()) + } + + fn server_hello_conf( + server_hello: &ServerHelloPayload, + server_hello_encoded: &Payload<'_>, + ) -> Message<'static> { + // The confirmation is computed over the server hello, which has had + // its `random` field altered to zero the final 8 bytes. + // + // nb. we don't require that we can round-trip a `ServerHelloPayload`, to + // allow for efficiency in its in-memory representation. That means + // we operate here on the received encoding, as the confirmation needs + // to be computed on that. + let mut encoded = server_hello_encoded.clone().into_vec(); + encoded[SERVER_HELLO_ECH_CONFIRMATION_SPAN].fill(0x00); + + Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::Handshake { + encoded: Payload::Owned(encoded), + parsed: HandshakeMessagePayload(HandshakePayload::ServerHello( + server_hello.clone(), + )), + }, + } + } + + fn hello_retry_request_conf(retry_req: &HelloRetryRequest) -> Message<'_> { + Self::ech_conf_message(HandshakeMessagePayload( + HandshakePayload::HelloRetryRequest(retry_req.clone()), + )) + } + + fn ech_conf_message(hmp: HandshakeMessagePayload<'_>) -> Message<'_> { + let mut hmp_encoded = Vec::new(); + hmp.payload_encode(&mut hmp_encoded, Encoding::EchConfirmation); + Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::Handshake { + encoded: Payload::new(hmp_encoded), + parsed: hmp, + }, + } + } +} + +/// The last eight bytes of the ServerHello's random, taken from a Handshake message containing it. +/// +/// This has: +/// - a HandshakeType (1 byte), +/// - an exterior length (3 bytes), +/// - the legacy_version (2 bytes), and +/// - the balance of the random field (24 bytes). +const SERVER_HELLO_ECH_CONFIRMATION_SPAN: core::ops::Range<usize> = + (1 + 3 + 2 + 24)..(1 + 3 + 2 + 32); + +/// Returned from EchState::check_acceptance when the server has accepted the ECH offer. +/// +/// Holds the state required to continue the handshake with the inner hello from the ECH offer. +pub(crate) struct EchAccepted { + pub(crate) transcript: HandshakeHash, + pub(crate) random: Random, + pub(crate) sent_extensions: Vec<ExtensionType>, +} + +pub(crate) fn fatal_alert_required( + retry_configs: Option<Vec<EchConfigPayload>>, + common: &mut CommonState, +) -> Error { + common.send_fatal_alert( + AlertDescription::EncryptedClientHelloRequired, + PeerIncompatible::ServerRejectedEncryptedClientHello(retry_configs), + ) +} + +#[cfg(test)] +mod tests { + use crate::enums::CipherSuite; + use crate::msgs::handshake::{Random, ServerExtensions, SessionId}; + + use super::*; + + #[test] + fn server_hello_conf_alters_server_hello_random() { + let server_hello = ServerHelloPayload { + legacy_version: ProtocolVersion::TLSv1_2, + random: Random([0xffu8; 32]), + session_id: SessionId::empty(), + cipher_suite: CipherSuite::TLS13_AES_256_GCM_SHA384, + compression_method: crate::msgs::enums::Compression::Null, + extensions: Box::new(ServerExtensions::default()), + }; + let message = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::ServerHello(server_hello.clone()), + )), + }; + let Message { + payload: + MessagePayload::Handshake { + encoded: server_hello_encoded_before, + .. + }, + .. + } = &message + else { + unreachable!("ServerHello is a handshake message"); + }; + + let message = EchState::server_hello_conf(&server_hello, server_hello_encoded_before); + + let Message { + payload: + MessagePayload::Handshake { + encoded: server_hello_encoded_after, + .. + }, + .. + } = &message + else { + unreachable!("ServerHello is a handshake message"); + }; + + assert_eq!( + std::format!("{server_hello_encoded_before:x?}"), + "020000280303ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001302000000", + "beforehand eight bytes at end of Random should be 0xff here ^^^^^^^^^^^^^^^^ " + ); + assert_eq!( + std::format!("{server_hello_encoded_after:x?}"), + "020000280303ffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000001302000000", + " afterwards those bytes are zeroed ^^^^^^^^^^^^^^^^ " + ); + } +} diff --git a/vendor/rustls/src/client/handy.rs b/vendor/rustls/src/client/handy.rs new file mode 100644 index 00000000..3ad3073b --- /dev/null +++ b/vendor/rustls/src/client/handy.rs @@ -0,0 +1,390 @@ +use pki_types::ServerName; + +use crate::enums::SignatureScheme; +use crate::msgs::persist; +use crate::sync::Arc; +use crate::{NamedGroup, client, sign}; + +/// An implementer of `ClientSessionStore` which does nothing. +#[derive(Debug)] +pub(super) struct NoClientSessionStorage; + +impl client::ClientSessionStore for NoClientSessionStorage { + fn set_kx_hint(&self, _: ServerName<'static>, _: NamedGroup) {} + + fn kx_hint(&self, _: &ServerName<'_>) -> Option<NamedGroup> { + None + } + + fn set_tls12_session(&self, _: ServerName<'static>, _: persist::Tls12ClientSessionValue) {} + + fn tls12_session(&self, _: &ServerName<'_>) -> Option<persist::Tls12ClientSessionValue> { + None + } + + fn remove_tls12_session(&self, _: &ServerName<'_>) {} + + fn insert_tls13_ticket(&self, _: ServerName<'static>, _: persist::Tls13ClientSessionValue) {} + + fn take_tls13_ticket(&self, _: &ServerName<'_>) -> Option<persist::Tls13ClientSessionValue> { + None + } +} + +#[cfg(any(feature = "std", feature = "hashbrown"))] +mod cache { + use alloc::collections::VecDeque; + use core::fmt; + + use pki_types::ServerName; + + use crate::lock::Mutex; + use crate::msgs::persist; + use crate::{NamedGroup, limited_cache}; + + const MAX_TLS13_TICKETS_PER_SERVER: usize = 8; + + struct ServerData { + kx_hint: Option<NamedGroup>, + + // Zero or one TLS1.2 sessions. + #[cfg(feature = "tls12")] + tls12: Option<persist::Tls12ClientSessionValue>, + + // Up to MAX_TLS13_TICKETS_PER_SERVER TLS1.3 tickets, oldest first. + tls13: VecDeque<persist::Tls13ClientSessionValue>, + } + + 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), + } + } + } + + /// 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<limited_cache::LimitedCache<ServerName<'static>, ServerData>>, + } + + impl ClientSessionMemoryCache { + /// Make a new ClientSessionMemoryCache. `size` is the + /// maximum number of stored sessions. + #[cfg(feature = "std")] + 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)), + } + } + + /// Make a new ClientSessionMemoryCache. `size` is the + /// maximum number of stored sessions. + #[cfg(not(feature = "std"))] + pub fn new<M: crate::lock::MakeMutex>(size: usize) -> Self { + let max_servers = size.saturating_add(MAX_TLS13_TICKETS_PER_SERVER - 1) + / MAX_TLS13_TICKETS_PER_SERVER; + Self { + servers: Mutex::new::<M>(limited_cache::LimitedCache::new(max_servers)), + } + } + } + + 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)); + } + + fn kx_hint(&self, server_name: &ServerName<'_>) -> Option<NamedGroup> { + self.servers + .lock() + .unwrap() + .get(server_name) + .and_then(|sd| sd.kx_hint) + } + + 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 tls12_session( + &self, + _server_name: &ServerName<'_>, + ) -> Option<persist::Tls12ClientSessionValue> { + #[cfg(not(feature = "tls12"))] + return None; + + #[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<persist::Tls13ClientSessionValue> { + 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() + } + } +} + +#[cfg(any(feature = "std", feature = "hashbrown"))] +pub use cache::ClientSessionMemoryCache; + +#[derive(Debug)] +pub(super) struct FailResolveClientCert {} + +impl client::ResolvesClientCert for FailResolveClientCert { + fn resolve( + &self, + _root_hint_subjects: &[&[u8]], + _sigschemes: &[SignatureScheme], + ) -> Option<Arc<sign::CertifiedKey>> { + None + } + + fn has_certs(&self) -> bool { + false + } +} + +/// An exemplar `ResolvesClientCert` implementation that always resolves to a single +/// [RFC 7250] raw public key. +/// +/// [RFC 7250]: https://tools.ietf.org/html/rfc7250 +#[derive(Clone, Debug)] +pub struct AlwaysResolvesClientRawPublicKeys(Arc<sign::CertifiedKey>); +impl AlwaysResolvesClientRawPublicKeys { + /// Create a new `AlwaysResolvesClientRawPublicKeys` instance. + pub fn new(certified_key: Arc<sign::CertifiedKey>) -> Self { + Self(certified_key) + } +} + +impl client::ResolvesClientCert for AlwaysResolvesClientRawPublicKeys { + fn resolve( + &self, + _root_hint_subjects: &[&[u8]], + _sigschemes: &[SignatureScheme], + ) -> Option<Arc<sign::CertifiedKey>> { + Some(self.0.clone()) + } + + fn only_raw_public_keys(&self) -> bool { + true + } + + /// Returns true if the resolver is ready to present an identity. + /// + /// Even though the function is called `has_certs`, it returns true + /// although only an RPK (Raw Public Key) is available, not an actual certificate. + fn has_certs(&self) -> bool { + true + } +} + +#[cfg(test)] +#[macro_rules_attribute::apply(test_for_each_provider)] +mod tests { + use std::prelude::v1::*; + + use pki_types::{ServerName, UnixTime}; + + use super::NoClientSessionStorage; + use super::provider::cipher_suite; + use crate::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; + use crate::client::{ClientSessionStore, ResolvesClientCert}; + use crate::msgs::base::PayloadU16; + use crate::msgs::enums::NamedGroup; + use crate::msgs::handshake::CertificateChain; + #[cfg(feature = "tls12")] + use crate::msgs::handshake::SessionId; + use crate::msgs::persist::Tls13ClientSessionValue; + use crate::pki_types::CertificateDer; + use crate::suites::SupportedCipherSuite; + use crate::sync::Arc; + use crate::{DigitallySignedStruct, Error, SignatureScheme, sign}; + + #[test] + fn test_noclientsessionstorage_does_nothing() { + let c = NoClientSessionStorage {}; + let name = ServerName::try_from("example.com").unwrap(); + let now = UnixTime::now(); + let server_cert_verifier: Arc<dyn ServerCertVerifier> = Arc::new(DummyServerCertVerifier); + let resolves_client_cert: Arc<dyn ResolvesClientCert> = Arc::new(DummyResolvesClientCert); + + c.set_kx_hint(name.clone(), NamedGroup::X25519); + assert_eq!(None, c.kx_hint(&name)); + + #[cfg(feature = "tls12")] + { + use crate::msgs::persist::Tls12ClientSessionValue; + let SupportedCipherSuite::Tls12(tls12_suite) = + cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + else { + unreachable!() + }; + + c.set_tls12_session( + name.clone(), + Tls12ClientSessionValue::new( + tls12_suite, + SessionId::empty(), + Arc::new(PayloadU16::empty()), + &[], + CertificateChain::default(), + &server_cert_verifier, + &resolves_client_cert, + now, + 0, + true, + ), + ); + assert!(c.tls12_session(&name).is_none()); + c.remove_tls12_session(&name); + } + + let SupportedCipherSuite::Tls13(tls13_suite) = cipher_suite::TLS13_AES_256_GCM_SHA384 + else { + unreachable!(); + }; + c.insert_tls13_ticket( + name.clone(), + Tls13ClientSessionValue::new( + tls13_suite, + Arc::new(PayloadU16::empty()), + &[], + CertificateChain::default(), + &server_cert_verifier, + &resolves_client_cert, + now, + 0, + 0, + 0, + ), + ); + assert!(c.take_tls13_ticket(&name).is_none()); + } + + #[derive(Debug)] + struct DummyServerCertVerifier; + + impl ServerCertVerifier for DummyServerCertVerifier { + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp_response: &[u8], + _now: UnixTime, + ) -> Result<ServerCertVerified, Error> { + unreachable!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, Error> { + unreachable!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, Error> { + unreachable!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { + unreachable!() + } + } + + #[derive(Debug)] + struct DummyResolvesClientCert; + + impl ResolvesClientCert for DummyResolvesClientCert { + #[cfg_attr(coverage_nightly, coverage(off))] + fn resolve( + &self, + _root_hint_subjects: &[&[u8]], + _sigschemes: &[SignatureScheme], + ) -> Option<Arc<sign::CertifiedKey>> { + unreachable!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn has_certs(&self) -> bool { + unreachable!() + } + } +} diff --git a/vendor/rustls/src/client/hs.rs b/vendor/rustls/src/client/hs.rs new file mode 100644 index 00000000..4669f33d --- /dev/null +++ b/vendor/rustls/src/client/hs.rs @@ -0,0 +1,1178 @@ +use alloc::borrow::ToOwned; +use alloc::boxed::Box; +use alloc::vec; +use alloc::vec::Vec; +use core::ops::Deref; + +use pki_types::ServerName; + +#[cfg(feature = "tls12")] +use super::tls12; +use super::{ResolvesClientCert, Tls12Resumption}; +use crate::SupportedCipherSuite; +#[cfg(feature = "logging")] +use crate::bs_debug; +use crate::check::inappropriate_handshake_message; +use crate::client::client_conn::ClientConnectionData; +use crate::client::common::ClientHelloDetails; +use crate::client::ech::EchState; +use crate::client::{ClientConfig, EchMode, EchStatus, tls13}; +use crate::common_state::{CommonState, HandshakeKind, KxState, State}; +use crate::conn::ConnectionRandoms; +use crate::crypto::{ActiveKeyExchange, KeyExchangeAlgorithm}; +use crate::enums::{ + AlertDescription, CertificateType, CipherSuite, ContentType, HandshakeType, ProtocolVersion, +}; +use crate::error::{Error, PeerIncompatible, PeerMisbehaved}; +use crate::hash_hs::HandshakeHashBuffer; +use crate::log::{debug, trace}; +use crate::msgs::base::Payload; +use crate::msgs::enums::{Compression, ExtensionType}; +use crate::msgs::handshake::{ + CertificateStatusRequest, ClientExtensions, ClientExtensionsInput, ClientHelloPayload, + ClientSessionTicket, EncryptedClientHello, HandshakeMessagePayload, HandshakePayload, + HelloRetryRequest, KeyShareEntry, ProtocolName, PskKeyExchangeModes, Random, ServerNamePayload, + SessionId, SupportedEcPointFormats, SupportedProtocolVersions, TransportParameters, +}; +use crate::msgs::message::{Message, MessagePayload}; +use crate::msgs::persist; +use crate::sync::Arc; +use crate::tls13::key_schedule::KeyScheduleEarly; +use crate::verify::ServerCertVerifier; + +pub(super) type NextState<'a> = Box<dyn State<ClientConnectionData> + 'a>; +pub(super) type NextStateOrError<'a> = Result<NextState<'a>, Error>; +pub(super) type ClientContext<'a> = crate::common_state::Context<'a, ClientConnectionData>; + +struct ExpectServerHello { + input: ClientHelloInput, + transcript_buffer: HandshakeHashBuffer, + // The key schedule for sending early data. + // + // If the server accepts the PSK used for early data then + // this is used to compute the rest of the key schedule. + // Otherwise, it is thrown away. + // + // If this is `None` then we do not support early data. + early_data_key_schedule: Option<KeyScheduleEarly>, + offered_key_share: Option<Box<dyn ActiveKeyExchange>>, + suite: Option<SupportedCipherSuite>, + ech_state: Option<EchState>, +} + +struct ExpectServerHelloOrHelloRetryRequest { + next: ExpectServerHello, + extra_exts: ClientExtensionsInput<'static>, +} + +pub(super) struct ClientHelloInput { + pub(super) config: Arc<ClientConfig>, + pub(super) resuming: Option<persist::Retrieved<ClientSessionValue>>, + pub(super) random: Random, + pub(super) sent_tls13_fake_ccs: bool, + pub(super) hello: ClientHelloDetails, + pub(super) session_id: SessionId, + pub(super) server_name: ServerName<'static>, + pub(super) prev_ech_ext: Option<EncryptedClientHello>, +} + +impl ClientHelloInput { + pub(super) fn new( + server_name: ServerName<'static>, + extra_exts: &ClientExtensionsInput<'_>, + cx: &mut ClientContext<'_>, + config: Arc<ClientConfig>, + ) -> Result<Self, Error> { + let mut resuming = ClientSessionValue::retrieve(&server_name, &config, cx); + let session_id = match &mut resuming { + Some(_resuming) => { + debug!("Resuming session"); + match &mut _resuming.value { + #[cfg(feature = "tls12")] + ClientSessionValue::Tls12(inner) => { + // If we have a ticket, we use the sessionid as a signal that + // we're doing an abbreviated handshake. See section 3.4 in + // RFC5077. + if !inner.ticket().0.is_empty() { + inner.session_id = SessionId::random(config.provider.secure_random)?; + } + Some(inner.session_id) + } + _ => None, + } + } + _ => { + debug!("Not resuming any session"); + None + } + }; + + // https://tools.ietf.org/html/rfc8446#appendix-D.4 + // https://tools.ietf.org/html/draft-ietf-quic-tls-34#section-8.4 + let session_id = match session_id { + Some(session_id) => session_id, + None if cx.common.is_quic() => SessionId::empty(), + None if !config.supports_version(ProtocolVersion::TLSv1_3) => SessionId::empty(), + None => SessionId::random(config.provider.secure_random)?, + }; + + let hello = ClientHelloDetails::new( + extra_exts + .protocols + .clone() + .unwrap_or_default(), + crate::rand::random_u16(config.provider.secure_random)?, + ); + + Ok(Self { + resuming, + random: Random::new(config.provider.secure_random)?, + sent_tls13_fake_ccs: false, + hello, + session_id, + server_name, + prev_ech_ext: None, + config, + }) + } + + pub(super) fn start_handshake( + self, + extra_exts: ClientExtensionsInput<'static>, + cx: &mut ClientContext<'_>, + ) -> NextStateOrError<'static> { + let mut transcript_buffer = HandshakeHashBuffer::new(); + if self + .config + .client_auth_cert_resolver + .has_certs() + { + transcript_buffer.set_client_auth_enabled(); + } + + let key_share = if self.config.needs_key_share() { + Some(tls13::initial_key_share( + &self.config, + &self.server_name, + &mut cx.common.kx_state, + )?) + } else { + None + }; + + let ech_state = match self.config.ech_mode.as_ref() { + Some(EchMode::Enable(ech_config)) => { + Some(ech_config.state(self.server_name.clone(), &self.config)?) + } + _ => None, + }; + + emit_client_hello_for_retry( + transcript_buffer, + None, + key_share, + extra_exts, + None, + self, + cx, + ech_state, + ) + } +} + +/// Emits the initial ClientHello or a ClientHello in response to +/// a HelloRetryRequest. +/// +/// `retryreq` and `suite` are `None` if this is the initial +/// ClientHello. +fn emit_client_hello_for_retry( + mut transcript_buffer: HandshakeHashBuffer, + retryreq: Option<&HelloRetryRequest>, + key_share: Option<Box<dyn ActiveKeyExchange>>, + extra_exts: ClientExtensionsInput<'static>, + suite: Option<SupportedCipherSuite>, + mut input: ClientHelloInput, + cx: &mut ClientContext<'_>, + mut ech_state: Option<EchState>, +) -> NextStateOrError<'static> { + let config = &input.config; + // Defense in depth: the ECH state should be None if ECH is disabled based on config + // builder semantics. + let forbids_tls12 = cx.common.is_quic() || ech_state.is_some(); + + let supported_versions = SupportedProtocolVersions { + tls12: config.supports_version(ProtocolVersion::TLSv1_2) && !forbids_tls12, + tls13: config.supports_version(ProtocolVersion::TLSv1_3), + }; + + // should be unreachable thanks to config builder + assert!(supported_versions.any(|_| true)); + + let mut exts = Box::new(ClientExtensions { + // offer groups which are usable for any offered version + named_groups: Some( + config + .provider + .kx_groups + .iter() + .filter(|skxg| supported_versions.any(|v| skxg.usable_for_version(v))) + .map(|skxg| skxg.name()) + .collect(), + ), + supported_versions: Some(supported_versions), + signature_schemes: Some( + config + .verifier + .supported_verify_schemes(), + ), + extended_master_secret_request: Some(()), + certificate_status_request: Some(CertificateStatusRequest::build_ocsp()), + protocols: extra_exts.protocols.clone(), + ..Default::default() + }); + + match extra_exts.transport_parameters.clone() { + Some(TransportParameters::Quic(v)) => exts.transport_parameters = Some(v), + Some(TransportParameters::QuicDraft(v)) => exts.transport_parameters_draft = Some(v), + None => {} + }; + + if supported_versions.tls13 { + if let Some(cas_extension) = config.verifier.root_hint_subjects() { + exts.certificate_authority_names = Some(cas_extension.to_owned()); + } + } + + // Send the ECPointFormat extension only if we are proposing ECDHE + if config + .provider + .kx_groups + .iter() + .any(|skxg| skxg.name().key_exchange_algorithm() == KeyExchangeAlgorithm::ECDHE) + { + exts.ec_point_formats = Some(SupportedEcPointFormats::default()); + } + + exts.server_name = match (ech_state.as_ref(), config.enable_sni) { + // If we have ECH state we have a "cover name" to send in the outer hello + // as the SNI domain name. This happens unconditionally so we ignore the + // `enable_sni` value. That will be used later to decide what to do for + // the protected inner hello's SNI. + (Some(ech_state), _) => Some(ServerNamePayload::from(&ech_state.outer_name)), + + // If we have no ECH state, and SNI is enabled, try to use the input server_name + // for the SNI domain name. + (None, true) => match &input.server_name { + ServerName::DnsName(dns_name) => Some(ServerNamePayload::from(dns_name)), + _ => None, + }, + + // If we have no ECH state, and SNI is not enabled, there's nothing to do. + (None, false) => None, + }; + + if let Some(key_share) = &key_share { + debug_assert!(supported_versions.tls13); + let mut shares = vec![KeyShareEntry::new(key_share.group(), key_share.pub_key())]; + + if !retryreq + .map(|rr| rr.key_share.is_some()) + .unwrap_or_default() + { + // Only for the initial client hello, or a HRR that does not specify a kx group, + // see if we can send a second KeyShare for "free". We only do this if the same + // algorithm is also supported separately by our provider for this version + // (`find_kx_group` looks that up). + if let Some((component_group, component_share)) = + key_share + .hybrid_component() + .filter(|(group, _)| { + config + .find_kx_group(*group, ProtocolVersion::TLSv1_3) + .is_some() + }) + { + shares.push(KeyShareEntry::new(component_group, component_share)); + } + } + + exts.key_shares = Some(shares); + } + + if let Some(cookie) = retryreq.and_then(|hrr| hrr.cookie.as_ref()) { + exts.cookie = Some(cookie.clone()); + } + + if supported_versions.tls13 { + // We could support PSK_KE here too. Such connections don't + // have forward secrecy, and are similar to TLS1.2 resumption. + exts.preshared_key_modes = Some(PskKeyExchangeModes { + psk: false, + psk_dhe: true, + }); + } + + input.hello.offered_cert_compression = + if supported_versions.tls13 && !config.cert_decompressors.is_empty() { + exts.certificate_compression_algorithms = Some( + config + .cert_decompressors + .iter() + .map(|dec| dec.algorithm()) + .collect(), + ); + true + } else { + false + }; + + if config + .client_auth_cert_resolver + .only_raw_public_keys() + { + exts.client_certificate_types = Some(vec![CertificateType::RawPublicKey]); + } + + if config + .verifier + .requires_raw_public_keys() + { + exts.server_certificate_types = Some(vec![CertificateType::RawPublicKey]); + } + + // If this is a second client hello we're constructing in response to an HRR, and + // we've rejected ECH or sent GREASE ECH, then we need to carry forward the + // exact same ECH extension we used in the first hello. + if matches!(cx.data.ech_status, EchStatus::Rejected | EchStatus::Grease) & retryreq.is_some() { + if let Some(prev_ech_ext) = input.prev_ech_ext.take() { + exts.encrypted_client_hello = Some(prev_ech_ext); + } + } + + // Do we have a SessionID or ticket cached for this host? + let tls13_session = prepare_resumption(&input.resuming, &mut exts, suite, cx, config); + + // Extensions MAY be randomized + // but they also need to keep the same order as the previous ClientHello + exts.order_seed = input.hello.extension_order_seed; + + let mut cipher_suites: Vec<_> = config + .provider + .cipher_suites + .iter() + .filter_map(|cs| match cs.usable_for_protocol(cx.common.protocol) { + true => Some(cs.suite()), + false => None, + }) + .collect(); + + if supported_versions.tls12 { + // We don't do renegotiation at all, in fact. + cipher_suites.push(CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV); + } + + let mut chp_payload = ClientHelloPayload { + client_version: ProtocolVersion::TLSv1_2, + random: input.random, + session_id: input.session_id, + cipher_suites, + compression_methods: vec![Compression::Null], + extensions: exts, + }; + + let ech_grease_ext = config + .ech_mode + .as_ref() + .and_then(|mode| match mode { + EchMode::Grease(cfg) => Some(cfg.grease_ext( + config.provider.secure_random, + input.server_name.clone(), + &chp_payload, + )), + _ => None, + }); + + match (cx.data.ech_status, &mut ech_state) { + // If we haven't offered ECH, or have offered ECH but got a non-rejecting HRR, then + // we need to replace the client hello payload with an ECH client hello payload. + (EchStatus::NotOffered | EchStatus::Offered, Some(ech_state)) => { + // Replace the client hello payload with an ECH client hello payload. + chp_payload = ech_state.ech_hello(chp_payload, retryreq, &tls13_session)?; + cx.data.ech_status = EchStatus::Offered; + // Store the ECH extension in case we need to carry it forward in a subsequent hello. + input.prev_ech_ext = chp_payload + .encrypted_client_hello + .clone(); + } + // If we haven't offered ECH, and have no ECH state, then consider whether to use GREASE + // ECH. + (EchStatus::NotOffered, None) => { + if let Some(grease_ext) = ech_grease_ext { + // Add the GREASE ECH extension. + let grease_ext = grease_ext?; + chp_payload.encrypted_client_hello = Some(grease_ext.clone()); + cx.data.ech_status = EchStatus::Grease; + // Store the GREASE ECH extension in case we need to carry it forward in a + // subsequent hello. + input.prev_ech_ext = Some(grease_ext); + } + } + _ => {} + } + + // Note what extensions we sent. + input.hello.sent_extensions = chp_payload.collect_used(); + + let mut chp = HandshakeMessagePayload(HandshakePayload::ClientHello(chp_payload)); + + let tls13_early_data_key_schedule = match (ech_state.as_mut(), tls13_session) { + // If we're performing ECH and resuming, then the PSK binder will have been dealt with + // separately, and we need to take the early_data_key_schedule computed for the inner hello. + (Some(ech_state), Some(tls13_session)) => ech_state + .early_data_key_schedule + .take() + .map(|schedule| (tls13_session.suite(), schedule)), + + // When we're not doing ECH and resuming, then the PSK binder need to be filled in as + // normal. + (_, Some(tls13_session)) => Some(( + tls13_session.suite(), + tls13::fill_in_psk_binder(&tls13_session, &transcript_buffer, &mut chp), + )), + + // No early key schedule in other cases. + _ => None, + }; + + let ch = Message { + version: match retryreq { + // <https://datatracker.ietf.org/doc/html/rfc8446#section-5.1>: + // "This value MUST be set to 0x0303 for all records generated + // by a TLS 1.3 implementation ..." + Some(_) => ProtocolVersion::TLSv1_2, + // "... other than an initial ClientHello (i.e., one not + // generated after a HelloRetryRequest), where it MAY also be + // 0x0301 for compatibility purposes" + // + // (retryreq == None means we're in the "initial ClientHello" case) + None => ProtocolVersion::TLSv1_0, + }, + payload: MessagePayload::handshake(chp), + }; + + if retryreq.is_some() { + // send dummy CCS to fool middleboxes prior + // to second client hello + tls13::emit_fake_ccs(&mut input.sent_tls13_fake_ccs, cx.common); + } + + trace!("Sending ClientHello {ch:#?}"); + + transcript_buffer.add_message(&ch); + cx.common.send_msg(ch, false); + + // Calculate the hash of ClientHello and use it to derive EarlyTrafficSecret + let early_data_key_schedule = + tls13_early_data_key_schedule.map(|(resuming_suite, schedule)| { + if !cx.data.early_data.is_enabled() { + return schedule; + } + + let (transcript_buffer, random) = match &ech_state { + // When using ECH the early data key schedule is derived based on the inner + // hello transcript and random. + Some(ech_state) => ( + &ech_state.inner_hello_transcript, + &ech_state.inner_hello_random.0, + ), + None => (&transcript_buffer, &input.random.0), + }; + + tls13::derive_early_traffic_secret( + &*config.key_log, + cx, + resuming_suite.common.hash_provider, + &schedule, + &mut input.sent_tls13_fake_ccs, + transcript_buffer, + random, + ); + schedule + }); + + let next = ExpectServerHello { + input, + transcript_buffer, + early_data_key_schedule, + offered_key_share: key_share, + suite, + ech_state, + }; + + Ok(if supported_versions.tls13 && retryreq.is_none() { + Box::new(ExpectServerHelloOrHelloRetryRequest { + next, + extra_exts: extra_exts.into_owned(), + }) + } else { + Box::new(next) + }) +} + +/// Prepares `exts` and `cx` with TLS 1.2 or TLS 1.3 session +/// resumption. +/// +/// - `suite` is `None` if this is the initial ClientHello, or +/// `Some` if we're retrying in response to +/// a HelloRetryRequest. +/// +/// This function will push onto `exts` to +/// +/// (a) request a new ticket if we don't have one, +/// (b) send our TLS 1.2 ticket after retrieving an 1.2 session, +/// (c) send a request for 1.3 early data if allowed and +/// (d) send a 1.3 preshared key if we have one. +/// +/// It returns the TLS 1.3 PSKs, if any, for further processing. +fn prepare_resumption<'a>( + resuming: &'a Option<persist::Retrieved<ClientSessionValue>>, + exts: &mut ClientExtensions<'_>, + suite: Option<SupportedCipherSuite>, + cx: &mut ClientContext<'_>, + config: &ClientConfig, +) -> Option<persist::Retrieved<&'a persist::Tls13ClientSessionValue>> { + // Check whether we're resuming with a non-empty ticket. + let resuming = match resuming { + Some(resuming) if !resuming.ticket().is_empty() => resuming, + _ => { + if config.supports_version(ProtocolVersion::TLSv1_2) + && config.resumption.tls12_resumption == Tls12Resumption::SessionIdOrTickets + { + // If we don't have a ticket, request one. + exts.session_ticket = Some(ClientSessionTicket::Request); + } + return None; + } + }; + + let Some(tls13) = resuming.map(|csv| csv.tls13()) else { + // TLS 1.2; send the ticket if we have support this protocol version + if config.supports_version(ProtocolVersion::TLSv1_2) + && config.resumption.tls12_resumption == Tls12Resumption::SessionIdOrTickets + { + exts.session_ticket = Some(ClientSessionTicket::Offer(Payload::new(resuming.ticket()))); + } + return None; // TLS 1.2, so nothing to return here + }; + + if !config.supports_version(ProtocolVersion::TLSv1_3) { + return None; + } + + // If the server selected TLS 1.2, we can't resume. + let suite = match suite { + Some(SupportedCipherSuite::Tls13(suite)) => Some(suite), + #[cfg(feature = "tls12")] + Some(SupportedCipherSuite::Tls12(_)) => return None, + None => None, + }; + + // If the selected cipher suite can't select from the session's, we can't resume. + if let Some(suite) = suite { + suite.can_resume_from(tls13.suite())?; + } + + tls13::prepare_resumption(config, cx, &tls13, exts, suite.is_some()); + Some(tls13) +} + +pub(super) fn process_alpn_protocol( + common: &mut CommonState, + offered_protocols: &[ProtocolName], + selected: Option<&ProtocolName>, +) -> Result<(), Error> { + common.alpn_protocol = selected.map(ToOwned::to_owned); + + if let Some(alpn_protocol) = &common.alpn_protocol { + if !offered_protocols.contains(alpn_protocol) { + return Err(common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::SelectedUnofferedApplicationProtocol, + )); + } + } + + // RFC 9001 says: "While ALPN only specifies that servers use this alert, QUIC clients MUST + // use error 0x0178 to terminate a connection when ALPN negotiation fails." We judge that + // the user intended to use ALPN (rather than some out-of-band protocol negotiation + // mechanism) if and only if any ALPN protocols were configured. This defends against badly-behaved + // servers which accept a connection that requires an application-layer protocol they do not + // understand. + if common.is_quic() && common.alpn_protocol.is_none() && !offered_protocols.is_empty() { + return Err(common.send_fatal_alert( + AlertDescription::NoApplicationProtocol, + Error::NoApplicationProtocol, + )); + } + + debug!( + "ALPN protocol is {:?}", + common + .alpn_protocol + .as_ref() + .map(|v| bs_debug::BsDebug(v.as_ref())) + ); + Ok(()) +} + +pub(super) fn process_server_cert_type_extension( + common: &mut CommonState, + config: &ClientConfig, + server_cert_extension: Option<&CertificateType>, +) -> Result<Option<(ExtensionType, CertificateType)>, Error> { + process_cert_type_extension( + common, + config + .verifier + .requires_raw_public_keys(), + server_cert_extension.copied(), + ExtensionType::ServerCertificateType, + ) +} + +pub(super) fn process_client_cert_type_extension( + common: &mut CommonState, + config: &ClientConfig, + client_cert_extension: Option<&CertificateType>, +) -> Result<Option<(ExtensionType, CertificateType)>, Error> { + process_cert_type_extension( + common, + config + .client_auth_cert_resolver + .only_raw_public_keys(), + client_cert_extension.copied(), + ExtensionType::ClientCertificateType, + ) +} + +impl State<ClientConnectionData> for ExpectServerHello { + fn handle<'m>( + mut self: Box<Self>, + cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> NextStateOrError<'m> + where + Self: 'm, + { + let server_hello = + require_handshake_msg!(m, HandshakeType::ServerHello, HandshakePayload::ServerHello)?; + trace!("We got ServerHello {server_hello:#?}"); + + use crate::ProtocolVersion::{TLSv1_2, TLSv1_3}; + let config = &self.input.config; + let tls13_supported = config.supports_version(TLSv1_3); + + let server_version = if server_hello.legacy_version == TLSv1_2 { + server_hello + .selected_version + .unwrap_or(server_hello.legacy_version) + } else { + server_hello.legacy_version + }; + + let version = match server_version { + TLSv1_3 if tls13_supported => TLSv1_3, + TLSv1_2 if config.supports_version(TLSv1_2) => { + if cx.data.early_data.is_enabled() && cx.common.early_traffic { + // The client must fail with a dedicated error code if the server + // responds with TLS 1.2 when offering 0-RTT. + return Err(PeerMisbehaved::OfferedEarlyDataWithOldProtocolVersion.into()); + } + + if server_hello.selected_version.is_some() { + return Err({ + cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::SelectedTls12UsingTls13VersionExtension, + ) + }); + } + + TLSv1_2 + } + _ => { + let reason = match server_version { + TLSv1_2 | TLSv1_3 => PeerIncompatible::ServerTlsVersionIsDisabledByOurConfig, + _ => PeerIncompatible::ServerDoesNotSupportTls12Or13, + }; + return Err(cx + .common + .send_fatal_alert(AlertDescription::ProtocolVersion, reason)); + } + }; + + if server_hello.compression_method != Compression::Null { + return Err({ + cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::SelectedUnofferedCompression, + ) + }); + } + + let allowed_unsolicited = [ExtensionType::RenegotiationInfo]; + if self + .input + .hello + .server_sent_unsolicited_extensions(server_hello, &allowed_unsolicited) + { + return Err(cx.common.send_fatal_alert( + AlertDescription::UnsupportedExtension, + PeerMisbehaved::UnsolicitedServerHelloExtension, + )); + } + + cx.common.negotiated_version = Some(version); + + // Extract ALPN protocol + if !cx.common.is_tls13() { + process_alpn_protocol( + cx.common, + &self.input.hello.alpn_protocols, + server_hello + .selected_protocol + .as_ref() + .map(|s| s.as_ref()), + )?; + } + + // If ECPointFormats extension is supplied by the server, it must contain + // Uncompressed. But it's allowed to be omitted. + if let Some(point_fmts) = &server_hello.ec_point_formats { + if !point_fmts.uncompressed { + return Err(cx.common.send_fatal_alert( + AlertDescription::HandshakeFailure, + PeerMisbehaved::ServerHelloMustOfferUncompressedEcPoints, + )); + } + } + + let suite = config + .find_cipher_suite(server_hello.cipher_suite) + .ok_or_else(|| { + cx.common.send_fatal_alert( + AlertDescription::HandshakeFailure, + PeerMisbehaved::SelectedUnofferedCipherSuite, + ) + })?; + + if version != suite.version().version { + return Err({ + cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::SelectedUnusableCipherSuiteForVersion, + ) + }); + } + + match self.suite { + Some(prev_suite) if prev_suite != suite => { + return Err({ + cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::SelectedDifferentCipherSuiteAfterRetry, + ) + }); + } + _ => { + debug!("Using ciphersuite {suite:?}"); + self.suite = Some(suite); + cx.common.suite = Some(suite); + } + } + + // Start our handshake hash, and input the server-hello. + let mut transcript = self + .transcript_buffer + .start_hash(suite.hash_provider()); + transcript.add_message(&m); + + let randoms = ConnectionRandoms::new(self.input.random, server_hello.random); + // For TLS1.3, start message encryption using + // handshake_traffic_secret. + match suite { + SupportedCipherSuite::Tls13(suite) => { + tls13::handle_server_hello( + cx, + server_hello, + randoms, + suite, + transcript, + self.early_data_key_schedule, + // We always send a key share when TLS 1.3 is enabled. + self.offered_key_share.unwrap(), + &m, + self.ech_state, + self.input, + ) + } + #[cfg(feature = "tls12")] + SupportedCipherSuite::Tls12(suite) => tls12::CompleteServerHelloHandling { + randoms, + transcript, + input: self.input, + } + .handle_server_hello(cx, suite, server_hello, tls13_supported), + } + } + + fn into_owned(self: Box<Self>) -> NextState<'static> { + self + } +} + +impl ExpectServerHelloOrHelloRetryRequest { + fn into_expect_server_hello(self) -> NextState<'static> { + Box::new(self.next) + } + + fn handle_hello_retry_request( + mut self, + cx: &mut ClientContext<'_>, + m: Message<'_>, + ) -> NextStateOrError<'static> { + let hrr = require_handshake_msg!( + m, + HandshakeType::HelloRetryRequest, + HandshakePayload::HelloRetryRequest + )?; + trace!("Got HRR {hrr:?}"); + + cx.common.check_aligned_handshake()?; + + // We always send a key share when TLS 1.3 is enabled. + let offered_key_share = self.next.offered_key_share.unwrap(); + + // A retry request is illegal if it contains no cookie and asks for + // retry of a group we already sent. + let config = &self.next.input.config; + + if let (None, Some(req_group)) = (&hrr.cookie, hrr.key_share) { + let offered_hybrid = offered_key_share + .hybrid_component() + .and_then(|(group_name, _)| { + config.find_kx_group(group_name, ProtocolVersion::TLSv1_3) + }) + .map(|skxg| skxg.name()); + + if req_group == offered_key_share.group() || Some(req_group) == offered_hybrid { + return Err({ + cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::IllegalHelloRetryRequestWithOfferedGroup, + ) + }); + } + } + + // Or has an empty cookie. + if let Some(cookie) = &hrr.cookie { + if cookie.0.is_empty() { + return Err({ + cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::IllegalHelloRetryRequestWithEmptyCookie, + ) + }); + } + } + + // Or asks us to change nothing. + if hrr.cookie.is_none() && hrr.key_share.is_none() { + return Err({ + cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::IllegalHelloRetryRequestWithNoChanges, + ) + }); + } + + // Or does not echo the session_id from our ClientHello: + // + // > the HelloRetryRequest has the same format as a ServerHello message, + // > and the legacy_version, legacy_session_id_echo, cipher_suite, and + // > legacy_compression_method fields have the same meaning + // <https://www.rfc-editor.org/rfc/rfc8446#section-4.1.4> + // + // and + // + // > A client which receives a legacy_session_id_echo field that does not + // > match what it sent in the ClientHello MUST abort the handshake with an + // > "illegal_parameter" alert. + // <https://www.rfc-editor.org/rfc/rfc8446#section-4.1.3> + if hrr.session_id != self.next.input.session_id { + return Err({ + cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::IllegalHelloRetryRequestWithWrongSessionId, + ) + }); + } + + // Or asks us to talk a protocol we didn't offer, or doesn't support HRR at all. + match hrr.supported_versions { + Some(ProtocolVersion::TLSv1_3) => { + cx.common.negotiated_version = Some(ProtocolVersion::TLSv1_3); + } + _ => { + return Err({ + cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::IllegalHelloRetryRequestWithUnsupportedVersion, + ) + }); + } + } + + // Or asks us to use a ciphersuite we didn't offer. + let Some(cs) = config.find_cipher_suite(hrr.cipher_suite) else { + return Err({ + cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::IllegalHelloRetryRequestWithUnofferedCipherSuite, + ) + }); + }; + + // Or offers ECH related extensions when we didn't offer ECH. + if cx.data.ech_status == EchStatus::NotOffered && hrr.encrypted_client_hello.is_some() { + return Err({ + cx.common.send_fatal_alert( + AlertDescription::UnsupportedExtension, + PeerMisbehaved::IllegalHelloRetryRequestWithInvalidEch, + ) + }); + } + + // HRR selects the ciphersuite. + cx.common.suite = Some(cs); + cx.common.handshake_kind = Some(HandshakeKind::FullWithHelloRetryRequest); + + // If we offered ECH, we need to confirm that the server accepted it. + match (self.next.ech_state.as_ref(), cs.tls13()) { + (Some(ech_state), Some(tls13_cs)) => { + if !ech_state.confirm_hrr_acceptance(hrr, tls13_cs, cx.common)? { + // If the server did not confirm, then note the new ECH status but + // continue the handshake. We will abort with an ECH required error + // at the end. + cx.data.ech_status = EchStatus::Rejected; + } + } + (Some(_), None) => { + unreachable!("ECH state should only be set when TLS 1.3 was negotiated") + } + _ => {} + }; + + // This is the draft19 change where the transcript became a tree + let transcript = self + .next + .transcript_buffer + .start_hash(cs.hash_provider()); + let mut transcript_buffer = transcript.into_hrr_buffer(); + transcript_buffer.add_message(&m); + + // If we offered ECH and the server accepted, we also need to update the separate + // ECH transcript with the hello retry request message. + if let Some(ech_state) = self.next.ech_state.as_mut() { + ech_state.transcript_hrr_update(cs.hash_provider(), &m); + } + + // Early data is not allowed after HelloRetryrequest + if cx.data.early_data.is_enabled() { + cx.data.early_data.rejected(); + } + + let key_share = match hrr.key_share { + Some(group) if group != offered_key_share.group() => { + let Some(skxg) = config.find_kx_group(group, ProtocolVersion::TLSv1_3) else { + return Err(cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::IllegalHelloRetryRequestWithUnofferedNamedGroup, + )); + }; + + cx.common.kx_state = KxState::Start(skxg); + skxg.start()? + } + _ => offered_key_share, + }; + + emit_client_hello_for_retry( + transcript_buffer, + Some(hrr), + Some(key_share), + self.extra_exts, + Some(cs), + self.next.input, + cx, + self.next.ech_state, + ) + } +} + +impl State<ClientConnectionData> for ExpectServerHelloOrHelloRetryRequest { + fn handle<'m>( + self: Box<Self>, + cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> NextStateOrError<'m> + where + Self: 'm, + { + match m.payload { + MessagePayload::Handshake { + parsed: HandshakeMessagePayload(HandshakePayload::ServerHello(..)), + .. + } => self + .into_expect_server_hello() + .handle(cx, m), + MessagePayload::Handshake { + parsed: HandshakeMessagePayload(HandshakePayload::HelloRetryRequest(..)), + .. + } => self.handle_hello_retry_request(cx, m), + payload => Err(inappropriate_handshake_message( + &payload, + &[ContentType::Handshake], + &[HandshakeType::ServerHello, HandshakeType::HelloRetryRequest], + )), + } + } + + fn into_owned(self: Box<Self>) -> NextState<'static> { + self + } +} + +fn process_cert_type_extension( + common: &mut CommonState, + client_expects: bool, + server_negotiated: Option<CertificateType>, + extension_type: ExtensionType, +) -> Result<Option<(ExtensionType, CertificateType)>, Error> { + match (client_expects, server_negotiated) { + (true, Some(CertificateType::RawPublicKey)) => { + Ok(Some((extension_type, CertificateType::RawPublicKey))) + } + (true, _) => Err(common.send_fatal_alert( + AlertDescription::HandshakeFailure, + Error::PeerIncompatible(PeerIncompatible::IncorrectCertificateTypeExtension), + )), + (_, Some(CertificateType::RawPublicKey)) => { + unreachable!("Caught by `PeerMisbehaved::UnsolicitedEncryptedExtension`") + } + (_, _) => Ok(None), + } +} + +pub(super) enum ClientSessionValue { + Tls13(persist::Tls13ClientSessionValue), + #[cfg(feature = "tls12")] + Tls12(persist::Tls12ClientSessionValue), +} + +impl ClientSessionValue { + fn retrieve( + server_name: &ServerName<'static>, + config: &ClientConfig, + cx: &mut ClientContext<'_>, + ) -> Option<persist::Retrieved<Self>> { + let found = config + .resumption + .store + .take_tls13_ticket(server_name) + .map(ClientSessionValue::Tls13) + .or_else(|| { + #[cfg(feature = "tls12")] + { + config + .resumption + .store + .tls12_session(server_name) + .map(ClientSessionValue::Tls12) + } + + #[cfg(not(feature = "tls12"))] + None + }) + .and_then(|resuming| { + resuming.compatible_config(&config.verifier, &config.client_auth_cert_resolver) + }) + .and_then(|resuming| { + 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, + } + }) + .or_else(|| { + debug!("No cached session for {server_name:?}"); + None + }); + + if let Some(resuming) = &found { + if cx.common.is_quic() { + cx.common.quic.params = resuming + .tls13() + .map(|v| v.quic_params()); + } + } + + found + } + + fn common(&self) -> &persist::ClientSessionCommon { + match self { + Self::Tls13(inner) => &inner.common, + #[cfg(feature = "tls12")] + Self::Tls12(inner) => &inner.common, + } + } + + fn tls13(&self) -> Option<&persist::Tls13ClientSessionValue> { + match self { + Self::Tls13(v) => Some(v), + #[cfg(feature = "tls12")] + Self::Tls12(_) => None, + } + } + + fn compatible_config( + self, + server_cert_verifier: &Arc<dyn ServerCertVerifier>, + client_creds: &Arc<dyn ResolvesClientCert>, + ) -> Option<Self> { + match &self { + Self::Tls13(v) => v + .compatible_config(server_cert_verifier, client_creds) + .then_some(self), + #[cfg(feature = "tls12")] + Self::Tls12(v) => v + .compatible_config(server_cert_verifier, client_creds) + .then_some(self), + } + } +} + +impl Deref for ClientSessionValue { + type Target = persist::ClientSessionCommon; + + fn deref(&self) -> &Self::Target { + self.common() + } +} diff --git a/vendor/rustls/src/client/test.rs b/vendor/rustls/src/client/test.rs new file mode 100644 index 00000000..f4ea9580 --- /dev/null +++ b/vendor/rustls/src/client/test.rs @@ -0,0 +1,712 @@ +#![cfg(any(feature = "ring", feature = "aws_lc_rs"))] +use core::sync::atomic::{AtomicBool, Ordering}; +use std::prelude::v1::*; +use std::vec; + +use pki_types::{CertificateDer, ServerName}; + +use crate::client::{ClientConfig, ClientConnection, Resumption, Tls12Resumption}; +use crate::crypto::CryptoProvider; +use crate::enums::{CipherSuite, ProtocolVersion, SignatureScheme}; +use crate::msgs::base::PayloadU16; +use crate::msgs::codec::Reader; +use crate::msgs::enums::{Compression, NamedGroup}; +use crate::msgs::handshake::{ + ClientHelloPayload, HandshakeMessagePayload, HandshakePayload, HelloRetryRequest, Random, + ServerHelloPayload, SessionId, +}; +use crate::msgs::message::{Message, MessagePayload, OutboundOpaqueMessage}; +use crate::sync::Arc; +use crate::{Error, PeerIncompatible, PeerMisbehaved, RootCertStore}; + +#[macro_rules_attribute::apply(test_for_each_provider)] +mod tests { + use std::sync::OnceLock; + + use super::super::*; + use crate::client::AlwaysResolvesClientRawPublicKeys; + use crate::crypto::cipher::MessageEncrypter; + use crate::crypto::tls13::OkmBlock; + use crate::enums::CertificateType; + use crate::msgs::base::PayloadU8; + use crate::msgs::enums::ECCurveType; + use crate::msgs::handshake::{ + CertificateChain, EcParameters, HelloRetryRequestExtensions, KeyShareEntry, + ServerEcdhParams, ServerExtensions, ServerKeyExchange, ServerKeyExchangeParams, + ServerKeyExchangePayload, + }; + use crate::msgs::message::PlainMessage; + use crate::pki_types::pem::PemObject; + use crate::pki_types::{PrivateKeyDer, UnixTime}; + use crate::sign::CertifiedKey; + use crate::tls13::key_schedule::{derive_traffic_iv, derive_traffic_key}; + use crate::verify::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; + use crate::{DigitallySignedStruct, DistinguishedName, KeyLog, version}; + + /// Tests that session_ticket(35) extension + /// is not sent if the client does not support TLS 1.2. + #[test] + fn test_no_session_ticket_request_on_tls_1_3() { + let mut config = + ClientConfig::builder_with_provider(super::provider::default_provider().into()) + .with_protocol_versions(&[&version::TLS13]) + .unwrap() + .with_root_certificates(roots()) + .with_no_client_auth(); + config.resumption = Resumption::in_memory_sessions(128) + .tls12_resumption(Tls12Resumption::SessionIdOrTickets); + let ch = client_hello_sent_for_config(config).unwrap(); + assert!(ch.extensions.session_ticket.is_none()); + } + + #[test] + fn test_no_renegotiation_scsv_on_tls_1_3() { + let ch = client_hello_sent_for_config( + ClientConfig::builder_with_provider(super::provider::default_provider().into()) + .with_protocol_versions(&[&version::TLS13]) + .unwrap() + .with_root_certificates(roots()) + .with_no_client_auth(), + ) + .unwrap(); + assert!( + !ch.cipher_suites + .contains(&CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV) + ); + } + + #[test] + fn test_client_does_not_offer_sha1() { + for version in crate::ALL_VERSIONS { + let config = + ClientConfig::builder_with_provider(super::provider::default_provider().into()) + .with_protocol_versions(&[version]) + .unwrap() + .with_root_certificates(roots()) + .with_no_client_auth(); + let ch = client_hello_sent_for_config(config).unwrap(); + assert!( + !ch.extensions + .signature_schemes + .as_ref() + .unwrap() + .contains(&SignatureScheme::RSA_PKCS1_SHA1), + "sha1 unexpectedly offered" + ); + } + } + + #[test] + fn test_client_rejects_hrr_with_varied_session_id() { + let config = + ClientConfig::builder_with_provider(super::provider::default_provider().into()) + .with_safe_default_protocol_versions() + .unwrap() + .with_root_certificates(roots()) + .with_no_client_auth(); + let mut conn = + ClientConnection::new(config.into(), ServerName::try_from("localhost").unwrap()) + .unwrap(); + let mut sent = Vec::new(); + conn.write_tls(&mut sent).unwrap(); + + // server replies with HRR, but does not echo `session_id` as required. + let hrr = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::HelloRetryRequest(HelloRetryRequest { + cipher_suite: CipherSuite::TLS13_AES_128_GCM_SHA256, + legacy_version: ProtocolVersion::TLSv1_2, + session_id: SessionId::empty(), + extensions: HelloRetryRequestExtensions { + cookie: Some(PayloadU16::new(vec![1, 2, 3, 4])), + ..HelloRetryRequestExtensions::default() + }, + }), + )), + }; + + conn.read_tls(&mut hrr.into_wire_bytes().as_slice()) + .unwrap(); + assert_eq!( + conn.process_new_packets().unwrap_err(), + PeerMisbehaved::IllegalHelloRetryRequestWithWrongSessionId.into() + ); + } + + #[cfg(feature = "tls12")] + #[test] + fn test_client_rejects_no_extended_master_secret_extension_when_require_ems_or_fips() { + let mut config = + ClientConfig::builder_with_provider(super::provider::default_provider().into()) + .with_safe_default_protocol_versions() + .unwrap() + .with_root_certificates(roots()) + .with_no_client_auth(); + if config.provider.fips() { + assert!(config.require_ems); + } else { + config.require_ems = true; + } + + let config = Arc::new(config); + let mut conn = + ClientConnection::new(config.clone(), ServerName::try_from("localhost").unwrap()) + .unwrap(); + let mut sent = Vec::new(); + conn.write_tls(&mut sent).unwrap(); + + let sh = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::ServerHello(ServerHelloPayload { + random: Random::new(config.provider.secure_random).unwrap(), + compression_method: Compression::Null, + cipher_suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + legacy_version: ProtocolVersion::TLSv1_2, + session_id: SessionId::empty(), + extensions: Box::new(ServerExtensions::default()), + }), + )), + }; + conn.read_tls(&mut sh.into_wire_bytes().as_slice()) + .unwrap(); + + assert_eq!( + conn.process_new_packets(), + Err(PeerIncompatible::ExtendedMasterSecretExtensionRequired.into()) + ); + } + + #[test] + fn cas_extension_in_client_hello_if_server_verifier_requests_it() { + let cas_sending_server_verifier = + ServerVerifierWithAuthorityNames(vec![DistinguishedName::from(b"hello".to_vec())]); + + for (protocol_version, cas_extension_expected) in + [(&version::TLS12, false), (&version::TLS13, true)] + { + let client_hello = client_hello_sent_for_config( + ClientConfig::builder_with_provider(super::provider::default_provider().into()) + .with_protocol_versions(&[protocol_version]) + .unwrap() + .dangerous() + .with_custom_certificate_verifier(Arc::new(cas_sending_server_verifier.clone())) + .with_no_client_auth(), + ) + .unwrap(); + assert_eq!( + client_hello + .extensions + .certificate_authority_names + .is_some(), + cas_extension_expected + ); + } + } + + /// Regression test for <https://github.com/seanmonstar/reqwest/issues/2191> + #[cfg(feature = "tls12")] + #[test] + fn test_client_with_custom_verifier_can_accept_ecdsa_sha1_signatures() { + let verifier = Arc::new(ExpectSha1EcdsaVerifier::default()); + let config = ClientConfig::builder_with_provider(x25519_provider().into()) + .with_safe_default_protocol_versions() + .unwrap() + .dangerous() + .with_custom_certificate_verifier(verifier.clone()) + .with_no_client_auth(); + + let mut conn = + ClientConnection::new(config.into(), ServerName::try_from("localhost").unwrap()) + .unwrap(); + let mut sent = Vec::new(); + conn.write_tls(&mut sent).unwrap(); + + let sh = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::ServerHello(ServerHelloPayload { + random: Random([0u8; 32]), + compression_method: Compression::Null, + cipher_suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + legacy_version: ProtocolVersion::TLSv1_2, + session_id: SessionId::empty(), + extensions: Box::new(ServerExtensions { + extended_master_secret_ack: Some(()), + ..ServerExtensions::default() + }), + }), + )), + }; + conn.read_tls(&mut sh.into_wire_bytes().as_slice()) + .unwrap(); + conn.process_new_packets().unwrap(); + + let cert = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::Certificate(CertificateChain(vec![CertificateDer::from( + &b"does not matter"[..], + )])), + )), + }; + conn.read_tls(&mut cert.into_wire_bytes().as_slice()) + .unwrap(); + conn.process_new_packets().unwrap(); + + let server_kx = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::ServerKeyExchange(ServerKeyExchangePayload::Known( + ServerKeyExchange { + dss: DigitallySignedStruct::new( + SignatureScheme::ECDSA_SHA1_Legacy, + b"also does not matter".to_vec(), + ), + params: ServerKeyExchangeParams::Ecdh(ServerEcdhParams { + curve_params: EcParameters { + curve_type: ECCurveType::NamedCurve, + named_group: NamedGroup::X25519, + }, + public: PayloadU8::new(vec![0xab; 32]), + }), + }, + )), + )), + }; + conn.read_tls(&mut server_kx.into_wire_bytes().as_slice()) + .unwrap(); + conn.process_new_packets().unwrap(); + + let server_done = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::ServerHelloDone, + )), + }; + conn.read_tls(&mut server_done.into_wire_bytes().as_slice()) + .unwrap(); + conn.process_new_packets().unwrap(); + + assert!( + verifier + .seen_sha1_signature + .load(Ordering::SeqCst) + ); + } + + #[derive(Debug, Default)] + struct ExpectSha1EcdsaVerifier { + seen_sha1_signature: AtomicBool, + } + + impl ServerCertVerifier for ExpectSha1EcdsaVerifier { + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp_response: &[u8], + _now: UnixTime, + ) -> Result<ServerCertVerified, Error> { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, Error> { + assert_eq!(dss.scheme, SignatureScheme::ECDSA_SHA1_Legacy); + self.seen_sha1_signature + .store(true, Ordering::SeqCst); + Ok(HandshakeSignatureValid::assertion()) + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, Error> { + todo!() + } + + fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { + vec![SignatureScheme::ECDSA_SHA1_Legacy] + } + } + + #[test] + fn test_client_requiring_rpk_rejects_server_that_only_offers_x509_id_by_omission() { + assert_eq!( + client_requiring_rpk_receives_server_ee(ServerExtensions::default()), + Err(PeerIncompatible::IncorrectCertificateTypeExtension.into()) + ); + } + + #[test] + fn test_client_requiring_rpk_rejects_server_that_only_offers_x509_id() { + assert_eq!( + client_requiring_rpk_receives_server_ee(ServerExtensions { + server_certificate_type: Some(CertificateType::X509), + ..ServerExtensions::default() + }), + Err(PeerIncompatible::IncorrectCertificateTypeExtension.into()) + ); + } + + #[test] + fn test_client_requiring_rpk_rejects_server_that_only_demands_x509_by_omission() { + assert_eq!( + client_requiring_rpk_receives_server_ee(ServerExtensions { + server_certificate_type: Some(CertificateType::RawPublicKey), + ..ServerExtensions::default() + }), + Err(PeerIncompatible::IncorrectCertificateTypeExtension.into()) + ); + } + + #[test] + fn test_client_requiring_rpk_rejects_server_that_only_demands_x509() { + assert_eq!( + client_requiring_rpk_receives_server_ee(ServerExtensions { + client_certificate_type: Some(CertificateType::X509), + server_certificate_type: Some(CertificateType::RawPublicKey), + ..ServerExtensions::default() + }), + Err(PeerIncompatible::IncorrectCertificateTypeExtension.into()) + ); + } + + #[test] + fn test_client_requiring_rpk_accepts_rpk_server() { + assert_eq!( + client_requiring_rpk_receives_server_ee(ServerExtensions { + client_certificate_type: Some(CertificateType::RawPublicKey), + server_certificate_type: Some(CertificateType::RawPublicKey), + ..ServerExtensions::default() + }), + Ok(()) + ); + } + + fn client_requiring_rpk_receives_server_ee( + encrypted_extensions: ServerExtensions<'_>, + ) -> Result<(), Error> { + let fake_server_crypto = Arc::new(FakeServerCrypto::new()); + let mut conn = ClientConnection::new( + client_config_for_rpk(fake_server_crypto.clone()).into(), + ServerName::try_from("localhost").unwrap(), + ) + .unwrap(); + let mut sent = Vec::new(); + conn.write_tls(&mut sent).unwrap(); + + let sh = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::ServerHello(ServerHelloPayload { + random: Random([0; 32]), + compression_method: Compression::Null, + cipher_suite: CipherSuite::TLS13_AES_128_GCM_SHA256, + legacy_version: ProtocolVersion::TLSv1_3, + session_id: SessionId::empty(), + extensions: Box::new(ServerExtensions { + key_share: Some(KeyShareEntry { + group: NamedGroup::X25519, + payload: PayloadU16::new(vec![0xaa; 32]), + }), + ..ServerExtensions::default() + }), + }), + )), + }; + conn.read_tls(&mut sh.into_wire_bytes().as_slice()) + .unwrap(); + conn.process_new_packets().unwrap(); + + let ee = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::EncryptedExtensions(Box::new(encrypted_extensions)), + )), + }; + + let mut encrypter = fake_server_crypto.server_handshake_encrypter(); + let enc_ee = encrypter + .encrypt(PlainMessage::from(ee).borrow_outbound(), 0) + .unwrap(); + conn.read_tls(&mut enc_ee.encode().as_slice()) + .unwrap(); + conn.process_new_packets().map(|_| ()) + } + + fn client_config_for_rpk(key_log: Arc<dyn KeyLog>) -> ClientConfig { + let mut config = ClientConfig::builder_with_provider(x25519_provider().into()) + .with_protocol_versions(&[&version::TLS13]) + .unwrap() + .dangerous() + .with_custom_certificate_verifier(Arc::new(ServerVerifierRequiringRpk)) + .with_client_cert_resolver(Arc::new(AlwaysResolvesClientRawPublicKeys::new(Arc::new( + client_certified_key(), + )))); + config.key_log = key_log; + config + } + + fn client_certified_key() -> CertifiedKey { + let key = super::provider::default_provider() + .key_provider + .load_private_key(client_key()) + .unwrap(); + let public_key_as_cert = vec![CertificateDer::from( + key.public_key() + .unwrap() + .as_ref() + .to_vec(), + )]; + CertifiedKey::new(public_key_as_cert, key) + } + + fn client_key() -> PrivateKeyDer<'static> { + PrivateKeyDer::from_pem_reader( + &mut include_bytes!("../../../test-ca/rsa-2048/client.key").as_slice(), + ) + .unwrap() + } + + fn x25519_provider() -> CryptoProvider { + // ensures X25519 is offered irrespective of cfg(feature = "fips"), which eases + // creation of fake server messages. + CryptoProvider { + kx_groups: vec![super::provider::kx_group::X25519], + ..super::provider::default_provider() + } + } + + #[derive(Clone, Debug)] + struct ServerVerifierWithAuthorityNames(Vec<DistinguishedName>); + + impl ServerCertVerifier for ServerVerifierWithAuthorityNames { + fn root_hint_subjects(&self) -> Option<&[DistinguishedName]> { + Some(self.0.as_slice()) + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp_response: &[u8], + _now: UnixTime, + ) -> Result<ServerCertVerified, Error> { + unreachable!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, Error> { + unreachable!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, Error> { + unreachable!() + } + + fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { + vec![SignatureScheme::RSA_PKCS1_SHA1] + } + } + + #[derive(Debug)] + struct ServerVerifierRequiringRpk; + + impl ServerCertVerifier for ServerVerifierRequiringRpk { + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp_response: &[u8], + _now: UnixTime, + ) -> Result<ServerCertVerified, Error> { + todo!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, Error> { + todo!() + } + + #[cfg_attr(coverage_nightly, coverage(off))] + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, Error> { + todo!() + } + + fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { + vec![SignatureScheme::RSA_PKCS1_SHA1] + } + + fn requires_raw_public_keys(&self) -> bool { + true + } + } + + #[derive(Debug)] + struct FakeServerCrypto { + server_handshake_secret: OnceLock<Vec<u8>>, + } + + impl FakeServerCrypto { + fn new() -> Self { + Self { + server_handshake_secret: OnceLock::new(), + } + } + + fn server_handshake_encrypter(&self) -> Box<dyn MessageEncrypter> { + let cipher_suite = super::provider::cipher_suite::TLS13_AES_128_GCM_SHA256 + .tls13() + .unwrap(); + + let secret = self + .server_handshake_secret + .get() + .unwrap(); + + let expander = cipher_suite + .hkdf_provider + .expander_for_okm(&OkmBlock::new(secret)); + + // Derive Encrypter + let key = derive_traffic_key(expander.as_ref(), cipher_suite.aead_alg); + let iv = derive_traffic_iv(expander.as_ref()); + cipher_suite.aead_alg.encrypter(key, iv) + } + } + + impl KeyLog for FakeServerCrypto { + fn will_log(&self, _label: &str) -> bool { + true + } + + fn log(&self, label: &str, _client_random: &[u8], secret: &[u8]) { + if label == "SERVER_HANDSHAKE_TRAFFIC_SECRET" { + self.server_handshake_secret + .set(secret.to_vec()) + .unwrap(); + } + } + } +} + +// invalid with fips, as we can't offer X25519 separately +#[cfg(all( + feature = "aws-lc-rs", + feature = "prefer-post-quantum", + not(feature = "fips") +))] +#[test] +fn hybrid_kx_component_share_offered_if_supported_separately() { + let ch = client_hello_sent_for_config( + ClientConfig::builder_with_provider(crate::crypto::aws_lc_rs::default_provider().into()) + .with_safe_default_protocol_versions() + .unwrap() + .with_root_certificates(roots()) + .with_no_client_auth(), + ) + .unwrap(); + + let key_shares = ch + .extensions + .key_shares + .as_ref() + .unwrap(); + assert_eq!(key_shares.len(), 2); + assert_eq!(key_shares[0].group, NamedGroup::X25519MLKEM768); + assert_eq!(key_shares[1].group, NamedGroup::X25519); +} + +#[cfg(feature = "aws-lc-rs")] +#[test] +fn hybrid_kx_component_share_not_offered_unless_supported_separately() { + use crate::crypto::aws_lc_rs; + let provider = CryptoProvider { + kx_groups: vec![aws_lc_rs::kx_group::X25519MLKEM768], + ..aws_lc_rs::default_provider() + }; + let ch = client_hello_sent_for_config( + ClientConfig::builder_with_provider(provider.into()) + .with_safe_default_protocol_versions() + .unwrap() + .with_root_certificates(roots()) + .with_no_client_auth(), + ) + .unwrap(); + + let key_shares = ch + .extensions + .key_shares + .as_ref() + .unwrap(); + assert_eq!(key_shares.len(), 1); + assert_eq!(key_shares[0].group, NamedGroup::X25519MLKEM768); +} + +fn client_hello_sent_for_config(config: ClientConfig) -> Result<ClientHelloPayload, Error> { + let mut conn = + ClientConnection::new(config.into(), ServerName::try_from("localhost").unwrap())?; + let mut bytes = Vec::new(); + conn.write_tls(&mut bytes).unwrap(); + + let message = OutboundOpaqueMessage::read(&mut Reader::init(&bytes)) + .unwrap() + .into_plain_message(); + + match Message::try_from(message).unwrap() { + Message { + payload: + MessagePayload::Handshake { + parsed: HandshakeMessagePayload(HandshakePayload::ClientHello(ch)), + .. + }, + .. + } => Ok(ch), + other => panic!("unexpected message {other:?}"), + } +} + +fn roots() -> RootCertStore { + let mut r = RootCertStore::empty(); + r.add(CertificateDer::from_slice(include_bytes!( + "../../../test-ca/rsa-2048/ca.der" + ))) + .unwrap(); + r +} diff --git a/vendor/rustls/src/client/tls12.rs b/vendor/rustls/src/client/tls12.rs new file mode 100644 index 00000000..0fc5acee --- /dev/null +++ b/vendor/rustls/src/client/tls12.rs @@ -0,0 +1,1372 @@ +use alloc::borrow::ToOwned; +use alloc::boxed::Box; +use alloc::vec; +use alloc::vec::Vec; + +use pki_types::ServerName; +pub(super) use server_hello::CompleteServerHelloHandling; +use subtle::ConstantTimeEq; + +use super::client_conn::ClientConnectionData; +use super::hs::ClientContext; +use crate::ConnectionTrafficSecrets; +use crate::check::{inappropriate_handshake_message, inappropriate_message}; +use crate::client::common::{ClientAuthDetails, ServerCertDetails}; +use crate::client::{ClientConfig, hs}; +use crate::common_state::{CommonState, HandshakeKind, KxState, Side, State}; +use crate::conn::ConnectionRandoms; +use crate::conn::kernel::{Direction, KernelContext, KernelState}; +use crate::crypto::KeyExchangeAlgorithm; +use crate::enums::{AlertDescription, ContentType, HandshakeType, ProtocolVersion}; +use crate::error::{Error, InvalidMessage, PeerIncompatible, PeerMisbehaved}; +use crate::hash_hs::HandshakeHash; +use crate::log::{debug, trace, warn}; +use crate::msgs::base::{Payload, PayloadU8, PayloadU16}; +use crate::msgs::ccs::ChangeCipherSpecPayload; +use crate::msgs::handshake::{ + CertificateChain, ClientDhParams, ClientEcdhParams, ClientKeyExchangeParams, + HandshakeMessagePayload, HandshakePayload, NewSessionTicketPayload, + NewSessionTicketPayloadTls13, ServerKeyExchangeParams, SessionId, +}; +use crate::msgs::message::{Message, MessagePayload}; +use crate::msgs::persist; +use crate::sign::Signer; +use crate::suites::{PartiallyExtractedSecrets, SupportedCipherSuite}; +use crate::sync::Arc; +use crate::tls12::{self, ConnectionSecrets, Tls12CipherSuite}; +use crate::verify::{self, DigitallySignedStruct}; + +mod server_hello { + use super::*; + use crate::client::hs::{ClientHelloInput, ClientSessionValue}; + use crate::msgs::handshake::ServerHelloPayload; + + pub(in crate::client) struct CompleteServerHelloHandling { + pub(in crate::client) randoms: ConnectionRandoms, + pub(in crate::client) transcript: HandshakeHash, + pub(in crate::client) input: ClientHelloInput, + } + + impl CompleteServerHelloHandling { + pub(in crate::client) fn handle_server_hello( + mut self, + cx: &mut ClientContext<'_>, + suite: &'static Tls12CipherSuite, + server_hello: &ServerHelloPayload, + tls13_supported: bool, + ) -> hs::NextStateOrError<'static> { + self.randoms + .server + .clone_from_slice(&server_hello.random.0[..]); + + // Look for TLS1.3 downgrade signal in server random + // both the server random and TLS12_DOWNGRADE_SENTINEL are + // public values and don't require constant time comparison + let has_downgrade_marker = self.randoms.server[24..] == tls12::DOWNGRADE_SENTINEL; + if tls13_supported && has_downgrade_marker { + return Err({ + cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::AttemptedDowngradeToTls12WhenTls13IsSupported, + ) + }); + } + + // If we didn't have an input session to resume, and we sent a session ID, + // that implies we sent a TLS 1.3 legacy_session_id for compatibility purposes. + // In this instance since we're now continuing a TLS 1.2 handshake the server + // should not have echoed it back: it's a randomly generated session ID it couldn't + // have known. + if self.input.resuming.is_none() + && !self.input.session_id.is_empty() + && self.input.session_id == server_hello.session_id + { + return Err({ + cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::ServerEchoedCompatibilitySessionId, + ) + }); + } + + let ClientHelloInput { + config, + server_name, + .. + } = self.input; + + let resuming_session = self + .input + .resuming + .and_then(|resuming| match resuming.value { + ClientSessionValue::Tls12(inner) => Some(inner), + ClientSessionValue::Tls13(_) => None, + }); + + // Doing EMS? + let using_ems = server_hello + .extended_master_secret_ack + .is_some(); + if config.require_ems && !using_ems { + return Err({ + cx.common.send_fatal_alert( + AlertDescription::HandshakeFailure, + PeerIncompatible::ExtendedMasterSecretExtensionRequired, + ) + }); + } + + // Might the server send a ticket? + let must_issue_new_ticket = if server_hello + .session_ticket_ack + .is_some() + { + debug!("Server supports tickets"); + true + } else { + false + }; + + // Might the server send a CertificateStatus between Certificate and + // ServerKeyExchange? + let may_send_cert_status = server_hello + .certificate_status_request_ack + .is_some(); + if may_send_cert_status { + debug!("Server may staple OCSP response"); + } + + // See if we're successfully resuming. + if let Some(resuming) = resuming_session { + if resuming.session_id == server_hello.session_id { + debug!("Server agreed to resume"); + + // Is the server telling lies about the ciphersuite? + if resuming.suite() != suite { + return Err(PeerMisbehaved::ResumptionOfferedWithVariedCipherSuite.into()); + } + + // And about EMS support? + if resuming.extended_ms() != using_ems { + return Err(PeerMisbehaved::ResumptionOfferedWithVariedEms.into()); + } + + let secrets = + ConnectionSecrets::new_resume(self.randoms, suite, resuming.secret()); + config.key_log.log( + "CLIENT_RANDOM", + &secrets.randoms.client, + &secrets.master_secret, + ); + cx.common + .start_encryption_tls12(&secrets, Side::Client); + + // Since we're resuming, we verified the certificate and + // proof of possession in the prior session. + cx.common.peer_certificates = Some( + resuming + .server_cert_chain() + .clone() + .into_owned(), + ); + cx.common.handshake_kind = Some(HandshakeKind::Resumed); + let cert_verified = verify::ServerCertVerified::assertion(); + let sig_verified = verify::HandshakeSignatureValid::assertion(); + + return if must_issue_new_ticket { + Ok(Box::new(ExpectNewTicket { + config, + secrets, + resuming_session: Some(resuming), + session_id: server_hello.session_id, + server_name, + using_ems, + transcript: self.transcript, + resuming: true, + cert_verified, + sig_verified, + })) + } else { + Ok(Box::new(ExpectCcs { + config, + secrets, + resuming_session: Some(resuming), + session_id: server_hello.session_id, + server_name, + using_ems, + transcript: self.transcript, + ticket: None, + resuming: true, + cert_verified, + sig_verified, + })) + }; + } + } + + cx.common.handshake_kind = Some(HandshakeKind::Full); + Ok(Box::new(ExpectCertificate { + config, + resuming_session: None, + session_id: server_hello.session_id, + server_name, + randoms: self.randoms, + using_ems, + transcript: self.transcript, + suite, + may_send_cert_status, + must_issue_new_ticket, + })) + } + } +} + +struct ExpectCertificate { + config: Arc<ClientConfig>, + resuming_session: Option<persist::Tls12ClientSessionValue>, + session_id: SessionId, + server_name: ServerName<'static>, + randoms: ConnectionRandoms, + using_ems: bool, + transcript: HandshakeHash, + pub(super) suite: &'static Tls12CipherSuite, + may_send_cert_status: bool, + must_issue_new_ticket: bool, +} + +impl State<ClientConnectionData> for ExpectCertificate { + fn handle<'m>( + mut self: Box<Self>, + _cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + self.transcript.add_message(&m); + let server_cert_chain = require_handshake_msg_move!( + m, + HandshakeType::Certificate, + HandshakePayload::Certificate + )?; + + if self.may_send_cert_status { + Ok(Box::new(ExpectCertificateStatusOrServerKx { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert_chain, + must_issue_new_ticket: self.must_issue_new_ticket, + })) + } else { + let server_cert = ServerCertDetails::new(server_cert_chain, vec![]); + + Ok(Box::new(ExpectServerKx { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert, + must_issue_new_ticket: self.must_issue_new_ticket, + })) + } + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + self + } +} + +struct ExpectCertificateStatusOrServerKx<'m> { + config: Arc<ClientConfig>, + resuming_session: Option<persist::Tls12ClientSessionValue>, + session_id: SessionId, + server_name: ServerName<'static>, + randoms: ConnectionRandoms, + using_ems: bool, + transcript: HandshakeHash, + suite: &'static Tls12CipherSuite, + server_cert_chain: CertificateChain<'m>, + must_issue_new_ticket: bool, +} + +impl State<ClientConnectionData> for ExpectCertificateStatusOrServerKx<'_> { + fn handle<'m>( + self: Box<Self>, + cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + match m.payload { + MessagePayload::Handshake { + parsed: HandshakeMessagePayload(HandshakePayload::ServerKeyExchange(..)), + .. + } => Box::new(ExpectServerKx { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert: ServerCertDetails::new(self.server_cert_chain, vec![]), + must_issue_new_ticket: self.must_issue_new_ticket, + }) + .handle(cx, m), + MessagePayload::Handshake { + parsed: HandshakeMessagePayload(HandshakePayload::CertificateStatus(..)), + .. + } => Box::new(ExpectCertificateStatus { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert_chain: self.server_cert_chain, + must_issue_new_ticket: self.must_issue_new_ticket, + }) + .handle(cx, m), + payload => Err(inappropriate_handshake_message( + &payload, + &[ContentType::Handshake], + &[ + HandshakeType::ServerKeyExchange, + HandshakeType::CertificateStatus, + ], + )), + } + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + Box::new(ExpectCertificateStatusOrServerKx { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert_chain: self.server_cert_chain.into_owned(), + must_issue_new_ticket: self.must_issue_new_ticket, + }) + } +} + +struct ExpectCertificateStatus<'a> { + config: Arc<ClientConfig>, + resuming_session: Option<persist::Tls12ClientSessionValue>, + session_id: SessionId, + server_name: ServerName<'static>, + randoms: ConnectionRandoms, + using_ems: bool, + transcript: HandshakeHash, + suite: &'static Tls12CipherSuite, + server_cert_chain: CertificateChain<'a>, + must_issue_new_ticket: bool, +} + +impl State<ClientConnectionData> for ExpectCertificateStatus<'_> { + fn handle<'m>( + mut self: Box<Self>, + _cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + self.transcript.add_message(&m); + let server_cert_ocsp_response = require_handshake_msg_move!( + m, + HandshakeType::CertificateStatus, + HandshakePayload::CertificateStatus + )? + .into_inner(); + + trace!( + "Server stapled OCSP response is {:?}", + &server_cert_ocsp_response + ); + + let server_cert = ServerCertDetails::new(self.server_cert_chain, server_cert_ocsp_response); + + Ok(Box::new(ExpectServerKx { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert, + must_issue_new_ticket: self.must_issue_new_ticket, + })) + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + Box::new(ExpectCertificateStatus { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert_chain: self.server_cert_chain.into_owned(), + must_issue_new_ticket: self.must_issue_new_ticket, + }) + } +} + +struct ExpectServerKx<'a> { + config: Arc<ClientConfig>, + resuming_session: Option<persist::Tls12ClientSessionValue>, + session_id: SessionId, + server_name: ServerName<'static>, + randoms: ConnectionRandoms, + using_ems: bool, + transcript: HandshakeHash, + suite: &'static Tls12CipherSuite, + server_cert: ServerCertDetails<'a>, + must_issue_new_ticket: bool, +} + +impl State<ClientConnectionData> for ExpectServerKx<'_> { + fn handle<'m>( + mut self: Box<Self>, + cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + let opaque_kx = require_handshake_msg!( + m, + HandshakeType::ServerKeyExchange, + HandshakePayload::ServerKeyExchange + )?; + self.transcript.add_message(&m); + + let kx = opaque_kx + .unwrap_given_kxa(self.suite.kx) + .ok_or_else(|| { + cx.common.send_fatal_alert( + AlertDescription::DecodeError, + InvalidMessage::MissingKeyExchange, + ) + })?; + + // Save the signature and signed parameters for later verification. + let mut kx_params = Vec::new(); + kx.params.encode(&mut kx_params); + let server_kx = ServerKxDetails::new(kx_params, kx.dss); + + #[cfg_attr(not(feature = "logging"), allow(unused_variables))] + { + match &kx.params { + ServerKeyExchangeParams::Ecdh(ecdhe) => { + debug!("ECDHE curve is {:?}", ecdhe.curve_params) + } + ServerKeyExchangeParams::Dh(dhe) => { + debug!("DHE params are p = {:?}, g = {:?}", dhe.dh_p, dhe.dh_g) + } + } + } + + Ok(Box::new(ExpectServerDoneOrCertReq { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert: self.server_cert, + server_kx, + must_issue_new_ticket: self.must_issue_new_ticket, + })) + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + Box::new(ExpectServerKx { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert: self.server_cert.into_owned(), + must_issue_new_ticket: self.must_issue_new_ticket, + }) + } +} + +fn emit_certificate( + transcript: &mut HandshakeHash, + cert_chain: CertificateChain<'static>, + common: &mut CommonState, +) { + let cert = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::handshake(HandshakeMessagePayload(HandshakePayload::Certificate( + cert_chain, + ))), + }; + + transcript.add_message(&cert); + common.send_msg(cert, false); +} + +fn emit_client_kx( + transcript: &mut HandshakeHash, + kxa: KeyExchangeAlgorithm, + common: &mut CommonState, + pub_key: &[u8], +) { + let mut buf = Vec::new(); + match kxa { + KeyExchangeAlgorithm::ECDHE => ClientKeyExchangeParams::Ecdh(ClientEcdhParams { + public: PayloadU8::new(pub_key.to_vec()), + }), + KeyExchangeAlgorithm::DHE => ClientKeyExchangeParams::Dh(ClientDhParams { + public: PayloadU16::new(pub_key.to_vec()), + }), + } + .encode(&mut buf); + let pubkey = Payload::new(buf); + + let ckx = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::ClientKeyExchange(pubkey), + )), + }; + + transcript.add_message(&ckx); + common.send_msg(ckx, false); +} + +fn emit_certverify( + transcript: &mut HandshakeHash, + signer: &dyn Signer, + common: &mut CommonState, +) -> Result<(), Error> { + let message = transcript + .take_handshake_buf() + .ok_or_else(|| Error::General("Expected transcript".to_owned()))?; + + let scheme = signer.scheme(); + let sig = signer.sign(&message)?; + let body = DigitallySignedStruct::new(scheme, sig); + + let m = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::CertificateVerify(body), + )), + }; + + transcript.add_message(&m); + common.send_msg(m, false); + Ok(()) +} + +fn emit_ccs(common: &mut CommonState) { + let ccs = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::ChangeCipherSpec(ChangeCipherSpecPayload {}), + }; + + common.send_msg(ccs, false); +} + +fn emit_finished( + secrets: &ConnectionSecrets, + transcript: &mut HandshakeHash, + common: &mut CommonState, +) { + let vh = transcript.current_hash(); + let verify_data = secrets.client_verify_data(&vh); + let verify_data_payload = Payload::new(verify_data); + + let f = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::handshake(HandshakeMessagePayload(HandshakePayload::Finished( + verify_data_payload, + ))), + }; + + transcript.add_message(&f); + common.send_msg(f, true); +} + +struct ServerKxDetails { + kx_params: Vec<u8>, + kx_sig: DigitallySignedStruct, +} + +impl ServerKxDetails { + fn new(params: Vec<u8>, sig: DigitallySignedStruct) -> Self { + Self { + kx_params: params, + kx_sig: sig, + } + } +} + +// --- Either a CertificateRequest, or a ServerHelloDone. --- +// Existence of the CertificateRequest tells us the server is asking for +// client auth. Otherwise we go straight to ServerHelloDone. +struct ExpectServerDoneOrCertReq<'a> { + config: Arc<ClientConfig>, + resuming_session: Option<persist::Tls12ClientSessionValue>, + session_id: SessionId, + server_name: ServerName<'static>, + randoms: ConnectionRandoms, + using_ems: bool, + transcript: HandshakeHash, + suite: &'static Tls12CipherSuite, + server_cert: ServerCertDetails<'a>, + server_kx: ServerKxDetails, + must_issue_new_ticket: bool, +} + +impl State<ClientConnectionData> for ExpectServerDoneOrCertReq<'_> { + fn handle<'m>( + mut self: Box<Self>, + cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + if matches!( + m.payload, + MessagePayload::Handshake { + parsed: HandshakeMessagePayload(HandshakePayload::CertificateRequest(_)), + .. + } + ) { + Box::new(ExpectCertificateRequest { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert: self.server_cert, + server_kx: self.server_kx, + must_issue_new_ticket: self.must_issue_new_ticket, + }) + .handle(cx, m) + } else { + self.transcript.abandon_client_auth(); + + Box::new(ExpectServerDone { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert: self.server_cert, + server_kx: self.server_kx, + client_auth: None, + must_issue_new_ticket: self.must_issue_new_ticket, + }) + .handle(cx, m) + } + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + Box::new(ExpectServerDoneOrCertReq { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert: self.server_cert.into_owned(), + server_kx: self.server_kx, + must_issue_new_ticket: self.must_issue_new_ticket, + }) + } +} + +struct ExpectCertificateRequest<'a> { + config: Arc<ClientConfig>, + resuming_session: Option<persist::Tls12ClientSessionValue>, + session_id: SessionId, + server_name: ServerName<'static>, + randoms: ConnectionRandoms, + using_ems: bool, + transcript: HandshakeHash, + suite: &'static Tls12CipherSuite, + server_cert: ServerCertDetails<'a>, + server_kx: ServerKxDetails, + must_issue_new_ticket: bool, +} + +impl State<ClientConnectionData> for ExpectCertificateRequest<'_> { + fn handle<'m>( + mut self: Box<Self>, + _cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + let certreq = require_handshake_msg!( + m, + HandshakeType::CertificateRequest, + HandshakePayload::CertificateRequest + )?; + self.transcript.add_message(&m); + debug!("Got CertificateRequest {certreq:?}"); + + // The RFC jovially describes the design here as 'somewhat complicated' + // and 'somewhat underspecified'. So thanks for that. + // + // We ignore certreq.certtypes as a result, since the information it contains + // is entirely duplicated in certreq.sigschemes. + + const NO_CONTEXT: Option<Vec<u8>> = None; // TLS 1.2 doesn't use a context. + let no_compression = None; // or compression + let client_auth = ClientAuthDetails::resolve( + self.config + .client_auth_cert_resolver + .as_ref(), + Some(&certreq.canames), + &certreq.sigschemes, + NO_CONTEXT, + no_compression, + ); + + Ok(Box::new(ExpectServerDone { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert: self.server_cert, + server_kx: self.server_kx, + client_auth: Some(client_auth), + must_issue_new_ticket: self.must_issue_new_ticket, + })) + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + Box::new(ExpectCertificateRequest { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert: self.server_cert.into_owned(), + server_kx: self.server_kx, + must_issue_new_ticket: self.must_issue_new_ticket, + }) + } +} + +struct ExpectServerDone<'a> { + config: Arc<ClientConfig>, + resuming_session: Option<persist::Tls12ClientSessionValue>, + session_id: SessionId, + server_name: ServerName<'static>, + randoms: ConnectionRandoms, + using_ems: bool, + transcript: HandshakeHash, + suite: &'static Tls12CipherSuite, + server_cert: ServerCertDetails<'a>, + server_kx: ServerKxDetails, + client_auth: Option<ClientAuthDetails>, + must_issue_new_ticket: bool, +} + +impl State<ClientConnectionData> for ExpectServerDone<'_> { + fn handle<'m>( + self: Box<Self>, + cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + match m.payload { + MessagePayload::Handshake { + parsed: HandshakeMessagePayload(HandshakePayload::ServerHelloDone), + .. + } => {} + payload => { + return Err(inappropriate_handshake_message( + &payload, + &[ContentType::Handshake], + &[HandshakeType::ServerHelloDone], + )); + } + } + + let mut st = *self; + st.transcript.add_message(&m); + + cx.common.check_aligned_handshake()?; + + trace!("Server cert is {:?}", st.server_cert.cert_chain); + debug!("Server DNS name is {:?}", st.server_name); + + let suite = st.suite; + + // 1. Verify the cert chain. + // 2. Verify that the top certificate signed their kx. + // 3. If doing client auth, send our Certificate. + // 4. Complete the key exchange: + // a) generate our kx pair + // b) emit a ClientKeyExchange containing it + // c) if doing client auth, emit a CertificateVerify + // d) derive the shared keys + // e) emit a CCS + // f) use the derived keys to start encryption + // 5. emit a Finished, our first encrypted message under the new keys. + + // 1. + let (end_entity, intermediates) = st + .server_cert + .cert_chain + .split_first() + .ok_or(Error::NoCertificatesPresented)?; + + let now = st.config.current_time()?; + + let cert_verified = st + .config + .verifier + .verify_server_cert( + end_entity, + intermediates, + &st.server_name, + &st.server_cert.ocsp_response, + now, + ) + .map_err(|err| { + cx.common + .send_cert_verify_error_alert(err) + })?; + + // 2. + // Build up the contents of the signed message. + // It's ClientHello.random || ServerHello.random || ServerKeyExchange.params + let sig_verified = { + let mut message = Vec::new(); + message.extend_from_slice(&st.randoms.client); + message.extend_from_slice(&st.randoms.server); + message.extend_from_slice(&st.server_kx.kx_params); + + // Check the signature is compatible with the ciphersuite. + let sig = &st.server_kx.kx_sig; + if !SupportedCipherSuite::from(suite) + .usable_for_signature_algorithm(sig.scheme.algorithm()) + { + warn!( + "peer signed kx with wrong algorithm (got {:?} expect {:?})", + sig.scheme.algorithm(), + suite.sign + ); + return Err(PeerMisbehaved::SignedKxWithWrongAlgorithm.into()); + } + + st.config + .verifier + .verify_tls12_signature(&message, end_entity, sig) + .map_err(|err| { + cx.common + .send_cert_verify_error_alert(err) + })? + }; + cx.common.peer_certificates = Some(st.server_cert.cert_chain.into_owned()); + + // 3. + if let Some(client_auth) = &st.client_auth { + let certs = match client_auth { + ClientAuthDetails::Empty { .. } => CertificateChain::default(), + ClientAuthDetails::Verify { certkey, .. } => CertificateChain(certkey.cert.clone()), + }; + emit_certificate(&mut st.transcript, certs, cx.common); + } + + // 4a. + let kx_params = tls12::decode_kx_params::<ServerKeyExchangeParams>( + st.suite.kx, + cx.common, + &st.server_kx.kx_params, + )?; + let maybe_skxg = match &kx_params { + ServerKeyExchangeParams::Ecdh(ecdh) => st + .config + .find_kx_group(ecdh.curve_params.named_group, ProtocolVersion::TLSv1_2), + ServerKeyExchangeParams::Dh(dh) => { + let ffdhe_group = dh.as_ffdhe_group(); + + st.config + .provider + .kx_groups + .iter() + .find(|kxg| kxg.ffdhe_group() == Some(ffdhe_group)) + .copied() + } + }; + let Some(skxg) = maybe_skxg else { + return Err(cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::SelectedUnofferedKxGroup, + )); + }; + cx.common.kx_state = KxState::Start(skxg); + let kx = skxg.start()?; + + // 4b. + let mut transcript = st.transcript; + emit_client_kx(&mut transcript, st.suite.kx, cx.common, kx.pub_key()); + // Note: EMS handshake hash only runs up to ClientKeyExchange. + let ems_seed = st + .using_ems + .then(|| transcript.current_hash()); + + // 4c. + if let Some(ClientAuthDetails::Verify { signer, .. }) = &st.client_auth { + emit_certverify(&mut transcript, signer.as_ref(), cx.common)?; + } + + // 4d. Derive secrets. + // An alert at this point will be sent in plaintext. That must happen + // prior to the CCS, or else the peer will try to decrypt it. + let secrets = ConnectionSecrets::from_key_exchange( + kx, + kx_params.pub_key(), + ems_seed, + st.randoms, + suite, + ) + .map_err(|err| { + cx.common + .send_fatal_alert(AlertDescription::IllegalParameter, err) + })?; + cx.common.kx_state.complete(); + + // 4e. CCS. We are definitely going to switch on encryption. + emit_ccs(cx.common); + + // 4f. Now commit secrets. + st.config.key_log.log( + "CLIENT_RANDOM", + &secrets.randoms.client, + &secrets.master_secret, + ); + cx.common + .start_encryption_tls12(&secrets, Side::Client); + cx.common + .record_layer + .start_encrypting(); + + // 5. + emit_finished(&secrets, &mut transcript, cx.common); + + if st.must_issue_new_ticket { + Ok(Box::new(ExpectNewTicket { + config: st.config, + secrets, + resuming_session: st.resuming_session, + session_id: st.session_id, + server_name: st.server_name, + using_ems: st.using_ems, + transcript, + resuming: false, + cert_verified, + sig_verified, + })) + } else { + Ok(Box::new(ExpectCcs { + config: st.config, + secrets, + resuming_session: st.resuming_session, + session_id: st.session_id, + server_name: st.server_name, + using_ems: st.using_ems, + transcript, + ticket: None, + resuming: false, + cert_verified, + sig_verified, + })) + } + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + Box::new(ExpectServerDone { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert: self.server_cert.into_owned(), + server_kx: self.server_kx, + client_auth: self.client_auth, + must_issue_new_ticket: self.must_issue_new_ticket, + }) + } +} + +struct ExpectNewTicket { + config: Arc<ClientConfig>, + secrets: ConnectionSecrets, + resuming_session: Option<persist::Tls12ClientSessionValue>, + session_id: SessionId, + server_name: ServerName<'static>, + using_ems: bool, + transcript: HandshakeHash, + resuming: bool, + cert_verified: verify::ServerCertVerified, + sig_verified: verify::HandshakeSignatureValid, +} + +impl State<ClientConnectionData> for ExpectNewTicket { + fn handle<'m>( + mut self: Box<Self>, + _cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + self.transcript.add_message(&m); + + let nst = require_handshake_msg_move!( + m, + HandshakeType::NewSessionTicket, + HandshakePayload::NewSessionTicket + )?; + + Ok(Box::new(ExpectCcs { + config: self.config, + secrets: self.secrets, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + using_ems: self.using_ems, + transcript: self.transcript, + ticket: Some(nst), + resuming: self.resuming, + cert_verified: self.cert_verified, + sig_verified: self.sig_verified, + })) + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + self + } +} + +// -- Waiting for their CCS -- +struct ExpectCcs { + config: Arc<ClientConfig>, + secrets: ConnectionSecrets, + resuming_session: Option<persist::Tls12ClientSessionValue>, + session_id: SessionId, + server_name: ServerName<'static>, + using_ems: bool, + transcript: HandshakeHash, + ticket: Option<NewSessionTicketPayload>, + resuming: bool, + cert_verified: verify::ServerCertVerified, + sig_verified: verify::HandshakeSignatureValid, +} + +impl State<ClientConnectionData> for ExpectCcs { + fn handle<'m>( + self: Box<Self>, + cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + match m.payload { + MessagePayload::ChangeCipherSpec(..) => {} + payload => { + return Err(inappropriate_message( + &payload, + &[ContentType::ChangeCipherSpec], + )); + } + } + // CCS should not be received interleaved with fragmented handshake-level + // message. + cx.common.check_aligned_handshake()?; + + // Note: msgs layer validates trivial contents of CCS. + cx.common + .record_layer + .start_decrypting(); + + Ok(Box::new(ExpectFinished { + config: self.config, + secrets: self.secrets, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + using_ems: self.using_ems, + transcript: self.transcript, + ticket: self.ticket, + resuming: self.resuming, + cert_verified: self.cert_verified, + sig_verified: self.sig_verified, + })) + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + self + } +} + +struct ExpectFinished { + config: Arc<ClientConfig>, + resuming_session: Option<persist::Tls12ClientSessionValue>, + session_id: SessionId, + server_name: ServerName<'static>, + using_ems: bool, + transcript: HandshakeHash, + ticket: Option<NewSessionTicketPayload>, + secrets: ConnectionSecrets, + resuming: bool, + cert_verified: verify::ServerCertVerified, + sig_verified: verify::HandshakeSignatureValid, +} + +impl ExpectFinished { + // -- Waiting for their finished -- + fn save_session(&mut self, cx: &ClientContext<'_>) { + // Save a ticket. If we got a new ticket, save that. Otherwise, save the + // original ticket again. + let (mut ticket, lifetime) = match self.ticket.take() { + Some(nst) => (nst.ticket, nst.lifetime_hint), + None => (Arc::new(PayloadU16::empty()), 0), + }; + + if ticket.0.is_empty() { + if let Some(resuming_session) = &mut self.resuming_session { + ticket = resuming_session.ticket(); + } + } + + if self.session_id.is_empty() && ticket.0.is_empty() { + debug!("Session not saved: server didn't allocate id or ticket"); + return; + } + + let Ok(now) = self.config.current_time() else { + debug!("Could not get current time"); + return; + }; + + let session_value = persist::Tls12ClientSessionValue::new( + self.secrets.suite(), + self.session_id, + ticket, + self.secrets.master_secret(), + cx.common + .peer_certificates + .clone() + .unwrap_or_default(), + &self.config.verifier, + &self.config.client_auth_cert_resolver, + now, + lifetime, + self.using_ems, + ); + + self.config + .resumption + .store + .set_tls12_session(self.server_name.clone(), session_value); + } +} + +impl State<ClientConnectionData> for ExpectFinished { + fn handle<'m>( + self: Box<Self>, + cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + let mut st = *self; + let finished = + require_handshake_msg!(m, HandshakeType::Finished, HandshakePayload::Finished)?; + + cx.common.check_aligned_handshake()?; + + // Work out what verify_data we expect. + let vh = st.transcript.current_hash(); + let expect_verify_data = st.secrets.server_verify_data(&vh); + + // Constant-time verification of this is relatively unimportant: they only + // get one chance. But it can't hurt. + let _fin_verified = + match ConstantTimeEq::ct_eq(&expect_verify_data[..], finished.bytes()).into() { + true => verify::FinishedMessageVerified::assertion(), + false => { + return Err(cx + .common + .send_fatal_alert(AlertDescription::DecryptError, Error::DecryptError)); + } + }; + + // Hash this message too. + st.transcript.add_message(&m); + + st.save_session(cx); + + if st.resuming { + emit_ccs(cx.common); + cx.common + .record_layer + .start_encrypting(); + emit_finished(&st.secrets, &mut st.transcript, cx.common); + } + + cx.common + .start_traffic(&mut cx.sendable_plaintext); + Ok(Box::new(ExpectTraffic { + secrets: st.secrets, + _cert_verified: st.cert_verified, + _sig_verified: st.sig_verified, + _fin_verified, + })) + } + + // we could not decrypt the encrypted handshake message with session resumption + // this might mean that the ticket was invalid for some reason, so we remove it + // from the store to restart a session from scratch + fn handle_decrypt_error(&self) { + if self.resuming { + self.config + .resumption + .store + .remove_tls12_session(&self.server_name); + } + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + self + } +} + +// -- Traffic transit state -- +struct ExpectTraffic { + secrets: ConnectionSecrets, + _cert_verified: verify::ServerCertVerified, + _sig_verified: verify::HandshakeSignatureValid, + _fin_verified: verify::FinishedMessageVerified, +} + +impl State<ClientConnectionData> for ExpectTraffic { + fn handle<'m>( + self: Box<Self>, + cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + match m.payload { + MessagePayload::ApplicationData(payload) => cx + .common + .take_received_plaintext(payload), + payload => { + return Err(inappropriate_message( + &payload, + &[ContentType::ApplicationData], + )); + } + } + Ok(self) + } + + fn export_keying_material( + &self, + output: &mut [u8], + label: &[u8], + context: Option<&[u8]>, + ) -> Result<(), Error> { + self.secrets + .export_keying_material(output, label, context); + Ok(()) + } + + fn extract_secrets(&self) -> Result<PartiallyExtractedSecrets, Error> { + self.secrets + .extract_secrets(Side::Client) + } + + fn into_external_state(self: Box<Self>) -> Result<Box<dyn KernelState + 'static>, Error> { + Ok(self) + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + self + } +} + +impl KernelState for ExpectTraffic { + fn update_secrets(&mut self, _: Direction) -> Result<ConnectionTrafficSecrets, Error> { + Err(Error::General( + "TLS 1.2 connections do not support traffic secret updates".into(), + )) + } + + fn handle_new_session_ticket( + &mut self, + _cx: &mut KernelContext<'_>, + _message: &NewSessionTicketPayloadTls13, + ) -> Result<(), Error> { + Err(Error::General( + "TLS 1.2 session tickets may not be sent once the handshake has completed".into(), + )) + } +} diff --git a/vendor/rustls/src/client/tls13.rs b/vendor/rustls/src/client/tls13.rs new file mode 100644 index 00000000..fe8495cc --- /dev/null +++ b/vendor/rustls/src/client/tls13.rs @@ -0,0 +1,1700 @@ +use alloc::boxed::Box; +use alloc::vec; +use alloc::vec::Vec; + +use pki_types::ServerName; +use subtle::ConstantTimeEq; + +use super::client_conn::ClientConnectionData; +use super::hs::{ClientContext, ClientHelloInput, ClientSessionValue}; +use crate::check::inappropriate_handshake_message; +use crate::client::common::{ClientAuthDetails, ClientHelloDetails, ServerCertDetails}; +use crate::client::ech::{self, EchState, EchStatus}; +use crate::client::{ClientConfig, ClientSessionStore, hs}; +use crate::common_state::{ + CommonState, HandshakeFlightTls13, HandshakeKind, KxState, Protocol, Side, State, +}; +use crate::conn::ConnectionRandoms; +use crate::conn::kernel::{Direction, KernelContext, KernelState}; +use crate::crypto::hash::Hash; +use crate::crypto::{ActiveKeyExchange, SharedSecret}; +use crate::enums::{ + AlertDescription, ContentType, HandshakeType, ProtocolVersion, SignatureScheme, +}; +use crate::error::{Error, InvalidMessage, PeerIncompatible, PeerMisbehaved}; +use crate::hash_hs::{HandshakeHash, HandshakeHashBuffer}; +use crate::log::{debug, trace, warn}; +use crate::msgs::base::{Payload, PayloadU8}; +use crate::msgs::ccs::ChangeCipherSpecPayload; +use crate::msgs::codec::{Codec, Reader}; +use crate::msgs::enums::{ExtensionType, KeyUpdateRequest}; +use crate::msgs::handshake::{ + CERTIFICATE_MAX_SIZE_LIMIT, CertificatePayloadTls13, ClientExtensions, EchConfigPayload, + HandshakeMessagePayload, HandshakePayload, KeyShareEntry, NewSessionTicketPayloadTls13, + PresharedKeyBinder, PresharedKeyIdentity, PresharedKeyOffer, ServerExtensions, + ServerHelloPayload, +}; +use crate::msgs::message::{Message, MessagePayload}; +use crate::msgs::persist::{self, Retrieved}; +use crate::sign::{CertifiedKey, Signer}; +use crate::suites::PartiallyExtractedSecrets; +use crate::sync::Arc; +use crate::tls13::key_schedule::{ + KeyScheduleEarly, KeyScheduleHandshake, KeySchedulePreHandshake, KeyScheduleResumption, + KeyScheduleTraffic, +}; +use crate::tls13::{ + Tls13CipherSuite, construct_client_verify_message, construct_server_verify_message, +}; +use crate::verify::{self, DigitallySignedStruct}; +use crate::{ConnectionTrafficSecrets, KeyLog, compress, crypto}; + +// Extensions we expect in plaintext in the ServerHello. +static ALLOWED_PLAINTEXT_EXTS: &[ExtensionType] = &[ + ExtensionType::KeyShare, + ExtensionType::PreSharedKey, + ExtensionType::SupportedVersions, +]; + +// Only the intersection of things we offer, and those disallowed +// in TLS1.3 +static DISALLOWED_TLS13_EXTS: &[ExtensionType] = &[ + ExtensionType::ECPointFormats, + ExtensionType::SessionTicket, + ExtensionType::RenegotiationInfo, + ExtensionType::ExtendedMasterSecret, +]; + +/// `early_data_key_schedule` is `Some` if we sent the +/// "early_data" extension to the server. +pub(super) fn handle_server_hello( + cx: &mut ClientContext<'_>, + server_hello: &ServerHelloPayload, + mut randoms: ConnectionRandoms, + suite: &'static Tls13CipherSuite, + mut transcript: HandshakeHash, + early_data_key_schedule: Option<KeyScheduleEarly>, + our_key_share: Box<dyn ActiveKeyExchange>, + server_hello_msg: &Message<'_>, + ech_state: Option<EchState>, + input: ClientHelloInput, +) -> hs::NextStateOrError<'static> { + validate_server_hello(cx.common, server_hello)?; + + let their_key_share = server_hello + .key_share + .as_ref() + .ok_or_else(|| { + cx.common.send_fatal_alert( + AlertDescription::MissingExtension, + PeerMisbehaved::MissingKeyShare, + ) + })?; + + let ClientHelloInput { + config, + resuming, + mut sent_tls13_fake_ccs, + mut hello, + server_name, + .. + } = input; + + let mut resuming_session = match resuming { + Some(Retrieved { + value: ClientSessionValue::Tls13(value), + .. + }) => Some(value), + _ => None, + }; + + let our_key_share = KeyExchangeChoice::new(&config, cx, our_key_share, their_key_share) + .map_err(|_| { + cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::WrongGroupForKeyShare, + ) + })?; + + let key_schedule_pre_handshake = match (server_hello.preshared_key, early_data_key_schedule) { + (Some(selected_psk), Some(early_key_schedule)) => { + match &resuming_session { + Some(resuming) => { + let Some(resuming_suite) = suite.can_resume_from(resuming.suite()) else { + return Err({ + cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::ResumptionOfferedWithIncompatibleCipherSuite, + ) + }); + }; + + // If the server varies the suite here, we will have encrypted early data with + // the wrong suite. + if cx.data.early_data.is_enabled() && resuming_suite != suite { + return Err({ + cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::EarlyDataOfferedWithVariedCipherSuite, + ) + }); + } + + if selected_psk != 0 { + return Err({ + cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::SelectedInvalidPsk, + ) + }); + } + + debug!("Resuming using PSK"); + // The key schedule has been initialized and set in fill_in_psk_binder() + } + _ => { + return Err(PeerMisbehaved::SelectedUnofferedPsk.into()); + } + } + KeySchedulePreHandshake::from(early_key_schedule) + } + _ => { + debug!("Not resuming"); + // Discard the early data key schedule. + cx.data.early_data.rejected(); + cx.common.early_traffic = false; + resuming_session.take(); + KeySchedulePreHandshake::new(suite) + } + }; + + cx.common.kx_state.complete(); + let shared_secret = our_key_share + .complete(&their_key_share.payload.0) + .map_err(|err| { + cx.common + .send_fatal_alert(AlertDescription::IllegalParameter, err) + })?; + + let mut key_schedule = key_schedule_pre_handshake.into_handshake(shared_secret); + + // If we have ECH state, check that the server accepted our offer. + if let Some(ech_state) = ech_state { + let Message { + payload: + MessagePayload::Handshake { + encoded: server_hello_encoded, + .. + }, + .. + } = &server_hello_msg + else { + unreachable!("ServerHello is a handshake message"); + }; + cx.data.ech_status = match ech_state.confirm_acceptance( + &mut key_schedule, + server_hello, + server_hello_encoded, + suite.common.hash_provider, + )? { + // The server accepted our ECH offer, so complete the inner transcript with the + // server hello message, and switch the relevant state to the copies for the + // inner client hello. + Some(mut accepted) => { + accepted + .transcript + .add_message(server_hello_msg); + transcript = accepted.transcript; + randoms.client = accepted.random.0; + hello.sent_extensions = accepted.sent_extensions; + EchStatus::Accepted + } + // The server rejected our ECH offer. + None => EchStatus::Rejected, + }; + } + + // Remember what KX group the server liked for next time. + config + .resumption + .store + .set_kx_hint(server_name.clone(), their_key_share.group); + + // If we change keying when a subsequent handshake message is being joined, + // the two halves will have different record layer protections. Disallow this. + cx.common.check_aligned_handshake()?; + + let hash_at_client_recvd_server_hello = transcript.current_hash(); + let key_schedule = key_schedule.derive_client_handshake_secrets( + cx.data.early_data.is_enabled(), + hash_at_client_recvd_server_hello, + suite, + &*config.key_log, + &randoms.client, + cx.common, + ); + + emit_fake_ccs(&mut sent_tls13_fake_ccs, cx.common); + + Ok(Box::new(ExpectEncryptedExtensions { + config, + resuming_session, + server_name, + randoms, + suite, + transcript, + key_schedule, + hello, + })) +} + +enum KeyExchangeChoice { + Whole(Box<dyn ActiveKeyExchange>), + Component(Box<dyn ActiveKeyExchange>), +} + +impl KeyExchangeChoice { + /// Decide between `our_key_share` or `our_key_share.hybrid_component()` + /// based on the selection of the server expressed in `their_key_share`. + fn new( + config: &Arc<ClientConfig>, + cx: &mut ClientContext<'_>, + our_key_share: Box<dyn ActiveKeyExchange>, + their_key_share: &KeyShareEntry, + ) -> Result<Self, ()> { + if our_key_share.group() == their_key_share.group { + return Ok(Self::Whole(our_key_share)); + } + + let (component_group, _) = our_key_share + .hybrid_component() + .ok_or(())?; + + if component_group != their_key_share.group { + return Err(()); + } + + // correct the record for the benefit of accuracy of + // `negotiated_key_exchange_group()` + let actual_skxg = config + .find_kx_group(component_group, ProtocolVersion::TLSv1_3) + .ok_or(())?; + cx.common.kx_state = KxState::Start(actual_skxg); + + Ok(Self::Component(our_key_share)) + } + + fn complete(self, peer_pub_key: &[u8]) -> Result<SharedSecret, Error> { + match self { + Self::Whole(akx) => akx.complete(peer_pub_key), + Self::Component(akx) => akx.complete_hybrid_component(peer_pub_key), + } + } +} + +fn validate_server_hello( + common: &mut CommonState, + server_hello: &ServerHelloPayload, +) -> Result<(), Error> { + if !server_hello.only_contains(ALLOWED_PLAINTEXT_EXTS) { + return Err(common.send_fatal_alert( + AlertDescription::UnsupportedExtension, + PeerMisbehaved::UnexpectedCleartextExtension, + )); + } + + Ok(()) +} + +pub(super) fn initial_key_share( + config: &ClientConfig, + server_name: &ServerName<'_>, + kx_state: &mut KxState, +) -> Result<Box<dyn ActiveKeyExchange>, Error> { + let group = config + .resumption + .store + .kx_hint(server_name) + .and_then(|group_name| config.find_kx_group(group_name, ProtocolVersion::TLSv1_3)) + .unwrap_or_else(|| { + config + .provider + .kx_groups + .iter() + .copied() + .next() + .expect("No kx groups configured") + }); + + *kx_state = KxState::Start(group); + group.start() +} + +/// This implements the horrifying TLS1.3 hack where PSK binders have a +/// data dependency on the message they are contained within. +pub(super) fn fill_in_psk_binder( + resuming: &persist::Tls13ClientSessionValue, + transcript: &HandshakeHashBuffer, + hmp: &mut HandshakeMessagePayload<'_>, +) -> KeyScheduleEarly { + // We need to know the hash function of the suite we're trying to resume into. + let suite = resuming.suite(); + let suite_hash = suite.common.hash_provider; + + // The binder is calculated over the clienthello, but doesn't include itself or its + // length, or the length of its container. + let binder_plaintext = hmp.encoding_for_binder_signing(); + let handshake_hash = transcript.hash_given(suite_hash, &binder_plaintext); + + // Run a fake key_schedule to simulate what the server will do if it chooses + // to resume. + let key_schedule = KeyScheduleEarly::new(suite, resuming.secret()); + let real_binder = key_schedule.resumption_psk_binder_key_and_sign_verify_data(&handshake_hash); + + if let HandshakePayload::ClientHello(ch) = &mut hmp.0 { + if let Some(PresharedKeyOffer { + binders, + identities, + }) = &mut ch.preshared_key_offer + { + // the caller of this function must have set up the desired identity, and a + // matching (dummy) binder; or else the binder we compute here will be incorrect. + // See `prepare_resumption()`. + debug_assert_eq!(identities.len(), 1); + debug_assert_eq!(binders.len(), 1); + debug_assert_eq!(binders[0].as_ref().len(), real_binder.as_ref().len()); + binders[0] = PresharedKeyBinder::from(real_binder.as_ref().to_vec()); + } + }; + + key_schedule +} + +pub(super) fn prepare_resumption( + config: &ClientConfig, + cx: &mut ClientContext<'_>, + resuming_session: &Retrieved<&persist::Tls13ClientSessionValue>, + exts: &mut ClientExtensions<'_>, + doing_retry: bool, +) { + let resuming_suite = resuming_session.suite(); + cx.common.suite = Some(resuming_suite.into()); + // The EarlyData extension MUST be supplied together with the + // PreSharedKey extension. + let max_early_data_size = resuming_session.max_early_data_size(); + if config.enable_early_data && max_early_data_size > 0 && !doing_retry { + cx.data + .early_data + .enable(max_early_data_size as usize); + exts.early_data_request = Some(()); + } + + // Finally, and only for TLS1.3 with a ticket resumption, include a binder + // for our ticket. This must go last. + // + // Include an empty binder. It gets filled in below because it depends on + // the message it's contained in (!!!). + let obfuscated_ticket_age = resuming_session.obfuscated_ticket_age(); + + let binder_len = resuming_suite + .common + .hash_provider + .output_len(); + let binder = vec![0u8; binder_len]; + + let psk_identity = + PresharedKeyIdentity::new(resuming_session.ticket().to_vec(), obfuscated_ticket_age); + let psk_offer = PresharedKeyOffer::new(psk_identity, binder); + exts.preshared_key_offer = Some(psk_offer); +} + +pub(super) fn derive_early_traffic_secret( + key_log: &dyn KeyLog, + cx: &mut ClientContext<'_>, + hash_alg: &'static dyn Hash, + early_key_schedule: &KeyScheduleEarly, + sent_tls13_fake_ccs: &mut bool, + transcript_buffer: &HandshakeHashBuffer, + client_random: &[u8; 32], +) { + // For middlebox compatibility + emit_fake_ccs(sent_tls13_fake_ccs, cx.common); + + let client_hello_hash = transcript_buffer.hash_given(hash_alg, &[]); + early_key_schedule.client_early_traffic_secret( + &client_hello_hash, + key_log, + client_random, + cx.common, + ); + + // Now the client can send encrypted early data + cx.common.early_traffic = true; + trace!("Starting early data traffic"); +} + +pub(super) fn emit_fake_ccs(sent_tls13_fake_ccs: &mut bool, common: &mut CommonState) { + if common.is_quic() { + return; + } + + if core::mem::replace(sent_tls13_fake_ccs, true) { + return; + } + + let m = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::ChangeCipherSpec(ChangeCipherSpecPayload {}), + }; + common.send_msg(m, false); +} + +fn validate_encrypted_extensions( + common: &mut CommonState, + hello: &ClientHelloDetails, + exts: &ServerExtensions<'_>, +) -> Result<(), Error> { + if hello.server_sent_unsolicited_extensions(exts, &[]) { + return Err(common.send_fatal_alert( + AlertDescription::UnsupportedExtension, + PeerMisbehaved::UnsolicitedEncryptedExtension, + )); + } + + if exts.contains_any(ALLOWED_PLAINTEXT_EXTS) || exts.contains_any(DISALLOWED_TLS13_EXTS) { + return Err(common.send_fatal_alert( + AlertDescription::UnsupportedExtension, + PeerMisbehaved::DisallowedEncryptedExtension, + )); + } + + Ok(()) +} + +struct ExpectEncryptedExtensions { + config: Arc<ClientConfig>, + resuming_session: Option<persist::Tls13ClientSessionValue>, + server_name: ServerName<'static>, + randoms: ConnectionRandoms, + suite: &'static Tls13CipherSuite, + transcript: HandshakeHash, + key_schedule: KeyScheduleHandshake, + hello: ClientHelloDetails, +} + +impl State<ClientConnectionData> for ExpectEncryptedExtensions { + fn handle<'m>( + mut self: Box<Self>, + cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + let exts = require_handshake_msg!( + m, + HandshakeType::EncryptedExtensions, + HandshakePayload::EncryptedExtensions + )?; + debug!("TLS1.3 encrypted extensions: {exts:?}"); + self.transcript.add_message(&m); + + validate_encrypted_extensions(cx.common, &self.hello, exts)?; + hs::process_alpn_protocol( + cx.common, + &self.hello.alpn_protocols, + exts.selected_protocol + .as_ref() + .map(|protocol| protocol.as_ref()), + )?; + hs::process_client_cert_type_extension( + cx.common, + &self.config, + exts.client_certificate_type.as_ref(), + )?; + hs::process_server_cert_type_extension( + cx.common, + &self.config, + exts.server_certificate_type.as_ref(), + )?; + + let ech_retry_configs = match (cx.data.ech_status, &exts.encrypted_client_hello_ack) { + // If we didn't offer ECH, or ECH was accepted, but the server sent an ECH encrypted + // extension with retry configs, we must error. + (EchStatus::NotOffered | EchStatus::Accepted, Some(_)) => { + return Err(cx.common.send_fatal_alert( + AlertDescription::UnsupportedExtension, + PeerMisbehaved::UnsolicitedEchExtension, + )); + } + // If we offered ECH, and it was rejected, store the retry configs (if any) from + // the server's ECH extension. We will return them in an error produced at the end + // of the handshake. + (EchStatus::Rejected, ext) => ext + .as_ref() + .map(|ext| ext.retry_configs.to_vec()), + _ => None, + }; + + // QUIC transport parameters + if cx.common.is_quic() { + match exts + .transport_parameters + .as_ref() + .or(exts.transport_parameters_draft.as_ref()) + { + Some(params) => cx.common.quic.params = Some(params.clone().into_vec()), + None => { + return Err(cx + .common + .missing_extension(PeerMisbehaved::MissingQuicTransportParameters)); + } + } + } + + match self.resuming_session { + Some(resuming_session) => { + let was_early_traffic = cx.common.early_traffic; + if was_early_traffic { + match exts.early_data_ack { + Some(()) => cx.data.early_data.accepted(), + None => { + cx.data.early_data.rejected(); + cx.common.early_traffic = false; + } + } + } + + if was_early_traffic && !cx.common.early_traffic { + // If no early traffic, set the encryption key for handshakes + self.key_schedule + .set_handshake_encrypter(cx.common); + } + + cx.common.peer_certificates = Some( + resuming_session + .server_cert_chain() + .clone(), + ); + cx.common.handshake_kind = Some(HandshakeKind::Resumed); + + // We *don't* reverify the certificate chain here: resumption is a + // continuation of the previous session in terms of security policy. + let cert_verified = verify::ServerCertVerified::assertion(); + let sig_verified = verify::HandshakeSignatureValid::assertion(); + Ok(Box::new(ExpectFinished { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + client_auth: None, + cert_verified, + sig_verified, + ech_retry_configs, + })) + } + _ => { + if exts.early_data_ack.is_some() { + return Err(PeerMisbehaved::EarlyDataExtensionWithoutResumption.into()); + } + cx.common + .handshake_kind + .get_or_insert(HandshakeKind::Full); + + Ok(if self.hello.offered_cert_compression { + Box::new(ExpectCertificateOrCompressedCertificateOrCertReq { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + ech_retry_configs, + }) + } else { + Box::new(ExpectCertificateOrCertReq { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + ech_retry_configs, + }) + }) + } + } + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + self + } +} + +struct ExpectCertificateOrCompressedCertificateOrCertReq { + config: Arc<ClientConfig>, + server_name: ServerName<'static>, + randoms: ConnectionRandoms, + suite: &'static Tls13CipherSuite, + transcript: HandshakeHash, + key_schedule: KeyScheduleHandshake, + ech_retry_configs: Option<Vec<EchConfigPayload>>, +} + +impl State<ClientConnectionData> for ExpectCertificateOrCompressedCertificateOrCertReq { + fn handle<'m>( + self: Box<Self>, + cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + match m.payload { + MessagePayload::Handshake { + parsed: HandshakeMessagePayload(HandshakePayload::CertificateTls13(..)), + .. + } => Box::new(ExpectCertificate { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + client_auth: None, + message_already_in_transcript: false, + ech_retry_configs: self.ech_retry_configs, + }) + .handle(cx, m), + MessagePayload::Handshake { + parsed: HandshakeMessagePayload(HandshakePayload::CompressedCertificate(..)), + .. + } => Box::new(ExpectCompressedCertificate { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + client_auth: None, + ech_retry_configs: self.ech_retry_configs, + }) + .handle(cx, m), + MessagePayload::Handshake { + parsed: HandshakeMessagePayload(HandshakePayload::CertificateRequestTls13(..)), + .. + } => Box::new(ExpectCertificateRequest { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + offered_cert_compression: true, + ech_retry_configs: self.ech_retry_configs, + }) + .handle(cx, m), + payload => Err(inappropriate_handshake_message( + &payload, + &[ContentType::Handshake], + &[ + HandshakeType::Certificate, + HandshakeType::CertificateRequest, + HandshakeType::CompressedCertificate, + ], + )), + } + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + self + } +} + +struct ExpectCertificateOrCompressedCertificate { + config: Arc<ClientConfig>, + server_name: ServerName<'static>, + randoms: ConnectionRandoms, + suite: &'static Tls13CipherSuite, + transcript: HandshakeHash, + key_schedule: KeyScheduleHandshake, + client_auth: Option<ClientAuthDetails>, + ech_retry_configs: Option<Vec<EchConfigPayload>>, +} + +impl State<ClientConnectionData> for ExpectCertificateOrCompressedCertificate { + fn handle<'m>( + self: Box<Self>, + cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + match m.payload { + MessagePayload::Handshake { + parsed: HandshakeMessagePayload(HandshakePayload::CertificateTls13(..)), + .. + } => Box::new(ExpectCertificate { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + client_auth: self.client_auth, + message_already_in_transcript: false, + ech_retry_configs: self.ech_retry_configs, + }) + .handle(cx, m), + MessagePayload::Handshake { + parsed: HandshakeMessagePayload(HandshakePayload::CompressedCertificate(..)), + .. + } => Box::new(ExpectCompressedCertificate { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + client_auth: self.client_auth, + ech_retry_configs: self.ech_retry_configs, + }) + .handle(cx, m), + payload => Err(inappropriate_handshake_message( + &payload, + &[ContentType::Handshake], + &[ + HandshakeType::Certificate, + HandshakeType::CompressedCertificate, + ], + )), + } + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + self + } +} + +struct ExpectCertificateOrCertReq { + config: Arc<ClientConfig>, + server_name: ServerName<'static>, + randoms: ConnectionRandoms, + suite: &'static Tls13CipherSuite, + transcript: HandshakeHash, + key_schedule: KeyScheduleHandshake, + ech_retry_configs: Option<Vec<EchConfigPayload>>, +} + +impl State<ClientConnectionData> for ExpectCertificateOrCertReq { + fn handle<'m>( + self: Box<Self>, + cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + match m.payload { + MessagePayload::Handshake { + parsed: HandshakeMessagePayload(HandshakePayload::CertificateTls13(..)), + .. + } => Box::new(ExpectCertificate { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + client_auth: None, + message_already_in_transcript: false, + ech_retry_configs: self.ech_retry_configs, + }) + .handle(cx, m), + MessagePayload::Handshake { + parsed: HandshakeMessagePayload(HandshakePayload::CertificateRequestTls13(..)), + .. + } => Box::new(ExpectCertificateRequest { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + offered_cert_compression: false, + ech_retry_configs: self.ech_retry_configs, + }) + .handle(cx, m), + payload => Err(inappropriate_handshake_message( + &payload, + &[ContentType::Handshake], + &[ + HandshakeType::Certificate, + HandshakeType::CertificateRequest, + ], + )), + } + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + self + } +} + +// TLS1.3 version of CertificateRequest handling. We then move to expecting the server +// Certificate. Unfortunately the CertificateRequest type changed in an annoying way +// in TLS1.3. +struct ExpectCertificateRequest { + config: Arc<ClientConfig>, + server_name: ServerName<'static>, + randoms: ConnectionRandoms, + suite: &'static Tls13CipherSuite, + transcript: HandshakeHash, + key_schedule: KeyScheduleHandshake, + offered_cert_compression: bool, + ech_retry_configs: Option<Vec<EchConfigPayload>>, +} + +impl State<ClientConnectionData> for ExpectCertificateRequest { + fn handle<'m>( + mut self: Box<Self>, + cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + let certreq = &require_handshake_msg!( + m, + HandshakeType::CertificateRequest, + HandshakePayload::CertificateRequestTls13 + )?; + self.transcript.add_message(&m); + debug!("Got CertificateRequest {certreq:?}"); + + // Fortunately the problems here in TLS1.2 and prior are corrected in + // TLS1.3. + + // Must be empty during handshake. + if !certreq.context.0.is_empty() { + warn!("Server sent non-empty certreq context"); + return Err(cx.common.send_fatal_alert( + AlertDescription::DecodeError, + InvalidMessage::InvalidCertRequest, + )); + } + + let compat_sigschemes = certreq + .extensions + .signature_algorithms + .as_deref() + .unwrap_or_default() + .iter() + .cloned() + .filter(SignatureScheme::supported_in_tls13) + .collect::<Vec<SignatureScheme>>(); + + if compat_sigschemes.is_empty() { + return Err(cx.common.send_fatal_alert( + AlertDescription::HandshakeFailure, + PeerIncompatible::NoCertificateRequestSignatureSchemesInCommon, + )); + } + + let compat_compressor = certreq + .extensions + .certificate_compression_algorithms + .as_deref() + .and_then(|offered| { + self.config + .cert_compressors + .iter() + .find(|compressor| offered.contains(&compressor.algorithm())) + }) + .cloned(); + + let client_auth = ClientAuthDetails::resolve( + self.config + .client_auth_cert_resolver + .as_ref(), + certreq + .extensions + .authority_names + .as_deref(), + &compat_sigschemes, + Some(certreq.context.0.clone()), + compat_compressor, + ); + + Ok(if self.offered_cert_compression { + Box::new(ExpectCertificateOrCompressedCertificate { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + client_auth: Some(client_auth), + ech_retry_configs: self.ech_retry_configs, + }) + } else { + Box::new(ExpectCertificate { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + client_auth: Some(client_auth), + message_already_in_transcript: false, + ech_retry_configs: self.ech_retry_configs, + }) + }) + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + self + } +} + +struct ExpectCompressedCertificate { + config: Arc<ClientConfig>, + server_name: ServerName<'static>, + randoms: ConnectionRandoms, + suite: &'static Tls13CipherSuite, + transcript: HandshakeHash, + key_schedule: KeyScheduleHandshake, + client_auth: Option<ClientAuthDetails>, + ech_retry_configs: Option<Vec<EchConfigPayload>>, +} + +impl State<ClientConnectionData> for ExpectCompressedCertificate { + fn handle<'m>( + mut self: Box<Self>, + cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + self.transcript.add_message(&m); + let compressed_cert = require_handshake_msg_move!( + m, + HandshakeType::CompressedCertificate, + HandshakePayload::CompressedCertificate + )?; + + let selected_decompressor = self + .config + .cert_decompressors + .iter() + .find(|item| item.algorithm() == compressed_cert.alg); + + let Some(decompressor) = selected_decompressor else { + return Err(cx.common.send_fatal_alert( + AlertDescription::BadCertificate, + PeerMisbehaved::SelectedUnofferedCertCompression, + )); + }; + + if compressed_cert.uncompressed_len as usize > CERTIFICATE_MAX_SIZE_LIMIT { + return Err(cx.common.send_fatal_alert( + AlertDescription::BadCertificate, + InvalidMessage::MessageTooLarge, + )); + } + + let mut decompress_buffer = vec![0u8; compressed_cert.uncompressed_len as usize]; + if let Err(compress::DecompressionFailed) = + decompressor.decompress(compressed_cert.compressed.0.bytes(), &mut decompress_buffer) + { + return Err(cx.common.send_fatal_alert( + AlertDescription::BadCertificate, + PeerMisbehaved::InvalidCertCompression, + )); + } + + let cert_payload = + match CertificatePayloadTls13::read(&mut Reader::init(&decompress_buffer)) { + Ok(cm) => cm, + Err(err) => { + return Err(cx + .common + .send_fatal_alert(AlertDescription::BadCertificate, err)); + } + }; + trace!( + "Server certificate decompressed using {:?} ({} bytes -> {})", + compressed_cert.alg, + compressed_cert + .compressed + .0 + .bytes() + .len(), + compressed_cert.uncompressed_len, + ); + + let m = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::CertificateTls13(cert_payload.into_owned()), + )), + }; + + Box::new(ExpectCertificate { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + client_auth: self.client_auth, + message_already_in_transcript: true, + ech_retry_configs: self.ech_retry_configs, + }) + .handle(cx, m) + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + self + } +} + +struct ExpectCertificate { + config: Arc<ClientConfig>, + server_name: ServerName<'static>, + randoms: ConnectionRandoms, + suite: &'static Tls13CipherSuite, + transcript: HandshakeHash, + key_schedule: KeyScheduleHandshake, + client_auth: Option<ClientAuthDetails>, + message_already_in_transcript: bool, + ech_retry_configs: Option<Vec<EchConfigPayload>>, +} + +impl State<ClientConnectionData> for ExpectCertificate { + fn handle<'m>( + mut self: Box<Self>, + cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + if !self.message_already_in_transcript { + self.transcript.add_message(&m); + } + let cert_chain = require_handshake_msg_move!( + m, + HandshakeType::Certificate, + HandshakePayload::CertificateTls13 + )?; + + // This is only non-empty for client auth. + if !cert_chain.context.0.is_empty() { + return Err(cx.common.send_fatal_alert( + AlertDescription::DecodeError, + InvalidMessage::InvalidCertRequest, + )); + } + + let end_entity_ocsp = cert_chain.end_entity_ocsp().to_vec(); + let server_cert = ServerCertDetails::new( + cert_chain + .into_certificate_chain() + .into_owned(), + end_entity_ocsp, + ); + + Ok(Box::new(ExpectCertificateVerify { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + server_cert, + client_auth: self.client_auth, + ech_retry_configs: self.ech_retry_configs, + })) + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + self + } +} + +// --- TLS1.3 CertificateVerify --- +struct ExpectCertificateVerify<'a> { + config: Arc<ClientConfig>, + server_name: ServerName<'static>, + randoms: ConnectionRandoms, + suite: &'static Tls13CipherSuite, + transcript: HandshakeHash, + key_schedule: KeyScheduleHandshake, + server_cert: ServerCertDetails<'a>, + client_auth: Option<ClientAuthDetails>, + ech_retry_configs: Option<Vec<EchConfigPayload>>, +} + +impl State<ClientConnectionData> for ExpectCertificateVerify<'_> { + fn handle<'m>( + mut self: Box<Self>, + cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + let cert_verify = require_handshake_msg!( + m, + HandshakeType::CertificateVerify, + HandshakePayload::CertificateVerify + )?; + + trace!("Server cert is {:?}", self.server_cert.cert_chain); + + // 1. Verify the certificate chain. + let (end_entity, intermediates) = self + .server_cert + .cert_chain + .split_first() + .ok_or(Error::NoCertificatesPresented)?; + + let now = self.config.current_time()?; + + let cert_verified = self + .config + .verifier + .verify_server_cert( + end_entity, + intermediates, + &self.server_name, + &self.server_cert.ocsp_response, + now, + ) + .map_err(|err| { + cx.common + .send_cert_verify_error_alert(err) + })?; + + // 2. Verify their signature on the handshake. + let handshake_hash = self.transcript.current_hash(); + let sig_verified = self + .config + .verifier + .verify_tls13_signature( + construct_server_verify_message(&handshake_hash).as_ref(), + end_entity, + cert_verify, + ) + .map_err(|err| { + cx.common + .send_cert_verify_error_alert(err) + })?; + + cx.common.peer_certificates = Some(self.server_cert.cert_chain.into_owned()); + self.transcript.add_message(&m); + + Ok(Box::new(ExpectFinished { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + client_auth: self.client_auth, + cert_verified, + sig_verified, + ech_retry_configs: self.ech_retry_configs, + })) + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + Box::new(ExpectCertificateVerify { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + server_cert: self.server_cert.into_owned(), + client_auth: self.client_auth, + ech_retry_configs: self.ech_retry_configs, + }) + } +} + +fn emit_compressed_certificate_tls13( + flight: &mut HandshakeFlightTls13<'_>, + certkey: &CertifiedKey, + auth_context: Option<Vec<u8>>, + compressor: &dyn compress::CertCompressor, + config: &ClientConfig, +) { + let mut cert_payload = CertificatePayloadTls13::new(certkey.cert.iter(), None); + cert_payload.context = PayloadU8::new(auth_context.clone().unwrap_or_default()); + + let Ok(compressed) = config + .cert_compression_cache + .compression_for(compressor, &cert_payload) + else { + return emit_certificate_tls13(flight, Some(certkey), auth_context); + }; + + flight.add(HandshakeMessagePayload( + HandshakePayload::CompressedCertificate(compressed.compressed_cert_payload()), + )); +} + +fn emit_certificate_tls13( + flight: &mut HandshakeFlightTls13<'_>, + certkey: Option<&CertifiedKey>, + auth_context: Option<Vec<u8>>, +) { + let certs = certkey + .map(|ck| ck.cert.as_ref()) + .unwrap_or(&[][..]); + let mut cert_payload = CertificatePayloadTls13::new(certs.iter(), None); + cert_payload.context = PayloadU8::new(auth_context.unwrap_or_default()); + + flight.add(HandshakeMessagePayload(HandshakePayload::CertificateTls13( + cert_payload, + ))); +} + +fn emit_certverify_tls13( + flight: &mut HandshakeFlightTls13<'_>, + signer: &dyn Signer, +) -> Result<(), Error> { + let message = construct_client_verify_message(&flight.transcript.current_hash()); + + let scheme = signer.scheme(); + let sig = signer.sign(message.as_ref())?; + let dss = DigitallySignedStruct::new(scheme, sig); + + flight.add(HandshakeMessagePayload( + HandshakePayload::CertificateVerify(dss), + )); + Ok(()) +} + +fn emit_finished_tls13(flight: &mut HandshakeFlightTls13<'_>, verify_data: &crypto::hmac::Tag) { + let verify_data_payload = Payload::new(verify_data.as_ref()); + + flight.add(HandshakeMessagePayload(HandshakePayload::Finished( + verify_data_payload, + ))); +} + +fn emit_end_of_early_data_tls13(transcript: &mut HandshakeHash, common: &mut CommonState) { + if common.is_quic() { + return; + } + + let m = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload( + HandshakePayload::EndOfEarlyData, + )), + }; + + transcript.add_message(&m); + common.send_msg(m, true); +} + +struct ExpectFinished { + config: Arc<ClientConfig>, + server_name: ServerName<'static>, + randoms: ConnectionRandoms, + suite: &'static Tls13CipherSuite, + transcript: HandshakeHash, + key_schedule: KeyScheduleHandshake, + client_auth: Option<ClientAuthDetails>, + cert_verified: verify::ServerCertVerified, + sig_verified: verify::HandshakeSignatureValid, + ech_retry_configs: Option<Vec<EchConfigPayload>>, +} + +impl State<ClientConnectionData> for ExpectFinished { + fn handle<'m>( + self: Box<Self>, + cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + let mut st = *self; + let finished = + require_handshake_msg!(m, HandshakeType::Finished, HandshakePayload::Finished)?; + + let handshake_hash = st.transcript.current_hash(); + let expect_verify_data = st + .key_schedule + .sign_server_finish(&handshake_hash); + + let fin = match ConstantTimeEq::ct_eq(expect_verify_data.as_ref(), finished.bytes()).into() + { + true => verify::FinishedMessageVerified::assertion(), + false => { + return Err(cx + .common + .send_fatal_alert(AlertDescription::DecryptError, Error::DecryptError)); + } + }; + + st.transcript.add_message(&m); + + let hash_after_handshake = st.transcript.current_hash(); + /* The EndOfEarlyData message to server is still encrypted with early data keys, + * but appears in the transcript after the server Finished. */ + if cx.common.early_traffic { + emit_end_of_early_data_tls13(&mut st.transcript, cx.common); + cx.common.early_traffic = false; + cx.data.early_data.finished(); + st.key_schedule + .set_handshake_encrypter(cx.common); + } + + let mut flight = HandshakeFlightTls13::new(&mut st.transcript); + + /* Send our authentication/finished messages. These are still encrypted + * with our handshake keys. */ + if let Some(client_auth) = st.client_auth { + match client_auth { + ClientAuthDetails::Empty { + auth_context_tls13: auth_context, + } => { + emit_certificate_tls13(&mut flight, None, auth_context); + } + ClientAuthDetails::Verify { + auth_context_tls13: auth_context, + .. + } if cx.data.ech_status == EchStatus::Rejected => { + // If ECH was offered, and rejected, we MUST respond with + // an empty certificate message. + emit_certificate_tls13(&mut flight, None, auth_context); + } + ClientAuthDetails::Verify { + certkey, + signer, + auth_context_tls13: auth_context, + compressor, + } => { + if let Some(compressor) = compressor { + emit_compressed_certificate_tls13( + &mut flight, + &certkey, + auth_context, + compressor, + &st.config, + ); + } else { + emit_certificate_tls13(&mut flight, Some(&certkey), auth_context); + } + emit_certverify_tls13(&mut flight, signer.as_ref())?; + } + } + } + + let (key_schedule_pre_finished, verify_data) = st + .key_schedule + .into_pre_finished_client_traffic( + hash_after_handshake, + flight.transcript.current_hash(), + &*st.config.key_log, + &st.randoms.client, + ); + + emit_finished_tls13(&mut flight, &verify_data); + flight.finish(cx.common); + + /* We're now sure this server supports TLS1.3. But if we run out of TLS1.3 tickets + * when connecting to it again, we definitely don't want to attempt a TLS1.2 resumption. */ + st.config + .resumption + .store + .remove_tls12_session(&st.server_name); + + /* Now move to our application traffic keys. */ + cx.common.check_aligned_handshake()?; + let (key_schedule, resumption) = + key_schedule_pre_finished.into_traffic(cx.common, st.transcript.current_hash()); + cx.common + .start_traffic(&mut cx.sendable_plaintext); + + // Now that we've reached the end of the normal handshake we must enforce ECH acceptance by + // sending an alert and returning an error (potentially with retry configs) if the server + // did not accept our ECH offer. + if cx.data.ech_status == EchStatus::Rejected { + return Err(ech::fatal_alert_required(st.ech_retry_configs, cx.common)); + } + + let st = ExpectTraffic { + config: st.config.clone(), + session_storage: st.config.resumption.store.clone(), + server_name: st.server_name, + suite: st.suite, + key_schedule, + resumption, + _cert_verified: st.cert_verified, + _sig_verified: st.sig_verified, + _fin_verified: fin, + }; + + Ok(match cx.common.is_quic() { + true => Box::new(ExpectQuicTraffic(st)), + false => Box::new(st), + }) + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + self + } +} + +// -- Traffic transit state (TLS1.3) -- +// In this state we can be sent tickets, key updates, +// and application data. +struct ExpectTraffic { + config: Arc<ClientConfig>, + session_storage: Arc<dyn ClientSessionStore>, + server_name: ServerName<'static>, + suite: &'static Tls13CipherSuite, + key_schedule: KeyScheduleTraffic, + resumption: KeyScheduleResumption, + _cert_verified: verify::ServerCertVerified, + _sig_verified: verify::HandshakeSignatureValid, + _fin_verified: verify::FinishedMessageVerified, +} + +impl ExpectTraffic { + fn handle_new_ticket_impl( + &mut self, + cx: &mut KernelContext<'_>, + nst: &NewSessionTicketPayloadTls13, + ) -> Result<(), Error> { + let secret = self + .resumption + .derive_ticket_psk(&nst.nonce.0); + + let now = self.config.current_time()?; + + #[allow(unused_mut)] + let mut value = persist::Tls13ClientSessionValue::new( + self.suite, + nst.ticket.clone(), + secret.as_ref(), + cx.peer_certificates + .cloned() + .unwrap_or_default(), + &self.config.verifier, + &self.config.client_auth_cert_resolver, + now, + nst.lifetime, + nst.age_add, + nst.extensions + .max_early_data_size + .unwrap_or_default(), + ); + + if cx.is_quic() { + if let Some(sz) = nst.extensions.max_early_data_size { + if sz != 0 && sz != 0xffff_ffff { + return Err(PeerMisbehaved::InvalidMaxEarlyDataSize.into()); + } + } + + if let Some(quic_params) = &cx.quic.params { + value.set_quic_params(quic_params); + } + } + + self.session_storage + .insert_tls13_ticket(self.server_name.clone(), value); + Ok(()) + } + + fn handle_new_ticket_tls13( + &mut self, + cx: &mut ClientContext<'_>, + nst: &NewSessionTicketPayloadTls13, + ) -> Result<(), Error> { + let mut kcx = KernelContext { + peer_certificates: cx.common.peer_certificates.as_ref(), + protocol: cx.common.protocol, + quic: &cx.common.quic, + }; + cx.common.tls13_tickets_received = cx + .common + .tls13_tickets_received + .saturating_add(1); + self.handle_new_ticket_impl(&mut kcx, nst) + } + + fn handle_key_update( + &mut self, + common: &mut CommonState, + key_update_request: &KeyUpdateRequest, + ) -> Result<(), Error> { + if let Protocol::Quic = common.protocol { + return Err(common.send_fatal_alert( + AlertDescription::UnexpectedMessage, + PeerMisbehaved::KeyUpdateReceivedInQuicConnection, + )); + } + + // Mustn't be interleaved with other handshake messages. + common.check_aligned_handshake()?; + + if common.should_update_key(key_update_request)? { + self.key_schedule + .update_encrypter_and_notify(common); + } + + // Update our read-side keys. + self.key_schedule + .update_decrypter(common); + Ok(()) + } +} + +impl State<ClientConnectionData> for ExpectTraffic { + fn handle<'m>( + mut self: Box<Self>, + cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + match m.payload { + MessagePayload::ApplicationData(payload) => cx + .common + .take_received_plaintext(payload), + MessagePayload::Handshake { + parsed: HandshakeMessagePayload(HandshakePayload::NewSessionTicketTls13(new_ticket)), + .. + } => self.handle_new_ticket_tls13(cx, &new_ticket)?, + MessagePayload::Handshake { + parsed: HandshakeMessagePayload(HandshakePayload::KeyUpdate(key_update)), + .. + } => self.handle_key_update(cx.common, &key_update)?, + payload => { + return Err(inappropriate_handshake_message( + &payload, + &[ContentType::ApplicationData, ContentType::Handshake], + &[HandshakeType::NewSessionTicket, HandshakeType::KeyUpdate], + )); + } + } + + Ok(self) + } + + fn send_key_update_request(&mut self, common: &mut CommonState) -> Result<(), Error> { + self.key_schedule + .request_key_update_and_update_encrypter(common) + } + + fn export_keying_material( + &self, + output: &mut [u8], + label: &[u8], + context: Option<&[u8]>, + ) -> Result<(), Error> { + self.key_schedule + .export_keying_material(output, label, context) + } + + fn extract_secrets(&self) -> Result<PartiallyExtractedSecrets, Error> { + self.key_schedule + .extract_secrets(Side::Client) + } + + fn into_external_state(self: Box<Self>) -> Result<Box<dyn KernelState + 'static>, Error> { + Ok(self) + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + self + } +} + +impl KernelState for ExpectTraffic { + fn update_secrets(&mut self, dir: Direction) -> Result<ConnectionTrafficSecrets, Error> { + self.key_schedule + .refresh_traffic_secret(match dir { + Direction::Transmit => Side::Client, + Direction::Receive => Side::Server, + }) + } + + fn handle_new_session_ticket( + &mut self, + cx: &mut KernelContext<'_>, + message: &NewSessionTicketPayloadTls13, + ) -> Result<(), Error> { + self.handle_new_ticket_impl(cx, message) + } +} + +struct ExpectQuicTraffic(ExpectTraffic); + +impl State<ClientConnectionData> for ExpectQuicTraffic { + fn handle<'m>( + mut self: Box<Self>, + cx: &mut ClientContext<'_>, + m: Message<'m>, + ) -> hs::NextStateOrError<'m> + where + Self: 'm, + { + let nst = require_handshake_msg!( + m, + HandshakeType::NewSessionTicket, + HandshakePayload::NewSessionTicketTls13 + )?; + self.0 + .handle_new_ticket_tls13(cx, nst)?; + Ok(self) + } + + fn export_keying_material( + &self, + output: &mut [u8], + label: &[u8], + context: Option<&[u8]>, + ) -> Result<(), Error> { + self.0 + .export_keying_material(output, label, context) + } + + fn into_external_state(self: Box<Self>) -> Result<Box<dyn KernelState + 'static>, Error> { + Ok(self) + } + + fn into_owned(self: Box<Self>) -> hs::NextState<'static> { + self + } +} + +impl KernelState for ExpectQuicTraffic { + fn update_secrets(&mut self, _: Direction) -> Result<ConnectionTrafficSecrets, Error> { + Err(Error::General( + "KeyUpdate is not supported for QUIC connections".into(), + )) + } + + fn handle_new_session_ticket( + &mut self, + cx: &mut KernelContext<'_>, + nst: &NewSessionTicketPayloadTls13, + ) -> Result<(), Error> { + self.0.handle_new_ticket_impl(cx, nst) + } +} |
