mirror of https://github.com/ctz/rustls
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:
parent
1340ea95e6
commit
c8c56a7aef
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 Diffie–Hellman 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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(),
|
||||
))
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
ÿÿÿÿÿÿÿÿøTX¢»Jš¯ÜV '=<ñعŃÎ-6•©á6Ad3ûÌ“<C38C>Î$›>ù}/ãccuØö<C398>²®ÄazÓßÕÕýea$3õ_nÐ…ceU=íóµW^WÉ5˜Opàæ‹w⦉ÚóïèrñX¡6ç50¬ÊOH:yz¼
|
||||
±‚³$ûaÑ©K²Èãû¹jÚ·`×ôhOB£Þ9Mô®Víçcr»§Èî
|
||||
mpžüáÍ÷âìÀ4Í(4/a‘rþœé…ƒÿŽO2îò<C3AE>ƒÃþ;Los;µü¼. Ŏñƒ}ƒ²ÆóJ&Á²ïúˆkB8a(\—ÿÿÿÿÿÿÿÿ
|
|
@ -0,0 +1,4 @@
|
|||
ÿÿÿÿÿÿÿÿøTX¢»Jš¯ÜV '=<ñعŃÎ-6•©á6Ad3ûÌ“<C38C>Î$›>ù}/ãccuØö<C398>²®ÄazÓßÕÕýea$3õ_nÐ…ceU=íóµW^WÉ5˜Opàæ‹w⦉ÚóïèrñX¡6ç50¬ÊOH:yz¼
|
||||
±‚³$ûaÑ©K²Èãû¹jÚ·`×ôhOB£Þ9Mô®Víçcr»§Èî
|
||||
mpžüáÍ÷âìÀ4Í(4/a‘rþœé…ƒÿŽO2îò<C3AE>ƒÃþ;Los;µü¼. Ŏñƒ}ƒ²ÆóJ&Á²ïúˆkB8aÏÜÞ5[;e[¼4ôÞùœ8a´oÉÖæÉzÙ&‘÷÷î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.
|
@ -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()?,
|
||||
};
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -18,6 +18,7 @@ pub(crate) mod persist;
|
|||
#[cfg(test)]
|
||||
mod handshake_test;
|
||||
|
||||
pub mod ffdhe_groups;
|
||||
#[cfg(test)]
|
||||
mod message_test;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue