diff --git a/src/merkle/types.rs b/src/merkle/types.rs index 97e59e0..cebef42 100644 --- a/src/merkle/types.rs +++ b/src/merkle/types.rs @@ -1,15 +1,13 @@ use x509_parser::prelude::{TbsCertificate, X509Certificate}; -#[repr(u8)] #[non_exhaustive] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Version { - V1 = 0 + V1 } pub type CtExtensions<'a> = &'a [u8]; -#[repr(u8)] #[non_exhaustive] #[derive(Debug)] pub enum MerkleLeafType<'a> { @@ -17,17 +15,19 @@ pub enum MerkleLeafType<'a> { timestamp: u64, entry_type: LogEntryType<'a>, extensions: CtExtensions<'a> - } = 0 + } +} + +#[derive(Debug)] +pub struct PreCert<'a> { + pub issuer_key_hash: &'a [u8], + pub tbs_certificate: TbsCertificate<'a> } -#[repr(u16)] #[derive(Debug)] pub enum LogEntryType<'a> { - X509Entry(x509_parser::certificate::X509Certificate<'a>) = 0, - PrecertEntry { - issuer_key_hash: [u8; 32], - tbs_certificate: TbsCertificate<'a> - } = 1 + X509Entry(x509_parser::certificate::X509Certificate<'a>), + PrecertEntry(PreCert<'a>) } #[derive(Debug)] diff --git a/src/parsing/entry.rs b/src/parsing/entry.rs index 70dd30b..99cf8ce 100644 --- a/src/parsing/entry.rs +++ b/src/parsing/entry.rs @@ -1,10 +1,8 @@ use x509_parser::{error::X509Error, prelude::X509Certificate}; -use super::{ - structures::{parse_tbs_certificate_der, parse_x509_der}, - LeafParsingEnumType, - LeafParsingError -}; +use super:: + structures::{parse_opaque_asn1_cert, parse_precert} +; use crate::merkle::types::{ ChainEntry, EntryExtraData, @@ -14,6 +12,65 @@ use crate::merkle::types::{ Version }; +#[derive(Debug, PartialEq)] +pub enum CtErrorKind { + InvalidVersion +} + +#[derive(Debug, PartialEq)] +pub struct CtError { + pub input: I, + pub code: CtErrorKind +} + +/// Parses a TimestampedEntry structure as specified in [RFC6962]: +/// ```txt +/// struct { +/// uint64 timestamp; +/// LogEntryType entry_type; +/// select(entry_type) { +/// case x509_entry: ASN.1Cert; +/// case precert_entry: PreCert; +/// } signed_entry; +/// CtExtensions extensions; +/// } TimestampedEntry; +/// ``` +/// +/// [RFC6962]: https://datatracker.ietf.org/doc/html/rfc6962 +pub fn parse_timestamped_entry( + input: &[u8] +) -> nom::IResult<&[u8], MerkleLeafType, X509Error> { + nom::combinator::map( + nom::sequence::tuple(( + nom::number::complete::be_u64, + nom::branch::alt(( + // x509 entry + nom::combinator::map( + nom::sequence::pair( + nom::bytes::complete::tag(0u16.to_be_bytes()), + parse_opaque_asn1_cert + ), + |(_, v)| LogEntryType::X509Entry(v) + ), + // precert entry + nom::combinator::map( + nom::sequence::pair( + nom::bytes::complete::tag(1u16.to_be_bytes()), + parse_precert + ), + |(_, v)| LogEntryType::PrecertEntry(v) + ) + )), + nom::multi::length_data(nom::number::complete::be_u16) + )), + |(timestamp, entry_type, extensions)| MerkleLeafType::TimeStampedEntry { + timestamp, + entry_type, + extensions + } + )(input) +} + /// Parses a MerkleTreeLeaf structure as specified in [RFC6962](https://datatracker.ietf.org/doc/html/rfc6962): /// ```txt /// struct { @@ -28,96 +85,22 @@ use crate::merkle::types::{ /// This function assumes a binary format for the leaf, rather than a /// base64-encoded version, so make sure to manually decode it from the HTTP /// response before use. -pub fn parse_merkle_tree_leaf( - input: &[u8] -) -> nom::IResult<&[u8], MerkleTreeLeaf, LeafParsingError<&[u8]>> { - let (input, version /* Version version; */) = nom::number::complete::u8(input) - .map_err(|e| e.map(|e| LeafParsingError::Nom(e)))?; - let (input, leaf_type /* MerkleLeafType leaf_type; */) = - nom::number::complete::u8(input) - .map_err(|e| e.map(|e| LeafParsingError::Nom(e)))?; - - let version = match version { - 0 => Version::V1, - _ => - return Err(nom::Err::Failure(LeafParsingError::InvalidEnum { - input, - enum_type: LeafParsingEnumType::Version - })), - }; - let (input, leaf_type) = match leaf_type { - // struct { - // uint64 timestamp; - // LogEntryType entry_type; /* 2 bytes */ - // select(entry_type) { - // case x509_entry: ASN.1Cert; - // case precert_entry: PreCert; - // } signed_entry; - // CtExtensions extensions; - // } TimestampedEntry; - 0 => { - let (input, timestamp /* uint64 timestamp; */) = - nom::number::complete::be_u64(input) - .map_err(|e| e.map(|e| LeafParsingError::Nom(e)))?; - let (input, entry_type /* LogEntryType entry_type; */) = - nom::number::complete::be_u16(input) - .map_err(|e| e.map(|e| LeafParsingError::Nom(e)))?; - - let (input, entry_type) = match entry_type { - 0 => { - let (input, der /* opaque ASN.1Cert<1..2^24-1> */) = - parse_x509_der(input) - .map_err(|e| e.map(|e| LeafParsingError::DerParsing(e)))?; - - (input, LogEntryType::X509Entry(der)) - } - // case precert_entry: PreCert; - 1 => { - let ( - input, - issuer_key_hash // opaque issuer_key_hash[32]; - ) = nom::bytes::complete::take(32usize)(input) - .map_err(|e| e.map(|e| LeafParsingError::Nom(e)))?; - let ( - input, - tbs_certificate // TBSCertificate tbs_certificate; - ) = parse_tbs_certificate_der(input) - .map_err(|e| e.map(|e| LeafParsingError::DerParsing(e)))?; - - (input, LogEntryType::PrecertEntry { - issuer_key_hash: issuer_key_hash.try_into().map_err(|_| { - nom::Err::Failure(LeafParsingError::InvalidTakeLength) - })?, - tbs_certificate - }) - } - _ => - return Err(nom::Err::Failure(LeafParsingError::InvalidEnum { - input, - enum_type: LeafParsingEnumType::LogEntryType - })), - }; - - let ( - input, - extensions // opaque CtExtensions<0..2^16-1> - ) = nom::multi::length_data(nom::number::complete::be_u16)(input) - .map_err(|e| e.map(|e| LeafParsingError::Nom(e)))?; - - (input, MerkleLeafType::TimeStampedEntry { - timestamp, - entry_type, - extensions - }) - } - _ => - return Err(nom::Err::Failure(LeafParsingError::InvalidEnum { - input, - enum_type: LeafParsingEnumType::MerkleLeafType - })), - }; - - Ok((input, MerkleTreeLeaf { version, leaf_type })) +pub fn parse_merkle_tree_leaf(input: &[u8]) -> nom::IResult<&[u8], MerkleTreeLeaf, X509Error> { + nom::combinator::map( + nom::sequence::pair( + // Parse version byte + nom::combinator::map_opt(nom::number::complete::u8, |v| match v { + 0 => Some(Version::V1), + _ => None + }), + // Parse leaf type byte + nom::branch::alt((nom::sequence::pair( + nom::bytes::complete::tag([0u8]), + parse_timestamped_entry + ),)) + ), + |(version, (_, leaf_type))| MerkleTreeLeaf { version, leaf_type } + )(input) } /// Constructs a parser for an entry `extra_data`, with prior knowledge of the @@ -145,16 +128,16 @@ pub fn parse_entry_extra_data_precert<'a>( pub fn parse_certificate_chain( input: &[u8] ) -> nom::IResult<&[u8], Vec, X509Error> { - dbg!(nom::combinator::map_parser( + nom::combinator::map_parser( nom::multi::length_data( // Get slice containing the full chain nom::number::complete::be_u24 // certificate_chain has a length field of u24 ), nom::multi::many0( // Parse a length-data x509 DER as many times as possible - parse_x509_der + parse_opaque_asn1_cert ) - )(input)) + )(input) } /// Pares an X509ChainEntry or PrecertChainEntry as specified in [RFC6962]: @@ -173,7 +156,7 @@ pub fn parse_certificate_chain( /// [RFC6962]: https://datatracker.ietf.org/doc/html/rfc6962 pub fn parse_chain_entry(input: &[u8]) -> nom::IResult<&[u8], ChainEntry, X509Error> { nom::combinator::map( - nom::sequence::pair(parse_x509_der, parse_certificate_chain), + nom::sequence::pair(parse_opaque_asn1_cert, parse_certificate_chain), |(main_certificate, certificate_chain)| ChainEntry { main_certificate, certificate_chain diff --git a/src/parsing/structures.rs b/src/parsing/structures.rs index c688cc0..c18b68d 100644 --- a/src/parsing/structures.rs +++ b/src/parsing/structures.rs @@ -3,8 +3,10 @@ use x509_parser::{ prelude::{TbsCertificate, X509Certificate} }; +use crate::merkle::types::PreCert; + /// Parses a type `opaque ASN.1Cert<1..2^24-1>;` -pub fn parse_x509_der(input: &[u8]) -> X509Result { +pub fn parse_opaque_asn1_cert(input: &[u8]) -> X509Result { nom::combinator::map_parser( nom::multi::length_data(nom::number::complete::be_u24), x509_parser::parse_x509_certificate @@ -12,10 +14,32 @@ pub fn parse_x509_der(input: &[u8]) -> X509Result { } /// Parses a type `opaque TBSCertificate<1..2^24-1>;` -pub fn parse_tbs_certificate_der(input: &[u8]) -> X509Result { +pub fn parse_opaque_tbs_certificate(input: &[u8]) -> X509Result { nom::combinator::map_parser( nom::multi::length_data(nom::number::complete::be_u24), x509_parser::certificate::TbsCertificateParser::new() .with_deep_parse_extensions(true) )(input) } + +/// Parses the PreCert structure from [RFC6962]: +/// ```txt +/// struct { +/// opaque issuer_key_hash[32]; +/// TBSCertificate tbs_certificate; +/// } PreCert; +/// ``` +/// +/// [RFC6962]: https://datatracker.ietf.org/doc/html/rfc6962 +pub fn parse_precert(input: &[u8]) -> X509Result { + nom::combinator::map( + nom::sequence::pair( + nom::bytes::complete::take(32usize), + parse_opaque_tbs_certificate + ), + |(issuer_key_hash, tbs_certificate)| PreCert { + issuer_key_hash, + tbs_certificate + } + )(input) +} \ No newline at end of file