Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 108 additions & 15 deletions src/dtls.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Datagram Transport Layer Security Version 1.2 (RFC 6347)

use core::cmp::min;

use alloc::vec::Vec;
use nom::bytes::streaming::take;
use nom::combinator::{complete, cond, map, map_parser, opt, verify};
Expand Down Expand Up @@ -54,6 +56,8 @@ pub struct DTLSClientHello<'a> {
/// A list of compression methods supported by client
pub comp: Vec<TlsCompressionID>,
pub ext: Option<&'a [u8]>,
/// Whether this ClientHello message is complete or a fragment
pub is_parsing_complete: bool,
}

impl<'a> ClientHello<'a> for DTLSClientHello<'a> {
Expand Down Expand Up @@ -99,6 +103,24 @@ pub struct DTLSMessageHandshake<'a> {
pub body: DTLSMessageHandshakeBody<'a>,
}

impl DTLSMessageHandshake<'_> {
pub fn is_parsing_complete(&self) -> bool {
match &self.body {
DTLSMessageHandshakeBody::ClientHello(ch) => ch.is_parsing_complete,
DTLSMessageHandshakeBody::Certificate(certs) => certs.is_parsing_complete,
_ => true,
}
}

pub fn update_parsing_status(&mut self, complete: bool) {
match &mut self.body {
DTLSMessageHandshakeBody::ClientHello(ch) => ch.is_parsing_complete = complete,
DTLSMessageHandshakeBody::Certificate(certs) => certs.is_parsing_complete = complete,
_ => {}
}
}
}

/// DTLS Generic handshake message
#[derive(Debug, PartialEq)]
pub enum DTLSMessageHandshakeBody<'a> {
Expand Down Expand Up @@ -171,7 +193,10 @@ fn parse_dtls_fragment(i: &[u8]) -> IResult<&[u8], DTLSMessageHandshakeBody<'_>>

/// DTLS Client Hello
// Section 4.2 of RFC6347
fn parse_dtls_client_hello(i: &[u8]) -> IResult<&[u8], DTLSMessageHandshakeBody<'_>> {
fn parse_dtls_client_hello_inner(
i: &[u8],
allow_partial: bool,
) -> IResult<&[u8], DTLSMessageHandshakeBody<'_>> {
let (i, version) = TlsVersion::parse(i)?;
let (i, random) = take(32usize)(i)?;
let (i, sidlen) = verify(be_u8, |&n| n <= 32)(i)?;
Expand All @@ -181,7 +206,13 @@ fn parse_dtls_client_hello(i: &[u8]) -> IResult<&[u8], DTLSMessageHandshakeBody<
let (i, ciphers) = parse_cipher_suites(i, ciphers_len as usize)?;
let (i, comp_len) = be_u8(i)?;
let (i, comp) = parse_compressions_algs(i, comp_len as usize)?;
let (i, ext) = opt(complete(length_data(be_u16)))(i)?;
let (i, ext_len) = be_u16(i)?;
let is_parsing_complete = ext_len as usize <= i.len();
let ext_len = match allow_partial {
false => ext_len as usize,
true => min(ext_len as usize, i.len()),
};
let (i, ext) = opt(complete(take(ext_len)))(i)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the logic here, but since ext_len is lost when returning, how will the caller be aware that the message is partial?
Not sure how if this can cause problems.
Shouldn't we either return something, or maybe store ext_len in the CH?

let content = DTLSClientHello {
version,
random,
Expand All @@ -190,6 +221,7 @@ fn parse_dtls_client_hello(i: &[u8]) -> IResult<&[u8], DTLSMessageHandshakeBody<
ciphers,
comp,
ext,
is_parsing_complete,
};
Ok((i, DTLSMessageHandshakeBody::ClientHello(content)))
}
Expand Down Expand Up @@ -232,28 +264,43 @@ fn parse_dtls_handshake_msg_clientkeyexchange(
)(i)
}

fn parse_dtls_handshake_msg_certificate(i: &[u8]) -> IResult<&[u8], DTLSMessageHandshakeBody<'_>> {
map(parse_tls_certificate, DTLSMessageHandshakeBody::Certificate)(i)
}

/// Parse a DTLS handshake message
pub fn parse_dtls_message_handshake(i: &[u8]) -> IResult<&[u8], DTLSMessage<'_>> {
parse_dtls_message_handshake_inner(i, false)
}

/// Parse a partial DTLS handshake message
pub fn parse_partial_dtls_message_handshake(i: &[u8]) -> IResult<&[u8], DTLSMessage<'_>> {
parse_dtls_message_handshake_inner(i, true)
}

fn parse_dtls_message_handshake_inner(
i: &[u8],
allow_partial: bool,
) -> IResult<&[u8], DTLSMessage<'_>> {
let (i, msg_type) = map(be_u8, TlsHandshakeType)(i)?;
let (i, length) = be_u24(i)?;
let (i, message_seq) = be_u16(i)?;
let (i, fragment_offset) = be_u24(i)?;
let (i, fragment_length) = be_u24(i)?;
// This packet contains fragment_length (which is less than length for fragmentation)
let (i, raw_msg) = take(fragment_length)(i)?;

// Handshake messages can be fragmented over multiple packets. When fragmented, the user
// needs the fragment_offset, fragment_length and length to determine whether they received
// all the fragments. The DTLS spec allows for overlapping and duplicated fragments.
let is_fragment = fragment_offset > 0 || fragment_length < length;

let is_data_complete = fragment_length <= i.len() as u32;

let fragment_length = match allow_partial {
false => fragment_length,
true => min(fragment_length, i.len() as u32),
};
// This packet contains fragment_length (which is less than length for fragmentation)
let (i, raw_msg) = take(fragment_length)(i)?;

let (_, body) = match msg_type {
_ if is_fragment => parse_dtls_fragment(raw_msg),
TlsHandshakeType::ClientHello => parse_dtls_client_hello(raw_msg),
_ if (is_fragment && !allow_partial) => parse_dtls_fragment(raw_msg),
TlsHandshakeType::ClientHello => parse_dtls_client_hello_inner(raw_msg, allow_partial),
TlsHandshakeType::HelloVerifyRequest => parse_dtls_hello_verify_request(raw_msg),
TlsHandshakeType::ServerHello => parse_dtls_handshake_msg_server_hello_tlsv12(raw_msg),
TlsHandshakeType::ServerDone => {
Expand All @@ -262,20 +309,24 @@ pub fn parse_dtls_message_handshake(i: &[u8]) -> IResult<&[u8], DTLSMessage<'_>>
TlsHandshakeType::ClientKeyExchange => {
parse_dtls_handshake_msg_clientkeyexchange(raw_msg, length as usize)
}
TlsHandshakeType::Certificate => parse_dtls_handshake_msg_certificate(raw_msg),
TlsHandshakeType::Certificate => parse_tls_certificate_inner(raw_msg, allow_partial)
.map(|(rem, certs)| (rem, DTLSMessageHandshakeBody::Certificate(certs))),
_ => {
// eprintln!("Unsupported message type {:?}", msg_type);
Err(Err::Error(make_error(i, ErrorKind::Switch)))
}
}?;
let msg = DTLSMessageHandshake {
let mut msg = DTLSMessageHandshake {
msg_type,
length,
message_seq,
fragment_offset,
fragment_length,
body,
};
if msg.is_parsing_complete() && !is_data_complete {
msg.update_parsing_status(is_data_complete);
}
Ok((i, DTLSMessage::Handshake(msg)))
}

Expand All @@ -296,11 +347,29 @@ pub fn parse_dtls_message_alert(i: &[u8]) -> IResult<&[u8], DTLSMessage<'_>> {
pub fn parse_dtls_record_with_header<'i>(
i: &'i [u8],
hdr: &DTLSRecordHeader,
) -> IResult<&'i [u8], Vec<DTLSMessage<'i>>> {
parse_dtls_record_with_header_inner(i, hdr, false)
}

pub fn parse_partial_dtls_record_with_header<'i>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: how can be caller know the parsed message is partial?

i: &'i [u8],
hdr: &DTLSRecordHeader,
) -> IResult<&'i [u8], Vec<DTLSMessage<'i>>> {
parse_dtls_record_with_header_inner(i, hdr, true)
}

fn parse_dtls_record_with_header_inner<'i>(
i: &'i [u8],
hdr: &DTLSRecordHeader,
allow_partial: bool,
) -> IResult<&'i [u8], Vec<DTLSMessage<'i>>> {
match hdr.content_type {
TlsRecordType::ChangeCipherSpec => many1(complete(parse_dtls_message_changecipherspec))(i),
TlsRecordType::Alert => many1(complete(parse_dtls_message_alert))(i),
TlsRecordType::Handshake => many1(complete(parse_dtls_message_handshake))(i),
TlsRecordType::Handshake => match allow_partial {
false => many1(complete(parse_dtls_message_handshake))(i),
true => many1(complete(parse_partial_dtls_message_handshake))(i),
},
// TlsRecordType::ApplicationData => many1(complete(parse_tls_message_applicationdata))(i),
// TlsRecordType::Heartbeat => parse_tls_message_heartbeat(i, hdr.length),
_ => {
Expand All @@ -313,13 +382,31 @@ pub fn parse_dtls_record_with_header<'i>(
/// Parse one DTLS plaintext record
// Section 4.1 of RFC6347
pub fn parse_dtls_plaintext_record(i: &[u8]) -> IResult<&[u8], DTLSPlaintext<'_>> {
parse_dtls_plaintext_record_inner(i, false)
}

/// Parse one partial DTLS plaintext record
// Section 4.1 of RFC6347
pub fn parse_partial_dtls_plaintext_record(i: &[u8]) -> IResult<&[u8], DTLSPlaintext<'_>> {
parse_dtls_plaintext_record_inner(i, true)
}

fn parse_dtls_plaintext_record_inner(
i: &[u8],
allow_partial: bool,
) -> IResult<&[u8], DTLSPlaintext<'_>> {
let (i, header) = parse_dtls_record_header(i)?;
// As in TLS 1.2, the length should not exceed 2^14.
if header.length > MAX_RECORD_LEN {
return Err(Err::Error(make_error(i, ErrorKind::TooLarge)));
}
let (i, messages) = map_parser(take(header.length as usize), |i| {
parse_dtls_record_with_header(i, &header)

let data_len = match allow_partial {
false => header.length as usize,
true => min(header.length as usize, i.len()),
};
let (i, messages) = map_parser(take(data_len), |i| {
parse_dtls_record_with_header_inner(i, &header, allow_partial)
})(i)?;
Ok((i, DTLSPlaintext { header, messages }))
}
Expand All @@ -329,3 +416,9 @@ pub fn parse_dtls_plaintext_record(i: &[u8]) -> IResult<&[u8], DTLSPlaintext<'_>
pub fn parse_dtls_plaintext_records(i: &[u8]) -> IResult<&[u8], Vec<DTLSPlaintext<'_>>> {
many1(complete(parse_dtls_plaintext_record))(i)
}

/// Parse multiple DTLS plaintext record
// Section 4.1 of RFC6347
pub fn parse_partial_dtls_plaintext_records(i: &[u8]) -> IResult<&[u8], Vec<DTLSPlaintext<'_>>> {
many1(complete(parse_partial_dtls_plaintext_record))(i)
}
1 change: 1 addition & 0 deletions src/tls_debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ impl<'a> fmt::Debug for TlsClientHelloContents<'a> {
.field("ciphers", &self.ciphers)
.field("comp", &self.comp)
.field("ext", &self.ext.map(HexSlice))
.field("is_parsing_complete", &self.is_parsing_complete)
.finish()
}
}
Expand Down
Loading