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
112 changes: 103 additions & 9 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 @@ -172,6 +174,19 @@ 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<'_>> {
parse_dtls_client_hello_inner(i, false)
}

/// DTLS Client Hello allow partial
// Section 4.2 of RFC6347
fn parse_partial_dtls_client_hello(i: &[u8]) -> IResult<&[u8], DTLSMessageHandshakeBody<'_>> {
parse_dtls_client_hello_inner(i, true)
}
Comment on lines +180 to +184
Copy link
Member

Choose a reason for hiding this comment

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

If I'm correct, the client hello can only be fragmented in the extensions. This should be added to the function documentation


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 +196,12 @@ 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 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 Down Expand Up @@ -236,24 +256,53 @@ fn parse_dtls_handshake_msg_certificate(i: &[u8]) -> IResult<&[u8], DTLSMessageH
map(parse_tls_certificate, DTLSMessageHandshakeBody::Certificate)(i)
}

fn parse_partial_dtls_handshake_msg_certificate(
i: &[u8],
) -> IResult<&[u8], DTLSMessageHandshakeBody<'_>> {
map(
parse_partial_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 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 => match allow_partial {
false => parse_dtls_client_hello(raw_msg),
true => parse_partial_dtls_client_hello(raw_msg),
},
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,7 +311,10 @@ 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 => match allow_partial {
false => parse_dtls_handshake_msg_certificate(raw_msg),
true => parse_partial_dtls_handshake_msg_certificate(raw_msg),
},
Comment on lines +314 to +317
Copy link
Member

Choose a reason for hiding this comment

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

comment/discussion:
The match here could be avoided be directly calling parse_tls_certificate_inner, something like:

let (rem,cert) = parse_tls_certificate_inner(raw_msg, allow_partial)?;
Ok((rem, DTLSMessageHandshakeBody::Certificate))

This reduces the number of if/sub-functions and makes the allow_partial explicit as argument, at the cost of adding code to map and add the Ok. Maybe ? after the match should be moved in each branch to avoid that. The code would become

let (_, body) = match msg_type {
  //...
  let (rem,cert) = parse_tls_certificate_inner(raw_msg, allow_partial)?;
  (rem, DTLSMessageHandshakeBody::Certificate)
  // ...
};

Could be done in a later PR

_ => {
// eprintln!("Unsupported message type {:?}", msg_type);
Err(Err::Error(make_error(i, ErrorKind::Switch)))
Expand Down Expand Up @@ -296,11 +348,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 +383,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 +417,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)
}
113 changes: 101 additions & 12 deletions src/tls_handshake.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use alloc::vec::Vec;
use core::cmp::min;
use core::convert::TryInto;
use core::fmt;
use core::ops::Deref;
Expand Down Expand Up @@ -477,6 +478,13 @@ pub fn parse_tls_handshake_msg_hello_request(i: &[u8]) -> IResult<&[u8], TlsMess
/// # }
/// ```
pub fn parse_tls_handshake_client_hello(i: &[u8]) -> IResult<&[u8], TlsClientHelloContents<'_>> {
parse_tls_handshake_client_hello_inner(i, false)
}

fn parse_tls_handshake_client_hello_inner(
i: &[u8],
allow_partial: bool,
) -> IResult<&[u8], TlsClientHelloContents<'_>> {
let (i, version) = be_u16(i)?;
let (i, random) = take(32usize)(i)?;
let (i, sidlen) = verify(be_u8, |&n| n <= 32)(i)?;
Expand All @@ -485,11 +493,34 @@ pub fn parse_tls_handshake_client_hello(i: &[u8]) -> IResult<&[u8], TlsClientHel
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 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)?;
let content = TlsClientHelloContents::new(version, random, sid, ciphers, comp, ext);
Ok((i, content))
}

/// Parse frag handshake message contents for ClientHello
///
/// ```rust
/// use tls_parser::*;
///
/// # pub fn do_stuff(bytes: &[u8]) {
/// if let Ok((_, ch)) = parse_partial_tls_handshake_client_hello(bytes) {
/// println!("ClientHello TLS version: {}", ch.version);
/// println!(" number of proposed ciphersuites: {}", ch.ciphers.len());
/// }
/// # }
/// ```
pub fn parse_partial_tls_handshake_client_hello(
i: &[u8],
) -> IResult<&[u8], TlsClientHelloContents<'_>> {
parse_tls_handshake_client_hello_inner(i, true)
}

/// Parse a ClientHello handshake message
///
/// This function returns a [TlsMessageHandshake]. To get only the `ClientHello` contents, use the
Expand All @@ -513,6 +544,16 @@ pub fn parse_tls_handshake_msg_client_hello(i: &[u8]) -> IResult<&[u8], TlsMessa
)(i)
}

/// Parse a partial ClientHello handshake message
pub fn parse_partial_tls_handshake_msg_client_hello(
i: &[u8],
) -> IResult<&[u8], TlsMessageHandshake<'_>> {
map(
parse_partial_tls_handshake_client_hello,
TlsMessageHandshake::ClientHello,
)(i)
}

pub(crate) fn parse_cipher_suites(i: &[u8], len: usize) -> IResult<&[u8], Vec<TlsCipherSuiteID>> {
if len == 0 {
return Ok((i, Vec::new()));
Expand Down Expand Up @@ -699,8 +740,25 @@ pub fn parse_tls_handshake_msg_hello_retry_request(
}

pub(crate) fn parse_tls_certificate(i: &[u8]) -> IResult<&[u8], TlsCertificateContents<'_>> {
parse_tls_certificate_inner(i, false)
}

pub(crate) fn parse_partial_tls_certificate(
i: &[u8],
) -> IResult<&[u8], TlsCertificateContents<'_>> {
parse_tls_certificate_inner(i, true)
}

fn parse_tls_certificate_inner(
i: &[u8],
allow_partial: bool,
) -> IResult<&[u8], TlsCertificateContents<'_>> {
let (i, cert_len) = be_u24(i)?;
let (i, cert_chain) = map_parser(take(cert_len as usize), parse_certs)(i)?;
let cert_len = match allow_partial {
false => cert_len as usize,
true => min(cert_len as usize, i.len()),
};
let (i, cert_chain) = map_parser(take(cert_len), parse_certs)(i)?;
let content = TlsCertificateContents { cert_chain };
Ok((i, content))
}
Expand All @@ -710,6 +768,16 @@ pub fn parse_tls_handshake_msg_certificate(i: &[u8]) -> IResult<&[u8], TlsMessag
map(parse_tls_certificate, TlsMessageHandshake::Certificate)(i)
}

/// Parse a partial Certificate handshake message
pub fn parse_partial_tls_handshake_msg_certificate(
i: &[u8],
) -> IResult<&[u8], TlsMessageHandshake<'_>> {
map(
parse_partial_tls_certificate,
TlsMessageHandshake::Certificate,
)(i)
}

/// Parse a ServerKeyExchange handshake message
pub fn parse_tls_handshake_msg_serverkeyexchange(
i: &[u8],
Expand Down Expand Up @@ -876,31 +944,52 @@ pub fn parse_tls_handshake_msg_key_update(i: &[u8]) -> IResult<&[u8], TlsMessage

/// Parse a TLS handshake message
pub fn parse_tls_message_handshake(i: &[u8]) -> IResult<&[u8], TlsMessage<'_>> {
parse_tls_message_handshake_inner(i, false)
}

/// Parse a partial TLS handshake message
pub fn parse_partial_tls_message_handshake(i: &[u8]) -> IResult<&[u8], TlsMessage<'_>> {
parse_tls_message_handshake_inner(i, true)
}

/// Parse a TLS handshake message
fn parse_tls_message_handshake_inner(
i: &[u8],
allow_partial: bool,
) -> IResult<&[u8], TlsMessage<'_>> {
let (i, ht) = be_u8(i)?;
let (i, hl) = be_u24(i)?;
let hl = match allow_partial {
false => hl as usize,
true => i.len(),
};
let (i, raw_msg) = take(hl)(i)?;
let (_, msg) = match TlsHandshakeType(ht) {
TlsHandshakeType::HelloRequest => parse_tls_handshake_msg_hello_request(raw_msg),
TlsHandshakeType::ClientHello => parse_tls_handshake_msg_client_hello(raw_msg),
TlsHandshakeType::ClientHello => match allow_partial {
false => parse_tls_handshake_msg_client_hello(raw_msg),
true => parse_partial_tls_handshake_msg_client_hello(raw_msg),
},
TlsHandshakeType::ServerHello => parse_tls_handshake_msg_server_hello(raw_msg),
TlsHandshakeType::NewSessionTicket => {
parse_tls_handshake_msg_newsessionticket(raw_msg, hl as usize)
}
TlsHandshakeType::NewSessionTicket => parse_tls_handshake_msg_newsessionticket(raw_msg, hl),
TlsHandshakeType::EndOfEarlyData => Ok((raw_msg, TlsMessageHandshake::EndOfEarlyData)),
TlsHandshakeType::HelloRetryRequest => parse_tls_handshake_msg_hello_retry_request(raw_msg),
TlsHandshakeType::Certificate => parse_tls_handshake_msg_certificate(raw_msg),
TlsHandshakeType::Certificate => match allow_partial {
false => parse_tls_handshake_msg_certificate(raw_msg),
true => parse_partial_tls_handshake_msg_certificate(raw_msg),
},
TlsHandshakeType::ServerKeyExchange => {
parse_tls_handshake_msg_serverkeyexchange(raw_msg, hl as usize)
parse_tls_handshake_msg_serverkeyexchange(raw_msg, hl)
}
TlsHandshakeType::CertificateRequest => parse_tls_handshake_msg_certificaterequest(raw_msg),
TlsHandshakeType::ServerDone => parse_tls_handshake_msg_serverdone(raw_msg, hl as usize),
TlsHandshakeType::ServerDone => parse_tls_handshake_msg_serverdone(raw_msg, hl),
TlsHandshakeType::CertificateVerify => {
parse_tls_handshake_msg_certificateverify(raw_msg, hl as usize)
parse_tls_handshake_msg_certificateverify(raw_msg, hl)
}
TlsHandshakeType::ClientKeyExchange => {
parse_tls_handshake_msg_clientkeyexchange(raw_msg, hl as usize)
parse_tls_handshake_msg_clientkeyexchange(raw_msg, hl)
}
TlsHandshakeType::Finished => parse_tls_handshake_msg_finished(raw_msg, hl as usize),
TlsHandshakeType::Finished => parse_tls_handshake_msg_finished(raw_msg, hl),
// TlsHandshakeType::CertificateURL => parse_tls_handshake_msg_certificateurl(raw_msg),
TlsHandshakeType::CertificateStatus => parse_tls_handshake_msg_certificatestatus(raw_msg),
TlsHandshakeType::KeyUpdate => parse_tls_handshake_msg_key_update(raw_msg),
Expand Down
Loading
Loading