Skip to content

Commit

Permalink
crypto/tls: Add basic DER parsing
Browse files Browse the repository at this point in the history
Deserializing ASN.1 encoded data structures will allow
us to parse x509 TLS Certificates.
  • Loading branch information
simonwuelker committed Sep 26, 2023
1 parent 07c9522 commit bb81bd0
Show file tree
Hide file tree
Showing 4 changed files with 314 additions and 11 deletions.
71 changes: 69 additions & 2 deletions crypto/tls/src/certificate.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,75 @@
use crate::der;

#[derive(Clone, Debug)]
pub struct X509v3Certificate(Vec<u8>);

#[derive(Clone, Copy, Debug)]
pub enum Error {
InvalidFormat,
ParsingFailed(der::Error),
TrailingBytes,
}

macro_rules! expect_type {
($item: expr, $expected_type: ident) => {
if let der::Item::$expected_type(value) = $item {
value
} else {
return Err(Error::InvalidFormat);
}
};
}

macro_rules! expect_next_item {
($sequence: expr) => {
$sequence
.next()
.ok_or(Error::InvalidFormat)?
.map_err(Error::ParsingFailed)?
};
}

impl X509v3Certificate {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
pub fn new(bytes: Vec<u8>) -> Result<Self, Error> {
log::info!("bytes {:?}", &bytes[..10]);
// The root sequence always has the following structure:
// * data
// * Signature algorithm used
// * Signature
let (root_item, root_length) = der::Item::parse(&bytes).map_err(Error::ParsingFailed)?;

if root_length != bytes.len() {
return Err(Error::TrailingBytes);
}

let mut root_sequence = match root_item {
der::Item::Sequence(sequence) => sequence,
_ => return Err(Error::InvalidFormat),
};

let certificate_item = expect_next_item!(root_sequence);

let mut certificate = match certificate_item {
der::Item::Sequence(sequence) => sequence,
_ => return Err(Error::InvalidFormat),
};

let (version_item, _) = der::Item::parse(expect_type!(
expect_next_item!(certificate),
ContextSpecific
))
.map_err(Error::ParsingFailed)?;

log::info!("{:?}", version_item);

let _signature_algorithm = expect_next_item!(root_sequence);

let _signature = expect_next_item!(root_sequence);

if root_sequence.next().is_some() {
return Err(Error::InvalidFormat);
}

Ok(Self(bytes))
}
}
229 changes: 229 additions & 0 deletions crypto/tls/src/der.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
use std::iter::FusedIterator;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ClassTag {
/// The type is native to ASN.1
Universal,

/// The type is only valid for one specific application
Application,

/// Meaning of this type depends on the context (such as within a sequence, set or choice)
ContextSpecific,

/// Defined in private specifications
Private,
}

#[derive(Clone, Copy, Debug)]
pub enum Item<'a> {
EndOfContent,
Boolean,
Integer,
BitString,
OctetString,
Null,
ObjectIdentifier,
ObjectDescriptor,
External,
Real,
Enumerated,
EmbeddedPDV,
Utf8String,
RelativeOID,
Time,
Sequence(Sequence<'a>),
Set,
NumericString,
PrintableString,
T61String,
VideotexString,
IA5String,
UtcTime,
GeneralizedTime,
GraphicString,
VisibleString,
GeneralString,
UniversalString,
CharacterString,
BMPString,
Date,
TimeOfDay,
DateTime,
Duration,
OidIri,
RelativeOidIri,
ContextSpecific(&'a [u8]),
}

#[derive(Clone, Copy, Debug)]
pub enum PrimitiveOrConstructed {
Primitive,
Constructed,
}

#[derive(Clone, Copy, Debug)]
pub enum Error {
ReservedTypeTag,
UnknownTypeTag,
/// A indefinite length was encountered
///
/// This is allowed in BER but not DER
IndefiniteLength,
ReservedLength,
UnexpectedEOF,
}

impl<'a> Item<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<(Self, usize), Error> {
// Read item identifier
let mut index = 0;
let byte = bytes.get(index).ok_or(Error::UnexpectedEOF)?;
index += 1;

let class_tag = match byte >> 6 {
0 => ClassTag::Universal,
1 => ClassTag::Application,
2 => ClassTag::ContextSpecific,
3 => ClassTag::Private,
_ => unreachable!(),
};

let _pc = if byte & 0b00100000 == 0 {
PrimitiveOrConstructed::Primitive
} else {
PrimitiveOrConstructed::Constructed
};

let type_tag = if byte & 0b00011111 == 31 {
// Type tag is encoded using long form
let mut done = false;
let mut type_tag_value = 0;
while !done {
let byte = bytes.get(index).ok_or(Error::UnexpectedEOF)?;
index += 1;

type_tag_value <<= 7;
type_tag_value |= (byte & 0b01111111) as u32;

done = byte & 0b1000000 == 0;
}
type_tag_value
} else {
(byte & 0b00011111) as u32
};

// Read item length
let byte = bytes.get(index).ok_or(Error::UnexpectedEOF)?;
index += 1;

let first_bit = byte & 0b10000000 != 0;
let remaining = byte & 0b01111111;
let length = match (first_bit, remaining) {
(false, _) => {
// Definite short form
remaining as usize
},
(true, 0) => return Err(Error::IndefiniteLength),
(true, 127) => return Err(Error::ReservedLength),
(true, number_of_octets) => {
// Definite long form
// First byte defines the number of length bytes that follow
const N_BYTES: u8 = (usize::BITS / 8) as u8;
let mut buffer = [0; N_BYTES as usize];

// Never read more than 8 bytes.
// If your type is longer than u64::MAX, then you likely
// have a problem anyways
let number_of_octets = number_of_octets.min(N_BYTES);
let n_bytes_to_skip = N_BYTES - number_of_octets;

let int_bytes = &bytes
.get(index..index + number_of_octets as usize)
.ok_or(Error::UnexpectedEOF)?;
buffer[n_bytes_to_skip as usize..].copy_from_slice(int_bytes);

index += number_of_octets as usize;
usize::from_be_bytes(buffer)
},
};

let value_bytes = bytes
.get(index..index + length)
.ok_or(Error::UnexpectedEOF)?;
index += length;

let item = if class_tag == ClassTag::ContextSpecific {
Item::ContextSpecific(value_bytes)
} else {
// Construct the actual item
match type_tag {
0 => Item::EndOfContent,
1 => Item::Boolean,
2 => Item::Integer,
3 => Item::BitString,
4 => Item::OctetString,
5 => Item::Null,
6 => Item::ObjectIdentifier,
7 => Item::ObjectDescriptor,
8 => Item::External,
9 => Item::Real,
10 => Item::Enumerated,
11 => Item::EmbeddedPDV,
12 => Item::Utf8String,
13 => Item::RelativeOID,
14 => Item::Time,
15 => return Err(Error::ReservedTypeTag),
16 => Item::Sequence(Sequence { bytes: value_bytes }),
17 => Item::Set,
18 => Item::NumericString,
19 => Item::PrintableString,
20 => Item::T61String,
21 => Item::VideotexString,
22 => Item::IA5String,
23 => Item::UtcTime,
24 => Item::GeneralizedTime,
25 => Item::GraphicString,
26 => Item::VisibleString,
27 => Item::GeneralString,
28 => Item::UniversalString,
29 => Item::CharacterString,
30 => Item::BMPString,
31 => Item::Date,
32 => Item::TimeOfDay,
33 => Item::DateTime,
34 => Item::Duration,
35 => Item::OidIri,
36 => Item::RelativeOidIri,
_ => return Err(Error::UnknownTypeTag),
}
};

Ok((item, index))
}
}

#[derive(Clone, Copy, Debug)]
pub struct Sequence<'a> {
bytes: &'a [u8],
}

impl<'a> Iterator for Sequence<'a> {
type Item = Result<Item<'a>, Error>;

fn next(&mut self) -> Option<Self::Item> {
if self.bytes.is_empty() {
return None;
}

match Item::parse(self.bytes) {
Ok((item, length)) => {
self.bytes = &self.bytes[length..];
Some(Ok(item))
},
Err(e) => Some(Err(e)),
}
}
}

impl<'a> FusedIterator for Sequence<'a> {}
24 changes: 15 additions & 9 deletions crypto/tls/src/handshake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,14 +171,14 @@ impl ClientHello {

#[allow(clippy::identity_op)]
let length: u32 = 2 // Protocol version
+ 32 // Client random
+ 1 // Session id length (always 0 since we don't resume sessions)
+ 0 // [session id]
+ 2 // Supported cipher suites length
+ cipher_suites_length as u32 // List of supported cipher suites
+ 2 // Compression method
+ 2 // Extension length
+ extensions_length as u32;
+ 32 // Client random
+ 1 // Session id length (always 0 since we don't resume sessions)
+ 0 // [session id]
+ 2 // Supported cipher suites length
+ cipher_suites_length as u32 // List of supported cipher suites
+ 2 // Compression method
+ 2 // Extension length
+ extensions_length as u32;

writer.write_all(&[HandshakeType::ClientHello.into()])?;
writer.write_all(&length.to_be_bytes()[1..])?;
Expand Down Expand Up @@ -246,6 +246,7 @@ impl HandshakeMessage {

match handshake_type {
HandshakeType::ServerHello => {
// https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.3
let mut server_version_bytes: [u8; 2] = [0, 0];
reader
.read_exact(&mut server_version_bytes)
Expand Down Expand Up @@ -301,7 +302,9 @@ impl HandshakeMessage {
.map_err(TLSError::IO)?;
let certificate_chain_length =
u32::from_be_bytes(certificate_chain_length_bytes) as usize;

let mut certificate_chain = vec![];

let mut bytes_read = 0;
while bytes_read != certificate_chain_length {
let mut certificate_length_bytes = [0; 4];
Expand All @@ -315,7 +318,10 @@ impl HandshakeMessage {
.read_exact(&mut certificate_bytes)
.map_err(TLSError::IO)?;

certificate_chain.push(X509v3Certificate::new(certificate_bytes));
// FIXME: propagate error
let certificate = X509v3Certificate::new(certificate_bytes)
.expect("certificate parsing failed");
certificate_chain.push(certificate);
bytes_read += certificate_length + 3;
}

Expand Down
1 change: 1 addition & 0 deletions crypto/tls/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
pub mod alert;
pub mod certificate;
mod connection;
pub mod der;
pub mod handshake;
pub mod random;
pub mod record_layer;
Expand Down

0 comments on commit bb81bd0

Please sign in to comment.