#![cfg(feature = "rcgen")]
#[path = "dtls13/common.rs"]
mod common;
use std::sync::Arc;
use std::time::Instant;
use dimpl::Dtls;
use dimpl::certificate::generate_self_signed_certificate;
use crate::common::{drain_outputs, dtls13_config};
fn cookie_extensions_start(body: &[u8], msg_type: u8) -> Option<usize> {
let mut pos = 0;
match msg_type {
0x01 => {
pos += 2 + 32;
let sid_len = *body.get(pos)? as usize;
pos += 1 + sid_len;
let cookie_len = *body.get(pos)? as usize;
pos += 1 + cookie_len;
let suites_len = u16::from_be_bytes([*body.get(pos)?, *body.get(pos + 1)?]) as usize;
pos += 2 + suites_len;
let compression_len = *body.get(pos)? as usize;
pos += 1 + compression_len;
}
0x02 => {
pos += 2 + 32;
let sid_len = *body.get(pos)? as usize;
pos += 1 + sid_len + 2 + 1;
}
_ => return None,
}
Some(pos)
}
fn shrink_dtls13_cookie_extension_inner_len(packet: &mut [u8]) -> bool {
const RECORD_HEADER_LEN: usize = 13;
const HANDSHAKE_HEADER_LEN: usize = 12;
const COOKIE_EXTENSION: u16 = 0x002C;
if packet.len() < RECORD_HEADER_LEN + HANDSHAKE_HEADER_LEN || packet[0] != 22 {
return false;
}
let handshake = &mut packet[RECORD_HEADER_LEN..];
let msg_type = handshake[0];
let body_len =
((handshake[1] as usize) << 16) | ((handshake[2] as usize) << 8) | handshake[3] as usize;
if handshake.len() < HANDSHAKE_HEADER_LEN + body_len {
return false;
}
let body = &mut handshake[HANDSHAKE_HEADER_LEN..HANDSHAKE_HEADER_LEN + body_len];
let mut pos = match cookie_extensions_start(body, msg_type) {
Some(pos) => pos,
None => return false,
};
if body.len() < pos + 2 {
return false;
}
let extensions_len = u16::from_be_bytes([body[pos], body[pos + 1]]) as usize;
pos += 2;
let extensions_end = pos + extensions_len;
if body.len() < extensions_end {
return false;
}
while pos + 4 <= extensions_end {
let extension_type = u16::from_be_bytes([body[pos], body[pos + 1]]);
let extension_len = u16::from_be_bytes([body[pos + 2], body[pos + 3]]) as usize;
let extension_body = pos + 4;
let next = extension_body + extension_len;
if next > extensions_end {
return false;
}
if extension_type == COOKIE_EXTENSION && extension_len > 2 {
let cookie_len = u16::from_be_bytes([body[extension_body], body[extension_body + 1]]);
if cookie_len == 0 {
return false;
}
body[extension_body..extension_body + 2]
.copy_from_slice(&(cookie_len - 1).to_be_bytes());
return true;
}
pos = next;
}
false
}
#[test]
fn dtls13_client_rejects_hrr_cookie_extension_trailing_bytes() {
let _ = env_logger::try_init();
let client_cert = generate_self_signed_certificate().expect("gen client cert");
let server_cert = generate_self_signed_certificate().expect("gen server cert");
let config = dtls13_config();
let now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
client.handle_timeout(now).expect("client timeout");
let client_out = drain_outputs(&mut client);
assert!(!client_out.packets.is_empty(), "client should send CH1");
for packet in &client_out.packets {
server.handle_packet(packet).expect("server receives CH1");
}
server.handle_timeout(now).expect("server timeout");
let server_out = drain_outputs(&mut server);
let mut hrr = server_out
.packets
.into_iter()
.next()
.expect("server should emit HRR");
assert!(
shrink_dtls13_cookie_extension_inner_len(&mut hrr),
"fixture should contain a Cookie extension"
);
client
.handle_packet(&hrr)
.expect("malformed HRR Cookie extension should be discarded");
client
.handle_timeout(now)
.expect("client timeout after error");
let client_out = drain_outputs(&mut client);
assert!(
client_out.packets.is_empty(),
"client must not send CH2 after malformed HRR Cookie"
);
}
#[test]
fn dtls13_server_rejects_clienthello_cookie_extension_trailing_bytes() {
let _ = env_logger::try_init();
let client_cert = generate_self_signed_certificate().expect("gen client cert");
let server_cert = generate_self_signed_certificate().expect("gen server cert");
let config = dtls13_config();
let now = Instant::now();
let mut client = Dtls::new_13(Arc::clone(&config), client_cert, now);
client.set_active(true);
let mut server = Dtls::new_13(config, server_cert, now);
server.set_active(false);
client.handle_timeout(now).expect("client timeout");
let client_out = drain_outputs(&mut client);
for packet in &client_out.packets {
server.handle_packet(packet).expect("server receives CH1");
}
server.handle_timeout(now).expect("server timeout");
let server_out = drain_outputs(&mut server);
let hrr = server_out
.packets
.first()
.expect("server should emit HRR")
.clone();
client
.handle_packet(&hrr)
.expect("client receives valid HRR");
client
.handle_timeout(now)
.expect("client timeout after HRR");
let client_out = drain_outputs(&mut client);
let mut ch2 = client_out
.packets
.into_iter()
.next()
.expect("client should emit CH2 with cookie");
assert!(
shrink_dtls13_cookie_extension_inner_len(&mut ch2),
"fixture should contain a Cookie extension"
);
server
.handle_packet(&ch2)
.expect("malformed ClientHello Cookie extension should be discarded");
}