use crate::ec::{BoxedEcdsaPrivateKey, CurveId};
use crate::hash::Sha256;
use crate::rng::HmacDrbg;
use crate::tls::pki::RootCertStore;
use crate::x509::{CertSigner, Certificate, DistinguishedName, Time, Validity};
use alloc::sync::Arc;
use alloc::vec::Vec;
use super::{
ClientConfig12Internal as PcClientConfig12, DtlsClientConnection12, DtlsServerConnection12,
ServerConfig12Internal as PcServerConfig12,
};
fn make_server() -> (PcServerConfig12, Vec<u8>) {
let mut rng = HmacDrbg::<Sha256>::new(b"dtls12-test-key", b"nonce", &[]);
let key = BoxedEcdsaPrivateKey::generate(CurveId::P256, &mut rng);
let name = DistinguishedName::common_name("dtls.example");
let validity = Validity::new(
Time::utc(2024, 1, 1, 0, 0, 0),
Time::utc(2034, 1, 1, 0, 0, 0),
);
let cert = Certificate::self_signed_general(
&CertSigner::Ecdsa(&key),
&name,
&validity,
1,
false,
&["dtls.example"],
)
.unwrap();
let der = cert.to_der().to_vec();
(
PcServerConfig12::with_ecdsa(alloc::vec![der.clone()], key),
der,
)
}
fn make_client(server_cert: &[u8]) -> DtlsClientConnection12 {
make_client_named(server_cert, "dtls.example")
}
fn make_client_named(server_cert: &[u8], server_name: &str) -> DtlsClientConnection12 {
let mut roots = RootCertStore::new();
roots.add_der(server_cert.to_vec()).unwrap();
let cfg = PcClientConfig12::new(roots, server_name)
.with_verification_time(Time::utc(2026, 6, 1, 0, 0, 0));
let mut crng = HmacDrbg::<Sha256>::new(b"dtls12-client", b"nonce", &[]);
DtlsClientConnection12::new(cfg, b"client-addr".to_vec(), &mut crng)
}
fn pump_handshake<R: crate::rng::RngCore>(
client: &mut DtlsClientConnection12,
server: &mut DtlsServerConnection12<R>,
) -> bool {
for _ in 0..32 {
let c_out = client.pop_outbound_datagrams();
for dg in &c_out {
server.feed_datagram(dg).unwrap();
}
let s_out = server.pop_outbound_datagrams();
for dg in &s_out {
client.feed_datagram(dg).unwrap();
}
if c_out.is_empty() && s_out.is_empty() {
break;
}
}
client.is_handshake_complete() && server.is_handshake_complete()
}
#[test]
fn loopback_no_cookie() {
let (server_cfg, cert) = make_server();
let server_cfg = server_cfg.require_cookie_exchange(false);
let mut client = make_client(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls12-server", b"nonce", &[]);
let mut server =
DtlsServerConnection12::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake(&mut client, &mut server));
}
#[test]
fn loopback_with_cookie() {
let (server_cfg, cert) = make_server();
let server_cfg = server_cfg
.with_cookie_secret([0xa5; 32])
.require_cookie_exchange(true);
let mut client = make_client(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls12-server-cookie", b"nonce", &[]);
let mut server =
DtlsServerConnection12::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake(&mut client, &mut server));
}
#[test]
fn require_cookie_without_secret_fails_closed_12() {
let (server_cfg, cert) = make_server();
let server_cfg = server_cfg.require_cookie_exchange(true);
let mut client = make_client(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls12-failclosed", b"nonce", &[]);
let mut server =
DtlsServerConnection12::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
let c1 = client.pop_outbound_datagrams();
assert!(!c1.is_empty(), "client should emit CH1");
let mut saw_err = false;
for dg in &c1 {
if server.feed_datagram(dg).is_err() {
saw_err = true;
}
}
assert!(
saw_err,
"cookie-required server with no secret must error on CH1"
);
assert!(
server.pop_outbound_datagrams().is_empty(),
"no server flight may be emitted to an unverified source"
);
}
#[test]
fn rejects_certificate_with_mismatched_hostname_12() {
let (server_cfg, cert) = make_server();
let server_cfg = server_cfg.require_cookie_exchange(false);
let mut client = make_client_named(&cert, "wrong.example");
let srng = HmacDrbg::<Sha256>::new(b"dtls12-server-badname", b"nonce", &[]);
let mut server =
DtlsServerConnection12::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
let c1 = client.pop_outbound_datagrams();
for dg in &c1 {
server.feed_datagram(dg).unwrap();
}
let s1 = server.pop_outbound_datagrams();
assert!(!s1.is_empty(), "server should have emitted flight");
let mut saw_err = false;
for dg in &s1 {
if client.feed_datagram(dg).is_err() {
saw_err = true;
}
}
assert!(
saw_err,
"client must reject a certificate that does not match the requested host"
);
assert!(
!client.is_handshake_complete(),
"handshake must not complete with a mismatched server certificate"
);
}
#[test]
fn accepts_certificate_with_matching_hostname_12() {
let (server_cfg, cert) = make_server();
let server_cfg = server_cfg.require_cookie_exchange(false);
let mut client = make_client_named(&cert, "dtls.example");
let srng = HmacDrbg::<Sha256>::new(b"dtls12-server-goodname", b"nonce", &[]);
let mut server =
DtlsServerConnection12::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake(&mut client, &mut server));
}
#[test]
fn reordered_server_flight() {
let (server_cfg, cert) = make_server();
let server_cfg = server_cfg.require_cookie_exchange(false);
let mut client = make_client(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls12-server-reorder", b"nonce", &[]);
let mut server =
DtlsServerConnection12::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
let c1 = client.pop_outbound_datagrams();
for dg in &c1 {
server.feed_datagram(dg).unwrap();
}
let s1 = server.pop_outbound_datagrams();
assert!(!s1.is_empty(), "server should have emitted flight");
for dg in s1.iter().rev() {
client.feed_datagram(dg).unwrap();
}
for _ in 0..16 {
let c = client.pop_outbound_datagrams();
for dg in &c {
server.feed_datagram(dg).unwrap();
}
let s = server.pop_outbound_datagrams();
for dg in &s {
client.feed_datagram(dg).unwrap();
}
if c.is_empty() && s.is_empty() {
break;
}
}
assert!(client.is_handshake_complete());
assert!(server.is_handshake_complete());
}
#[test]
fn replay_rejected_silently() {
let (server_cfg, cert) = make_server();
let server_cfg = server_cfg.require_cookie_exchange(false);
let mut client = make_client(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls12-server-replay", b"nonce", &[]);
let mut server =
DtlsServerConnection12::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake(&mut client, &mut server));
server.send(b"first").unwrap();
let s = server.pop_outbound_datagrams();
assert_eq!(s.len(), 1);
let recorded = s[0].clone();
client.feed_datagram(&recorded).unwrap();
assert_eq!(client.take_received(), b"first");
client.feed_datagram(&recorded).unwrap();
assert!(
client.take_received().is_empty(),
"replay should be silently dropped",
);
server.send(b"second").unwrap();
let s2 = server.pop_outbound_datagrams();
for dg in &s2 {
client.feed_datagram(dg).unwrap();
}
assert_eq!(client.take_received(), b"second");
}
#[test]
fn forged_record_does_not_burn_replay_slot_dtls12() {
let (server_cfg, cert) = make_server();
let server_cfg = server_cfg.require_cookie_exchange(false);
let mut client = make_client(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls12-server-fwd", b"nonce", &[]);
let mut server =
DtlsServerConnection12::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake(&mut client, &mut server));
server.send(b"one").unwrap();
let s1 = server.pop_outbound_datagrams();
assert_eq!(s1.len(), 1);
server.send(b"two").unwrap();
let s2 = server.pop_outbound_datagrams();
assert_eq!(s2.len(), 1);
let mut tampered = s2[0].clone();
let mid = tampered.len() / 2;
tampered[mid] ^= 1;
client.feed_datagram(&s1[0]).unwrap();
assert_eq!(client.take_received(), b"one");
let _ = client.feed_datagram(&tampered);
assert!(client.take_received().is_empty());
client.feed_datagram(&s2[0]).unwrap();
assert_eq!(client.take_received(), b"two");
}
#[test]
fn set_now_advances_cookie_clock_dtls12() {
use core::time::Duration;
let (server_cfg, cert) = make_server();
let server_cfg = server_cfg
.with_cookie_secret([0xa5; 32])
.require_cookie_exchange(true);
let mut client = make_client(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls12-server-set-now", b"nonce", &[]);
let mut server =
DtlsServerConnection12::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
let c1 = client.pop_outbound_datagrams();
for dg in &c1 {
server.feed_datagram(dg).unwrap();
}
let hvr_at_zero = server.pop_outbound_datagrams();
assert!(!hvr_at_zero.is_empty());
server.set_now(Duration::from_secs(60 * 5));
let mut other_client = make_client(&cert);
let other_c1 = other_client.pop_outbound_datagrams();
for dg in &other_c1 {
server.feed_datagram(dg).unwrap();
}
let hvr_at_five = server.pop_outbound_datagrams();
assert!(!hvr_at_five.is_empty());
assert_ne!(hvr_at_zero, hvr_at_five);
server.set_now(Duration::from_secs(0));
let mut third_client = make_client(&cert);
let third_c1 = third_client.pop_outbound_datagrams();
for dg in &third_c1 {
server.feed_datagram(dg).unwrap();
}
let hvr_rewound = server.pop_outbound_datagrams();
assert!(!hvr_rewound.is_empty());
}
#[cfg(feature = "std")]
#[test]
fn stale_ts_zero_cookie_rejected_without_caller_clock_12() {
use core::time::Duration;
let secret = [0x42u8; 32];
let (cfg_b, cert) = make_server();
let cfg_b = cfg_b
.with_cookie_secret(secret)
.require_cookie_exchange(true);
let mut client = make_client(&cert);
let rng_b = HmacDrbg::<Sha256>::new(b"dtls12-cookie-clk-b", b"nonce", &[]);
let mut server_b = DtlsServerConnection12::new(Arc::new(cfg_b), b"client-addr".to_vec(), rng_b);
server_b.set_now(Duration::from_secs(0));
for dg in client.pop_outbound_datagrams() {
server_b.feed_datagram(&dg).unwrap();
}
let hvr = server_b.pop_outbound_datagrams();
assert!(!hvr.is_empty());
for dg in &hvr {
client.feed_datagram(dg).unwrap();
}
let ch2 = client.pop_outbound_datagrams();
assert!(!ch2.is_empty());
let (cfg_a, _) = make_server();
let cfg_a = cfg_a
.with_cookie_secret(secret)
.require_cookie_exchange(true);
let rng_a = HmacDrbg::<Sha256>::new(b"dtls12-cookie-clk-a", b"nonce", &[]);
let mut server_a = DtlsServerConnection12::new(Arc::new(cfg_a), b"client-addr".to_vec(), rng_a);
for dg in &ch2 {
assert_eq!(server_a.feed_datagram(dg), Ok(()));
}
assert!(
server_a.pop_outbound_datagrams().is_empty(),
"expired TS=0 cookie must be silently rejected under a wall clock"
);
for dg in &ch2 {
server_b.feed_datagram(dg).unwrap();
}
assert!(
!server_b.pop_outbound_datagrams().is_empty(),
"control: TS=0 cookie validates on the t=0 caller-clock server"
);
}
#[test]
fn application_data_both_ways_12() {
let (server_cfg, cert) = make_server();
let server_cfg = server_cfg.require_cookie_exchange(false);
let mut client = make_client(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls12-server-app", b"nonce", &[]);
let mut server =
DtlsServerConnection12::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake(&mut client, &mut server));
client.send(b"hello world").unwrap();
let c = client.pop_outbound_datagrams();
for dg in &c {
server.feed_datagram(dg).unwrap();
}
assert_eq!(server.take_received(), b"hello world");
server.send(b"pong from server").unwrap();
let s = server.pop_outbound_datagrams();
for dg in &s {
client.feed_datagram(dg).unwrap();
}
assert_eq!(client.take_received(), b"pong from server");
}
#[test]
fn connection_survives_retransmit_backoff_after_handshake_12() {
use core::time::Duration;
let (server_cfg, cert) = make_server();
let server_cfg = server_cfg.require_cookie_exchange(false);
let mut client = make_client(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls12-server-backoff", b"nonce", &[]);
let mut server =
DtlsServerConnection12::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake(&mut client, &mut server));
assert_eq!(client.next_timeout(), None, "client timer must be disarmed");
assert_eq!(server.next_timeout(), None, "server timer must be disarmed");
for i in 1..=8u64 {
let t = Duration::from_secs(i * 120);
client.on_timeout(t);
server.on_timeout(t);
assert!(
client.pop_outbound_datagrams().is_empty(),
"client must not retransmit after establishment"
);
assert!(
server.pop_outbound_datagrams().is_empty(),
"server must not retransmit after establishment"
);
}
assert!(client.is_handshake_complete(), "client must stay Connected");
assert!(server.is_handshake_complete(), "server must stay Connected");
client.send(b"still alive").unwrap();
for dg in client.pop_outbound_datagrams() {
server.feed_datagram(&dg).unwrap();
}
assert_eq!(server.take_received(), b"still alive");
server.send(b"ack").unwrap();
for dg in server.pop_outbound_datagrams() {
client.feed_datagram(&dg).unwrap();
}
assert_eq!(client.take_received(), b"ack");
}
fn garbage_datagram() -> Vec<u8> {
(0..64u8)
.map(|i| i.wrapping_mul(37).wrapping_add(11))
.collect()
}
fn wrong_version_record() -> Vec<u8> {
alloc::vec![
22u8, 0xde, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x04, 0xaa, 0xbb, 0xcc, 0xdd,
]
}
fn short_fragment_record() -> Vec<u8> {
alloc::vec![
22u8, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x04, 0xaa, 0xbb, 0xcc, 0xdd,
]
}
#[test]
fn spoofed_records_are_silently_dropped_12() {
let (server_cfg, cert) = make_server();
let server_cfg = server_cfg.require_cookie_exchange(false);
let mut client = make_client(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls12-server-spoof", b"nonce", &[]);
let mut server =
DtlsServerConnection12::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake(&mut client, &mut server));
let garbage = garbage_datagram();
assert_eq!(client.feed_datagram(&garbage), Ok(()));
assert_eq!(server.feed_datagram(&garbage), Ok(()));
server.send(b"to-client").unwrap();
let s = server.pop_outbound_datagrams();
assert_eq!(s.len(), 1);
let mut tampered_s = s[0].clone();
let last = tampered_s.len() - 1;
tampered_s[last] ^= 0x5a;
assert_eq!(client.feed_datagram(&tampered_s), Ok(()));
assert!(client.take_received().is_empty());
client.send(b"to-server").unwrap();
let c = client.pop_outbound_datagrams();
assert_eq!(c.len(), 1);
let mut tampered_c = c[0].clone();
let last = tampered_c.len() - 1;
tampered_c[last] ^= 0x5a;
assert_eq!(server.feed_datagram(&tampered_c), Ok(()));
assert!(server.take_received().is_empty());
let bad_version = wrong_version_record();
assert_eq!(client.feed_datagram(&bad_version), Ok(()));
assert_eq!(server.feed_datagram(&bad_version), Ok(()));
client.feed_datagram(&s[0]).unwrap();
assert_eq!(client.take_received(), b"to-client");
server.feed_datagram(&c[0]).unwrap();
assert_eq!(server.take_received(), b"to-server");
client.send(b"still alive c2s").unwrap();
for dg in &client.pop_outbound_datagrams() {
server.feed_datagram(dg).unwrap();
}
assert_eq!(server.take_received(), b"still alive c2s");
server.send(b"still alive s2c").unwrap();
for dg in &server.pop_outbound_datagrams() {
client.feed_datagram(dg).unwrap();
}
assert_eq!(client.take_received(), b"still alive s2c");
}
#[test]
fn spoofed_records_during_handshake_are_ignored_12() {
let (server_cfg, cert) = make_server();
let server_cfg = server_cfg.require_cookie_exchange(false);
let mut client = make_client(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls12-server-spoof-hs", b"nonce", &[]);
let mut server =
DtlsServerConnection12::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
let spoofs = [
garbage_datagram(),
wrong_version_record(),
short_fragment_record(),
];
for _ in 0..32 {
for sp in &spoofs {
assert_eq!(client.feed_datagram(sp), Ok(()));
assert_eq!(server.feed_datagram(sp), Ok(()));
}
let c_out = client.pop_outbound_datagrams();
for dg in &c_out {
server.feed_datagram(dg).unwrap();
}
let s_out = server.pop_outbound_datagrams();
for dg in &s_out {
client.feed_datagram(dg).unwrap();
}
if c_out.is_empty() && s_out.is_empty() {
break;
}
}
assert!(client.is_handshake_complete());
assert!(server.is_handshake_complete());
}
#[test]
fn spoofed_client_hellos_mid_handshake_are_ignored_12() {
let (server_cfg, cert) = make_server();
let server_cfg = server_cfg
.with_cookie_secret([0xa5; 32])
.require_cookie_exchange(true);
let mut client = make_client(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls12-server-spoof-ch", b"nonce", &[]);
let mut server =
DtlsServerConnection12::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
for dg in &client.pop_outbound_datagrams() {
server.feed_datagram(dg).unwrap();
}
let hvr = server.pop_outbound_datagrams();
assert!(!hvr.is_empty(), "server should emit HVR");
for dg in &hvr {
client.feed_datagram(dg).unwrap();
}
let ch2 = client.pop_outbound_datagrams();
assert!(!ch2.is_empty(), "client should emit CH2");
let mut bad_cookie = ch2[0].clone();
bad_cookie[27] ^= 0x5a;
assert_eq!(server.feed_datagram(&bad_cookie), Ok(()));
assert!(
server.pop_outbound_datagrams().is_empty(),
"no flight may be emitted for a forged-cookie CH"
);
let mut big_seq = ch2[0].clone();
big_seq[17] = 0xff;
big_seq[18] = 0xff;
assert_eq!(server.feed_datagram(&big_seq), Ok(()));
assert!(
server.pop_outbound_datagrams().is_empty(),
"no flight may be emitted for an oversized-message_seq CH"
);
for dg in &ch2 {
server.feed_datagram(dg).unwrap();
}
assert!(pump_handshake(&mut client, &mut server));
}
#[test]
fn exporter_agrees_both_sides_12() {
let (server_cfg, cert) = make_server();
let server_cfg = server_cfg.require_cookie_exchange(false);
let mut client = make_client(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls12-server-exporter", b"nonce", &[]);
let mut server =
DtlsServerConnection12::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake(&mut client, &mut server));
let mut c_out = [0u8; 48];
let mut s_out = [0u8; 48];
client
.tls_exporter(b"EXPERIMENTAL-dtls", None, &mut c_out)
.unwrap();
server
.tls_exporter(b"EXPERIMENTAL-dtls", None, &mut s_out)
.unwrap();
assert_eq!(c_out, s_out);
let mut c_ctx = [0u8; 48];
let mut s_ctx = [0u8; 48];
client
.tls_exporter(b"EXPERIMENTAL-dtls", Some(b"binding"), &mut c_ctx)
.unwrap();
server
.tls_exporter(b"EXPERIMENTAL-dtls", Some(b"binding"), &mut s_ctx)
.unwrap();
assert_eq!(c_ctx, s_ctx);
let mut c_empty = [0u8; 48];
client
.tls_exporter(b"EXPERIMENTAL-dtls", Some(&[]), &mut c_empty)
.unwrap();
assert_ne!(c_out, c_empty);
}
mod dtls13 {
use super::*;
use crate::dtls::{
ClientConfig13Internal as PcClientConfig13, DtlsClientConnection13, DtlsServerConnection13,
ServerConfig13Internal as PcServerConfig13,
};
fn make_server13() -> (PcServerConfig13, Vec<u8>) {
let mut rng = HmacDrbg::<Sha256>::new(b"dtls13-test-key", b"nonce", &[]);
let key = BoxedEcdsaPrivateKey::generate(CurveId::P256, &mut rng);
let name = DistinguishedName::common_name("dtls.example");
let validity = Validity::new(
Time::utc(2024, 1, 1, 0, 0, 0),
Time::utc(2034, 1, 1, 0, 0, 0),
);
let cert = Certificate::self_signed_general(
&CertSigner::Ecdsa(&key),
&name,
&validity,
1,
false,
&["dtls.example"],
)
.unwrap();
let der = cert.to_der().to_vec();
(
PcServerConfig13::with_ecdsa(alloc::vec![der.clone()], key),
der,
)
}
fn make_client13(server_cert: &[u8]) -> DtlsClientConnection13 {
let mut roots = RootCertStore::new();
roots.add_der(server_cert.to_vec()).unwrap();
let cfg = PcClientConfig13::new(roots, "dtls.example")
.with_verification_time(Time::utc(2026, 6, 1, 0, 0, 0));
let mut crng = HmacDrbg::<Sha256>::new(b"dtls13-client", b"nonce", &[]);
DtlsClientConnection13::new(cfg, b"client-addr".to_vec(), &mut crng)
}
fn pump_handshake_13<R: crate::rng::RngCore>(
client: &mut DtlsClientConnection13,
server: &mut DtlsServerConnection13<R>,
) -> bool {
for _ in 0..32 {
let c_out = client.pop_outbound_datagrams();
for dg in &c_out {
server.feed_datagram(dg).unwrap();
}
let s_out = server.pop_outbound_datagrams();
for dg in &s_out {
client.feed_datagram(dg).unwrap();
}
if c_out.is_empty() && s_out.is_empty() {
break;
}
}
client.is_handshake_complete() && server.is_handshake_complete()
}
#[test]
fn loopback_no_cookie() {
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_no_cookie();
let mut client = make_client13(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-server", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
}
#[test]
fn loopback_with_cookie() {
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_cookie_secret([0xa5; 32]);
let mut client = make_client13(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-server-cookie", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
}
#[test]
fn spoofed_client_hellos_mid_handshake_are_ignored_13() {
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_cookie_secret([0xa5; 32]);
let mut client = make_client13(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-server-spoof-ch", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
for dg in &client.pop_outbound_datagrams() {
server.feed_datagram(dg).unwrap();
}
let hrr = server.pop_outbound_datagrams();
assert!(!hrr.is_empty(), "server should emit cookie HRR");
for dg in &hrr {
client.feed_datagram(dg).unwrap();
}
let ch2 = client.pop_outbound_datagrams();
assert!(!ch2.is_empty(), "client should emit CH2");
let mut tampered = ch2.clone();
tampered[0][27] ^= 0x5a;
for dg in &tampered {
assert_eq!(server.feed_datagram(dg), Ok(()));
}
assert!(
server.pop_outbound_datagrams().is_empty(),
"no flight may be emitted for a forged-cookie CH"
);
let mut big_seq = ch2[0].clone();
big_seq[17] = 0xff;
big_seq[18] = 0xff;
assert_eq!(server.feed_datagram(&big_seq), Ok(()));
assert!(
server.pop_outbound_datagrams().is_empty(),
"no flight may be emitted for an oversized-message_seq CH"
);
for dg in &ch2 {
server.feed_datagram(dg).unwrap();
}
assert!(pump_handshake_13(&mut client, &mut server));
}
#[test]
fn oversized_pre_cookie_ch_claim_is_dropped_13() {
let mut frag = alloc::vec![
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, ];
frag.extend_from_slice(&[0xab; 16]);
let mut spoof = alloc::vec![
22u8, 0xfe,
0xfd, 0x00,
0x00, 0x00,
0x00,
0x00,
0x00,
0x00,
0x07, 0x00,
frag.len() as u8, ];
spoof.extend_from_slice(&frag);
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_cookie_secret([0xa5; 32]);
let mut client = make_client13(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-server-bigclaim", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert_eq!(server.feed_datagram(&spoof), Ok(()));
assert!(server.pop_outbound_datagrams().is_empty());
assert!(pump_handshake_13(&mut client, &mut server));
}
#[test]
fn require_cookie_without_secret_fails_closed_13() {
let (server_cfg, cert) = make_server13();
let mut client = make_client13(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-failclosed", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
let c1 = client.pop_outbound_datagrams();
assert!(!c1.is_empty(), "client should emit CH1");
let mut saw_err = false;
for dg in &c1 {
if server.feed_datagram(dg).is_err() {
saw_err = true;
}
}
assert!(
saw_err,
"cookie-required server with no secret must error on CH1"
);
assert!(
server.pop_outbound_datagrams().is_empty(),
"no server flight may be emitted to an unverified source"
);
}
#[test]
fn exporter_agrees_both_sides_13() {
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_no_cookie();
let mut client = make_client13(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-server-exporter", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
let mut c_out = [0u8; 64];
let mut s_out = [0u8; 64];
client
.tls_exporter(b"EXPORTER-dtls-test", b"some context", &mut c_out)
.unwrap();
server
.tls_exporter(b"EXPORTER-dtls-test", b"some context", &mut s_out)
.unwrap();
assert_eq!(c_out, s_out);
let mut c_other = [0u8; 64];
client
.tls_exporter(b"EXPORTER-dtls-test", b"other context", &mut c_other)
.unwrap();
assert_ne!(c_out, c_other);
}
#[test]
fn loopback_ed25519() {
use crate::ec::Ed25519PrivateKey;
use crate::tls::conn::ServerKey;
let mut rng = HmacDrbg::<Sha256>::new(b"dtls13-ed25519-key", b"nonce", &[]);
let key = Ed25519PrivateKey::generate(&mut rng);
let name = DistinguishedName::common_name("dtls.example");
let validity = Validity::new(
Time::utc(2024, 1, 1, 0, 0, 0),
Time::utc(2034, 1, 1, 0, 0, 0),
);
let cert = Certificate::self_signed_general(
&CertSigner::Ed25519(&key),
&name,
&validity,
1,
false,
&["dtls.example"],
)
.unwrap();
let der = cert.to_der().to_vec();
let server_cfg =
PcServerConfig13::with_signing_key(alloc::vec![der.clone()], ServerKey::Ed25519(key))
.with_no_cookie();
let mut client = make_client13(&der);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-server-ed25519", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
client.send(b"ping-ed25519").unwrap();
let c = client.pop_outbound_datagrams();
for dg in &c {
server.feed_datagram(dg).unwrap();
}
assert_eq!(server.take_received(), b"ping-ed25519");
server.send(b"pong-ed25519").unwrap();
let s = server.pop_outbound_datagrams();
for dg in &s {
client.feed_datagram(dg).unwrap();
}
assert_eq!(client.take_received(), b"pong-ed25519");
}
#[test]
fn loopback_rsa_cert() {
use crate::rsa::BoxedRsaPrivateKey;
use crate::test_util::rsa_test_key_a;
use crate::tls::conn::ServerKey;
use crate::x509::CertSigner;
let rsa_key = rsa_test_key_a();
let boxed = BoxedRsaPrivateKey::from_pkcs1_der(&rsa_key.to_pkcs1_der()).unwrap();
let name = DistinguishedName::common_name("dtls.example");
let validity = Validity::new(
Time::utc(2024, 1, 1, 0, 0, 0),
Time::utc(2034, 1, 1, 0, 0, 0),
);
let cert = Certificate::self_signed_general(
&CertSigner::Rsa(&boxed),
&name,
&validity,
1,
false,
&["dtls.example"],
)
.unwrap();
let der = cert.to_der().to_vec();
let server_cfg =
PcServerConfig13::with_signing_key(alloc::vec![der.clone()], ServerKey::Rsa(boxed))
.with_no_cookie();
let mut client = make_client13(&der);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-server-rsa", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
client.send(b"ping-rsa").unwrap();
let c = client.pop_outbound_datagrams();
for dg in &c {
server.feed_datagram(dg).unwrap();
}
assert_eq!(server.take_received(), b"ping-rsa");
}
#[test]
fn loopback_mldsa65_cert() {
use crate::tls::conn::ServerKey;
let mut rng = HmacDrbg::<Sha256>::new(b"dtls13-mldsa-key", b"nonce", &[]);
let (sk, _pk) = crate::mldsa::MlDsa65PrivateKey::generate(&mut rng);
let name = DistinguishedName::common_name("dtls.example");
let validity = Validity::new(
Time::utc(2024, 1, 1, 0, 0, 0),
Time::utc(2034, 1, 1, 0, 0, 0),
);
let cert = Certificate::self_signed_general(
&CertSigner::MlDsa65(&sk),
&name,
&validity,
1,
false,
&["dtls.example"],
)
.unwrap();
let der = cert.to_der().to_vec();
let server_cfg =
PcServerConfig13::with_signing_key(alloc::vec![der.clone()], ServerKey::MlDsa65(sk))
.with_no_cookie();
let mut client = make_client13(&der);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-server-mldsa", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
client.send(b"ping-mldsa").unwrap();
let c = client.pop_outbound_datagrams();
for dg in &c {
server.feed_datagram(dg).unwrap();
}
assert_eq!(server.take_received(), b"ping-mldsa");
}
#[test]
fn keylog_loopback_agrees() {
use crate::tls::WriterKeyLog;
use alloc::collections::BTreeMap;
use alloc::string::ToString;
let buf: Vec<u8> = Vec::new();
let sink = Arc::new(WriterKeyLog::new(buf));
let (mut server_cfg, cert) = make_server13();
server_cfg.key_log = Some(sink.clone());
let server_cfg = server_cfg.with_no_cookie();
let mut roots = RootCertStore::new();
roots.add_der(cert.clone()).unwrap();
let mut client_cfg = PcClientConfig13::new(roots, "dtls.example")
.with_verification_time(Time::utc(2026, 6, 1, 0, 0, 0));
client_cfg.key_log = Some(sink.clone());
let mut crng = HmacDrbg::<Sha256>::new(b"dtls13-kl-client", b"nonce", &[]);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-kl-server", b"nonce", &[]);
let mut client =
DtlsClientConnection13::new(client_cfg, b"client-addr".to_vec(), &mut crng);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
drop(client);
drop(server);
let log_text: alloc::string::String = {
let buf = sink.writer_lock_for_test();
core::str::from_utf8(&buf).unwrap().to_string()
};
let want_labels = [
"CLIENT_HANDSHAKE_TRAFFIC_SECRET",
"SERVER_HANDSHAKE_TRAFFIC_SECRET",
"CLIENT_TRAFFIC_SECRET_0",
"SERVER_TRAFFIC_SECRET_0",
"EXPORTER_SECRET",
];
let mut per_label: BTreeMap<&str, Vec<&str>> = BTreeMap::new();
for line in log_text.lines() {
let parts: Vec<&str> = line.split_ascii_whitespace().collect();
assert_eq!(parts.len(), 3, "malformed keylog line: {line}");
assert_eq!(parts[1].len(), 64);
per_label.entry(parts[0]).or_default().push(parts[2]);
}
for label in want_labels {
let entries = per_label
.get(label)
.unwrap_or_else(|| panic!("missing label {label} in keylog:\n{log_text}"));
assert_eq!(
entries.len(),
2,
"expected {label} twice, got {}",
entries.len()
);
assert_eq!(entries[0], entries[1], "client/server disagree on {label}");
}
}
#[test]
fn application_data_both_ways() {
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_no_cookie();
let mut client = make_client13(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-server-app", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
client.send(b"hello world").unwrap();
let c = client.pop_outbound_datagrams();
for dg in &c {
server.feed_datagram(dg).unwrap();
}
assert_eq!(server.take_received(), b"hello world");
server.send(b"pong from server").unwrap();
let s = server.pop_outbound_datagrams();
for dg in &s {
client.feed_datagram(dg).unwrap();
}
assert_eq!(client.take_received(), b"pong from server");
}
#[test]
fn encrypted_seq_is_masked() {
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_no_cookie();
let mut client = make_client13(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-server-mask", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
for i in 0..4u8 {
client.send(&[i; 8]).unwrap();
}
let datagrams = client.pop_outbound_datagrams();
assert!(datagrams.len() >= 4);
let mut any_nonzero_seq_byte = false;
for dg in &datagrams {
assert_eq!(dg[0] & 0b1110_0000, 0b0010_0000);
if (dg[0] & 0b0000_1000) != 0 && (dg[1] != 0 || dg[2] != 0) {
any_nonzero_seq_byte = true;
}
}
assert!(
any_nonzero_seq_byte,
"expected at least one record to have a non-zero masked seq byte",
);
}
#[test]
fn ack_driven_retransmit() {
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_no_cookie();
let mut client = make_client13(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-server-ack-rt", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
let c1 = client.pop_outbound_datagrams();
for dg in &c1 {
server.feed_datagram(dg).unwrap();
}
let s1 = server.pop_outbound_datagrams();
assert!(
s1.len() >= 4,
"server should have emitted multi-record flight"
);
let dropped = s1.last().cloned().unwrap();
for dg in &s1[..s1.len() - 1] {
client.feed_datagram(dg).unwrap();
}
assert!(!client.is_handshake_complete());
let c_ack = client.pop_outbound_datagrams();
for dg in &c_ack {
server.feed_datagram(dg).unwrap();
}
let deadline = server.next_timeout().expect("server timer armed");
server.on_timeout(deadline);
let retransmitted = server.pop_outbound_datagrams();
assert!(!retransmitted.is_empty(), "server should retransmit");
assert!(
retransmitted.len() < s1.len(),
"retransmit should drop ACKed records ({} < {})",
retransmitted.len(),
s1.len()
);
let contains_dropped = retransmitted.iter().any(|dg| dg == &dropped);
assert!(
contains_dropped,
"retransmitted set should include the dropped server Finished"
);
for dg in &retransmitted {
client.feed_datagram(dg).unwrap();
}
for _ in 0..16 {
let c = client.pop_outbound_datagrams();
for dg in &c {
server.feed_datagram(dg).unwrap();
}
let s = server.pop_outbound_datagrams();
for dg in &s {
client.feed_datagram(dg).unwrap();
}
if c.is_empty() && s.is_empty() {
break;
}
}
assert!(client.is_handshake_complete());
assert!(server.is_handshake_complete());
}
#[test]
fn no_ack_of_ack_ping_pong_13() {
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_no_cookie();
let mut client = make_client13(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-server-ackack", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
let mut quiesced = false;
for _ in 0..4 {
let c = client.pop_outbound_datagrams();
for dg in &c {
server.feed_datagram(dg).unwrap();
}
let s = server.pop_outbound_datagrams();
for dg in &s {
client.feed_datagram(dg).unwrap();
}
if c.is_empty() && s.is_empty() {
quiesced = true;
break;
}
}
assert!(
quiesced,
"post-handshake ACK exchange must quiesce: an ACK is never ACKed"
);
}
#[test]
fn close_notify_closes_connection_13() {
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_no_cookie();
let mut client = make_client13(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-server-closenotify", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
server.send_alert_record_for_test(1, 0);
for dg in server.pop_outbound_datagrams() {
client.feed_datagram(&dg).unwrap();
}
assert!(
!client.is_handshake_complete(),
"close_notify must leave the Connected state"
);
assert!(
client.send(b"after close").is_err(),
"sends after close_notify must be refused"
);
let (server_cfg2, cert2) = make_server13();
let server_cfg2 = server_cfg2.with_no_cookie();
let mut client2 = make_client13(&cert2);
let srng2 = HmacDrbg::<Sha256>::new(b"dtls13-server-closenotify2", b"nonce", &[]);
let mut server2 =
DtlsServerConnection13::new(Arc::new(server_cfg2), b"client-addr".to_vec(), srng2);
assert!(pump_handshake_13(&mut client2, &mut server2));
client2.send_alert_record_for_test(1, 0);
for dg in client2.pop_outbound_datagrams() {
server2.feed_datagram(&dg).unwrap();
}
assert!(!server2.is_handshake_complete());
assert!(server2.send(b"after close").is_err());
}
#[test]
fn fatal_alert_surfaces_error_13() {
use crate::tls::{AlertDescription, Error};
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_no_cookie();
let mut client = make_client13(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-server-fatalalert", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
client.send_alert_record_for_test(2, 40);
let mut got = None;
for dg in client.pop_outbound_datagrams() {
if let Err(e) = server.feed_datagram(&dg) {
got = Some(e);
}
}
assert_eq!(
got,
Some(Error::AlertReceived(AlertDescription::HandshakeFailure))
);
assert!(!server.is_handshake_complete());
assert!(server.send(b"after fatal alert").is_err());
}
#[cfg(feature = "std")]
#[test]
fn stale_ts_zero_cookie_rejected_without_caller_clock_13() {
use core::time::Duration;
let secret = [0x42u8; 32];
let (cfg_b, cert) = make_server13();
let cfg_b = cfg_b.with_cookie_secret(secret);
let mut client = make_client13(&cert);
let rng_b = HmacDrbg::<Sha256>::new(b"dtls13-cookie-clk-b", b"nonce", &[]);
let mut server_b =
DtlsServerConnection13::new(Arc::new(cfg_b), b"client-addr".to_vec(), rng_b);
server_b.set_now(Duration::from_secs(0));
for dg in client.pop_outbound_datagrams() {
server_b.feed_datagram(&dg).unwrap();
}
let hrr = server_b.pop_outbound_datagrams();
assert!(!hrr.is_empty());
for dg in &hrr {
client.feed_datagram(dg).unwrap();
}
let ch2 = client.pop_outbound_datagrams();
assert!(!ch2.is_empty());
let (cfg_a, _) = make_server13();
let cfg_a = cfg_a.with_cookie_secret(secret);
let rng_a = HmacDrbg::<Sha256>::new(b"dtls13-cookie-clk-a", b"nonce", &[]);
let mut server_a =
DtlsServerConnection13::new(Arc::new(cfg_a), b"client-addr".to_vec(), rng_a);
for dg in &ch2 {
assert_eq!(server_a.feed_datagram(dg), Ok(()));
}
assert!(
server_a.pop_outbound_datagrams().is_empty(),
"expired TS=0 cookie must be silently rejected under a wall clock"
);
for dg in &ch2 {
server_b.feed_datagram(dg).unwrap();
}
assert!(
!server_b.pop_outbound_datagrams().is_empty(),
"control: TS=0 cookie validates on the t=0 caller-clock server"
);
}
#[test]
fn connection_survives_retransmit_backoff_after_handshake_13() {
use core::time::Duration;
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_no_cookie();
let mut client = make_client13(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-server-backoff", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
assert_eq!(client.next_timeout(), None, "client timer must be disarmed");
assert_eq!(server.next_timeout(), None, "server timer must be disarmed");
for i in 1..=8u64 {
let t = Duration::from_secs(i * 120);
client.on_timeout(t);
server.on_timeout(t);
assert!(
client.pop_outbound_datagrams().is_empty(),
"client must not retransmit after establishment"
);
assert!(
server.pop_outbound_datagrams().is_empty(),
"server must not retransmit after establishment"
);
}
assert!(client.is_handshake_complete(), "client must stay Connected");
assert!(server.is_handshake_complete(), "server must stay Connected");
client.send(b"still alive").unwrap();
for dg in client.pop_outbound_datagrams() {
server.feed_datagram(&dg).unwrap();
}
assert_eq!(server.take_received(), b"still alive");
server.send(b"ack").unwrap();
for dg in server.pop_outbound_datagrams() {
client.feed_datagram(&dg).unwrap();
}
assert_eq!(client.take_received(), b"ack");
}
#[test]
fn lost_final_ack_does_not_close_established_client_13() {
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_no_cookie();
let mut client = make_client13(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-server-lostack", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
for dg in client.pop_outbound_datagrams() {
server.feed_datagram(&dg).unwrap();
}
for dg in server.pop_outbound_datagrams() {
client.feed_datagram(&dg).unwrap();
}
for dg in client.pop_outbound_datagrams() {
server.feed_datagram(&dg).unwrap();
}
let dropped = server.pop_outbound_datagrams();
assert!(!dropped.is_empty(), "server should have queued the ACK");
assert!(client.is_handshake_complete());
assert!(server.is_handshake_complete());
let mut fired = 0;
while let Some(t) = client.next_timeout() {
client.on_timeout(t);
let _ = client.pop_outbound_datagrams();
fired += 1;
assert!(fired <= 16, "retransmit schedule must terminate");
}
assert!(
client.is_handshake_complete(),
"GiveUp must not close an established connection"
);
assert_eq!(client.next_timeout(), None);
client.send(b"ping").unwrap();
for dg in client.pop_outbound_datagrams() {
server.feed_datagram(&dg).unwrap();
}
assert_eq!(server.take_received(), b"ping");
}
#[test]
fn loopback_negotiates_chacha20() {
use crate::tls::codec::CipherSuite;
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_no_cookie();
let mut roots = RootCertStore::new();
roots.add_der(cert.clone()).unwrap();
let mut client_cfg = PcClientConfig13::new(roots, "dtls.example")
.with_verification_time(Time::utc(2026, 6, 1, 0, 0, 0));
client_cfg.cipher_suites = alloc::vec![CipherSuite::CHACHA20_POLY1305_SHA256];
let mut crng = HmacDrbg::<Sha256>::new(b"dtls13-cl-chacha", b"nonce", &[]);
let mut client =
DtlsClientConnection13::new(client_cfg, b"client-addr".to_vec(), &mut crng);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-srv-chacha", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
assert_eq!(client.negotiated_cipher_suite(), Some(0x1303));
client.send(b"ping-chacha").unwrap();
let c = client.pop_outbound_datagrams();
for dg in &c {
server.feed_datagram(dg).unwrap();
}
assert_eq!(server.take_received(), b"ping-chacha");
server.send(b"pong-chacha").unwrap();
let s = server.pop_outbound_datagrams();
for dg in &s {
client.feed_datagram(dg).unwrap();
}
assert_eq!(client.take_received(), b"pong-chacha");
}
#[test]
fn loopback_negotiates_aes256_gcm() {
use crate::tls::codec::CipherSuite;
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_no_cookie();
let mut roots = RootCertStore::new();
roots.add_der(cert.clone()).unwrap();
let mut client_cfg = PcClientConfig13::new(roots, "dtls.example")
.with_verification_time(Time::utc(2026, 6, 1, 0, 0, 0));
client_cfg.cipher_suites = alloc::vec![CipherSuite::AES_256_GCM_SHA384];
let mut crng = HmacDrbg::<Sha256>::new(b"dtls13-cl-aes256", b"nonce", &[]);
let mut client =
DtlsClientConnection13::new(client_cfg, b"client-addr".to_vec(), &mut crng);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-srv-aes256", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
assert_eq!(client.negotiated_cipher_suite(), Some(0x1302));
client.send(b"ping-aes256").unwrap();
let c = client.pop_outbound_datagrams();
for dg in &c {
server.feed_datagram(dg).unwrap();
}
assert_eq!(server.take_received(), b"ping-aes256");
server.send(b"pong-aes256").unwrap();
let s = server.pop_outbound_datagrams();
for dg in &s {
client.feed_datagram(dg).unwrap();
}
assert_eq!(client.take_received(), b"pong-aes256");
}
#[test]
fn loopback_prefers_aes128_when_offered() {
use crate::tls::codec::CipherSuite;
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_no_cookie();
let mut roots = RootCertStore::new();
roots.add_der(cert.clone()).unwrap();
let mut client_cfg = PcClientConfig13::new(roots, "dtls.example")
.with_verification_time(Time::utc(2026, 6, 1, 0, 0, 0));
client_cfg.cipher_suites = alloc::vec![
CipherSuite::AES_128_GCM_SHA256,
CipherSuite::AES_256_GCM_SHA384,
CipherSuite::CHACHA20_POLY1305_SHA256,
];
let mut crng = HmacDrbg::<Sha256>::new(b"dtls13-cl-all", b"nonce", &[]);
let mut client =
DtlsClientConnection13::new(client_cfg, b"client-addr".to_vec(), &mut crng);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-srv-all", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
assert_eq!(client.negotiated_cipher_suite(), Some(0x1301));
}
#[test]
fn negotiates_p256() {
use crate::tls::codec::NamedGroup;
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_no_cookie();
let mut roots = RootCertStore::new();
roots.add_der(cert.clone()).unwrap();
let mut client_cfg = PcClientConfig13::new(roots, "dtls.example")
.with_verification_time(Time::utc(2026, 6, 1, 0, 0, 0));
client_cfg.groups = alloc::vec![NamedGroup::SECP256R1];
let mut crng = HmacDrbg::<Sha256>::new(b"dtls13-cl-p256", b"nonce", &[]);
let mut client =
DtlsClientConnection13::new(client_cfg, b"client-addr".to_vec(), &mut crng);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-srv-p256", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
client.send(b"ping-p256").unwrap();
let c = client.pop_outbound_datagrams();
for dg in &c {
server.feed_datagram(dg).unwrap();
}
assert_eq!(server.take_received(), b"ping-p256");
}
#[test]
fn negotiates_p384() {
use crate::tls::codec::NamedGroup;
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_no_cookie();
let mut roots = RootCertStore::new();
roots.add_der(cert.clone()).unwrap();
let mut client_cfg = PcClientConfig13::new(roots, "dtls.example")
.with_verification_time(Time::utc(2026, 6, 1, 0, 0, 0));
client_cfg.groups = alloc::vec![NamedGroup::SECP384R1];
let mut crng = HmacDrbg::<Sha256>::new(b"dtls13-cl-p384", b"nonce", &[]);
let mut client =
DtlsClientConnection13::new(client_cfg, b"client-addr".to_vec(), &mut crng);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-srv-p384", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
client.send(b"ping-p384").unwrap();
let c = client.pop_outbound_datagrams();
for dg in &c {
server.feed_datagram(dg).unwrap();
}
assert_eq!(server.take_received(), b"ping-p384");
}
#[test]
fn negotiates_mlkem768() {
use crate::tls::codec::NamedGroup;
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_no_cookie();
let mut roots = RootCertStore::new();
roots.add_der(cert.clone()).unwrap();
let mut client_cfg = PcClientConfig13::new(roots, "dtls.example")
.with_verification_time(Time::utc(2026, 6, 1, 0, 0, 0));
client_cfg.groups = alloc::vec![NamedGroup::X25519MLKEM768];
let mut crng = HmacDrbg::<Sha256>::new(b"dtls13-cl-mlkem", b"nonce", &[]);
let mut client =
DtlsClientConnection13::new(client_cfg, b"client-addr".to_vec(), &mut crng);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-srv-mlkem", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
client.send(b"ping-mlkem").unwrap();
let c = client.pop_outbound_datagrams();
for dg in &c {
server.feed_datagram(dg).unwrap();
}
assert_eq!(server.take_received(), b"ping-mlkem");
server.send(b"pong-mlkem").unwrap();
let s = server.pop_outbound_datagrams();
for dg in &s {
client.feed_datagram(dg).unwrap();
}
assert_eq!(client.take_received(), b"pong-mlkem");
}
#[test]
fn hrr_upgrades_group() {
use crate::tls::codec::NamedGroup;
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_no_cookie();
let mut roots = RootCertStore::new();
roots.add_der(cert.clone()).unwrap();
let mut client_cfg = PcClientConfig13::new(roots, "dtls.example")
.with_verification_time(Time::utc(2026, 6, 1, 0, 0, 0));
client_cfg.groups = alloc::vec![
NamedGroup::X25519MLKEM768,
NamedGroup::X25519,
NamedGroup::SECP256R1,
];
client_cfg.key_share_groups = Some(alloc::vec![NamedGroup::SECP256R1]);
let mut crng = HmacDrbg::<Sha256>::new(b"dtls13-cl-hrr", b"nonce", &[]);
let mut client =
DtlsClientConnection13::new(client_cfg, b"client-addr".to_vec(), &mut crng);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-srv-hrr", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
client.send(b"ping-hrr").unwrap();
let c = client.pop_outbound_datagrams();
for dg in &c {
server.feed_datagram(dg).unwrap();
}
assert_eq!(server.take_received(), b"ping-hrr");
}
#[test]
fn cookie_round_does_not_pin_state() {
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_cookie_secret([0xa5; 32]);
let mut client = make_client13(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-server-no-pin", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
let c1 = client.pop_outbound_datagrams();
assert!(!c1.is_empty(), "client should have emitted CH1");
for dg in &c1 {
server.feed_datagram(dg).unwrap();
}
let hrr = server.pop_outbound_datagrams();
assert!(!hrr.is_empty(), "server should emit HRR with cookie");
let mut other_client = make_client13(&cert);
let other_c1 = other_client.pop_outbound_datagrams();
for dg in &other_c1 {
server.feed_datagram(dg).unwrap();
}
let hrr2 = server.pop_outbound_datagrams();
assert!(
!hrr2.is_empty(),
"server must re-issue HRR for the second client's CH1, not silently drop"
);
for dg in &hrr {
client.feed_datagram(dg).unwrap();
}
assert!(pump_handshake_13(&mut client, &mut server));
}
#[test]
fn cookie_binds_ch_content_fingerprint() {
use crate::dtls::cookie::{CookieGenerator, build_ch_fingerprint};
let cg = CookieGenerator::new([0x11; 32]);
let addr = b"client";
let rand = [0x77; 32];
let fp1 = build_ch_fingerprint(b"\x13\x01\x13\x02", None, None, b"\x00\x1d");
let fp2 = build_ch_fingerprint(b"\x13\x01", None, None, b"\x00\x1d");
let ts = 12_345_u32;
let cookie = cg.generate(addr, &rand, &fp1, ts);
assert!(cg.validate(addr, &rand, &fp1, ts, &cookie));
assert!(!cg.validate(addr, &rand, &fp2, ts, &cookie));
}
#[test]
fn set_now_advances_cookie_clock() {
use core::time::Duration;
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_cookie_secret([0xa5; 32]);
let mut client = make_client13(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-server-set-now", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
let c1 = client.pop_outbound_datagrams();
for dg in &c1 {
server.feed_datagram(dg).unwrap();
}
let hrr_at_zero = server.pop_outbound_datagrams();
assert!(!hrr_at_zero.is_empty());
server.set_now(Duration::from_secs(60 * 5));
let mut other_client = make_client13(&cert);
let other_c1 = other_client.pop_outbound_datagrams();
for dg in &other_c1 {
server.feed_datagram(dg).unwrap();
}
let hrr_at_five = server.pop_outbound_datagrams();
assert!(!hrr_at_five.is_empty());
assert_ne!(hrr_at_zero, hrr_at_five);
server.set_now(Duration::from_secs(0));
let mut third_client = make_client13(&cert);
let third_c1 = third_client.pop_outbound_datagrams();
for dg in &third_c1 {
server.feed_datagram(dg).unwrap();
}
let hrr_after_rewind = server.pop_outbound_datagrams();
assert!(!hrr_after_rewind.is_empty(), "clock rewind is a no-op");
}
#[test]
fn forged_record_does_not_burn_replay_slot_dtls13() {
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_no_cookie();
let mut client = make_client13(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-fwd", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
server.send(b"one").unwrap();
let s1 = server.pop_outbound_datagrams();
assert_eq!(s1.len(), 1);
server.send(b"two").unwrap();
let s2 = server.pop_outbound_datagrams();
assert_eq!(s2.len(), 1);
let mut tampered = s2[0].clone();
let mid = tampered.len() / 2;
tampered[mid] ^= 1;
client.feed_datagram(&s1[0]).unwrap();
assert_eq!(client.take_received(), b"one");
let _ = client.feed_datagram(&tampered);
assert!(client.take_received().is_empty());
client.feed_datagram(&s2[0]).unwrap();
assert_eq!(client.take_received(), b"two");
}
fn cid_record() -> Vec<u8> {
let mut v = alloc::vec![0x3Cu8]; v.extend_from_slice(&[0u8; 24]);
v
}
fn short_body_record() -> Vec<u8> {
alloc::vec![0x2Cu8, 0xBE, 0xEF, 0x00, 0x05, 1, 2, 3, 4, 5]
}
fn wrong_epoch_record() -> Vec<u8> {
let mut v = alloc::vec![0x2Du8, 0x12, 0x34, 0x00, 0x14]; v.extend_from_slice(&[0x42u8; 20]);
v
}
#[test]
fn spoofed_records_are_silently_dropped_13() {
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_no_cookie();
let mut client = make_client13(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-server-spoof", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump_handshake_13(&mut client, &mut server));
let _ = client.pop_outbound_datagrams();
let _ = server.pop_outbound_datagrams();
for sp in [
garbage_datagram(),
cid_record(),
short_body_record(),
wrong_epoch_record(),
] {
assert_eq!(client.feed_datagram(&sp), Ok(()));
assert_eq!(server.feed_datagram(&sp), Ok(()));
}
server.send(b"to-client").unwrap();
let s = server.pop_outbound_datagrams();
assert_eq!(s.len(), 1);
let mut tampered_s = s[0].clone();
let last = tampered_s.len() - 1;
tampered_s[last] ^= 0x5a;
assert_eq!(client.feed_datagram(&tampered_s), Ok(()));
assert!(client.take_received().is_empty());
client.send(b"to-server").unwrap();
let c = client.pop_outbound_datagrams();
assert_eq!(c.len(), 1);
let mut tampered_c = c[0].clone();
let last = tampered_c.len() - 1;
tampered_c[last] ^= 0x5a;
assert_eq!(server.feed_datagram(&tampered_c), Ok(()));
assert!(server.take_received().is_empty());
for sp in [wrong_version_record(), short_fragment_record()] {
assert_eq!(client.feed_datagram(&sp), Ok(()));
assert_eq!(server.feed_datagram(&sp), Ok(()));
}
client.feed_datagram(&s[0]).unwrap();
assert_eq!(client.take_received(), b"to-client");
server.feed_datagram(&c[0]).unwrap();
assert_eq!(server.take_received(), b"to-server");
client.send(b"still alive c2s").unwrap();
for dg in &client.pop_outbound_datagrams() {
server.feed_datagram(dg).unwrap();
}
assert_eq!(server.take_received(), b"still alive c2s");
server.send(b"still alive s2c").unwrap();
for dg in &server.pop_outbound_datagrams() {
client.feed_datagram(dg).unwrap();
}
assert_eq!(client.take_received(), b"still alive s2c");
}
#[test]
fn spoofed_records_during_handshake_are_ignored_13() {
let (server_cfg, cert) = make_server13();
let server_cfg = server_cfg.with_no_cookie();
let mut client = make_client13(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls13-server-spoof-hs", b"nonce", &[]);
let mut server =
DtlsServerConnection13::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
let spoofs = [
garbage_datagram(),
wrong_version_record(),
short_fragment_record(),
cid_record(),
short_body_record(),
wrong_epoch_record(),
];
for _ in 0..32 {
for sp in &spoofs {
assert_eq!(client.feed_datagram(sp), Ok(()));
assert_eq!(server.feed_datagram(sp), Ok(()));
}
let c_out = client.pop_outbound_datagrams();
for dg in &c_out {
server.feed_datagram(dg).unwrap();
}
let s_out = server.pop_outbound_datagrams();
for dg in &s_out {
client.feed_datagram(dg).unwrap();
}
if c_out.is_empty() && s_out.is_empty() {
break;
}
}
assert!(client.is_handshake_complete());
assert!(server.is_handshake_complete());
}
}
mod dtls12 {
use super::*;
use crate::tls::codec::CipherSuite;
fn pump<R: crate::rng::RngCore>(
client: &mut DtlsClientConnection12,
server: &mut DtlsServerConnection12<R>,
) -> bool {
super::pump_handshake(client, server)
}
fn client_with_suites(server_cert: &[u8], suites: Vec<CipherSuite>) -> DtlsClientConnection12 {
let mut roots = RootCertStore::new();
roots.add_der(server_cert.to_vec()).unwrap();
let mut cfg = PcClientConfig12::new(roots, "dtls.example")
.with_verification_time(Time::utc(2026, 6, 1, 0, 0, 0));
cfg.cipher_suites = suites;
let mut crng = HmacDrbg::<Sha256>::new(b"dtls12-multi-suite-cli", b"nonce", &[]);
DtlsClientConnection12::new(cfg, b"client-addr".to_vec(), &mut crng)
}
#[test]
fn negotiates_chacha20() {
let (server_cfg, cert) = make_server();
let server_cfg = server_cfg.require_cookie_exchange(false);
let mut client = client_with_suites(
&cert,
alloc::vec![CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256],
);
let srng = HmacDrbg::<Sha256>::new(b"dtls12-srv-chacha", b"nonce", &[]);
let mut server =
DtlsServerConnection12::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump(&mut client, &mut server));
assert_eq!(
client.negotiated_cipher_suite(),
Some(CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256.0)
);
assert_eq!(
server.negotiated_cipher_suite(),
Some(CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256.0)
);
client.send(b"ping-chacha").unwrap();
let c = client.pop_outbound_datagrams();
for dg in &c {
server.feed_datagram(dg).unwrap();
}
assert_eq!(server.take_received(), b"ping-chacha");
server.send(b"pong-chacha").unwrap();
let s = server.pop_outbound_datagrams();
for dg in &s {
client.feed_datagram(dg).unwrap();
}
assert_eq!(client.take_received(), b"pong-chacha");
}
#[test]
fn negotiates_aes256_sha384() {
let (server_cfg, cert) = make_server();
let server_cfg = server_cfg.require_cookie_exchange(false);
let mut client = client_with_suites(
&cert,
alloc::vec![CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384],
);
let srng = HmacDrbg::<Sha256>::new(b"dtls12-srv-aes256", b"nonce", &[]);
let mut server =
DtlsServerConnection12::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump(&mut client, &mut server));
assert_eq!(
client.negotiated_cipher_suite(),
Some(CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384.0)
);
assert_eq!(
server.negotiated_cipher_suite(),
Some(CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384.0)
);
client.send(b"ping-sha384").unwrap();
let c = client.pop_outbound_datagrams();
for dg in &c {
server.feed_datagram(dg).unwrap();
}
assert_eq!(server.take_received(), b"ping-sha384");
server.send(b"pong-sha384").unwrap();
let s = server.pop_outbound_datagrams();
for dg in &s {
client.feed_datagram(dg).unwrap();
}
assert_eq!(client.take_received(), b"pong-sha384");
}
#[test]
fn prefers_aes128_when_offered() {
let (server_cfg, cert) = make_server();
let server_cfg = server_cfg.require_cookie_exchange(false);
let mut client = client_with_suites(
&cert,
alloc::vec![
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
],
);
let srng = HmacDrbg::<Sha256>::new(b"dtls12-srv-pref", b"nonce", &[]);
let mut server =
DtlsServerConnection12::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump(&mut client, &mut server));
assert_eq!(
client.negotiated_cipher_suite(),
Some(CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256.0)
);
assert_eq!(
server.negotiated_cipher_suite(),
Some(CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256.0)
);
}
#[test]
fn negotiates_p256() {
use crate::tls::codec::NamedGroup;
let (server_cfg, cert) = make_server();
let server_cfg = server_cfg.require_cookie_exchange(false);
let mut roots = RootCertStore::new();
roots.add_der(cert.clone()).unwrap();
let mut client_cfg = PcClientConfig12::new(roots, "dtls.example")
.with_verification_time(Time::utc(2026, 6, 1, 0, 0, 0));
client_cfg.groups = alloc::vec![NamedGroup::SECP256R1];
let mut crng = HmacDrbg::<Sha256>::new(b"dtls12-p256-cli", b"nonce", &[]);
let mut client =
DtlsClientConnection12::new(client_cfg, b"client-addr".to_vec(), &mut crng);
let srng = HmacDrbg::<Sha256>::new(b"dtls12-srv-p256", b"nonce", &[]);
let mut server =
DtlsServerConnection12::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump(&mut client, &mut server));
client.send(b"ping-p256").unwrap();
let c = client.pop_outbound_datagrams();
for dg in &c {
server.feed_datagram(dg).unwrap();
}
assert_eq!(server.take_received(), b"ping-p256");
server.send(b"pong-p256").unwrap();
let s = server.pop_outbound_datagrams();
for dg in &s {
client.feed_datagram(dg).unwrap();
}
assert_eq!(client.take_received(), b"pong-p256");
}
fn make_server_rsa() -> (PcServerConfig12, Vec<u8>) {
use crate::rsa::BoxedRsaPrivateKey;
use crate::test_util::rsa_test_key_a;
let rsa_key = rsa_test_key_a();
let boxed = BoxedRsaPrivateKey::from_pkcs1_der(&rsa_key.to_pkcs1_der()).unwrap();
let name = DistinguishedName::common_name("dtls.example");
let validity = Validity::new(
Time::utc(2024, 1, 1, 0, 0, 0),
Time::utc(2034, 1, 1, 0, 0, 0),
);
let cert = Certificate::self_signed_general(
&CertSigner::Rsa(&boxed),
&name,
&validity,
1,
false,
&["dtls.example"],
)
.unwrap();
let der = cert.to_der().to_vec();
(
PcServerConfig12::with_rsa(alloc::vec![der.clone()], boxed),
der,
)
}
#[test]
fn loopback_rsa_cert() {
let (server_cfg, cert) = make_server_rsa();
let server_cfg = server_cfg.require_cookie_exchange(false);
let mut client = make_client(&cert);
let srng = HmacDrbg::<Sha256>::new(b"dtls12-srv-rsa", b"nonce", &[]);
let mut server =
DtlsServerConnection12::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump(&mut client, &mut server));
let suite = client.negotiated_cipher_suite().expect("negotiated");
assert!(
suite == CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256.0
|| suite == CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256.0
|| suite == CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384.0,
"unexpected suite 0x{suite:04x}"
);
assert_eq!(server.negotiated_cipher_suite(), Some(suite));
client.send(b"ping-rsa").unwrap();
let c = client.pop_outbound_datagrams();
for dg in &c {
server.feed_datagram(dg).unwrap();
}
assert_eq!(server.take_received(), b"ping-rsa");
server.send(b"pong-rsa").unwrap();
let s = server.pop_outbound_datagrams();
for dg in &s {
client.feed_datagram(dg).unwrap();
}
assert_eq!(client.take_received(), b"pong-rsa");
}
#[test]
fn loopback_ecdhe_rsa_chacha() {
let (server_cfg, cert) = make_server_rsa();
let server_cfg = server_cfg.require_cookie_exchange(false);
let mut client = client_with_suites(
&cert,
alloc::vec![CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256],
);
let srng = HmacDrbg::<Sha256>::new(b"dtls12-srv-rsa-chacha", b"nonce", &[]);
let mut server =
DtlsServerConnection12::new(Arc::new(server_cfg), b"client-addr".to_vec(), srng);
assert!(pump(&mut client, &mut server));
assert_eq!(
client.negotiated_cipher_suite(),
Some(CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256.0)
);
assert_eq!(
server.negotiated_cipher_suite(),
Some(CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256.0)
);
}
}