From d241e93d8e8bb6892838f97e83eee4b8c3cb86d9 Mon Sep 17 00:00:00 2001 From: occheung Date: Fri, 23 Oct 2020 17:06:31 +0800 Subject: [PATCH] certificate: parseable --- src/certificate.rs | 92 ++++++++--- src/parse.rs | 395 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 445 insertions(+), 42 deletions(-) diff --git a/src/certificate.rs b/src/certificate.rs index 4fc819c..73a8e23 100644 --- a/src/certificate.rs +++ b/src/certificate.rs @@ -3,23 +3,25 @@ use num_enum::TryFromPrimitive; use alloc::vec::Vec; +#[derive(Debug, Clone)] pub struct Certificate<'a> { - tbs_certificate: TBSCertificate<'a>, - signature_algorithm: AlgorithmIdentifier<'a>, - signature_value: &'a [u8] + pub tbs_certificate: TBSCertificate<'a>, + pub signature_algorithm: AlgorithmIdentifier<'a>, + pub signature_value: &'a [u8] } +#[derive(Debug, Clone)] pub struct TBSCertificate<'a> { - version: Version, - serial_number: &'a [u8], - signature: AlgorithmIdentifier<'a>, - issuer: &'a [u8], - validity: Validity<'a>, - subject: &'a [u8], - subject_public_key_info: SubjectPublicKeyInfo<'a>, - issuer_unique_id: Option<&'a [u8]>, - subject_unique_id: Option<&'a [u8]>, - extensions: Extensions<'a>, + pub version: Version, + pub serial_number: &'a [u8], + pub signature: AlgorithmIdentifier<'a>, + pub issuer: &'a [u8], + pub validity: Validity<'a>, + pub subject: &'a [u8], + pub subject_public_key_info: SubjectPublicKeyInfo<'a>, + pub issuer_unique_id: Option<&'a [u8]>, + pub subject_unique_id: Option<&'a [u8]>, + pub extensions: Extensions<'a>, } #[derive(Debug, PartialEq, Eq, Clone, Copy, IntoPrimitive, TryFromPrimitive)] @@ -31,44 +33,98 @@ pub enum Version { v3 = 2, } +#[derive(Debug, Clone)] pub struct Validity<'a> { pub not_before: Time<'a>, pub not_after: Time<'a>, } +#[derive(Debug, Clone)] pub enum Time<'a> { UTCTime(&'a [u8]), GeneralizedTime(&'a [u8]), } +#[derive(Debug, Clone)] pub struct SubjectPublicKeyInfo<'a> { pub algorithm: AlgorithmIdentifier<'a>, pub subject_public_key: &'a [u8], } +#[derive(Debug, Clone)] pub struct Extensions<'a> { - extensions: Vec> + // TODO: Give a limit to the number of policies, migrate to heapless vec + // An arbitrary upper limit does not violate RFC5280 + pub extensions: Vec> } -pub enum Extension<'a> { +#[derive(Debug, Clone)] +pub struct Extension<'a> { + pub extension_id: &'a [u8], + pub critical: bool, + pub extension_value: ExtensionValue<'a>, +} + +#[derive(Debug, Clone)] +pub enum ExtensionValue<'a> { KeyUsage { // Acceptable usage of this certificate // Cross verify with ExtendedKeyUsage - usage: u8 + // MSb is bit 0 + usage: u16 }, + CertificatePolicies { // Policies listed in an extension // Need to verify its validity - policies: Vec<&'a [u8]> + // TODO: Give a limit to the number of policies, migrate to heapless vec + // An arbitrary upper limit does not violate RFC5280 + info: Vec> }, - SubjectAlternativeName, + + // Permitted subtrees and excluded subtrees are not implemented + // SubjectAlternativeName, + BasicConstraints { is_ca: bool, path_len_constraint: Option, }, + // Permitted subtrees and excluded subtrees are not implemented + // NameConstraints, + + // Policy mapping will not be supported + // PolicyConstraints, + + ExtendedKeyUsage { + // A list of all possible extended key usage in OID + // Cross check validity with regular KeyUsage + any_extended_key_usage: bool, + id_kp_server_auth: bool, + id_kp_client_auth: bool, + id_kp_code_signing: bool, + id_kp_email_protection: bool, + id_kp_time_stamping: bool, + id_kp_oscp_signing: bool, + }, + + InhibitAnyPolicy { + // Number of certificates in the path that may still allow AnyPolicy + // Certificate chain size should be limited to a small number + skip_certs: u8 + }, + + // Extension data from an unsupported extension type + Unrecognized, } +#[derive(Debug, Clone)] +pub struct PolicyInformation<'a> { + pub id: &'a [u8], + pub qualifier: &'a [u8], +} + +#[derive(Debug, Clone)] pub struct AlgorithmIdentifier<'a> { pub algorithm: &'a [u8], pub parameters: &'a [u8], diff --git a/src/parse.rs b/src/parse.rs index 54582fa..9693a9c 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -3,6 +3,7 @@ use nom::bytes::complete::take; use nom::bytes::complete::tag; use nom::bytes::complete::take_till; use nom::combinator::complete; +use nom::combinator::opt; use nom::sequence::preceded; use nom::sequence::tuple; use nom::error::make_error; @@ -13,13 +14,20 @@ use smoltcp::Result; use byteorder::{ByteOrder, NetworkEndian, BigEndian}; use crate::tls_packet::*; -use crate::certificate::Certificate as Asn1DerCertificate; -use crate::certificate::Version as Asn1DerVersion; -use crate::certificate::AlgorithmIdentifier as Asn1DerAlgId; -use crate::certificate::Time as Asn1DerTime; -use crate::certificate::Validity as Asn1DerValidity; -use crate::certificate::SubjectPublicKeyInfo as Asn1DerSubjectPublicKeyInfo; -use crate::certificate::Extensions as Asn1DerExtensions; + +use crate::certificate::{ + Certificate as Asn1DerCertificate, + Version as Asn1DerVersion, + AlgorithmIdentifier as Asn1DerAlgId, + Time as Asn1DerTime, + Validity as Asn1DerValidity, + SubjectPublicKeyInfo as Asn1DerSubjectPublicKeyInfo, + Extensions as Asn1DerExtensions, + Extension as Asn1DerExtension, + ExtensionValue as Asn1DerExtensionValue, + PolicyInformation as Asn1DerPolicyInformation, + TBSCertificate as Asn1DerTBSCertificate, +}; use core::convert::TryFrom; use core::convert::TryInto; @@ -368,22 +376,73 @@ pub fn parse_asn1_der_header(bytes: &[u8]) -> IResult<&[u8], (u8, usize)> { // TODO: Not return length // It is quite useless when the value slice of the exact same length is returned +// i.e. `length` can be replaced by `value.len()` pub fn parse_asn1_der_object(bytes: &[u8]) -> IResult<&[u8], (u8, usize, &[u8])> { let (rest, (tag, length)) = parse_asn1_der_header(bytes)?; let (rest, value) = take(length)(rest)?; Ok((rest, (tag, length, value))) } -pub fn parse_asn1_der_certificate(bytes: &[u8]) -> IResult<&[u8], (&[u8], &[u8], &[u8])> { - let (_, (_, _, rest)) = parse_asn1_der_object(bytes)?; - let (rest, (_, _, tbscertificate_slice)) = parse_asn1_der_object(rest)?; - let (rest, (_, _, signature_alg)) = parse_asn1_der_object(rest)?; - let (rest, (_, _, sig_val)) = parse_asn1_der_object(rest)?; - Ok((rest, (tbscertificate_slice, signature_alg, sig_val))) +pub fn parse_asn1_der_certificate(bytes: &[u8]) -> IResult<&[u8], Asn1DerCertificate> { + let (excluded, (_, _, rest)) = parse_asn1_der_object(bytes)?; + let (_, (tbs_certificate, sig_alg, sig_value)) = complete( + tuple(( + parse_asn1_der_tbs_certificate, + parse_asn1_der_algorithm_identifier, + parse_asn1_der_bit_string + )) + )(rest)?; + Ok(( + excluded, + Asn1DerCertificate { + tbs_certificate, + signature_algorithm: sig_alg, + signature_value: sig_value, + } + )) } -pub fn parse_asn1_der_tbs_certificate(bytes: &[u8]) -> IResult<&[u8], Vec<&[u8]>> { - todo!() +// Parser for TBSCertificate (Sequence: 0x30) +pub fn parse_asn1_der_tbs_certificate(bytes: &[u8]) -> IResult<&[u8], Asn1DerTBSCertificate> { + let (rest, (tag_val, length, value)) = parse_asn1_der_object(bytes)?; + // Verify the tag is indeed 0x30 + if tag_val != 0x30 { + return Err(nom::Err::Failure((&[], ErrorKind::Verify))); + } + + let (_, ( + version, serial_number, signature, issuer, validity, subject, + subject_public_key_info, issuer_unique_id, subject_unique_id, extensions + )) = complete( + tuple(( + parse_asn1_der_version, + parse_asn1_der_serial_number, + parse_asn1_der_algorithm_identifier, + parse_asn1_der_sequence, + parse_asn1_der_validity, + parse_asn1_der_sequence, + parse_asn1_der_subject_key_public_info, + opt(parse_asn1_der_bit_string), + opt(parse_asn1_der_bit_string), + parse_asn1_der_extensions + )) + )(value)?; + + Ok(( + rest, + Asn1DerTBSCertificate { + version, + serial_number, + signature, + issuer, + validity, + subject, + subject_public_key_info, + issuer_unique_id, + subject_unique_id, + extensions, + } + )) } // version: [0] EXPLICIT Version DEFAULT V1 @@ -398,9 +457,7 @@ pub fn parse_asn1_der_version(bytes: &[u8]) -> IResult<&[u8], Asn1DerVersion> { // Parse the encapsulated INTEGER, force completeness let (_, integer) = complete(parse_asn1_der_integer)(value)?; // Either 0, 1, or 2, take the last byte and assert all former bytes to be 0 - let (zeroes, version_byte) = take(integer.len()-1)(integer)?; - complete(take_till(|byte| byte != 0))(zeroes)?; - Ok((rest, Asn1DerVersion::try_from(version_byte[0]).unwrap())) + Ok((rest, Asn1DerVersion::try_from(integer[0]).unwrap())) } // INTEGER: tag: 0x02 @@ -410,8 +467,6 @@ pub fn parse_asn1_der_integer(bytes: &[u8]) -> IResult<&[u8], &[u8]> { if tag_val != 0x02 { return Err(nom::Err::Failure((&[], ErrorKind::Verify))); } - // Consume the leading 0x00 byte - let (value, _) = tag(&[0x00])(value)?; Ok((rest, value)) } @@ -422,12 +477,35 @@ pub fn parse_asn1_der_bit_string(bytes: &[u8]) -> IResult<&[u8], &[u8]> { let (rest, (tag_val, length, value)) = parse_asn1_der_object(bytes)?; // Verify the tag is indeed 0x03 if tag_val != 0x03 { - return Err(nom::Err::Failure((&[], ErrorKind::Verify))); + return Err(nom::Err::Error((bytes, ErrorKind::Verify))); } // Dump `unused_bit` field let (value, unused_bit_byte) = take(1_usize)(value)?; // Assert no unused bits, otherwise it is a malformatted key - if value[0] != 0 { + if unused_bit_byte[0] != 0 { + return Err(nom::Err::Error((bytes, ErrorKind::Verify))); + } + Ok((rest, value)) +} + +// BOOLEAN: tag: 0x01 +// Length should be 1 +// 0x00 -> false; 0xFF -> true +pub fn parse_asn1_der_boolean(bytes: &[u8]) -> IResult<&[u8], bool> { + let (rest, (tag_val, length, value)) = parse_asn1_der_object(bytes)?; + // Verify the tag is indeed 0x01 and the length is 1 + // The value should be 0x00 or 0xFF + if tag_val != 0x01 || length != 1 || (value[0] != 0x00 && value[0] != 0xFF) { + return Err(nom::Err::Error((bytes, ErrorKind::Verify))); + } + Ok((rest, value[0] == 0xFF)) +} + +// SEQUENCE: tag: 0x30 +pub fn parse_asn1_der_sequence(bytes: &[u8]) -> IResult<&[u8], &[u8]> { + let (rest, (tag_val, length, value)) = parse_asn1_der_object(bytes)?; + // Verify the tag is indeed 0x03 + if tag_val != 0x30 { return Err(nom::Err::Failure((&[], ErrorKind::Verify))); } Ok((rest, value)) @@ -537,12 +615,281 @@ pub fn parse_asn1_der_subject_key_public_info(bytes: &[u8]) -> IResult<&[u8], As )) } -// Parser for extensions (Sequence: 0xA3) +// Parser for extensions (Context-specific Sequence: 0xA3, then universal Sequence: 0x30) pub fn parse_asn1_der_extensions(bytes: &[u8]) -> IResult<&[u8], Asn1DerExtensions> { let (rest, (tag_val, length, value)) = parse_asn1_der_object(bytes)?; // Verify the tag_val is indeed 0xA3 if tag_val != 0xA3 { return Err(nom::Err::Failure((&[], ErrorKind::Verify))); } - todo!() + + let (_, (tag_val, length, mut value)) = complete( + parse_asn1_der_object + )(value)?; + // Verify the tag_val is indeed 0x30 + if tag_val != 0x30 { + return Err(nom::Err::Failure((&[], ErrorKind::Verify))); + } + + let mut extensions = Vec::new(); + while value.len() != 0 { + let (rem, extension) = parse_asn1_der_extension(value)?; + value = rem; + extensions.push(extension); + } + + Ok(( + rest, + Asn1DerExtensions { extensions } + )) +} + +// Parser for an extension (Sequence: 0x30) +pub fn parse_asn1_der_extension(bytes: &[u8]) -> IResult<&[u8], Asn1DerExtension> { + let (rest, (tag_val, length, value)) = parse_asn1_der_object(bytes)?; + // Verify the tag_val is indeed 0x30 + if tag_val != 0x30 { + return Err(nom::Err::Failure((&[], ErrorKind::Verify))); + } + + // Parse an appropriate extension according to OID and critical-ness + let (_, (oid, critical, rem_ext_data)) = complete( + tuple(( + parse_asn1_der_oid, + opt(parse_asn1_der_boolean), + parse_asn1_der_octet_string + )) + )(value)?; + + let extension_value = match oid { + oid::CERT_KEY_USAGE => { + let (_, extension_value) = complete( + parse_asn1_der_key_usage + )(rem_ext_data)?; + extension_value + }, + oid::CERT_POLICIES => { + let (_, extension_value) = complete( + parse_asn1_der_certificate_policies + )(rem_ext_data)?; + extension_value + }, + oid::CERT_BASIC_CONSTRAINTS => { + let (_, extension_value) = complete( + parse_asn1_der_basic_constraints + )(rem_ext_data)?; + extension_value + }, + oid::CERT_EXT_KEY_USAGE => { + let (_, extension_value) = complete( + parse_asn1_der_extended_key_usage + )(rem_ext_data)?; + extension_value + }, + oid::CERT_INHIBIT_ANY_POLICY => { + let (_, extension_value) = complete( + parse_inhibit_any_policy + )(rem_ext_data)?; + extension_value + }, + // TODO: Parse extension value for recognized extensions + _ => Asn1DerExtensionValue::Unrecognized + }; + Ok(( + rest, + Asn1DerExtension { + extension_id: oid, + critical: critical.map_or(false, |b| b), + extension_value + } + )) +} + +// Parser for KeyUsage Extension, may have bit padding +// Do not use parse_asn1_der_bit_string, that assumes no bit padding +pub fn parse_asn1_der_key_usage(bytes: &[u8]) -> IResult<&[u8], Asn1DerExtensionValue> { + let (rest, (tag_val, length, value)) = parse_asn1_der_object(bytes)?; + // Verify the tag_val represents a bitstring, and it must have length 2 + // i.e. bit-padding | bit-string + if tag_val != 0x03 || (length != 2 && length != 3) { + return Err(nom::Err::Failure((&[], ErrorKind::Verify))); + } + // Erase the padded bits + let padding = value[0]; + let usage_array: [u8; 2] = if length == 2 { + [value[1], 0] + } else { + [value[1], value[2]] + }; + let usage = (NetworkEndian::read_u16(&usage_array) >> padding) << padding; + Ok(( + rest, + Asn1DerExtensionValue::KeyUsage { + usage + } + )) +} + +// Parser for CertificatePolicies Extension (sequence: 0x30) +pub fn parse_asn1_der_certificate_policies(bytes: &[u8]) -> IResult<&[u8], Asn1DerExtensionValue> { + let (rest, (tag_val, length, mut value)) = parse_asn1_der_object(bytes)?; + // Verify tag value + if tag_val != 0x30 { + return Err(nom::Err::Failure((&[], ErrorKind::Verify))); + } + + let mut vec: Vec = Vec::new(); + + while value.len() != 0 { + let (rem, info) = parse_asn1_der_policy_information(value)?; + value = rem; + vec.push(info); + } + + Ok(( + rest, + Asn1DerExtensionValue::CertificatePolicies { + info: vec, + } + )) +} + +// Parser for PolicyInformation (Sequence: 0x30) +pub fn parse_asn1_der_policy_information(bytes: &[u8]) -> IResult<&[u8], Asn1DerPolicyInformation> { + let (rest, (tag_val, length, value)) = parse_asn1_der_object(bytes)?; + // Verify tag value + if tag_val != 0x30 { + return Err(nom::Err::Failure((&[], ErrorKind::Verify))); + } + + let (_, (oid, (_, _, qualifier))) = complete( + tuple(( + parse_asn1_der_oid, + parse_asn1_der_object + )) + )(value)?; + + Ok(( + rest, + Asn1DerPolicyInformation { + id: oid, + qualifier + } + )) +} + +// Parser for BasicConstraints (Sequence: 0x30) +pub fn parse_asn1_der_basic_constraints(bytes: &[u8]) -> IResult<&[u8], Asn1DerExtensionValue> { + let (rest, (tag_val, length, value)) = parse_asn1_der_object(bytes)?; + // Verify tag value + if tag_val != 0x30 { + return Err(nom::Err::Failure((&[], ErrorKind::Verify))); + } + let (_, (is_ca, path_len_constraint)) = complete( + tuple(( + opt(parse_asn1_der_boolean), + opt(parse_asn1_der_integer) + )) + )(value)?; + let is_ca = is_ca.map_or(false, |b| b); + let path_len_constraint = path_len_constraint.map( + |slice| { + if slice.len() != 1 { + 255 + } else { + slice[0] + } + } + ); + Ok(( + rest, + Asn1DerExtensionValue::BasicConstraints { + is_ca, + path_len_constraint + } + )) +} + +// Parser for Extended Key Usage Extension (Sequence: 0x30) +pub fn parse_asn1_der_extended_key_usage(bytes: &[u8]) -> IResult<&[u8], Asn1DerExtensionValue> { + let (rest, (tag_val, length, mut value)) = parse_asn1_der_object(bytes)?; + // Verify tag value + if tag_val != 0x30 { + return Err(nom::Err::Failure((&[], ErrorKind::Verify))); + } + + let mut flags: [bool; 7] = [false; 7]; + + while value.len() != 0 { + let (rem, oid_val) = parse_asn1_der_oid(value)?; + value = rem; + match oid_val { + oid::ANY_EXTENDED_KEY_USAGE => flags[0] = true, + oid::ID_KP_SERVER_AUTH => flags[1] = true, + oid::ID_KP_CLIENT_AUTH => flags[2] = true, + oid::ID_KP_CODE_SIGNING => flags[3] = true, + oid::ID_KP_EMAIL_PROTECTION => flags[4] = true, + oid::ID_KP_TIME_STAMPING => flags[5] = true, + oid::ID_KP_OCSP_SIGNING => flags[6] = true, + _ => {}, + } + } + + Ok(( + rest, + Asn1DerExtensionValue::ExtendedKeyUsage { + any_extended_key_usage: flags[0], + id_kp_server_auth: flags[1], + id_kp_client_auth: flags[2], + id_kp_code_signing: flags[3], + id_kp_email_protection: flags[4], + id_kp_time_stamping: flags[5], + id_kp_oscp_signing: flags[6], + } + )) +} + +// Parser for inhibit anyPolicy extension (integer) +pub fn parse_inhibit_any_policy(bytes: &[u8]) -> IResult<&[u8], Asn1DerExtensionValue> { + let (rest, integer_slice) = parse_asn1_der_integer(bytes)?; + Ok(( + rest, + Asn1DerExtensionValue::InhibitAnyPolicy { + skip_certs: { + if integer_slice.len() == 1 { + integer_slice[0] + } else { + 255 + } + } + } + )) +} + + +// Parser for octet string (tag: 0x04) +pub fn parse_asn1_der_octet_string(bytes: &[u8]) -> IResult<&[u8], &[u8]> { + let (rest, (tag_val, length, value)) = parse_asn1_der_object(bytes)?; + // Verify tag value + if tag_val != 0x04 { + return Err(nom::Err::Failure((&[], ErrorKind::Verify))); + } + Ok((rest, value)) +} + +mod oid { + // Extensions + pub const CERT_KEY_USAGE: &'static [u8] = &[85, 29, 15]; // 2.5.29.15 + pub const CERT_POLICIES: &'static [u8] = &[85, 29, 32]; // 2.5.29.32 + pub const CERT_BASIC_CONSTRAINTS: &'static [u8] = &[85, 29, 19]; // 2.5.29.19 + pub const CERT_EXT_KEY_USAGE: &'static [u8] = &[85, 29, 37]; // 2.5.29.37 + pub const CERT_INHIBIT_ANY_POLICY: &'static [u8] = &[85, 29, 54]; // 2.5.29.54 + // Extended Key Extensions + pub const ANY_EXTENDED_KEY_USAGE: &'static [u8] = &[85, 29, 37, 0]; // 2.5.29.37.0 + pub const ID_KP_SERVER_AUTH: &'static [u8] = &[43, 6, 1, 5, 5, 7, 3, 1]; // 1.3.6.1.5.5.7.3.1 + pub const ID_KP_CLIENT_AUTH: &'static [u8] = &[43, 6, 1, 5, 5, 7, 3, 2]; // 1.3.6.1.5.5.7.3.2 + pub const ID_KP_CODE_SIGNING: &'static [u8] = &[43, 6, 1, 5, 5, 7, 3, 3]; // 1.3.6.1.5.5.7.3.3 + pub const ID_KP_EMAIL_PROTECTION: &'static [u8] = &[43, 6, 1, 5, 5, 7, 3, 4]; // 1.3.6.1.5.5.7.3.4 + pub const ID_KP_TIME_STAMPING: &'static [u8] = &[43, 6, 1, 5, 5, 7, 3, 8]; // 1.3.6.1.5.5.7.3.8 + pub const ID_KP_OCSP_SIGNING: &'static [u8] = &[43, 6, 1, 5, 5, 7, 3, 9]; // 1.3.6.1.5.5.7.3.9 }