Skip to content

Commit

Permalink
Select key exchange group and cipher suite together
Browse files Browse the repository at this point in the history
This is complex because the choice of usable cipher suites depends
on selected protocol version, and the set of mutually supported
key exchange groups.  Then, the usable set of key exchange groups
depends on the actually-selected cipher suite.
  • Loading branch information
ctz committed Feb 19, 2024
1 parent d23e58c commit 651b5a4
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 214 deletions.
6 changes: 0 additions & 6 deletions rustls/src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,6 @@ impl CryptoProvider {
&& secure_random.fips()
&& key_provider.fips()
}

pub(crate) fn supported_kx_group_names(&self) -> impl Iterator<Item = NamedGroup> + '_ {
self.kx_groups
.iter()
.map(|skxg| skxg.name())
}
}

static PROCESS_DEFAULT_PROVIDER: OnceCell<Arc<CryptoProvider>> = OnceCell::new();
Expand Down
204 changes: 142 additions & 62 deletions rustls/src/server/hs.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
use crate::common_state::State;
use crate::common_state::{Protocol, State};
use crate::conn::ConnectionRandoms;
#[cfg(feature = "tls12")]
use crate::enums::CipherSuite;
use crate::enums::{AlertDescription, HandshakeType, ProtocolVersion, SignatureScheme};
use crate::crypto::SupportedKxGroup;
use crate::enums::{
AlertDescription, CipherSuite, HandshakeType, ProtocolVersion, SignatureAlgorithm,
SignatureScheme,
};
use crate::error::{Error, PeerIncompatible, PeerMisbehaved};
use crate::hash_hs::{HandshakeHash, HandshakeHashBuffer};
#[cfg(feature = "logging")]
use crate::log::{debug, trace};
use crate::msgs::enums::{Compression, ExtensionType};
use crate::msgs::enums::{Compression, ExtensionType, NamedGroup};
#[cfg(feature = "tls12")]
use crate::msgs::handshake::SessionId;
use crate::msgs::handshake::{ClientHelloPayload, Random, ServerExtension};
use crate::msgs::handshake::{ClientHelloPayload, KeyExchangeAlgorithm, Random, ServerExtension};
use crate::msgs::handshake::{ConvertProtocolNameList, ConvertServerNameList, HandshakePayload};
use crate::msgs::message::{Message, MessagePayload};
use crate::msgs::persist;
Expand Down Expand Up @@ -334,63 +336,20 @@ impl ExpectClientHello {
};
let certkey = ActiveCertifiedKey::from_certified_key(&certkey);

let mut suitable_suites = self
.config
.provider
.cipher_suites
.iter()
.filter(|suite| {
// Reduce our supported ciphersuites by the certificate.
suite.usable_for_signature_algorithm(certkey.get_key().algorithm())
// And version
&& suite.version().version == version && suite.usable_for_protocol(cx.common.protocol)
})
.copied()
.collect::<Vec<_>>();

let suitable_suites_before_kx_reduce_not_empty = !suitable_suites.is_empty();

// And supported kx groups
suites::reduce_given_kx_groups(
&mut suitable_suites,
client_hello.namedgroups_extension(),
&self
.config
.provider
.supported_kx_group_names()
.collect::<Vec<_>>(),
);

if suitable_suites_before_kx_reduce_not_empty && suitable_suites.is_empty() {
return Err(cx.common.send_fatal_alert(
AlertDescription::HandshakeFailure,
PeerIncompatible::NoKxGroupsInCommon,
));
}

// RFC 7919 (https://datatracker.ietf.org/doc/html/rfc7919#section-4) requires us to send
// the InsufficientSecurity alert in case we don't recognize client's FFDHE groups (i.e.,
// `suitable_suites` becomes empty). But that does not make a lot of sense (e.g., client
// proposes FFDHE4096 and we only support FFDHE2048), so we ignore that requirement here,
// and continue to send HandshakeFailure.

let suite = if self.config.ignore_client_order {
suites::choose_ciphersuite_preferring_server(
&client_hello.cipher_suites,
&suitable_suites,
)
} else {
suites::choose_ciphersuite_preferring_client(
let (suite, skxg) = self
.choose_suite_and_kx_group(
version,
certkey.get_key().algorithm(),
cx.common.protocol,
client_hello
.namedgroups_extension()
.unwrap_or(&[]),
&client_hello.cipher_suites,
&suitable_suites,
)
}
.ok_or_else(|| {
cx.common.send_fatal_alert(
AlertDescription::HandshakeFailure,
PeerIncompatible::NoCipherSuitesInCommon,
)
})?;
.map_err(|incompat| {
cx.common
.send_fatal_alert(AlertDescription::HandshakeFailure, incompat)
})?;

debug!("decided upon suite {:?}", suite);
cx.common.suite = Some(suite);
Expand Down Expand Up @@ -427,7 +386,7 @@ impl ExpectClientHello {
send_tickets: self.send_tickets,
extra_exts: self.extra_exts,
}
.handle_client_hello(cx, certkey, m, client_hello, sig_schemes),
.handle_client_hello(cx, certkey, m, client_hello, skxg, sig_schemes),
#[cfg(feature = "tls12")]
SupportedCipherSuite::Tls12(suite) => tls12::CompleteClientHelloHandling {
config: self.config,
Expand All @@ -444,11 +403,132 @@ impl ExpectClientHello {
certkey,
m,
client_hello,
skxg,
sig_schemes,
tls13_enabled,
),
}
}

fn choose_suite_and_kx_group(
&self,
selected_version: ProtocolVersion,
sig_key_algorithm: SignatureAlgorithm,
protocol: Protocol,
client_groups: &[NamedGroup],
client_suites: &[CipherSuite],
) -> Result<(SupportedCipherSuite, &'static dyn SupportedKxGroup), PeerIncompatible> {
// Determine which `KeyExchangeAlgorithm`s are theoretically possible, based
// on the offered and supported groups.
let mut ecdhe_possible = false;
let mut ffdhe_possible = false;
let mut ffdhe_offered = false;
let mut supported_groups = Vec::with_capacity(client_groups.len());

for offered_group in client_groups {
let supported = self
.config
.provider
.kx_groups
.iter()
.find(|skxg| skxg.name() == *offered_group);

match offered_group.key_exchange_algorithm() {
KeyExchangeAlgorithm::DHE => {
ffdhe_possible |= supported.is_some();
ffdhe_offered = true;
}
KeyExchangeAlgorithm::ECDHE => {
ecdhe_possible |= supported.is_some();
}
}

supported_groups.push(supported);
}

let first_supported_dhe_kxg = if selected_version == ProtocolVersion::TLSv1_2 {
// https://datatracker.ietf.org/doc/html/rfc7919#section-4 (paragraph 2)
let first_supported_dhe_kxg = self
.config
.provider
.kx_groups
.iter()
.find(|skxg| skxg.name().key_exchange_algorithm() == KeyExchangeAlgorithm::DHE);
ffdhe_possible |= !ffdhe_offered && first_supported_dhe_kxg.is_some();
first_supported_dhe_kxg
} else {
// In TLS1.3, the server may only directly negotiate a group.
None
};

if !ecdhe_possible && !ffdhe_possible {
return Err(PeerIncompatible::NoKxGroupsInCommon);
}

let suitable_suites = self
.config
.provider
.cipher_suites
.iter()
.filter(|suite| {
// Reduce our supported ciphersuites by the certified key's algorithm.
suite.usable_for_signature_algorithm(sig_key_algorithm)
// And version
&& suite.version().version == selected_version
// And protocol
&& suite.usable_for_protocol(protocol)
// And key exchange groups
&& (!ecdhe_possible || suite.usable_for_kx_algorithm(KeyExchangeAlgorithm::ECDHE))
&& (!ffdhe_possible || suite.usable_for_kx_algorithm(KeyExchangeAlgorithm::DHE))
})
.copied()
.collect::<Vec<_>>();

// RFC 7919 (https://datatracker.ietf.org/doc/html/rfc7919#section-4) requires us to send
// the InsufficientSecurity alert in case we don't recognize client's FFDHE groups (i.e.,
// `suitable_suites` becomes empty). But that does not make a lot of sense (e.g., client
// proposes FFDHE4096 and we only support FFDHE2048), so we ignore that requirement here,
// and continue to send HandshakeFailure.

let suite = if self.config.ignore_client_order {
suites::choose_ciphersuite_preferring_server(client_suites, &suitable_suites)
} else {
suites::choose_ciphersuite_preferring_client(client_suites, &suitable_suites)
}
.ok_or(PeerIncompatible::NoCipherSuitesInCommon)?;

// Finally, choose a key exchange group that is compatible with the selected cipher
// suite.
let maybe_skxg = supported_groups
.iter()
.find_map(|maybe_skxg| match maybe_skxg {
Some(skxg) => suite
.usable_for_kx_algorithm(skxg.name().key_exchange_algorithm())
.then(|| *skxg),
None => None,
});

if selected_version == ProtocolVersion::TLSv1_3 {
// This unwrap is structurally guaranteed by the early return for `!ffdhe_possible && !ecdhe_possible`
return Ok((suite, *maybe_skxg.unwrap()));
}

// For TLS1.2, the server can unilaterally choose a DHE group if it has one and
// there was no better option.
match maybe_skxg {
Some(skxg) => Ok((suite, *skxg)),
None if suite.usable_for_kx_algorithm(KeyExchangeAlgorithm::DHE) => {
// If kx for the selected cipher suite is DHE and no DHE groups are specified in the extension,
// the server is free to choose DHE params, we choose the first DHE kx group of the provider.
if let Some(server_selected_ffdhe_skxg) = first_supported_dhe_kxg {
Ok((suite, *server_selected_ffdhe_skxg))
} else {
Err(PeerIncompatible::NoKxGroupsInCommon)
}
}
None => Err(PeerIncompatible::NoKxGroupsInCommon),
}
}
}

impl State<ServerConnectionData> for ExpectClientHello {
Expand Down
60 changes: 3 additions & 57 deletions rustls/src/server/tls12.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub(super) use client_hello::CompleteClientHelloHandling;
mod client_hello {
use pki_types::CertificateDer;

use crate::crypto::{KeyExchangeAlgorithm, SupportedKxGroup};
use crate::crypto::SupportedKxGroup;
use crate::enums::SignatureScheme;
use crate::msgs::enums::ECPointFormat;
use crate::msgs::enums::{ClientCertificateType, Compression};
Expand Down Expand Up @@ -73,6 +73,7 @@ mod client_hello {
server_key: ActiveCertifiedKey,
chm: &Message,
client_hello: &ClientHelloPayload,
selected_kxg: &'static dyn SupportedKxGroup,
sigschemes_ext: Vec<SignatureScheme>,
tls13_enabled: bool,
) -> hs::NextStateOrError<'static> {
Expand Down Expand Up @@ -175,8 +176,6 @@ mod client_hello {
));
}

let group = self.pick_kx_group(client_hello, cx)?;

let ecpoint = ECPointFormat::SUPPORTED
.iter()
.find(|format| ecpoints_ext.contains(format))
Expand Down Expand Up @@ -220,7 +219,7 @@ mod client_hello {
&mut self.transcript,
cx.common,
sigschemes,
group,
selected_kxg,
server_key.get_key(),
&self.randoms,
)?;
Expand Down Expand Up @@ -253,59 +252,6 @@ mod client_hello {
}
}

fn pick_kx_group(
&self,
client_hello: &ClientHelloPayload,
cx: &mut ServerContext<'_>,
) -> Result<&'static dyn SupportedKxGroup, Error> {
let peer_groups_ext = client_hello.namedgroups_extension();

if peer_groups_ext.is_none() && self.suite.kx == KeyExchangeAlgorithm::ECDHE {
return Err(cx.common.send_fatal_alert(
AlertDescription::HandshakeFailure,
PeerIncompatible::NamedGroupsExtensionRequired,
));
}

trace!("namedgroups {:?}", peer_groups_ext);

let peer_kx_groups = peer_groups_ext.unwrap_or(&[]);
let our_kx_groups = &self.config.provider.kx_groups;

let matching_kx_group = our_kx_groups.iter().find(|skxg| {
skxg.name().key_exchange_algorithm() == self.suite.kx
&& peer_kx_groups.contains(&skxg.name())
});
if let Some(&kx_group) = matching_kx_group {
return Ok(kx_group);
}

let mut send_err = || {
cx.common.send_fatal_alert(
AlertDescription::HandshakeFailure,
PeerIncompatible::NoKxGroupsInCommon,
)
};

// If kx for the selected cipher suite is DHE and no DHE groups are specified in the extension,
// the server is free to choose DHE params, we choose the first DHE kx group of the provider.
use KeyExchangeAlgorithm::DHE;
let we_get_to_choose_dhe_group = self.suite.kx == DHE
&& !peer_kx_groups
.iter()
.any(|g| g.key_exchange_algorithm() == DHE);

if !we_get_to_choose_dhe_group {
return Err(send_err());
}
trace!("No DHE groups specified in ClientHello groups extension, server choosing DHE parameters");
our_kx_groups
.iter()
.find(|skxg| skxg.name().key_exchange_algorithm() == DHE)
.cloned()
.ok_or_else(send_err)
}

fn start_resumption(
mut self,
cx: &mut ServerContext<'_>,
Expand Down
Loading

0 comments on commit 651b5a4

Please sign in to comment.