#![forbid(unsafe_code)]
#![warn(clippy::all)]
#![allow(unknown_lints)]
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#[macro_use]
extern crate log;
use std::fmt;
use std::sync::Arc;
use std::time::Instant;
mod types;
pub use types::{
CompressionMethod, ContentType, HashAlgorithm, NamedGroup, ProtocolVersion, Sequence,
SignatureAlgorithm,
};
mod dtls12;
mod dtls13;
use dtls12::{Client as Client12, Server as Server12};
use dtls13::{Client as Client13, Server as Server13};
use auto::ClientPending;
mod auto;
mod time_tricks;
pub(crate) mod buffer;
mod window;
mod util;
mod error;
pub(crate) use error::InternalError;
pub use error::{
CertificateError, ConfigError, CryptoError, CryptoOperation, CryptoProviderValidationError,
Error, InvalidStateError, PskError, SecurityError, TimeoutError, UnexpectedMessageError,
};
mod config;
pub use config::{Config, ConfigBuilder, Psk, PskResolver};
#[cfg(feature = "rcgen")]
pub mod certificate;
pub mod crypto;
pub use crypto::{KeyingMaterial, SrtpProfile};
mod timer;
mod rng;
pub(crate) use rng::SeededRng;
#[derive(Clone)]
pub struct DtlsCertificate {
pub certificate: Vec<u8>,
pub private_key: Vec<u8>,
}
impl fmt::Debug for DtlsCertificate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DtlsCertificate")
.field("certificate", &self.certificate.len())
.field("private_key", &self.private_key.len())
.finish()
}
}
pub struct Dtls {
inner: Option<Inner>,
}
enum Inner {
Client12(Client12),
Server12(Server12),
Client13(Client13),
Server13(Server13),
ClientPending(ClientPending),
}
fn is_dtls12_psk_only(config: &Config) -> bool {
if config.dtls13_cipher_suites().next().is_some() {
return false;
}
let mut suites = config.dtls12_cipher_suites().map(|cs| cs.suite());
suites
.next()
.is_some_and(|first| first.is_psk() && suites.all(|s| s.is_psk()))
}
fn client_hello_handshake(packet: &[u8]) -> Option<&[u8]> {
if packet.len() < 13 || packet[0] != 0x16 {
return None;
}
let record_len = u16::from_be_bytes([packet[11], packet[12]]) as usize;
let record_body = packet.get(13..13 + record_len)?;
if record_body.len() < 12 || record_body[0] != 0x01 {
return None;
}
Some(record_body)
}
#[cfg(all(test, feature = "rcgen"))]
fn looks_like_client_hello(packet: &[u8]) -> bool {
let Some(record_body) = client_hello_handshake(packet) else {
return false;
};
let length = ((record_body[1] as usize) << 16)
| ((record_body[2] as usize) << 8)
| record_body[3] as usize;
let frag_off = ((record_body[6] as usize) << 16)
| ((record_body[7] as usize) << 8)
| record_body[8] as usize;
let frag_len = ((record_body[9] as usize) << 16)
| ((record_body[10] as usize) << 8)
| record_body[11] as usize;
if frag_off != 0 {
return false;
}
if frag_len > length {
return false;
}
if 12usize.saturating_add(frag_len) > record_body.len() {
return false;
}
const MIN_CH_BODY: usize = 41;
let is_unfragmented = frag_len == length;
if is_unfragmented && length < MIN_CH_BODY {
return false;
}
true
}
fn client_hello_wants_psk(packet: &[u8], config: &Config) -> bool {
use dtls12::message::Dtls12CipherSuite;
let Some(record_body) = client_hello_handshake(packet) else {
return false;
};
let frag_off =
((record_body[6] as u32) << 16) | ((record_body[7] as u32) << 8) | record_body[8] as u32;
if frag_off != 0 {
return false;
}
let frag_len = ((record_body[9] as usize) << 16)
| ((record_body[10] as usize) << 8)
| record_body[11] as usize;
let Some(body) = record_body.get(12..12 + frag_len) else {
return false;
};
let mut pos = 2 + 32;
let Some(&sid_len) = body.get(pos) else {
return false;
};
pos += 1 + sid_len as usize;
let Some(&cookie_len) = body.get(pos) else {
return false;
};
pos += 1 + cookie_len as usize;
if pos + 2 > body.len() {
return false;
}
let suites_len = u16::from_be_bytes([body[pos], body[pos + 1]]) as usize;
pos += 2;
if pos + suites_len > body.len() || suites_len % 2 != 0 {
return false;
}
let allowed: Vec<_> = config.dtls12_cipher_suites().map(|cs| cs.suite()).collect();
for chunk in body[pos..pos + suites_len].chunks_exact(2) {
let suite = Dtls12CipherSuite::from_u16(u16::from_be_bytes([chunk[0], chunk[1]]));
if allowed.contains(&suite) {
return suite.is_psk();
}
}
false
}
impl Dtls {
pub fn new_12(config: Arc<Config>, certificate: DtlsCertificate, now: Instant) -> Self {
let inner = Inner::Server12(Server12::new(config, certificate, now));
Dtls { inner: Some(inner) }
}
pub fn new_12_psk(config: Arc<Config>, now: Instant) -> Self {
assert!(
config.psk().is_some(),
"Dtls::new_12_psk requires a PSK configuration; \
set one via ConfigBuilder::with_psk_client or with_psk_server"
);
let inner = Inner::Server12(Server12::new_psk(config, now));
Dtls { inner: Some(inner) }
}
pub fn new_13(config: Arc<Config>, certificate: DtlsCertificate, now: Instant) -> Self {
let inner = Inner::Server13(Server13::new(config, certificate, now));
Dtls { inner: Some(inner) }
}
pub fn new_auto(config: Arc<Config>, certificate: DtlsCertificate, now: Instant) -> Self {
let inner = if is_dtls12_psk_only(config.as_ref()) {
Inner::Server12(Server12::new_psk(config, now))
} else {
Inner::Server13(Server13::new_auto(config, certificate, now))
};
Dtls { inner: Some(inner) }
}
pub fn protocol_version(&self) -> Option<ProtocolVersion> {
match self.inner.as_ref()? {
Inner::Client12(_) | Inner::Server12(_) => Some(ProtocolVersion::DTLS1_2),
Inner::Client13(_) => Some(ProtocolVersion::DTLS1_3),
Inner::Server13(s) => {
if s.is_auto_mode() {
None
} else {
Some(ProtocolVersion::DTLS1_3)
}
}
Inner::ClientPending(_) => None,
}
}
pub fn is_closing(&self) -> bool {
let Some(inner) = self.inner.as_ref() else {
return false;
};
match inner {
Inner::Client12(client) => client.is_closing(),
Inner::Server12(server) => server.is_closing(),
Inner::Client13(client) => client.is_closing(),
Inner::Server13(server) => {
if server.is_auto_mode() {
false
} else {
server.is_closing()
}
}
Inner::ClientPending(_) => false,
}
}
pub fn is_closed(&self) -> bool {
let Some(inner) = self.inner.as_ref() else {
return false;
};
match inner {
Inner::Client12(client) => client.is_closed(),
Inner::Server12(server) => server.is_closed(),
Inner::Client13(client) => client.is_closed(),
Inner::Server13(server) => {
if server.is_auto_mode() {
false
} else {
server.is_closed()
}
}
Inner::ClientPending(_) => false,
}
}
pub fn is_active(&self) -> bool {
matches!(
self.inner,
Some(Inner::Client12(_) | Inner::Client13(_) | Inner::ClientPending(_))
)
}
pub fn set_active(&mut self, active: bool) {
match (self.is_active(), active) {
(true, false) => {
let inner = self.inner.take().unwrap();
match inner {
Inner::Client12(c) => {
self.inner = Some(Inner::Server12(c.into_server()));
}
Inner::Client13(c) => {
self.inner = Some(Inner::Server13(c.into_server()));
}
Inner::ClientPending(_) => {
panic!("cannot switch auto-sense client back to server: version unknown");
}
_ => unreachable!(),
}
}
(false, true) => {
let inner = self.inner.take().unwrap();
match inner {
Inner::Server12(s) => {
self.inner = Some(Inner::Client12(s.into_client()));
}
Inner::Server13(s) => {
if s.is_auto_mode() {
let (config, certificate, now, _) = s.into_parts();
let cp = ClientPending::new(config, certificate, now)
.expect("failed to build hybrid ClientHello");
self.inner = Some(Inner::ClientPending(cp));
} else {
self.inner = Some(Inner::Client13(s.into_client()));
}
}
_ => unreachable!(),
}
}
_ => {}
}
}
pub fn handle_packet(&mut self, packet: &[u8]) -> Result<(), Error> {
let inner = self.inner.as_mut().unwrap();
if inner.is_pending() {
return self.handle_pending_auto(packet);
}
match self.inner.as_mut().unwrap() {
Inner::Client12(client) => client.handle_packet(packet),
Inner::Server12(server) => server.handle_packet(packet),
Inner::Client13(client) => client.handle_packet(packet),
Inner::Server13(server) => server.handle_packet(packet),
Inner::ClientPending(_) => unreachable!(),
}
}
fn handle_pending_auto(&mut self, packet: &[u8]) -> Result<(), Error> {
match self.inner.as_mut().unwrap() {
Inner::ClientPending(_) => self.handle_pending_auto_client(packet),
Inner::Server13(server) if server.is_auto_mode() => {
match server.handle_packet(packet) {
Ok(()) => Ok(()),
Err(Error::Dtls12Fallback) => {
self.handle_pending_auto_server()
}
Err(e) => Err(e),
}
}
_ => unreachable!(),
}
}
fn handle_pending_auto_client(&mut self, packet: &[u8]) -> Result<(), Error> {
let version = auto::server_hello_version(packet);
if matches!(version, auto::DetectedVersion::Unknown) {
return Err(Error::UnexpectedMessage(
crate::UnexpectedMessageError::UnrecognizedAutoServerResponse,
));
}
let inner = self.inner.take().unwrap();
let Inner::ClientPending(cp) = inner else {
unreachable!()
};
let (hybrid, config, certificate, now) = cp.into_parts();
match version {
auto::DetectedVersion::Dtls12 => {
let mut client12 = Client12::new_from_hybrid(
hybrid.random,
&hybrid.handshake_fragment,
config,
certificate,
now,
)?;
if let Err(e) = client12.handle_packet(packet) {
self.inner = Some(Inner::Client12(client12));
return Err(e);
}
self.inner = Some(Inner::Client12(client12));
Ok(())
}
auto::DetectedVersion::Dtls13 => {
let mut client13 = Client13::new_from_hybrid(hybrid, config, certificate, now)?;
if let Err(e) = client13.handle_packet(packet) {
self.inner = Some(Inner::Client13(client13));
return Err(e);
}
self.inner = Some(Inner::Client13(client13));
Ok(())
}
auto::DetectedVersion::Unknown => unreachable!(),
}
}
fn handle_pending_auto_server(&mut self) -> Result<(), Error> {
let server = match self.inner.take().unwrap() {
Inner::Server13(server) => server,
_ => unreachable!(),
};
let (config, cert, now, buffered) = server.into_parts();
let use_psk =
config.psk().is_some() && buffered.iter().any(|p| client_hello_wants_psk(p, &config));
let mut server12 = if use_psk {
Server12::new_psk(config, now)
} else {
Server12::new(config, cert, now)
};
server12.handle_timeout(now)?;
self.inner = Some(Inner::Server12(server12));
for p in &buffered {
self.handle_packet(p)?;
}
Ok(())
}
pub fn poll_output<'a>(&mut self, buf: &'a mut [u8]) -> Output<'a> {
match self.inner.as_mut().unwrap() {
Inner::Client12(client) => client.poll_output(buf),
Inner::Server12(server) => server.poll_output(buf),
Inner::Client13(client) => client.poll_output(buf),
Inner::Server13(server) => server.poll_output(buf),
Inner::ClientPending(cp) => cp.poll_output(buf),
}
}
pub fn handle_timeout(&mut self, now: Instant) -> Result<(), Error> {
match self.inner.as_mut().unwrap() {
Inner::Client12(client) => client.handle_timeout(now),
Inner::Server12(server) => server.handle_timeout(now),
Inner::Client13(client) => client.handle_timeout(now),
Inner::Server13(server) => server.handle_timeout(now),
Inner::ClientPending(cp) => cp.handle_timeout(now),
}
}
pub fn send_application_data(&mut self, data: &[u8]) -> Result<(), Error> {
let inner = self.inner.as_mut().unwrap();
if inner.is_pending() {
return Err(Error::HandshakePending);
}
match inner {
Inner::Client12(client) => client.send_application_data(data),
Inner::Server12(server) => server.send_application_data(data),
Inner::Client13(client) => client.send_application_data(data),
Inner::Server13(server) => server.send_application_data(data),
Inner::ClientPending(_) => Err(Error::HandshakePending),
}
}
pub fn close(&mut self) -> Result<(), Error> {
let inner = self.inner.as_mut().unwrap();
if inner.is_pending() {
return Err(Error::HandshakePending);
}
match inner {
Inner::Client12(client) => client.close(),
Inner::Server12(server) => server.close(),
Inner::Client13(client) => client.close(),
Inner::Server13(server) => server.close(),
Inner::ClientPending(_) => Err(Error::HandshakePending),
}
}
}
impl Inner {
fn is_pending(&self) -> bool {
match self {
Inner::Server13(v) => v.is_auto_mode(),
Inner::ClientPending(_) => true,
_ => false,
}
}
}
impl fmt::Debug for Dtls {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (role, state) = match &self.inner {
Some(Inner::Client12(c)) => ("Client12", c.state_name()),
Some(Inner::Server12(s)) => ("Server12", s.state_name()),
Some(Inner::Client13(c)) => ("Client13", c.state_name()),
Some(Inner::Server13(s)) => ("Server13", s.state_name()),
Some(Inner::ClientPending(_)) => ("ClientPending", ""),
None => ("None", ""),
};
f.debug_struct("Dtls")
.field("role", &role)
.field("state", &state)
.finish()
}
}
#[non_exhaustive]
pub enum Output<'a> {
Packet(&'a [u8]),
BufferTooSmall {
needed: usize,
},
Timeout(Instant),
Connected,
PeerCert(&'a [u8]),
KeyingMaterial(KeyingMaterial, SrtpProfile),
ApplicationData(&'a [u8]),
CloseNotify,
}
impl fmt::Debug for Output<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Packet(v) => write!(f, "Packet({})", v.len()),
Self::BufferTooSmall { needed } => write!(f, "BufferTooSmall({needed})"),
Self::Timeout(v) => write!(f, "Timeout({:?})", v),
Self::Connected => write!(f, "Connected"),
Self::PeerCert(v) => write!(f, "PeerCert({})", v.len()),
Self::KeyingMaterial(v, p) => write!(f, "KeyingMaterial({}, {:?})", v.len(), p),
Self::ApplicationData(v) => write!(f, "ApplicationData({})", v.len()),
Self::CloseNotify => write!(f, "CloseNotify"),
}
}
}
#[cfg(test)]
#[cfg(feature = "rcgen")]
mod test {
use std::panic::UnwindSafe;
use crate::certificate::generate_self_signed_certificate;
use crate::crypto::Dtls12CipherSuite;
use super::*;
struct FixedPsk;
impl PskResolver for FixedPsk {
fn resolve(&self, _identity: &[u8]) -> Option<Vec<u8>> {
Some(b"0123456789abcdef".to_vec())
}
}
fn new_instance() -> Dtls {
let client_cert =
generate_self_signed_certificate().expect("Failed to generate client cert");
let config = Arc::new(Config::default());
Dtls::new_12(config, client_cert, Instant::now())
}
fn new_instance_12_no_cookie() -> Dtls {
let cert = generate_self_signed_certificate().expect("Failed to generate cert");
let config = Arc::new(
Config::builder()
.use_server_cookie(false)
.build()
.expect("config"),
);
Dtls::new_12(config, cert, Instant::now())
}
fn new_instance_13() -> Dtls {
let cert = generate_self_signed_certificate().expect("Failed to generate cert");
let config = Arc::new(Config::default());
Dtls::new_13(config, cert, Instant::now())
}
fn new_instance_auto() -> Dtls {
let cert = generate_self_signed_certificate().expect("Failed to generate cert");
let config = Arc::new(Config::default());
Dtls::new_auto(config, cert, Instant::now())
}
#[test]
fn test_dtls_default() {
let mut dtls = new_instance();
assert!(!dtls.is_active());
dtls.set_active(true);
assert!(dtls.is_active());
dtls.set_active(false);
}
#[test]
fn test_dtls13_default() {
let mut dtls = new_instance_13();
assert!(!dtls.is_active());
dtls.set_active(true);
assert!(dtls.is_active());
dtls.set_active(false);
}
#[test]
fn test_auto_sense_set_active_creates_client_pending() {
let mut dtls = new_instance_auto();
assert!(!dtls.is_active());
dtls.set_active(true);
assert!(dtls.is_active());
assert!(matches!(dtls.inner, Some(Inner::ClientPending(_))));
}
#[test]
fn test_auto_sense_client_sends_hybrid_ch() {
let mut dtls = new_instance_auto();
dtls.set_active(true);
let now = Instant::now();
dtls.handle_timeout(now).unwrap();
let output = &mut [0u8; 2048];
let result = dtls.poll_output(output);
assert!(matches!(result, Output::Packet(_)));
let result = dtls.poll_output(output);
assert!(matches!(result, Output::Timeout(_)));
}
#[test]
fn test_auto_client_unknown_version_no_panic() {
let mut dtls = new_instance_auto();
dtls.set_active(true);
let now = Instant::now();
dtls.handle_timeout(now).unwrap();
let mut buf = [0u8; 2048];
loop {
if matches!(dtls.poll_output(&mut buf), Output::Timeout(_)) {
break;
}
}
let garbage = [0xFF; 64];
let err = dtls.handle_packet(&garbage).unwrap_err();
assert!(matches!(err, Error::UnexpectedMessage(_)));
dtls.handle_timeout(now).unwrap();
let _ = dtls.poll_output(&mut buf);
}
#[test]
fn test_auto_psk_only_dtls12_uses_dtls12_path() {
let cert = generate_self_signed_certificate().expect("Failed to generate cert");
let config = Arc::new(
Config::builder()
.with_psk_client(b"identity".to_vec(), Arc::new(FixedPsk))
.dtls12_cipher_suites(&[Dtls12CipherSuite::PSK_AES128_CCM_8])
.dtls13_cipher_suites(&[])
.build()
.expect("PSK-only DTLS 1.2 config should build"),
);
let mut dtls = Dtls::new_auto(config, cert, Instant::now());
dtls.set_active(true);
assert!(dtls.is_active(), "client should become active");
assert!(
matches!(dtls.inner, Some(Inner::Client12(_))),
"PSK-only DTLS 1.2 auto config should reuse the DTLS 1.2 client path"
);
}
#[test]
fn is_send() {
fn is_send<T: Send>(_t: T) {}
fn is_sync<T: Sync>(_t: T) {}
is_send(new_instance());
is_sync(new_instance());
is_send(new_instance_13());
is_sync(new_instance_13());
is_send(new_instance_auto());
is_sync(new_instance_auto());
}
#[test]
fn is_unwind_safe() {
fn is_unwind_safe<T: UnwindSafe>(_t: T) {}
is_unwind_safe(new_instance());
is_unwind_safe(new_instance_13());
is_unwind_safe(new_instance_auto());
}
#[test]
fn test_protocol_version_12() {
let dtls = new_instance();
assert_eq!(dtls.protocol_version(), Some(ProtocolVersion::DTLS1_2));
}
#[test]
fn test_protocol_version_13() {
let dtls = new_instance_13();
assert_eq!(dtls.protocol_version(), Some(ProtocolVersion::DTLS1_3));
}
#[test]
fn test_protocol_version_auto_pending() {
let dtls = new_instance_auto();
assert_eq!(dtls.protocol_version(), None);
}
#[test]
#[should_panic(expected = "requires a PSK configuration")]
fn new_12_psk_panics_without_psk_config() {
let config = Arc::new(Config::default());
let _ = Dtls::new_12_psk(config, Instant::now());
}
#[test]
#[should_panic(expected = "Server certificate cannot be empty")]
fn new_12_panics_on_empty_certificate() {
let cert = generate_self_signed_certificate().expect("Failed to generate cert");
let config = Arc::new(Config::default());
let empty = DtlsCertificate {
certificate: vec![],
private_key: cert.private_key,
};
let _ = Dtls::new_12(config, empty, Instant::now());
}
#[test]
fn test_auto_server_send_application_data_pending() {
let mut dtls = new_instance_auto();
let err = dtls.send_application_data(b"early data").unwrap_err();
assert!(matches!(err, Error::HandshakePending));
}
#[test]
fn test_auto_close_pending() {
let mut dtls = new_instance_auto();
let err = dtls.close().unwrap_err();
assert!(matches!(err, Error::HandshakePending));
}
fn make_record(content_type: u8, body: &[u8]) -> Vec<u8> {
let mut pkt = Vec::with_capacity(13 + body.len());
pkt.push(content_type);
pkt.extend_from_slice(&[0xFE, 0xFD]); pkt.extend_from_slice(&[0x00, 0x00]); pkt.extend_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); pkt.extend_from_slice(&(body.len() as u16).to_be_bytes());
pkt.extend_from_slice(body);
pkt
}
fn make_handshake(
msg_type: u8,
length: u32,
frag_off: u32,
frag_len: u32,
body: &[u8],
) -> Vec<u8> {
let mut hs = Vec::with_capacity(12 + body.len());
hs.push(msg_type);
hs.extend_from_slice(&length.to_be_bytes()[1..]); hs.extend_from_slice(&[0x00, 0x00]); hs.extend_from_slice(&frag_off.to_be_bytes()[1..]); hs.extend_from_slice(&frag_len.to_be_bytes()[1..]); hs.extend_from_slice(body);
hs
}
fn min_ch_body() -> Vec<u8> {
let mut body = Vec::new();
body.extend_from_slice(&[0xFE, 0xFD]); body.extend_from_slice(&[0u8; 32]); body.push(0); body.push(0); body.extend_from_slice(&[0x00, 0x02]); body.extend_from_slice(&[0xC0, 0x2B]); body.push(1); body.push(0); body
}
fn dtls13_ch_body_with_extension(extension_type: u16, extension_data: &[u8]) -> Vec<u8> {
let mut extensions = Vec::new();
extensions.extend_from_slice(&extension_type.to_be_bytes());
extensions.extend_from_slice(&(extension_data.len() as u16).to_be_bytes());
extensions.extend_from_slice(extension_data);
dtls13_ch_body_with_extensions(&extensions)
}
fn dtls13_ch_body_with_extensions(extensions: &[u8]) -> Vec<u8> {
let mut body = Vec::new();
body.extend_from_slice(&[0xFE, 0xFD]); body.extend_from_slice(&[0u8; 32]); body.push(0); body.push(0); body.extend_from_slice(&[0x00, 0x02]); body.extend_from_slice(&[0x13, 0x01]); body.push(1); body.push(0);
body.extend_from_slice(&(extensions.len() as u16).to_be_bytes());
body.extend_from_slice(extensions);
body
}
fn dtls12_ch_body_with_extensions(extensions: &[u8]) -> Vec<u8> {
let mut body = Vec::new();
body.extend_from_slice(&[0xFE, 0xFD]); body.extend_from_slice(&[0u8; 32]); body.push(0); body.push(0); body.extend_from_slice(&[0x00, 0x02]); body.extend_from_slice(&[0xC0, 0x2B]); body.push(1); body.push(0); body.extend_from_slice(&(extensions.len() as u16).to_be_bytes());
body.extend_from_slice(extensions);
body
}
#[test]
fn looks_like_client_hello_accepts_minimum_shape_ch() {
let body = min_ch_body();
let len = body.len() as u32;
let hs = make_handshake(0x01, len, 0, len, &body);
let pkt = make_record(0x16, &hs);
assert!(looks_like_client_hello(&pkt));
}
#[test]
fn looks_like_client_hello_rejects_non_handshake_record() {
let body = min_ch_body();
let len = body.len() as u32;
let hs = make_handshake(0x01, len, 0, len, &body);
let pkt = make_record(0x17, &hs); assert!(!looks_like_client_hello(&pkt));
}
#[test]
fn looks_like_client_hello_rejects_other_handshake_msg_types() {
let body = min_ch_body();
let len = body.len() as u32;
for msg_type in [0x02, 0x03, 0x04, 0x0B, 0x0E, 0x14] {
let hs = make_handshake(msg_type, len, 0, len, &body);
let pkt = make_record(0x16, &hs);
assert!(
!looks_like_client_hello(&pkt),
"msg_type {:#x} should not look like a CH",
msg_type
);
}
}
#[test]
fn looks_like_client_hello_rejects_truncated_packets() {
assert!(!looks_like_client_hello(&[]));
assert!(!looks_like_client_hello(&[0x16; 12])); let mut pkt = vec![0x16, 0xFE, 0xFD, 0, 0, 0, 0, 0, 0, 0, 0];
pkt.extend_from_slice(&100u16.to_be_bytes());
assert!(!looks_like_client_hello(&pkt));
}
#[test]
fn looks_like_client_hello_rejects_short_handshake_body() {
let pkt = make_record(0x16, &[0x01, 0x00, 0x00]);
assert!(!looks_like_client_hello(&pkt));
}
#[test]
fn looks_like_client_hello_rejects_header_only_ch() {
let hs = make_handshake(0x01, 0, 0, 0, &[]);
let pkt = make_record(0x16, &hs);
assert!(!looks_like_client_hello(&pkt));
}
#[test]
fn looks_like_client_hello_rejects_undersized_unfragmented_ch() {
let body = vec![0xAA; 20];
let hs = make_handshake(0x01, 20, 0, 20, &body);
let pkt = make_record(0x16, &hs);
assert!(!looks_like_client_hello(&pkt));
}
#[test]
fn looks_like_client_hello_rejects_inconsistent_fragment_overflow() {
let body = min_ch_body();
let hs = make_handshake(0x01, 50, 0, 100, &body);
let pkt = make_record(0x16, &hs);
assert!(!looks_like_client_hello(&pkt));
}
#[test]
fn looks_like_client_hello_rejects_missing_fragment_bytes() {
let body = min_ch_body();
let hs = make_handshake(0x01, 200, 0, 200, &body);
let pkt = make_record(0x16, &hs);
assert!(!looks_like_client_hello(&pkt));
}
#[test]
fn looks_like_client_hello_accepts_first_fragment_of_fragmented_ch() {
let body = vec![0xAA; 20];
let hs = make_handshake(0x01, 200, 0, 20, &body);
let pkt = make_record(0x16, &hs);
assert!(looks_like_client_hello(&pkt));
}
#[test]
fn looks_like_client_hello_rejects_non_first_fragment() {
let body = vec![0xBB; 20];
let hs = make_handshake(0x01, 200, 20, 20, &body);
let pkt = make_record(0x16, &hs);
assert!(!looks_like_client_hello(&pkt));
}
fn ch_shaped_malformed_body() -> Vec<u8> {
let mut body = Vec::new();
body.extend_from_slice(&[0xFE, 0xFD]); body.extend_from_slice(&[0u8; 32]); body.push(0); body.push(0); body.extend_from_slice(&[0x00, 0x04]); body.extend_from_slice(&[0xC0, 0x2B]); body.push(1); body.push(0); body
}
#[test]
fn auto_server_discards_ch_shaped_malformed_packet_without_fallback() {
let body = ch_shaped_malformed_body();
let len = body.len() as u32;
let hs = make_handshake(0x01, len, 0, len, &body);
let pkt = make_record(0x16, &hs);
assert!(
looks_like_client_hello(&pkt),
"fixture must pass the structural gate"
);
let mut dtls = new_instance_auto();
dtls.handle_packet(&pkt)
.expect("malformed ClientHello-shaped packet should be discarded");
assert!(matches!(
dtls.inner,
Some(Inner::Server13(ref server)) if server.is_auto_mode()
));
}
#[test]
fn dtls13_server_accepts_distinct_supported_key_shares_before_cookie_hrr() {
let supported_versions = [
0x02, 0xFE, 0xFC, ];
let mut supported_groups = Vec::new();
supported_groups
.extend_from_slice(&(NamedGroup::supported().len() as u16 * 2).to_be_bytes());
for group in NamedGroup::supported() {
supported_groups.extend_from_slice(&group.as_u16().to_be_bytes());
}
let mut key_share = Vec::new();
key_share.extend_from_slice(&(NamedGroup::supported().len() as u16 * 5).to_be_bytes());
for group in NamedGroup::supported() {
key_share.extend_from_slice(&group.as_u16().to_be_bytes());
key_share.extend_from_slice(&1u16.to_be_bytes());
key_share.push(0x42);
}
let mut extensions = Vec::new();
extensions.extend_from_slice(&0x002Bu16.to_be_bytes()); extensions.extend_from_slice(&(supported_versions.len() as u16).to_be_bytes());
extensions.extend_from_slice(&supported_versions);
extensions.extend_from_slice(&0x000Au16.to_be_bytes()); extensions.extend_from_slice(&(supported_groups.len() as u16).to_be_bytes());
extensions.extend_from_slice(&supported_groups);
extensions.extend_from_slice(&0x0033u16.to_be_bytes()); extensions.extend_from_slice(&(key_share.len() as u16).to_be_bytes());
extensions.extend_from_slice(&key_share);
let body = dtls13_ch_body_with_extensions(&extensions);
let len = body.len() as u32;
let hs = make_handshake(0x01, len, 0, len, &body);
let pkt = make_record(0x16, &hs);
let mut dtls = new_instance_13();
dtls.handle_packet(&pkt).expect(
"all distinct supported key shares should fit server-side parsing before cookie HRR",
);
}
#[test]
fn dtls13_server_accepts_unknown_supported_versions_before_cookie_hrr() {
let supported_versions = [
0x08, 0xFE, 0xFE, 0xFE, 0xFC, 0xFE, 0xFD, 0xFE, 0xFF, ];
let body = dtls13_ch_body_with_extension(0x002B, &supported_versions);
let len = body.len() as u32;
let hs = make_handshake(0x01, len, 0, len, &body);
let pkt = make_record(0x16, &hs);
let mut dtls = new_instance_13();
dtls.handle_packet(&pkt)
.expect("unknown supported_versions values should not overflow server-side parsing");
}
#[test]
fn dtls12_server_discards_oversized_ec_point_formats_extension() {
let mut extensions = Vec::new();
extensions.extend_from_slice(&[0x00, 0x0B]); extensions.extend_from_slice(&[0x00, 0x05]); extensions.extend_from_slice(&[
0x04, 0x00, 0x00, 0x00, 0x00, ]);
extensions.extend_from_slice(&[0x00, 0x17]); extensions.extend_from_slice(&[0x00, 0x00]);
let body = dtls12_ch_body_with_extensions(&extensions);
let len = body.len() as u32;
let hs = make_handshake(0x01, len, 0, len, &body);
let pkt = make_record(0x16, &hs);
let mut dtls = new_instance_12_no_cookie();
dtls.handle_packet(&pkt)
.expect("malformed extension should be discarded");
}
#[test]
fn dtls12_server_discards_trailing_ec_point_formats_extension() {
let mut extensions = Vec::new();
extensions.extend_from_slice(&[0x00, 0x0B]); extensions.extend_from_slice(&[0x00, 0x03]); extensions.extend_from_slice(&[
0x01, 0x00, 0xFF, ]);
extensions.extend_from_slice(&[0x00, 0x17]); extensions.extend_from_slice(&[0x00, 0x00]);
let body = dtls12_ch_body_with_extensions(&extensions);
let len = body.len() as u32;
let hs = make_handshake(0x01, len, 0, len, &body);
let pkt = make_record(0x16, &hs);
let mut dtls = new_instance_12_no_cookie();
dtls.handle_packet(&pkt)
.expect("malformed extension should be discarded");
}
#[test]
fn dtls12_server_accepts_unknown_ec_point_formats_extension() {
let mut extensions = Vec::new();
extensions.extend_from_slice(&[0x00, 0x0B]); extensions.extend_from_slice(&[0x00, 0x04]); extensions.extend_from_slice(&[
0x03, 0x02, 0x00, 0xFF, ]);
extensions.extend_from_slice(&[0x00, 0x17]); extensions.extend_from_slice(&[0x00, 0x00]);
let body = dtls12_ch_body_with_extensions(&extensions);
let len = body.len() as u32;
let hs = make_handshake(0x01, len, 0, len, &body);
let pkt = make_record(0x16, &hs);
let mut dtls = new_instance_12_no_cookie();
dtls.handle_timeout(Instant::now()).unwrap();
dtls.handle_packet(&pkt)
.expect("unknown ec_point_formats values should not fail server-side parsing");
}
#[test]
fn auto_server_drops_garbage_without_falling_back() {
let mut dtls = new_instance_auto();
let garbage = [0xFF; 64];
let _ = dtls.handle_packet(&garbage);
let still_pending = match &dtls.inner {
Some(Inner::Server13(s)) => s.is_auto_mode(),
_ => false,
};
assert!(
still_pending,
"auto-sense server must not fall back to DTLS 1.2 on garbage input"
);
}
}