summaryrefslogtreecommitdiff
path: root/vendor/rustls/src/client
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/rustls/src/client')
-rw-r--r--vendor/rustls/src/client/builder.rs186
-rw-r--r--vendor/rustls/src/client/client_conn.rs1062
-rw-r--r--vendor/rustls/src/client/common.rs119
-rw-r--r--vendor/rustls/src/client/ech.rs899
-rw-r--r--vendor/rustls/src/client/handy.rs390
-rw-r--r--vendor/rustls/src/client/hs.rs1178
-rw-r--r--vendor/rustls/src/client/test.rs712
-rw-r--r--vendor/rustls/src/client/tls12.rs1372
-rw-r--r--vendor/rustls/src/client/tls13.rs1700
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)
+ }
+}