Skip to content

Commit 3bd6b17

Browse files
authored
Make cipher and kx configurable
1 parent 6dfb32f commit 3bd6b17

11 files changed

Lines changed: 378 additions & 89 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Unreleased
22

3+
* Make cipher and kx configurable #73
4+
35
# 0.4.0
46

57
* Restrict DTLS 1.2 key exchange to P-256/P-384 (for now) #70

src/config.rs

Lines changed: 322 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use std::time::Duration;
22

3-
use crate::crypto::CryptoProvider;
3+
use crate::crypto::{CryptoProvider, SupportedDtls12CipherSuite};
4+
use crate::crypto::{SupportedDtls13CipherSuite, SupportedKxGroup};
5+
use crate::dtls12::message::Dtls12CipherSuite;
6+
use crate::types::{Dtls13CipherSuite, NamedGroup};
47
use crate::Error;
58

69
#[cfg(feature = "aws-lc-rs")]
@@ -25,6 +28,9 @@ pub struct Config {
2528
crypto_provider: CryptoProvider,
2629
rng_seed: Option<u64>,
2730
aead_encryption_limit: u64,
31+
dtls12_cipher_suites: Option<Vec<Dtls12CipherSuite>>,
32+
dtls13_cipher_suites: Option<Vec<Dtls13CipherSuite>>,
33+
kx_groups: Option<Vec<NamedGroup>>,
2834
}
2935

3036
impl Config {
@@ -42,6 +48,9 @@ impl Config {
4248
crypto_provider: None,
4349
rng_seed: None,
4450
aead_encryption_limit: 1 << 23,
51+
dtls12_cipher_suites: None,
52+
dtls13_cipher_suites: None,
53+
kx_groups: None,
4554
}
4655
}
4756

@@ -138,6 +147,66 @@ impl Config {
138147
pub fn aead_encryption_limit(&self) -> u64 {
139148
self.aead_encryption_limit
140149
}
150+
151+
/// Allowed DTLS 1.2 cipher suites, filtered by the config's allow-list.
152+
///
153+
/// Returns all provider-supported DTLS 1.2 cipher suites when no filter
154+
/// is set. When a filter is set via the builder's `dtls12_cipher_suites`
155+
/// method, only suites in both the provider and the filter are returned.
156+
pub fn dtls12_cipher_suites(
157+
&self,
158+
) -> impl Iterator<Item = &'static dyn SupportedDtls12CipherSuite> + '_ {
159+
let filter = self.dtls12_cipher_suites.as_ref();
160+
self.crypto_provider
161+
.supported_cipher_suites()
162+
.filter(move |cs| match filter {
163+
Some(list) => list.contains(&cs.suite()),
164+
None => true,
165+
})
166+
}
167+
168+
/// Allowed DTLS 1.3 cipher suites, filtered by the config's allow-list.
169+
///
170+
/// Returns all provider DTLS 1.3 cipher suites when no filter is set.
171+
/// When a filter is set via the builder's `dtls13_cipher_suites` method,
172+
/// only suites in both the provider and the filter are returned.
173+
pub fn dtls13_cipher_suites(
174+
&self,
175+
) -> impl Iterator<Item = &'static dyn SupportedDtls13CipherSuite> + '_ {
176+
let filter = self.dtls13_cipher_suites.as_ref();
177+
self.crypto_provider
178+
.dtls13_cipher_suites
179+
.iter()
180+
.copied()
181+
.filter(move |cs| match filter {
182+
Some(list) => list.contains(&cs.suite()),
183+
None => true,
184+
})
185+
}
186+
187+
/// Allowed key exchange groups, filtered by the config's allow-list.
188+
///
189+
/// Returns all provider-supported key exchange groups when no filter
190+
/// is set. When a filter is set via the builder's `kx_groups` method,
191+
/// only groups in both the provider and the filter are returned.
192+
pub fn kx_groups(&self) -> impl Iterator<Item = &'static dyn SupportedKxGroup> + '_ {
193+
let filter = self.kx_groups.as_ref();
194+
self.crypto_provider
195+
.supported_kx_groups()
196+
.filter(move |kx| match filter {
197+
Some(list) => list.contains(&kx.name()),
198+
None => true,
199+
})
200+
}
201+
202+
/// Allowed key exchange groups for DTLS 1.2.
203+
///
204+
/// Like [`kx_groups`](Self::kx_groups) but additionally restricted to
205+
/// groups that DTLS 1.2 supports (currently P-256 and P-384).
206+
pub fn dtls12_kx_groups(&self) -> impl Iterator<Item = &'static dyn SupportedKxGroup> + '_ {
207+
self.kx_groups()
208+
.filter(|kx| matches!(kx.name(), NamedGroup::Secp256r1 | NamedGroup::Secp384r1))
209+
}
141210
}
142211

143212
/// Builder for [`Config`]. See each setter for defaults.
@@ -153,6 +222,9 @@ pub struct ConfigBuilder {
153222
crypto_provider: Option<CryptoProvider>,
154223
rng_seed: Option<u64>,
155224
aead_encryption_limit: u64,
225+
dtls12_cipher_suites: Option<Vec<Dtls12CipherSuite>>,
226+
dtls13_cipher_suites: Option<Vec<Dtls13CipherSuite>>,
227+
kx_groups: Option<Vec<NamedGroup>>,
156228
}
157229

158230
impl ConfigBuilder {
@@ -261,6 +333,41 @@ impl ConfigBuilder {
261333
self
262334
}
263335

336+
/// Restrict which DTLS 1.2 cipher suites are offered and accepted.
337+
///
338+
/// Only cipher suites present in both this list and the provider will
339+
/// be used. Passing an empty slice disables DTLS 1.2 (as long as
340+
/// DTLS 1.3 suites remain).
341+
///
342+
/// By default all provider-supported DTLS 1.2 cipher suites are used.
343+
pub fn dtls12_cipher_suites(mut self, suites: &[Dtls12CipherSuite]) -> Self {
344+
self.dtls12_cipher_suites = Some(suites.to_vec());
345+
self
346+
}
347+
348+
/// Restrict which DTLS 1.3 cipher suites are offered and accepted.
349+
///
350+
/// Only cipher suites present in both this list and the provider will
351+
/// be used. Passing an empty slice disables DTLS 1.3 (as long as
352+
/// DTLS 1.2 suites remain).
353+
///
354+
/// By default all provider DTLS 1.3 cipher suites are used.
355+
pub fn dtls13_cipher_suites(mut self, suites: &[Dtls13CipherSuite]) -> Self {
356+
self.dtls13_cipher_suites = Some(suites.to_vec());
357+
self
358+
}
359+
360+
/// Restrict which key exchange groups are offered and accepted.
361+
///
362+
/// Only groups present in both this list and the provider will be
363+
/// used. Order determines preference (first = most preferred).
364+
///
365+
/// By default all provider-supported key exchange groups are used.
366+
pub fn kx_groups(mut self, groups: &[NamedGroup]) -> Self {
367+
self.kx_groups = Some(groups.to_vec());
368+
self
369+
}
370+
264371
/// Build the configuration.
265372
///
266373
/// This validates the crypto provider before returning the configuration.
@@ -307,6 +414,63 @@ impl ConfigBuilder {
307414
));
308415
}
309416

417+
// Validate cipher suite filters: at least one version must have suites
418+
let dtls12_count = {
419+
let all = crypto_provider.supported_cipher_suites();
420+
match &self.dtls12_cipher_suites {
421+
Some(list) => all.filter(|cs| list.contains(&cs.suite())).count(),
422+
None => all.count(),
423+
}
424+
};
425+
let dtls13_count = {
426+
let all = crypto_provider.dtls13_cipher_suites.iter();
427+
match &self.dtls13_cipher_suites {
428+
Some(list) => all.filter(|cs| list.contains(&cs.suite())).count(),
429+
None => all.count(),
430+
}
431+
};
432+
if dtls12_count + dtls13_count == 0 {
433+
return Err(Error::ConfigError(
434+
"No cipher suites remain after filtering. \
435+
At least one DTLS 1.2 or DTLS 1.3 cipher suite must be available."
436+
.to_string(),
437+
));
438+
}
439+
440+
// Validate kx_groups filter: each enabled version needs compatible groups
441+
let filtered_kx = |kx: &&'static dyn SupportedKxGroup| -> bool {
442+
match &self.kx_groups {
443+
Some(list) => list.contains(&kx.name()),
444+
None => true,
445+
}
446+
};
447+
if dtls12_count > 0 {
448+
let dtls12_kx_count = crypto_provider
449+
.supported_dtls12_kx_groups()
450+
.filter(|kx| filtered_kx(kx))
451+
.count();
452+
if dtls12_kx_count == 0 {
453+
return Err(Error::ConfigError(
454+
"DTLS 1.2 cipher suites are enabled but no compatible key exchange \
455+
groups remain after filtering. DTLS 1.2 requires P-256 or P-384."
456+
.to_string(),
457+
));
458+
}
459+
}
460+
if dtls13_count > 0 {
461+
let kx_count = crypto_provider
462+
.supported_kx_groups()
463+
.filter(|kx| filtered_kx(kx))
464+
.count();
465+
if kx_count == 0 {
466+
return Err(Error::ConfigError(
467+
"DTLS 1.3 cipher suites are enabled but no key exchange groups \
468+
remain after filtering."
469+
.to_string(),
470+
));
471+
}
472+
}
473+
310474
Ok(Config {
311475
mtu: self.mtu,
312476
max_queue_rx: self.max_queue_rx,
@@ -319,6 +483,9 @@ impl ConfigBuilder {
319483
crypto_provider,
320484
rng_seed: self.rng_seed,
321485
aead_encryption_limit: self.aead_encryption_limit,
486+
dtls12_cipher_suites: self.dtls12_cipher_suites,
487+
dtls13_cipher_suites: self.dtls13_cipher_suites,
488+
kx_groups: self.kx_groups,
322489
})
323490
}
324491
}
@@ -384,4 +551,158 @@ mod tests {
384551
.build()
385552
.expect("aead_encryption_limit 1 should be accepted");
386553
}
554+
555+
#[test]
556+
fn filter_dtls12_cipher_suite() {
557+
let config = Config::builder()
558+
.dtls12_cipher_suites(&[Dtls12CipherSuite::ECDHE_ECDSA_AES128_GCM_SHA256])
559+
.build()
560+
.expect("should accept single DTLS 1.2 suite");
561+
let suites: Vec<_> = config.dtls12_cipher_suites().map(|cs| cs.suite()).collect();
562+
assert_eq!(suites, &[Dtls12CipherSuite::ECDHE_ECDSA_AES128_GCM_SHA256]);
563+
}
564+
565+
#[test]
566+
fn filter_dtls13_cipher_suite() {
567+
let config = Config::builder()
568+
.dtls13_cipher_suites(&[Dtls13CipherSuite::AES_256_GCM_SHA384])
569+
.build()
570+
.expect("should accept single DTLS 1.3 suite");
571+
let suites: Vec<_> = config.dtls13_cipher_suites().map(|cs| cs.suite()).collect();
572+
assert_eq!(suites, &[Dtls13CipherSuite::AES_256_GCM_SHA384]);
573+
}
574+
575+
#[test]
576+
fn filter_kx_groups() {
577+
let config = Config::builder()
578+
.kx_groups(&[NamedGroup::Secp256r1])
579+
.build()
580+
.expect("should accept single kx group");
581+
let groups: Vec<_> = config.kx_groups().map(|g| g.name()).collect();
582+
assert_eq!(groups, &[NamedGroup::Secp256r1]);
583+
}
584+
585+
#[test]
586+
fn empty_dtls12_filter_disables_version() {
587+
let config = Config::builder()
588+
.dtls12_cipher_suites(&[])
589+
.build()
590+
.expect("should accept empty DTLS 1.2 when 1.3 has suites");
591+
assert_eq!(config.dtls12_cipher_suites().count(), 0);
592+
assert!(config.dtls13_cipher_suites().count() > 0);
593+
}
594+
595+
#[test]
596+
fn empty_dtls13_filter_disables_version() {
597+
let config = Config::builder()
598+
.dtls13_cipher_suites(&[])
599+
.build()
600+
.expect("should accept empty DTLS 1.3 when 1.2 has suites");
601+
assert!(config.dtls12_cipher_suites().count() > 0);
602+
assert_eq!(config.dtls13_cipher_suites().count(), 0);
603+
}
604+
605+
#[test]
606+
fn both_empty_filters_rejected() {
607+
match Config::builder()
608+
.dtls12_cipher_suites(&[])
609+
.dtls13_cipher_suites(&[])
610+
.build()
611+
{
612+
Err(Error::ConfigError(msg)) => {
613+
assert!(
614+
msg.contains("No cipher suites"),
615+
"error should mention cipher suites: {msg}"
616+
)
617+
}
618+
Err(other) => panic!("expected ConfigError, got: {other:?}"),
619+
Ok(_) => panic!("expected error when both versions are empty"),
620+
}
621+
}
622+
623+
#[test]
624+
fn empty_kx_groups_filter_rejected() {
625+
match Config::builder().kx_groups(&[]).build() {
626+
Err(Error::ConfigError(msg)) => {
627+
assert!(
628+
msg.contains("key exchange"),
629+
"error should mention key exchange: {msg}"
630+
)
631+
}
632+
Err(other) => panic!("expected ConfigError, got: {other:?}"),
633+
Ok(_) => panic!("expected error for empty kx groups"),
634+
}
635+
}
636+
637+
#[test]
638+
fn x25519_only_rejected_for_dtls12() {
639+
// X25519 is not yet supported for DTLS 1.2, so filtering to X25519-only
640+
// while DTLS 1.2 suites are enabled should fail.
641+
match Config::builder()
642+
.dtls13_cipher_suites(&[])
643+
.kx_groups(&[NamedGroup::X25519])
644+
.build()
645+
{
646+
Err(Error::ConfigError(msg)) => {
647+
assert!(
648+
msg.contains("DTLS 1.2") && msg.contains("P-256 or P-384"),
649+
"error should mention DTLS 1.2 and required groups: {msg}"
650+
)
651+
}
652+
Err(other) => panic!("expected ConfigError, got: {other:?}"),
653+
Ok(_) => panic!("expected error for X25519-only with DTLS 1.2"),
654+
}
655+
}
656+
657+
#[test]
658+
fn x25519_only_accepted_for_dtls13_only() {
659+
// X25519-only is fine when DTLS 1.2 is disabled.
660+
let config = Config::builder()
661+
.dtls12_cipher_suites(&[])
662+
.kx_groups(&[NamedGroup::X25519])
663+
.build()
664+
.expect("X25519-only should be accepted for DTLS 1.3-only config");
665+
let groups: Vec<_> = config.kx_groups().map(|g| g.name()).collect();
666+
assert_eq!(groups, &[NamedGroup::X25519]);
667+
}
668+
669+
#[test]
670+
fn dtls12_kx_groups_excludes_x25519() {
671+
let config = Config::default();
672+
let dtls12_groups: Vec<_> = config.dtls12_kx_groups().map(|g| g.name()).collect();
673+
assert!(!dtls12_groups.contains(&NamedGroup::X25519));
674+
assert!(dtls12_groups.contains(&NamedGroup::Secp256r1));
675+
}
676+
677+
#[test]
678+
fn no_filter_returns_all() {
679+
let config = Config::default();
680+
// Default provider should have at least 2 DTLS 1.2 and 2 DTLS 1.3 suites
681+
assert!(config.dtls12_cipher_suites().count() >= 2);
682+
assert!(config.dtls13_cipher_suites().count() >= 2);
683+
assert!(config.kx_groups().count() >= 2);
684+
}
685+
686+
#[test]
687+
fn filter_with_explicit_provider() {
688+
#[cfg(feature = "aws-lc-rs")]
689+
{
690+
let config = Config::builder()
691+
.with_crypto_provider(aws_lc_rs::default_provider())
692+
.dtls12_cipher_suites(&[Dtls12CipherSuite::ECDHE_ECDSA_AES256_GCM_SHA384])
693+
.dtls13_cipher_suites(&[Dtls13CipherSuite::AES_128_GCM_SHA256])
694+
.kx_groups(&[NamedGroup::X25519, NamedGroup::Secp256r1])
695+
.build()
696+
.expect("should accept filtered config with explicit provider");
697+
let suites12: Vec<_> = config.dtls12_cipher_suites().map(|cs| cs.suite()).collect();
698+
assert_eq!(
699+
suites12,
700+
&[Dtls12CipherSuite::ECDHE_ECDSA_AES256_GCM_SHA384]
701+
);
702+
let suites13: Vec<_> = config.dtls13_cipher_suites().map(|cs| cs.suite()).collect();
703+
assert_eq!(suites13, &[Dtls13CipherSuite::AES_128_GCM_SHA256]);
704+
let groups: Vec<_> = config.kx_groups().map(|g| g.name()).collect();
705+
assert_eq!(groups, &[NamedGroup::X25519, NamedGroup::Secp256r1]);
706+
}
707+
}
387708
}

0 commit comments

Comments
 (0)