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 13, 2024
1 parent b1fda4c commit 9725fab
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 216 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
234 changes: 170 additions & 64 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,16 @@ 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(
&client_hello.cipher_suites,
&suitable_suites,
)
}
.ok_or_else(|| {
cx.common.send_fatal_alert(
AlertDescription::HandshakeFailure,
PeerIncompatible::NoCipherSuitesInCommon,
)
})?;
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,
cx,
)?;

debug!("decided upon suite {:?}", suite);
cx.common.suite = Some(suite);
Expand Down Expand Up @@ -427,7 +382,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 +399,162 @@ 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],
cx: &mut ServerContext<'_>,
) -> Result<(SupportedCipherSuite, &'static dyn SupportedKxGroup), Error> {
// Determine which `KeyExchangeAlgorithm`s are theoretically possible, based
// on the offered and supported groups.
let (ecdhe_possible, mut ffdhe_possible, ffdhe_offered, supported_groups) =
client_groups.iter().fold(
(false, false, false, Vec::with_capacity(client_groups.len())),
|(
mut ecdhe_possible,
mut ffdhe_possible,
mut ffdhe_offered,
mut supported_groups,
),
item| {
let supported = self
.config
.provider
.kx_groups
.iter()
.find(|skxg| skxg.name() == *item);

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

supported_groups.push(supported);

(
ecdhe_possible,
ffdhe_possible,
ffdhe_offered,
supported_groups,
)
},
);

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(cx.common.send_fatal_alert(
AlertDescription::HandshakeFailure,
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)

Check warning on line 506 in rustls/src/server/hs.rs

View check run for this annotation

Codecov / codecov/patch

rustls/src/server/hs.rs#L506

Added line #L506 was not covered by tests
} else {
suites::choose_ciphersuite_preferring_client(client_suites, &suitable_suites)
}
.ok_or_else(|| {
cx.common.send_fatal_alert(
AlertDescription::HandshakeFailure,
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,
});

// For TLS1.2, the server can unilaterally choose a DHE group if it has one and
// there was no better option.
let skxg = if selected_version == ProtocolVersion::TLSv1_2 {
match maybe_skxg {
Some(skxg) => *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 {
*server_selected_ffdhe_skxg
} else {
return Err(cx.common.send_fatal_alert(
AlertDescription::HandshakeFailure,
PeerIncompatible::NoKxGroupsInCommon,
));

Check warning on line 542 in rustls/src/server/hs.rs

View check run for this annotation

Codecov / codecov/patch

rustls/src/server/hs.rs#L539-L542

Added lines #L539 - L542 were not covered by tests
}
}
None => {
return Err(cx.common.send_fatal_alert(
AlertDescription::HandshakeFailure,
PeerIncompatible::NoKxGroupsInCommon,
));

Check warning on line 549 in rustls/src/server/hs.rs

View check run for this annotation

Codecov / codecov/patch

rustls/src/server/hs.rs#L546-L549

Added lines #L546 - L549 were not covered by tests
}
}
} else {
*maybe_skxg.unwrap()
};

Ok((suite, skxg))
}
}

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 9725fab

Please sign in to comment.