From 2682eb7446e203be45cb6c102feba728cf699c06 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 2 Feb 2023 12:18:50 +0100 Subject: [PATCH] client: add support for sending unsupported cipher suites --- rustls/src/client/builder.rs | 1 + rustls/src/client/client_conn.rs | 35 ++++++++++++++++++++++++++------ rustls/src/client/hs.rs | 25 ++++++++--------------- rustls/src/error.rs | 6 ++++++ 4 files changed, 45 insertions(+), 22 deletions(-) diff --git a/rustls/src/client/builder.rs b/rustls/src/client/builder.rs index 464bfb9e..eff09f84 100644 --- a/rustls/src/client/builder.rs +++ b/rustls/src/client/builder.rs @@ -187,6 +187,7 @@ impl ConfigBuilder { #[cfg(feature = "secret_extraction")] enable_secret_extraction: false, enable_early_data: false, + unsupported_suites: Vec::new(), } } } diff --git a/rustls/src/client/client_conn.rs b/rustls/src/client/client_conn.rs index 72d9ac67..aafdde3e 100644 --- a/rustls/src/client/client_conn.rs +++ b/rustls/src/client/client_conn.rs @@ -1,12 +1,10 @@ use crate::builder::{ConfigBuilder, WantsCipherSuites}; use crate::conn::{CommonState, ConnectionCommon, Protocol, Side}; use crate::enums::{CipherSuite, ProtocolVersion, SignatureScheme}; -use crate::error::Error; +use crate::error::{Error, PeerMisbehaved}; use crate::kx::SupportedKxGroup; #[cfg(feature = "logging")] use crate::log::trace; - -#[cfg(feature = "quic")] use crate::msgs::enums::AlertDescription; use crate::msgs::enums::NamedGroup; use crate::msgs::handshake::ClientExtension; @@ -195,6 +193,11 @@ pub struct ClientConfig { /// /// The default is false. pub enable_early_data: bool, + + /// Send unsupported cipher suites to appease fingerprinting servers. + /// + /// Note: if the server selects one of these suites, the connection will fail. + pub unsupported_suites: Vec, } impl fmt::Debug for ClientConfig { @@ -238,11 +241,31 @@ impl ClientConfig { danger::DangerousClientConfig { cfg: self } } - pub(super) fn find_cipher_suite(&self, suite: CipherSuite) -> Option { - self.cipher_suites + pub(super) fn find_cipher_suite( + &self, + suite: CipherSuite, + retried: bool, + common: &mut CommonState, + ) -> Result { + let supported = self + .cipher_suites .iter() .copied() - .find(|&scs| scs.suite() == suite) + .find(|&scs| scs.suite() == suite); + + if let Some(supported) = supported { + Ok(supported) + } else if self.unsupported_suites.contains(&suite) { + Err(Error::UnsupportedSuiteSelected) + } else if retried { + Err(common + .illegal_param(PeerMisbehaved::IllegalHelloRetryRequestWithUnofferedCipherSuite)) + } else { + common.send_fatal_alert(AlertDescription::HandshakeFailure); + Err(Error::PeerMisbehaved( + PeerMisbehaved::SelectedUnofferedCipherSuite, + )) + } } } diff --git a/rustls/src/client/hs.rs b/rustls/src/client/hs.rs index b49707d2..81c77745 100644 --- a/rustls/src/client/hs.rs +++ b/rustls/src/client/hs.rs @@ -339,6 +339,12 @@ fn emit_client_hello_for_retry( .collect(); // We don't do renegotiation at all, in fact. cipher_suites.push(CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV); + cipher_suites.extend( + config + .unsupported_suites + .iter() + .copied(), + ); let mut chp = HandshakeMessagePayload { typ: HandshakeType::ClientHello, @@ -555,12 +561,7 @@ impl State for ExpectServerHello { let suite = self .config - .find_cipher_suite(server_hello.cipher_suite) - .ok_or_else(|| { - cx.common - .send_fatal_alert(AlertDescription::HandshakeFailure); - Error::PeerMisbehaved(PeerMisbehaved::SelectedUnofferedCipherSuite) - })?; + .find_cipher_suite(server_hello.cipher_suite, false, cx.common)?; if version != suite.version().version { return Err(cx @@ -715,18 +716,10 @@ impl ExpectServerHelloOrHelloRetryRequest { } // Or asks us to use a ciphersuite we didn't offer. - let maybe_cs = self + let cs = self .next .config - .find_cipher_suite(hrr.cipher_suite); - let cs = match maybe_cs { - Some(cs) => cs, - None => { - return Err(cx.common.illegal_param( - PeerMisbehaved::IllegalHelloRetryRequestWithUnofferedCipherSuite, - )); - } - }; + .find_cipher_suite(hrr.cipher_suite, true, cx.common)?; // HRR selects the ciphersuite. cx.common.suite = Some(cs); diff --git a/rustls/src/error.rs b/rustls/src/error.rs index a547ec6b..0bffecf8 100644 --- a/rustls/src/error.rs +++ b/rustls/src/error.rs @@ -92,6 +92,9 @@ pub enum Error { /// The `max_fragment_size` value supplied in configuration was too small, /// or too large. BadMaxFragmentSize, + + /// The peer selected a unsupported cipher suite that we configured. + UnsupportedSuiteSelected, } #[non_exhaustive] @@ -321,6 +324,9 @@ impl fmt::Display for Error { Self::BadMaxFragmentSize => { write!(f, "the supplied max_fragment_size was too small or large") } + Self::UnsupportedSuiteSelected => { + write!(f, "configured unsupported cipher suite selected") + } Self::General(ref err) => write!(f, "unexpected error: {}", err), } }