mirror of https://github.com/ctz/rustls
client: add support for sending unsupported cipher suites
This commit is contained in:
parent
8c75101878
commit
2682eb7446
|
@ -187,6 +187,7 @@ impl ConfigBuilder<ClientConfig, WantsClientCert> {
|
||||||
#[cfg(feature = "secret_extraction")]
|
#[cfg(feature = "secret_extraction")]
|
||||||
enable_secret_extraction: false,
|
enable_secret_extraction: false,
|
||||||
enable_early_data: false,
|
enable_early_data: false,
|
||||||
|
unsupported_suites: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
use crate::builder::{ConfigBuilder, WantsCipherSuites};
|
use crate::builder::{ConfigBuilder, WantsCipherSuites};
|
||||||
use crate::conn::{CommonState, ConnectionCommon, Protocol, Side};
|
use crate::conn::{CommonState, ConnectionCommon, Protocol, Side};
|
||||||
use crate::enums::{CipherSuite, ProtocolVersion, SignatureScheme};
|
use crate::enums::{CipherSuite, ProtocolVersion, SignatureScheme};
|
||||||
use crate::error::Error;
|
use crate::error::{Error, PeerMisbehaved};
|
||||||
use crate::kx::SupportedKxGroup;
|
use crate::kx::SupportedKxGroup;
|
||||||
#[cfg(feature = "logging")]
|
#[cfg(feature = "logging")]
|
||||||
use crate::log::trace;
|
use crate::log::trace;
|
||||||
|
|
||||||
#[cfg(feature = "quic")]
|
|
||||||
use crate::msgs::enums::AlertDescription;
|
use crate::msgs::enums::AlertDescription;
|
||||||
use crate::msgs::enums::NamedGroup;
|
use crate::msgs::enums::NamedGroup;
|
||||||
use crate::msgs::handshake::ClientExtension;
|
use crate::msgs::handshake::ClientExtension;
|
||||||
|
@ -195,6 +193,11 @@ pub struct ClientConfig {
|
||||||
///
|
///
|
||||||
/// The default is false.
|
/// The default is false.
|
||||||
pub enable_early_data: bool,
|
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<CipherSuite>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for ClientConfig {
|
impl fmt::Debug for ClientConfig {
|
||||||
|
@ -238,11 +241,31 @@ impl ClientConfig {
|
||||||
danger::DangerousClientConfig { cfg: self }
|
danger::DangerousClientConfig { cfg: self }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn find_cipher_suite(&self, suite: CipherSuite) -> Option<SupportedCipherSuite> {
|
pub(super) fn find_cipher_suite(
|
||||||
self.cipher_suites
|
&self,
|
||||||
|
suite: CipherSuite,
|
||||||
|
retried: bool,
|
||||||
|
common: &mut CommonState,
|
||||||
|
) -> Result<SupportedCipherSuite, Error> {
|
||||||
|
let supported = self
|
||||||
|
.cipher_suites
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.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,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -339,6 +339,12 @@ fn emit_client_hello_for_retry(
|
||||||
.collect();
|
.collect();
|
||||||
// We don't do renegotiation at all, in fact.
|
// We don't do renegotiation at all, in fact.
|
||||||
cipher_suites.push(CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
|
cipher_suites.push(CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
|
||||||
|
cipher_suites.extend(
|
||||||
|
config
|
||||||
|
.unsupported_suites
|
||||||
|
.iter()
|
||||||
|
.copied(),
|
||||||
|
);
|
||||||
|
|
||||||
let mut chp = HandshakeMessagePayload {
|
let mut chp = HandshakeMessagePayload {
|
||||||
typ: HandshakeType::ClientHello,
|
typ: HandshakeType::ClientHello,
|
||||||
|
@ -555,12 +561,7 @@ impl State<ClientConnectionData> for ExpectServerHello {
|
||||||
|
|
||||||
let suite = self
|
let suite = self
|
||||||
.config
|
.config
|
||||||
.find_cipher_suite(server_hello.cipher_suite)
|
.find_cipher_suite(server_hello.cipher_suite, false, cx.common)?;
|
||||||
.ok_or_else(|| {
|
|
||||||
cx.common
|
|
||||||
.send_fatal_alert(AlertDescription::HandshakeFailure);
|
|
||||||
Error::PeerMisbehaved(PeerMisbehaved::SelectedUnofferedCipherSuite)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if version != suite.version().version {
|
if version != suite.version().version {
|
||||||
return Err(cx
|
return Err(cx
|
||||||
|
@ -715,18 +716,10 @@ impl ExpectServerHelloOrHelloRetryRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Or asks us to use a ciphersuite we didn't offer.
|
// Or asks us to use a ciphersuite we didn't offer.
|
||||||
let maybe_cs = self
|
let cs = self
|
||||||
.next
|
.next
|
||||||
.config
|
.config
|
||||||
.find_cipher_suite(hrr.cipher_suite);
|
.find_cipher_suite(hrr.cipher_suite, true, cx.common)?;
|
||||||
let cs = match maybe_cs {
|
|
||||||
Some(cs) => cs,
|
|
||||||
None => {
|
|
||||||
return Err(cx.common.illegal_param(
|
|
||||||
PeerMisbehaved::IllegalHelloRetryRequestWithUnofferedCipherSuite,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// HRR selects the ciphersuite.
|
// HRR selects the ciphersuite.
|
||||||
cx.common.suite = Some(cs);
|
cx.common.suite = Some(cs);
|
||||||
|
|
|
@ -92,6 +92,9 @@ pub enum Error {
|
||||||
/// The `max_fragment_size` value supplied in configuration was too small,
|
/// The `max_fragment_size` value supplied in configuration was too small,
|
||||||
/// or too large.
|
/// or too large.
|
||||||
BadMaxFragmentSize,
|
BadMaxFragmentSize,
|
||||||
|
|
||||||
|
/// The peer selected a unsupported cipher suite that we configured.
|
||||||
|
UnsupportedSuiteSelected,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
@ -321,6 +324,9 @@ impl fmt::Display for Error {
|
||||||
Self::BadMaxFragmentSize => {
|
Self::BadMaxFragmentSize => {
|
||||||
write!(f, "the supplied max_fragment_size was too small or large")
|
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),
|
Self::General(ref err) => write!(f, "unexpected error: {}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue