Implement FFDHE support

+ Make server avoid cipher suites with kx without common kx groups with client
+ Handle FFDHE shared secret leading zeros correctly
This commit is contained in:
Arash Sahebolamri 2023-12-13 16:34:42 -08:00 committed by Joe Birr-Pixton
parent 1340ea95e6
commit c8c56a7aef
25 changed files with 1099 additions and 69 deletions

12
Cargo.lock generated
View File

@ -1498,6 +1498,17 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "num-bigint"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-bigint-dig"
version = "0.8.4"
@ -2038,6 +2049,7 @@ dependencies = [
"bencher",
"env_logger",
"log",
"num-bigint",
"ring",
"rustls-pemfile 2.0.0",
"rustls-pki-types",

View File

@ -38,6 +38,7 @@ base64 = "0.21"
bencher = "0.1.5"
env_logger = "0.10" # 0.11 requires 1.71 MSRV even as a dev-dep (due to manifest features)
log = "0.4.4"
num-bigint = "0.4.4"
rustls-pemfile = "2"
webpki-roots = "0.26"

View File

@ -1,8 +1,9 @@
use crate::crypto::CryptoProvider;
use crate::error::Error;
use crate::versions;
use crate::{crypto::CryptoProvider, msgs::handshake::ALL_KEY_EXCHANGE_ALGORITHMS};
use alloc::format;
use alloc::vec::Vec;
use core::fmt;
use core::marker::PhantomData;
use std::sync::Arc;
@ -210,6 +211,35 @@ impl<S: ConfigSide> ConfigBuilder<S, WantsVersions> {
return Err(Error::General("no kx groups configured".into()));
}
// verifying cipher suites have matching kx groups
let mut supported_kx_algos = Vec::with_capacity(ALL_KEY_EXCHANGE_ALGORITHMS.len());
for group in self.state.provider.kx_groups.iter() {
let kx = group.name().key_exchange_algorithm();
if !supported_kx_algos.contains(&kx) {
supported_kx_algos.push(kx);
}
// Small optimization. We don't need to go over other key exchange groups
// if we already cover all supported key exchange algorithms
if supported_kx_algos.len() == ALL_KEY_EXCHANGE_ALGORITHMS.len() {
break;
}
}
for cs in self.state.provider.cipher_suites.iter() {
let cs_kx = cs.key_exchange_algorithms();
if cs_kx
.iter()
.any(|kx| supported_kx_algos.contains(kx))
{
continue;
}
let suite_name = cs.common().suite;
return Err(Error::General(alloc::format!(
"Ciphersuite {suite_name:?} requires {cs_kx:?} key exchange, but no {cs_kx:?}-compatible \
key exchange groups were present in `CryptoProvider`'s `kx_groups` field",
)));
}
Ok(ConfigBuilder {
state: WantsVerifier {
provider: self.state.provider,

View File

@ -3,7 +3,7 @@ use crate::bs_debug;
use crate::check::inappropriate_handshake_message;
use crate::common_state::{CommonState, State};
use crate::conn::ConnectionRandoms;
use crate::crypto::ActiveKeyExchange;
use crate::crypto::{ActiveKeyExchange, KeyExchangeAlgorithm};
use crate::enums::{AlertDescription, CipherSuite, ContentType, HandshakeType, ProtocolVersion};
use crate::error::{Error, PeerIncompatible, PeerMisbehaved};
use crate::hash_hs::HandshakeHashBuffer;
@ -216,7 +216,6 @@ fn emit_client_hello_for_retry(
let mut exts = vec![
ClientExtension::SupportedVersions(supported_versions),
ClientExtension::EcPointFormats(ECPointFormat::SUPPORTED.to_vec()),
ClientExtension::NamedGroups(
config
.provider
@ -234,6 +233,18 @@ fn emit_client_hello_for_retry(
ClientExtension::CertificateStatusRequest(CertificateStatusRequest::build_ocsp()),
];
// 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.push(ClientExtension::EcPointFormats(
ECPointFormat::SUPPORTED.to_vec(),
));
}
if let (ServerName::DnsName(dns), true) = (&input.server_name, config.enable_sni) {
// We only want to send the SNI extension if the server name contains a DNS name.
exts.push(ClientExtension::make_sni(dns));

View File

@ -1,18 +1,19 @@
use crate::check::{inappropriate_handshake_message, inappropriate_message};
use crate::common_state::{CommonState, Side, State};
use crate::conn::ConnectionRandoms;
use crate::crypto::KeyExchangeAlgorithm;
use crate::enums::ProtocolVersion;
use crate::enums::{AlertDescription, ContentType, HandshakeType};
use crate::error::{Error, InvalidMessage, PeerIncompatible, PeerMisbehaved};
use crate::hash_hs::HandshakeHash;
#[cfg(feature = "logging")]
use crate::log::{debug, trace, warn};
use crate::msgs::base::{Payload, PayloadU8};
use crate::msgs::base::{Payload, PayloadU16, PayloadU8};
use crate::msgs::ccs::ChangeCipherSpecPayload;
use crate::msgs::codec::Codec;
use crate::msgs::handshake::{
CertificateChain, HandshakeMessagePayload, HandshakePayload, NewSessionTicketPayload,
ServerEcdhParams, SessionId,
CertificateChain, ClientDhParams, ClientEcdhParams, ClientKeyExchangeParams,
HandshakeMessagePayload, HandshakePayload, NewSessionTicketPayload, ServerKeyExchangeParams,
SessionId,
};
use crate::msgs::message::{Message, MessagePayload};
use crate::msgs::persist;
@ -460,8 +461,14 @@ impl State<ClientConnectionData> for ExpectServerKx<'_> {
#[cfg_attr(not(feature = "logging"), allow(unused_variables))]
{
let crate::msgs::handshake::ServerKeyExchangeParams::Ecdh(ecdh) = kx.params;
debug!("ECDHE curve is {:?}", ecdh.curve_params);
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 {
@ -512,10 +519,22 @@ fn emit_certificate(
common.send_msg(cert, false);
}
fn emit_client_kx(transcript: &mut HandshakeHash, common: &mut CommonState, pub_key: &[u8]) {
fn emit_client_kx(
transcript: &mut HandshakeHash,
kxa: KeyExchangeAlgorithm,
common: &mut CommonState,
pub_key: &[u8],
) {
let mut buf = Vec::new();
let ecpoint = PayloadU8::new(Vec::from(pub_key));
ecpoint.encode(&mut buf);
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 {
@ -894,9 +913,14 @@ impl State<ClientConnectionData> for ExpectServerDone<'_> {
}
// 5a.
let ecdh_params =
tls12::decode_kx_params::<ServerEcdhParams>(cx.common, &st.server_kx.kx_params)?;
let named_group = ecdh_params.curve_params.named_group;
let kx_params = tls12::decode_kx_params::<ServerKeyExchangeParams>(
st.suite.kx,
cx.common,
&st.server_kx.kx_params,
)?;
let named_group = kx_params
.named_group()
.ok_or(PeerMisbehaved::SelectedUnofferedKxGroup)?;
let skxg = match st.config.find_kx_group(named_group) {
Some(skxg) => skxg,
None => {
@ -909,7 +933,7 @@ impl State<ClientConnectionData> for ExpectServerDone<'_> {
// 5b.
let mut transcript = st.transcript;
emit_client_kx(&mut transcript, cx.common, kx.pub_key());
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
@ -926,7 +950,7 @@ impl State<ClientConnectionData> for ExpectServerDone<'_> {
// 5e. Now commit secrets.
let secrets = ConnectionSecrets::from_key_exchange(
kx,
&ecdh_params.public.0,
kx_params.pub_key(),
ems_seed,
st.randoms,
suite,

View File

@ -10,6 +10,7 @@ use crate::msgs::fragmenter::MAX_FRAGMENT_LEN;
use crate::msgs::message::{BorrowedPlainMessage, OpaqueMessage};
use crate::suites::{CipherSuiteCommon, ConnectionTrafficSecrets, SupportedCipherSuite};
use crate::tls12::Tls12CipherSuite;
use crate::version::TLS12;
use alloc::boxed::Box;
use alloc::vec::Vec;
@ -442,7 +443,7 @@ impl Prf for Tls12Prf {
) -> Result<(), Error> {
self.for_secret(
output,
kx.complete(peer_pub_key)?
kx.complete_for_tls_version(peer_pub_key, &TLS12)?
.secret_bytes(),
label,
seed,

View File

@ -1,5 +1,5 @@
use crate::sign::SigningKey;
use crate::suites;
use crate::{suites, ProtocolVersion, SupportedProtocolVersion};
use crate::{Error, NamedGroup};
use alloc::boxed::Box;
@ -39,6 +39,7 @@ pub mod hash;
/// HMAC interfaces.
pub mod hmac;
#[cfg(feature = "tls12")]
/// Cryptography specific to TLS1.2.
pub mod tls12;
@ -165,6 +166,9 @@ pub struct CryptoProvider {
/// is the highest priority.
///
/// The `SupportedCipherSuite` type carries both configuration and implementation.
///
/// A valid `CryptoProvider` must ensure that all cipher suites are accompanied by at least
/// one matching key exchange group in [`CryptoProvider::kx_groups`].
pub cipher_suites: Vec<suites::SupportedCipherSuite>,
/// List of supported key exchange groups, in preference order -- the
@ -213,6 +217,12 @@ 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())
}
}
/// A source of cryptographically secure randomness.
@ -304,16 +314,64 @@ pub trait ActiveKeyExchange: Send + Sync {
/// mis-encoded, or an invalid public key (such as, but not limited to, being
/// in a small order subgroup).
///
/// If the key exchange algorithm is FFDHE, the result must be left-padded with zeros,
/// as required by [RFC 8446](https://www.rfc-editor.org/rfc/rfc8446#section-7.4.1)
/// (see [`complete_for_tls_version()`](Self::complete_for_tls_version) for more details).
///
/// The shared secret is returned as a [`SharedSecret`] which can be constructed
/// from a `&[u8]`.
///
/// This consumes and so terminates the [`ActiveKeyExchange`].
fn complete(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, Error>;
/// Completes the key exchange for the given TLS version, given the peer's public key.
///
/// Note that finite-field DiffieHellman key exchange has different requirements for the derived
/// shared secret in TLS 1.2 and TLS 1.3 (ECDHE key exchange is the same in TLS 1.2 and TLS 1.3):
///
/// In TLS 1.2, the calculated secret is required to be stripped of leading zeros
/// [(RFC 5246)](https://www.rfc-editor.org/rfc/rfc5246#section-8.1.2).
///
/// In TLS 1.3, the calculated secret is required to be padded with leading zeros to be the same
/// byte-length as the group modulus [(RFC 8446)](https://www.rfc-editor.org/rfc/rfc8446#section-7.4.1).
///
/// The default implementation of this method delegates to [`complete()`](Self::complete) assuming it is
/// implemented for TLS 1.3 (i.e., for FFDHE KX, removes padding as needed). Implementers of this trait
/// are encouraged to just implement [`complete()`](Self::complete) assuming TLS 1.3, and let the default
/// implementation of this method handle TLS 1.2-specific requirements.
///
/// This method must return an error if `peer_pub_key` is invalid: either
/// mis-encoded, or an invalid public key (such as, but not limited to, being
/// in a small order subgroup).
///
/// The shared secret is returned as a [`SharedSecret`] which can be constructed
/// from a `&[u8]`.
///
/// This consumes and so terminates the [`ActiveKeyExchange`].
fn complete_for_tls_version(
self: Box<Self>,
peer_pub_key: &[u8],
tls_version: &SupportedProtocolVersion,
) -> Result<SharedSecret, Error> {
if tls_version.version != ProtocolVersion::TLSv1_2 {
return self.complete(peer_pub_key);
}
let group = self.group();
let mut complete_res = self.complete(peer_pub_key)?;
if group.key_exchange_algorithm() == KeyExchangeAlgorithm::DHE {
complete_res.strip_leading_zeros();
}
Ok(complete_res)
}
/// Return the public key being used.
///
/// The encoding required is defined in
/// For ECDHE, the encoding required is defined in
/// [RFC8446 section 4.2.8.2](https://www.rfc-editor.org/rfc/rfc8446#section-4.2.8.2).
///
/// For FFDHE, the encoding required is defined in
/// [RFC8446 section 4.2.8.1](https://www.rfc-editor.org/rfc/rfc8446#section-4.2.8.1).
fn pub_key(&self) -> &[u8];
/// Return the group being used.
@ -323,12 +381,27 @@ pub trait ActiveKeyExchange: Send + Sync {
/// The result from [`ActiveKeyExchange::complete`].
pub struct SharedSecret {
buf: Vec<u8>,
offset: usize,
}
impl SharedSecret {
/// Returns the shared secret as a slice of bytes.
pub fn secret_bytes(&self) -> &[u8] {
&self.buf
&self.buf[self.offset..]
}
/// Removes leading zeros from `secret_bytes()` by adjusting the `offset`.
///
/// This function does not re-allocate.
fn strip_leading_zeros(&mut self) {
let start = self
.secret_bytes()
.iter()
.enumerate()
.find(|(_i, x)| **x != 0)
.map(|(i, _x)| i)
.unwrap_or(self.secret_bytes().len());
self.offset += start;
}
}
@ -340,7 +413,10 @@ impl Drop for SharedSecret {
impl From<&[u8]> for SharedSecret {
fn from(source: &[u8]) -> Self {
Self { buf: source.to_vec() }
Self {
buf: source.to_vec(),
offset: 0,
}
}
}
@ -381,3 +457,26 @@ pub(crate) fn default_provider() -> CryptoProvider {
pub fn default_fips_provider() -> CryptoProvider {
crate::crypto::aws_lc_rs::default_provider()
}
#[cfg(test)]
mod tests {
use super::SharedSecret;
#[test]
fn test_shared_secret_strip_leading_zeros() {
let test_cases = [
(vec![0, 1], vec![1]),
(vec![1], vec![1]),
(vec![1, 0, 2], vec![1, 0, 2]),
(vec![0, 0, 1, 2], vec![1, 2]),
(vec![0, 0, 0], vec![]),
(vec![], vec![]),
];
for (buf, expected) in test_cases {
let mut secret = SharedSecret::from(&buf[..]);
assert_eq!(secret.secret_bytes(), buf);
secret.strip_leading_zeros();
assert_eq!(secret.secret_bytes(), expected);
}
}
}

View File

@ -1,6 +1,7 @@
use super::hmac;
use super::ActiveKeyExchange;
use crate::error::Error;
use crate::version::TLS12;
use alloc::boxed::Box;
@ -20,7 +21,7 @@ impl<'a> Prf for PrfUsingHmac<'a> {
output,
self.0
.with_key(
kx.complete(peer_pub_key)?
kx.complete_for_tls_version(peer_pub_key, &TLS12)?
.secret_bytes(),
)
.as_ref(),

View File

@ -1,6 +1,7 @@
use super::hmac;
use super::ActiveKeyExchange;
use crate::error::Error;
use crate::version::TLS13;
use alloc::boxed::Box;
use zeroize::Zeroize;
@ -160,7 +161,7 @@ pub trait Hkdf: Send + Sync {
) -> Result<Box<dyn HkdfExpander>, Error> {
Ok(self.extract_from_secret(
salt,
kx.complete(peer_pub_key)?
kx.complete_for_tls_version(peer_pub_key, &TLS13)?
.secret_bytes(),
))
}

View File

@ -541,6 +541,7 @@ pub use crate::error::{
pub use crate::key_log::{KeyLog, NoKeyLog};
pub use crate::key_log_file::KeyLogFile;
pub use crate::msgs::enums::NamedGroup;
pub use crate::msgs::ffdhe_groups;
pub use crate::msgs::handshake::DistinguishedName;
pub use crate::stream::{Stream, StreamOwned};
pub use crate::suites::{ConnectionTrafficSecrets, ExtractedSecrets, SupportedCipherSuite};

View File

@ -1,6 +1,7 @@
#![allow(clippy::upper_case_acronyms)]
#![allow(non_camel_case_types)]
/// This file is autogenerated. See https://github.com/ctz/tls-hacking/
use crate::crypto::KeyExchangeAlgorithm;
use crate::msgs::codec::{Codec, Reader};
enum_builder! {
@ -193,6 +194,16 @@ enum_builder! {
}
}
impl NamedGroup {
/// Return the key exchange algorithm associated with this `NamedGroup`
pub fn key_exchange_algorithm(&self) -> KeyExchangeAlgorithm {
match self.get_u16() {
x if (0x100..0x200).contains(&x) => KeyExchangeAlgorithm::DHE,
_ => KeyExchangeAlgorithm::ECDHE,
}
}
}
enum_builder! {
/// The `ECPointFormat` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.

View File

@ -0,0 +1,111 @@
//! This module contains parameters for FFDHE named groups as defined
//! in [RFC 7919 Appendix A](https://datatracker.ietf.org/doc/html/rfc7919#appendix-A).
use crate::NamedGroup;
#[derive(Clone, Copy, PartialEq, Eq)]
/// Parameters of an FFDHE group, with Big-endian byte order
pub struct FfdheGroup<'a> {
pub p: &'a [u8],
pub g: &'a [u8],
}
impl FfdheGroup<'static> {
/// Return the `FfdheGroup` corresponding to the provided `NamedGroup`
/// if it is indeed an FFDHE group
pub fn from_named_group(named_group: NamedGroup) -> Option<Self> {
match named_group {
NamedGroup::FFDHE2048 => Some(FFDHE2048),
NamedGroup::FFDHE3072 => Some(FFDHE3072),
NamedGroup::FFDHE4096 => Some(FFDHE4096),
NamedGroup::FFDHE6144 => Some(FFDHE6144),
NamedGroup::FFDHE8192 => Some(FFDHE8192),
_ => None,
}
}
}
impl<'a> FfdheGroup<'a> {
/// Return the `NamedGroup` for the `FfdheGroup` if it represents one.
pub fn named_group(&self) -> Option<NamedGroup> {
match *self {
FFDHE2048 => Some(NamedGroup::FFDHE2048),
FFDHE3072 => Some(NamedGroup::FFDHE3072),
FFDHE4096 => Some(NamedGroup::FFDHE4096),
FFDHE6144 => Some(NamedGroup::FFDHE6144),
FFDHE8192 => Some(NamedGroup::FFDHE8192),
_ => None,
}
}
/// Construct an `FfdheGroup` from the given `p` and `g`, trimming any potential leading zeros.
pub fn from_params_trimming_leading_zeros(p: &'a [u8], g: &'a [u8]) -> Self {
fn trim_leading_zeros(buf: &[u8]) -> &[u8] {
for start in 0..buf.len() {
if buf[start] != 0 {
return &buf[start..];
}
}
&[]
}
FfdheGroup {
p: trim_leading_zeros(p),
g: trim_leading_zeros(g),
}
}
}
/// FFDHE2048 group defined in [RFC 7919 Appendix A.1]
///
/// [RFC 7919 Appendix A.1]: https://datatracker.ietf.org/doc/html/rfc7919#appendix-A.1
pub const FFDHE2048: FfdheGroup = FfdheGroup {
p: include_bytes!("ffdhe_groups/ffdhe2048-modulus.bin"),
g: &[2],
};
/// FFDHE3072 group defined in [RFC 7919 Appendix A.2]
///
/// [RFC 7919 Appendix A.2]: https://datatracker.ietf.org/doc/html/rfc7919#appendix-A.2
pub const FFDHE3072: FfdheGroup = FfdheGroup {
p: include_bytes!("ffdhe_groups/ffdhe3072-modulus.bin"),
g: &[2],
};
/// FFDHE4096 group defined in [RFC 7919 Appendix A.3]
///
/// [RFC 7919 Appendix A.3]: https://datatracker.ietf.org/doc/html/rfc7919#appendix-A.3
pub const FFDHE4096: FfdheGroup = FfdheGroup {
p: include_bytes!("ffdhe_groups/ffdhe4096-modulus.bin"),
g: &[2],
};
/// FFDHE6144 group defined in [RFC 7919 Appendix A.4]
///
/// [RFC 7919 Appendix A.4]: https://datatracker.ietf.org/doc/html/rfc7919#appendix-A.4
pub const FFDHE6144: FfdheGroup = FfdheGroup {
p: include_bytes!("ffdhe_groups/ffdhe6144-modulus.bin"),
g: &[2],
};
/// FFDHE8192 group defined in [RFC 7919 Appendix A.5]
///
/// [RFC 7919 Appendix A.5]: https://datatracker.ietf.org/doc/html/rfc7919#appendix-A.5
pub const FFDHE8192: FfdheGroup = FfdheGroup {
p: include_bytes!("ffdhe_groups/ffdhe8192-modulus.bin"),
g: &[2],
};
#[test]
fn named_group_ffdhe_group_roudtrip() {
use NamedGroup::*;
let ffdhe_groups = [FFDHE2048, FFDHE3072, FFDHE4096, FFDHE6144, FFDHE8192];
for g in ffdhe_groups {
assert_eq!(
FfdheGroup::from_named_group(g)
.unwrap()
.named_group(),
Some(g)
);
}
}

View File

@ -0,0 +1,3 @@
ÿÿÿÿÿÿÿÿ­øTX¢»Jš¯ÜV '=<ñعŃÎ-6•©á6Ad3ûÌ“<C38C>Î$>ù}/ãcc uØö<C398>²®ÄazÓßÕÕýea$3õ_nÐ…ceU=íóµW^WÉ5˜O pàæw⦉ÚóïèrñX¡6­ç50¬ÊOH:yz¼
±‚³$ûaÑ©K²Èãû¹jÚ·`×ôhOB£Þ9Mô®Víçcr» §Èî
mpžüáÍ÷âìÀ4Í(4/arþœé…ƒÿŽO2îò<C3AE>ƒÃþ;Lo­s;µü¼. ÅŽñƒ}ƒ²ÆóJ&Á²ïúˆkB8a(\—ÿÿÿÿÿÿÿÿ

View File

@ -0,0 +1,4 @@
ÿÿÿÿÿÿÿÿ­øTX¢»Jš¯ÜV '=<ñعŃÎ-6•©á6Ad3ûÌ“<C38C>Î$>ù}/ãcc uØö<C398>²®ÄazÓßÕÕýea$3õ_nÐ…ceU=íóµW^WÉ5˜O pàæw⦉ÚóïèrñX¡6­ç50¬ÊOH:yz¼
±‚³$ûaÑ©K²Èãû¹jÚ·`×ôhOB£Þ9Mô®Víçcr» §Èî
mpžüáÍ÷âìÀ4Í(4/arþœé…ƒÿŽO2îò<C3AE>ƒÃþ;Lo­s;µü¼. ÅŽñƒ}ƒ²ÆóJ&Á²ïúˆkB8aÏÜÞ5[;e[¼4ôÞùœ8a´oÉÖæÉ&÷÷îYŒ°úÁ†Ù®þp´ “¼CyDôýDRâ×MÓdòâqõKÿ\®«œ<C2AB>öžèm+Å"6: «Å!—› êÚ¿šBÕÄHN
¼ÐkúSÝï< î?Õ<>|%ä+fÆ.7ÿÿÿÿÿÿÿÿ

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -5,6 +5,8 @@ use crate::crypto::ActiveKeyExchange;
use crate::crypto::SecureRandom;
use crate::enums::{CipherSuite, HandshakeType, ProtocolVersion, SignatureScheme};
use crate::error::InvalidMessage;
#[cfg(feature = "tls12")]
use crate::ffdhe_groups::FfdheGroup;
#[cfg(feature = "logging")]
use crate::log::warn;
use crate::msgs::base::{Payload, PayloadU16, PayloadU24, PayloadU8};
@ -1490,10 +1492,17 @@ impl CertificatePayloadTls13 {
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub enum KeyExchangeAlgorithm {
/// Diffie-Hellman Key exchange (with only known parameters as defined in [RFC 7919]).
///
/// [RFC 7919]: https://datatracker.ietf.org/doc/html/rfc7919
DHE,
/// Key exchange performed via elliptic curve Diffie-Hellman.
ECDHE,
}
pub(crate) static ALL_KEY_EXCHANGE_ALGORITHMS: &[KeyExchangeAlgorithm] =
&[KeyExchangeAlgorithm::ECDHE, KeyExchangeAlgorithm::DHE];
// We don't support arbitrary curves. It's a terrible
// idea and unnecessary attack surface. Please,
// get a grip.
@ -1524,6 +1533,45 @@ impl Codec<'_> for EcParameters {
}
}
pub(crate) trait KxDecode<'a>: fmt::Debug + Sized {
/// Decode a key exchange message given the key_exchange `algo`
fn decode(r: &mut Reader<'a>, algo: KeyExchangeAlgorithm) -> Result<Self, InvalidMessage>;
}
#[derive(Debug)]
pub(crate) enum ClientKeyExchangeParams {
Ecdh(ClientEcdhParams),
Dh(ClientDhParams),
}
impl ClientKeyExchangeParams {
#[cfg(feature = "tls12")]
pub(crate) fn pub_key(&self) -> &[u8] {
match self {
Self::Ecdh(ecdh) => &ecdh.public.0,
Self::Dh(dh) => &dh.public.0,
}
}
#[cfg(feature = "tls12")]
pub(crate) fn encode(&self, buf: &mut Vec<u8>) {
match self {
Self::Ecdh(ecdh) => ecdh.encode(buf),
Self::Dh(dh) => dh.encode(buf),
}
}
}
impl KxDecode<'_> for ClientKeyExchangeParams {
fn decode(r: &mut Reader, algo: KeyExchangeAlgorithm) -> Result<Self, InvalidMessage> {
use KeyExchangeAlgorithm::*;
Ok(match algo {
ECDHE => Self::Ecdh(ClientEcdhParams::read(r)?),
DHE => Self::Dh(ClientDhParams::read(r)?),
})
}
}
#[derive(Debug)]
pub(crate) struct ClientEcdhParams {
pub(crate) public: PayloadU8,
@ -1540,6 +1588,23 @@ impl Codec<'_> for ClientEcdhParams {
}
}
#[derive(Debug)]
pub(crate) struct ClientDhParams {
pub(crate) public: PayloadU16,
}
impl Codec<'_> for ClientDhParams {
fn encode(&self, bytes: &mut Vec<u8>) {
self.public.encode(bytes);
}
fn read(r: &mut Reader) -> Result<Self, InvalidMessage> {
Ok(Self {
public: PayloadU16::read(r)?,
})
}
}
#[derive(Debug)]
pub(crate) struct ServerEcdhParams {
pub(crate) curve_params: EcParameters,
@ -1576,17 +1641,98 @@ impl Codec<'_> for ServerEcdhParams {
}
}
#[derive(Debug)]
#[allow(non_snake_case)]
pub(crate) struct ServerDhParams {
pub(crate) dh_p: PayloadU16,
pub(crate) dh_g: PayloadU16,
pub(crate) dh_Ys: PayloadU16,
}
impl ServerDhParams {
#[cfg(feature = "tls12")]
pub(crate) fn new(kx: &dyn ActiveKeyExchange) -> Self {
let params = match FfdheGroup::from_named_group(kx.group()) {
Some(params) => params,
None => panic!("invalid NamedGroup for DHE key exchange: {:?}", kx.group()),
};
Self {
dh_p: PayloadU16::new(params.p.to_vec()),
dh_g: PayloadU16::new(params.g.to_vec()),
dh_Ys: PayloadU16::new(kx.pub_key().to_vec()),
}
}
#[cfg(feature = "tls12")]
fn named_group(&self) -> Option<NamedGroup> {
FfdheGroup::from_params_trimming_leading_zeros(&self.dh_p.0, &self.dh_g.0).named_group()
}
}
impl Codec<'_> for ServerDhParams {
fn encode(&self, bytes: &mut Vec<u8>) {
self.dh_p.encode(bytes);
self.dh_g.encode(bytes);
self.dh_Ys.encode(bytes);
}
fn read(r: &mut Reader) -> Result<Self, InvalidMessage> {
Ok(Self {
dh_p: PayloadU16::read(r)?,
dh_g: PayloadU16::read(r)?,
dh_Ys: PayloadU16::read(r)?,
})
}
}
#[derive(Debug)]
pub(crate) enum ServerKeyExchangeParams {
Ecdh(ServerEcdhParams),
Dh(ServerDhParams),
}
impl ServerKeyExchangeParams {
#[cfg(feature = "tls12")]
pub(crate) fn new(kx: &dyn ActiveKeyExchange) -> Self {
match kx.group().key_exchange_algorithm() {
KeyExchangeAlgorithm::DHE => Self::Dh(ServerDhParams::new(kx)),
KeyExchangeAlgorithm::ECDHE => Self::Ecdh(ServerEcdhParams::new(kx)),
}
}
#[cfg(feature = "tls12")]
pub(crate) fn pub_key(&self) -> &[u8] {
match self {
Self::Ecdh(ecdh) => &ecdh.public.0,
Self::Dh(dh) => &dh.dh_Ys.0,
}
}
pub(crate) fn encode(&self, buf: &mut Vec<u8>) {
match self {
Self::Ecdh(ecdh) => ecdh.encode(buf),
Self::Dh(dh) => dh.encode(buf),
}
}
#[cfg(feature = "tls12")]
pub(crate) fn named_group(&self) -> Option<NamedGroup> {
match self {
Self::Ecdh(ecdh) => Some(ecdh.curve_params.named_group),
Self::Dh(dh) => dh.named_group(),
}
}
}
impl KxDecode<'_> for ServerKeyExchangeParams {
fn decode(r: &mut Reader, algo: KeyExchangeAlgorithm) -> Result<Self, InvalidMessage> {
use KeyExchangeAlgorithm::*;
Ok(match algo {
ECDHE => Self::Ecdh(ServerEcdhParams::read(r)?),
DHE => Self::Dh(ServerDhParams::read(r)?),
})
}
}
#[derive(Debug)]
@ -1636,11 +1782,7 @@ impl ServerKeyExchangePayload {
let mut rd = Reader::init(unk.bytes());
let result = ServerKeyExchange {
params: match kxa {
KeyExchangeAlgorithm::ECDHE => {
ServerKeyExchangeParams::Ecdh(ServerEcdhParams::read(&mut rd).ok()?)
},
},
params: ServerKeyExchangeParams::decode(&mut rd, kxa).ok()?,
dss: DigitallySignedStruct::read(&mut rd).ok()?,
};

View File

@ -20,7 +20,7 @@ use crate::verify::DigitallySignedStruct;
use pki_types::{CertificateDer, DnsName};
use super::handshake::{ServerKeyExchange, ServerKeyExchangeParams};
use super::handshake::{ServerDhParams, ServerKeyExchange, ServerKeyExchangeParams};
#[test]
fn rejects_short_random() {
@ -822,6 +822,17 @@ fn get_sample_serverkeyexchangepayload_ecdhe() -> ServerKeyExchangePayload {
})
}
fn get_sample_serverkeyexchangepayload_dhe() -> ServerKeyExchangePayload {
ServerKeyExchangePayload::Known(ServerKeyExchange {
params: ServerKeyExchangeParams::Dh(ServerDhParams {
dh_p: PayloadU16(vec![1, 2, 3]),
dh_g: PayloadU16(vec![2]),
dh_Ys: PayloadU16(vec![1, 2]),
}),
dss: DigitallySignedStruct::new(SignatureScheme::RSA_PSS_SHA256, vec![1, 2, 3]),
})
}
fn get_sample_serverkeyexchangepayload_unknown() -> ServerKeyExchangePayload {
ServerKeyExchangePayload::Unknown(Payload::Borrowed(&[1, 2, 3]))
}
@ -908,6 +919,10 @@ fn get_all_tls12_handshake_payloads() -> Vec<HandshakeMessagePayload<'static>> {
get_sample_serverkeyexchangepayload_ecdhe(),
),
},
HandshakeMessagePayload {
typ: HandshakeType::ServerKeyExchange,
payload: HandshakePayload::ServerKeyExchange(get_sample_serverkeyexchangepayload_dhe()),
},
HandshakeMessagePayload {
typ: HandshakeType::ServerKeyExchange,
payload: HandshakePayload::ServerKeyExchange(
@ -1036,6 +1051,10 @@ fn get_all_tls13_handshake_payloads() -> Vec<HandshakeMessagePayload<'static>> {
get_sample_serverkeyexchangepayload_ecdhe(),
),
},
HandshakeMessagePayload {
typ: HandshakeType::ServerKeyExchange,
payload: HandshakePayload::ServerKeyExchange(get_sample_serverkeyexchangepayload_dhe()),
},
HandshakeMessagePayload {
typ: HandshakeType::ServerKeyExchange,
payload: HandshakePayload::ServerKeyExchange(

View File

@ -18,6 +18,7 @@ pub(crate) mod persist;
#[cfg(test)]
mod handshake_test;
pub mod ffdhe_groups;
#[cfg(test)]
mod message_test;

View File

@ -334,7 +334,7 @@ impl ExpectClientHello {
};
let certkey = ActiveCertifiedKey::from_certified_key(&certkey);
let suitable_suites = self
let mut suitable_suites = self
.config
.provider
.cipher_suites
@ -348,6 +348,32 @@ impl ExpectClientHello {
.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,

View File

@ -12,7 +12,7 @@ use crate::msgs::base::Payload;
use crate::msgs::ccs::ChangeCipherSpecPayload;
use crate::msgs::codec::Codec;
use crate::msgs::handshake::{
CertificateChain, ClientEcdhParams, HandshakeMessagePayload, HandshakePayload,
CertificateChain, ClientKeyExchangeParams, HandshakeMessagePayload, HandshakePayload,
};
use crate::msgs::handshake::{NewSessionTicketPayload, SessionId};
use crate::msgs::message::{Message, MessagePayload};
@ -40,14 +40,12 @@ pub(super) use client_hello::CompleteClientHelloHandling;
mod client_hello {
use pki_types::CertificateDer;
use crate::crypto::SupportedKxGroup;
use crate::crypto::{KeyExchangeAlgorithm, SupportedKxGroup};
use crate::enums::SignatureScheme;
use crate::msgs::enums::ECPointFormat;
use crate::msgs::enums::{ClientCertificateType, Compression};
use crate::msgs::handshake::CertificateStatus;
use crate::msgs::handshake::{
CertificateChain, ServerEcdhParams, ServerKeyExchange, ServerKeyExchangeParams,
};
use crate::msgs::handshake::{CertificateChain, ServerKeyExchange, ServerKeyExchangeParams};
use crate::msgs::handshake::{CertificateRequestPayload, ClientSessionTicket, Random};
use crate::msgs::handshake::{ClientExtension, SessionId};
use crate::msgs::handshake::{ClientHelloPayload, ServerHelloPayload};
@ -260,29 +258,52 @@ mod client_hello {
client_hello: &ClientHelloPayload,
cx: &mut ServerContext<'_>,
) -> Result<&'static dyn SupportedKxGroup, Error> {
let peer_groups_ext = client_hello
.namedgroups_extension()
.ok_or_else(|| {
cx.common.send_fatal_alert(
AlertDescription::HandshakeFailure,
PeerIncompatible::NamedGroupsExtensionRequired,
)
})?;
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);
self.config
.provider
.kx_groups
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| peer_groups_ext.contains(&skxg.name()))
.find(|skxg| skxg.name().key_exchange_algorithm() == DHE)
.cloned()
.ok_or_else(|| {
cx.common.send_fatal_alert(
AlertDescription::HandshakeFailure,
PeerIncompatible::NoKxGroupsInCommon,
)
})
.ok_or_else(send_err)
}
fn start_resumption(
@ -438,7 +459,7 @@ mod client_hello {
let kx = selected_group
.start()
.map_err(|_| Error::FailedToGetRandomBytes)?;
let kx_params = ServerEcdhParams::new(&*kx);
let kx_params = ServerKeyExchangeParams::new(&*kx);
let mut msg = Vec::new();
msg.extend(randoms.client);
@ -451,8 +472,8 @@ mod client_hello {
let sigscheme = signer.scheme();
let sig = signer.sign(&msg)?;
let skx = ServerKeyExchangePayload::Known(ServerKeyExchange {
params: ServerKeyExchangeParams::Ecdh(kx_params),
let skx = ServerKeyExchangePayload::from(ServerKeyExchange {
params: kx_params,
dss: DigitallySignedStruct::new(sigscheme, sig),
});
@ -637,11 +658,14 @@ impl State<ServerConnectionData> for ExpectClientKx<'_> {
// Complete key agreement, and set up encryption with the
// resulting premaster secret.
let peer_kx_params =
tls12::decode_kx_params::<ClientEcdhParams>(cx.common, client_kx.bytes())?;
let peer_kx_params = tls12::decode_kx_params::<ClientKeyExchangeParams>(
self.suite.kx,
cx.common,
client_kx.bytes(),
)?;
let secrets = ConnectionSecrets::from_key_exchange(
self.server_kx,
&peer_kx_params.public.0,
peer_kx_params.pub_key(),
ems_seed,
self.randoms,
self.suite,

View File

@ -1,13 +1,16 @@
use crate::common_state::Protocol;
use crate::crypto;
use crate::crypto::cipher::{AeadKey, Iv};
use crate::crypto::{self, KeyExchangeAlgorithm};
use crate::enums::{CipherSuite, SignatureAlgorithm, SignatureScheme};
use crate::msgs::handshake::ALL_KEY_EXCHANGE_ALGORITHMS;
#[cfg(feature = "tls12")]
use crate::tls12::Tls12CipherSuite;
use crate::tls13::Tls13CipherSuite;
#[cfg(feature = "tls12")]
use crate::versions::TLS12;
use crate::versions::{SupportedProtocolVersion, TLS13};
use crate::NamedGroup;
use alloc::vec::Vec;
use core::fmt;
@ -134,6 +137,18 @@ impl SupportedCipherSuite {
Self::Tls13(cs) => cs.fips(),
}
}
/// Return the list of `KeyExchangeAlgorithm`s supported by this cipher suite.
///
/// TLS 1.3 cipher suites support both ECDHE and DHE key exchange, but TLS 1.2 suites
/// support one or the other.
pub(crate) fn key_exchange_algorithms(&self) -> &[KeyExchangeAlgorithm] {
match self {
#[cfg(feature = "tls12")]
Self::Tls12(tls12) => core::slice::from_ref(&tls12.kx),
Self::Tls13(_) => ALL_KEY_EXCHANGE_ALGORITHMS,
}
}
}
impl fmt::Debug for SupportedCipherSuite {
@ -173,6 +188,60 @@ pub(crate) fn choose_ciphersuite_preferring_server(
None
}
/// Return a list of the ciphersuites in `all` with the suites
/// incompatible with the Groups extension removed.
pub(crate) fn reduce_given_kx_groups(
all: &mut Vec<SupportedCipherSuite>,
groups_ext: Option<&[NamedGroup]>,
supported_groups: &[NamedGroup],
) {
let mut ecdhe_kx_ok = false;
#[cfg(feature = "tls12")]
let mut ext_has_ffdhe_groups = false;
let mut ext_has_known_ffdhe_groups = false;
for g in groups_ext.into_iter().flatten() {
if g.key_exchange_algorithm() == KeyExchangeAlgorithm::DHE {
#[cfg(feature = "tls12")]
{
ext_has_ffdhe_groups = true;
}
if supported_groups.contains(g) {
ext_has_known_ffdhe_groups = true;
}
} else if supported_groups.contains(g) {
ecdhe_kx_ok = true;
}
if ecdhe_kx_ok & ext_has_known_ffdhe_groups {
break;
}
}
#[cfg(feature = "tls12")]
let ffdhe_kx_ok_tls12 = ext_has_known_ffdhe_groups ||
// https://datatracker.ietf.org/doc/html/rfc7919#section-4 (paragraph 2)
!ext_has_ffdhe_groups && supported_groups
.iter()
.any(|g| g.key_exchange_algorithm() == KeyExchangeAlgorithm::DHE);
let ffdhe_kx_ok_tls13 = ext_has_known_ffdhe_groups;
all.retain(|suite| {
let suite_kx = suite.key_exchange_algorithms();
// echde:
ecdhe_kx_ok && suite_kx.contains(&KeyExchangeAlgorithm::ECDHE) ||
// dhe:
{
let ffdhe_kx_ok = match suite {
#[cfg(feature = "tls12")]
SupportedCipherSuite::Tls12(_) => ffdhe_kx_ok_tls12,
SupportedCipherSuite::Tls13(_) => ffdhe_kx_ok_tls13,
};
ffdhe_kx_ok && suite_kx.contains(&KeyExchangeAlgorithm::DHE)
}
})
}
/// Return true if `sigscheme` is usable by any of the given suites.
pub(crate) fn compatible_sigscheme_for_suites(
sigscheme: SignatureScheme,

View File

@ -6,7 +6,7 @@ use crate::crypto::hash;
use crate::enums::{AlertDescription, SignatureScheme};
use crate::error::{Error, InvalidMessage};
use crate::msgs::codec::{Codec, Reader};
use crate::msgs::handshake::KeyExchangeAlgorithm;
use crate::msgs::handshake::{KeyExchangeAlgorithm, KxDecode};
use crate::suites::{CipherSuiteCommon, PartiallyExtractedSecrets, SupportedCipherSuite};
use alloc::boxed::Box;
@ -320,14 +320,15 @@ fn join_randoms(first: &[u8; 32], second: &[u8; 32]) -> [u8; 64] {
type MessageCipherPair = (Box<dyn MessageDecrypter>, Box<dyn MessageEncrypter>);
pub(crate) fn decode_kx_params<'a, T: Codec<'a>>(
pub(crate) fn decode_kx_params<'a, T: KxDecode<'a>>(
kx_algorithm: KeyExchangeAlgorithm,
common: &mut CommonState,
kx_params: &'a [u8],
) -> Result<T, Error> {
let mut rd = Reader::init(kx_params);
let ecdh_params = T::read(&mut rd)?;
let kx_params = T::decode(&mut rd, kx_algorithm)?;
match rd.any_left() {
false => Ok(ecdh_params),
false => Ok(kx_params),
true => Err(common.send_fatal_alert(
AlertDescription::DecodeError,
InvalidMessage::InvalidDhParams,
@ -341,8 +342,8 @@ pub(crate) const DOWNGRADE_SENTINEL: [u8; 8] = [0x44, 0x4f, 0x57, 0x4e, 0x47, 0x
mod tests {
use super::*;
use crate::common_state::{CommonState, Side};
use crate::msgs::handshake::{ClientEcdhParams, ServerEcdhParams};
use crate::test_provider::kx_group::X25519;
use crate::msgs::handshake::ServerEcdhParams;
use crate::{msgs::handshake::ServerKeyExchangeParams, test_provider::kx_group::X25519};
#[test]
fn server_ecdhe_remaining_bytes() {
@ -353,12 +354,22 @@ mod tests {
server_buf.push(34);
let mut common = CommonState::new(Side::Client);
assert!(decode_kx_params::<ServerEcdhParams>(&mut common, &server_buf).is_err());
assert!(decode_kx_params::<ServerKeyExchangeParams>(
KeyExchangeAlgorithm::ECDHE,
&mut common,
&server_buf
)
.is_err());
}
#[test]
fn client_ecdhe_invalid() {
let mut common = CommonState::new(Side::Server);
assert!(decode_kx_params::<ClientEcdhParams>(&mut common, &[34]).is_err());
assert!(decode_kx_params::<ServerKeyExchangeParams>(
KeyExchangeAlgorithm::ECDHE,
&mut common,
&[34],
)
.is_err());
}
}

428
rustls/tests/api_ffdhe.rs Normal file
View File

@ -0,0 +1,428 @@
#![cfg(feature = "tls12")]
#![cfg(any(feature = "ring", feature = "aws_lc_rs"))]
//! This file contains tests that use the test-only FFDHE KX group (defined in submodule `ffdhe`)
mod common;
use crate::common::*;
use rustls::crypto::CryptoProvider;
use rustls::internal::msgs::handshake::{ClientExtension, HandshakePayload};
use rustls::internal::msgs::message::{Message, MessagePayload};
use rustls::internal::msgs::{base::Payload, codec::Codec};
use rustls::version::{TLS12, TLS13};
use rustls::{CipherSuite, ClientConfig};
#[test]
fn config_builder_for_client_rejects_cipher_suites_without_compatible_kx_groups() {
let bad_crypto_provider = CryptoProvider {
kx_groups: vec![&ffdhe::FFDHE2048_KX_GROUP],
cipher_suites: vec![
provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
ffdhe::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
],
..provider::default_provider()
};
let build_err = ClientConfig::builder_with_provider(bad_crypto_provider.into())
.with_safe_default_protocol_versions()
.unwrap_err()
.to_string();
// Current expected error:
// Ciphersuite TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 requires [ECDHE] key exchange, but no \
// [ECDHE]-compatible key exchange groups were present in `CryptoProvider`'s `kx_groups` field
assert!(build_err.contains("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"));
assert!(build_err.contains("ECDHE"));
assert!(build_err.contains("key exchange"));
}
#[test]
fn ffdhe_ciphersuite() {
use provider::cipher_suite;
use rustls::version::{TLS12, TLS13};
let test_cases = [
(&TLS12, ffdhe::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256),
(&TLS13, cipher_suite::TLS13_CHACHA20_POLY1305_SHA256),
];
for (expected_protocol, expected_cipher_suite) in test_cases {
let client_config = finish_client_config(
KeyType::Rsa,
rustls::ClientConfig::builder_with_provider(ffdhe::ffdhe_provider().into())
.with_protocol_versions(&[expected_protocol])
.unwrap(),
);
let server_config = finish_server_config(
KeyType::Rsa,
rustls::ServerConfig::builder_with_provider(ffdhe::ffdhe_provider().into())
.with_safe_default_protocol_versions()
.unwrap(),
);
do_suite_test(
client_config,
server_config,
expected_cipher_suite,
expected_protocol.version,
);
}
}
#[test]
fn server_picks_ffdhe_group_when_clienthello_has_no_ffdhe_group_in_groups_ext() {
fn clear_named_groups_ext(msg: &mut Message) -> Altered {
if let MessagePayload::Handshake { parsed, encoded } = &mut msg.payload {
if let HandshakePayload::ClientHello(ch) = &mut parsed.payload {
for mut ext in ch.extensions.iter_mut() {
if let ClientExtension::NamedGroups(ngs) = &mut ext {
ngs.clear();
}
}
}
*encoded = Payload::new(parsed.get_encoding());
}
Altered::InPlace
}
let client_config = finish_client_config(
KeyType::Rsa,
rustls::ClientConfig::builder_with_provider(ffdhe::ffdhe_provider().into())
.with_protocol_versions(&[&rustls::version::TLS12])
.unwrap(),
);
let server_config = finish_server_config(
KeyType::Rsa,
rustls::ServerConfig::builder_with_provider(ffdhe::ffdhe_provider().into())
.with_protocol_versions(&[&rustls::version::TLS12])
.unwrap(),
);
let (client, server) = make_pair_for_configs(client_config, server_config);
let (mut client, mut server) = (client.into(), server.into());
transfer_altered(&mut client, clear_named_groups_ext, &mut server);
assert!(server.process_new_packets().is_ok());
}
#[test]
fn server_picks_ffdhe_group_when_clienthello_has_no_groups_ext() {
fn remove_named_groups_ext(msg: &mut Message) -> Altered {
if let MessagePayload::Handshake { parsed, encoded } = &mut msg.payload {
if let HandshakePayload::ClientHello(ch) = &mut parsed.payload {
ch.extensions
.retain(|ext| !matches!(ext, ClientExtension::NamedGroups(_)));
}
*encoded = Payload::new(parsed.get_encoding());
}
Altered::InPlace
}
let client_config = finish_client_config(
KeyType::Rsa,
rustls::ClientConfig::builder_with_provider(ffdhe::ffdhe_provider().into())
.with_protocol_versions(&[&rustls::version::TLS12])
.unwrap(),
);
let server_config = finish_server_config(
KeyType::Rsa,
rustls::ServerConfig::builder_with_provider(ffdhe::ffdhe_provider().into())
.with_safe_default_protocol_versions()
.unwrap(),
);
let (client, server) = make_pair_for_configs(client_config, server_config);
let (mut client, mut server) = (client.into(), server.into());
transfer_altered(&mut client, remove_named_groups_ext, &mut server);
assert!(server.process_new_packets().is_ok());
}
#[test]
fn server_avoids_dhe_cipher_suites_when_client_has_no_known_dhe_in_groups_ext() {
use rustls::{CipherSuite, NamedGroup};
let client_config = finish_client_config(
KeyType::Rsa,
rustls::ClientConfig::builder_with_provider(
CryptoProvider {
cipher_suites: vec![
ffdhe::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
],
kx_groups: vec![
&ffdhe::FfdheKxGroup(NamedGroup::FFDHE4096),
provider::kx_group::SECP256R1,
],
..provider::default_provider()
}
.into(),
)
.with_safe_default_protocol_versions()
.unwrap(),
);
let server_config = finish_server_config(
KeyType::Rsa,
rustls::ServerConfig::builder_with_provider(
CryptoProvider {
cipher_suites: vec![
ffdhe::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
],
kx_groups: vec![&ffdhe::FFDHE2048_KX_GROUP, provider::kx_group::SECP256R1],
..provider::default_provider()
}
.into(),
)
.with_safe_default_protocol_versions()
.unwrap(),
);
let (mut client, mut server) = make_pair_for_configs(client_config, server_config);
transfer(&mut client, &mut server);
assert!(server.process_new_packets().is_ok());
assert_eq!(
server
.negotiated_cipher_suite()
.unwrap()
.suite(),
CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
);
}
#[test]
fn server_accepts_client_with_no_ecpoints_extension_and_only_ffdhe_cipher_suites() {
fn remove_ecpoints_ext(msg: &mut Message) -> Altered {
if let MessagePayload::Handshake { parsed, encoded } = &mut msg.payload {
if let HandshakePayload::ClientHello(ch) = &mut parsed.payload {
ch.extensions
.retain(|ext| !matches!(ext, ClientExtension::EcPointFormats(_)));
}
*encoded = Payload::new(parsed.get_encoding());
}
Altered::InPlace
}
let client_config = finish_client_config(
KeyType::Rsa,
rustls::ClientConfig::builder_with_provider(ffdhe::ffdhe_provider().into())
.with_protocol_versions(&[&rustls::version::TLS12])
.unwrap(),
);
let server_config = finish_server_config(
KeyType::Rsa,
rustls::ServerConfig::builder_with_provider(ffdhe::ffdhe_provider().into())
.with_safe_default_protocol_versions()
.unwrap(),
);
let (client, server) = make_pair_for_configs(client_config, server_config);
let (mut client, mut server) = (client.into(), server.into());
transfer_altered(&mut client, remove_ecpoints_ext, &mut server);
assert!(server.process_new_packets().is_ok());
}
#[test]
fn server_avoids_cipher_suite_with_no_common_kx_groups() {
let server_config = finish_server_config(
KeyType::Rsa,
rustls::ServerConfig::builder_with_provider(
CryptoProvider {
cipher_suites: vec![
provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
ffdhe::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
provider::cipher_suite::TLS13_AES_128_GCM_SHA256,
],
kx_groups: vec![provider::kx_group::SECP256R1, &ffdhe::FFDHE2048_KX_GROUP],
..provider::default_provider()
}
.into(),
)
.with_safe_default_protocol_versions()
.unwrap(),
)
.into();
let test_cases = [
(
vec![
// this matches:
provider::kx_group::SECP256R1,
&ffdhe::FFDHE3072_KX_GROUP,
],
&TLS12,
CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
),
(
vec![
provider::kx_group::SECP384R1,
// this matches:
&ffdhe::FFDHE2048_KX_GROUP,
],
&TLS12,
CipherSuite::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
),
(
vec![
// this matches:
provider::kx_group::SECP256R1,
&ffdhe::FFDHE3072_KX_GROUP,
],
&TLS13,
CipherSuite::TLS13_AES_128_GCM_SHA256,
),
(
vec![
provider::kx_group::SECP384R1,
// this matches:
&ffdhe::FFDHE2048_KX_GROUP,
],
&TLS13,
CipherSuite::TLS13_AES_128_GCM_SHA256,
),
];
for (client_kx_groups, protocol_version, expected_cipher_suite) in test_cases {
let client_config = finish_client_config(
KeyType::Rsa,
rustls::ClientConfig::builder_with_provider(
CryptoProvider {
cipher_suites: vec![
provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
ffdhe::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
provider::cipher_suite::TLS13_AES_128_GCM_SHA256,
],
kx_groups: client_kx_groups,
..provider::default_provider()
}
.into(),
)
.with_protocol_versions(&[protocol_version])
.unwrap(),
)
.into();
let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config);
transfer(&mut client, &mut server);
assert!(dbg!(server.process_new_packets()).is_ok());
assert_eq!(
server
.negotiated_cipher_suite()
.unwrap()
.suite(),
expected_cipher_suite
);
assert_eq!(server.protocol_version(), Some(protocol_version.version));
}
}
mod ffdhe {
use crate::common::provider;
use num_bigint::BigUint;
use rustls::crypto::{
ActiveKeyExchange, CipherSuiteCommon, CryptoProvider, KeyExchangeAlgorithm, SharedSecret,
SupportedKxGroup,
};
use rustls::{
ffdhe_groups::FfdheGroup, CipherSuite, NamedGroup, SupportedCipherSuite, Tls12CipherSuite,
};
/// A test-only `CryptoProvider`, only supporting FFDHE key exchange
pub fn ffdhe_provider() -> CryptoProvider {
CryptoProvider {
cipher_suites: FFDHE_CIPHER_SUITES.to_vec(),
kx_groups: FFDHE_KX_GROUPS.to_vec(),
..provider::default_provider()
}
}
static FFDHE_KX_GROUPS: &[&dyn SupportedKxGroup] = &[&FFDHE2048_KX_GROUP, &FFDHE3072_KX_GROUP];
pub const FFDHE2048_KX_GROUP: FfdheKxGroup = FfdheKxGroup(NamedGroup::FFDHE2048);
pub const FFDHE3072_KX_GROUP: FfdheKxGroup = FfdheKxGroup(NamedGroup::FFDHE3072);
static FFDHE_CIPHER_SUITES: &[rustls::SupportedCipherSuite] = &[
TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
provider::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256,
];
/// The (test-only) TLS1.2 ciphersuite TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
pub static TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: SupportedCipherSuite =
SupportedCipherSuite::Tls12(&TLS12_DHE_RSA_WITH_AES_128_GCM_SHA256);
static TLS12_DHE_RSA_WITH_AES_128_GCM_SHA256: Tls12CipherSuite =
match &provider::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 {
SupportedCipherSuite::Tls12(provider) => Tls12CipherSuite {
common: CipherSuiteCommon {
suite: CipherSuite::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
..provider.common
},
kx: KeyExchangeAlgorithm::DHE,
..**provider
},
_ => unreachable!(),
};
#[derive(Debug)]
pub struct FfdheKxGroup(pub NamedGroup);
impl SupportedKxGroup for FfdheKxGroup {
fn start(&self) -> Result<Box<dyn ActiveKeyExchange>, rustls::Error> {
let mut x = vec![0; 64];
ffdhe_provider()
.secure_random
.fill(&mut x)?;
let x = BigUint::from_bytes_be(&x);
let group = FfdheGroup::from_named_group(self.0).unwrap();
let p = BigUint::from_bytes_be(group.p);
let g = BigUint::from_bytes_be(group.g);
let x_pub = g.modpow(&x, &p);
let x_pub = to_bytes_be_with_len(x_pub, group.p.len());
Ok(Box::new(ActiveFfdheKx {
x_pub,
x,
p,
group,
named_group: self.0,
}))
}
fn name(&self) -> NamedGroup {
self.0
}
}
struct ActiveFfdheKx {
x_pub: Vec<u8>,
x: BigUint,
p: BigUint,
group: FfdheGroup<'static>,
named_group: NamedGroup,
}
impl ActiveKeyExchange for ActiveFfdheKx {
fn complete(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, rustls::Error> {
let peer_pub = BigUint::from_bytes_be(peer_pub_key);
let secret = peer_pub.modpow(&self.x, &self.p);
let secret = to_bytes_be_with_len(secret, self.group.p.len());
Ok(SharedSecret::from(&secret[..]))
}
fn pub_key(&self) -> &[u8] {
&self.x_pub
}
fn group(&self) -> NamedGroup {
self.named_group
}
}
fn to_bytes_be_with_len(n: BigUint, len_bytes: usize) -> Vec<u8> {
let mut bytes = n.to_bytes_le();
bytes.resize(len_bytes, 0);
bytes.reverse();
bytes
}
}