diff --git a/Cargo.toml b/Cargo.toml index 322ae24..ce3446a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,11 @@ version = "5.1.2" default-features = false features = [] +[dependencies.chrono] +version = "0.4.19" +default-features = false +features = [] + [dependencies.simple_logger] version = "1.11.0" optional = true diff --git a/src/certificate.rs b/src/certificate.rs index 814832a..890aa39 100644 --- a/src/certificate.rs +++ b/src/certificate.rs @@ -3,6 +3,8 @@ use num_enum::TryFromPrimitive; use generic_array::GenericArray; +use chrono::{DateTime, FixedOffset}; + use crate::parse::parse_asn1_der_rsa_public_key; use crate::parse::parse_rsa_ssa_pss_parameters; use crate::parse::parse_ecdsa_signature; @@ -38,7 +40,7 @@ pub struct TBSCertificate<'a> { pub serial_number: &'a [u8], pub signature: AlgorithmIdentifier<'a>, pub issuer: Name<'a>, - pub validity: Validity<'a>, + pub validity: Validity, pub subject: Name<'a>, pub subject_public_key_info: SubjectPublicKeyInfo<'a>, pub issuer_unique_id: Option<&'a [u8]>, @@ -57,9 +59,9 @@ pub enum Version { } #[derive(Debug, Clone)] -pub struct Validity<'a> { - pub not_before: Time<'a>, - pub not_after: Time<'a>, +pub struct Validity { + pub not_before: DateTime, + pub not_after: DateTime, } #[derive(Debug, Clone)] @@ -118,12 +120,19 @@ pub enum ExtensionValue<'a> { // Owns a list of acceptable/unacceptable GeneralNames // Maximum field should not exist, minimum field is always 0 // Vector size of 0 equivalent to NIL + // While it doesn't make sense to have both subtrees, + // the RFC (RFC 5280) mandated that any subtree stated in + // excluded subtree cannot be permitted, even if it is part of + // the permitted subtree. + // It is probably intentional to have OPTIONAL over CHOICE permitted_subtrees: Vec>, excluded_subtrees: Vec>, }, - // Policy mapping will not be supported - // PolicyConstraints, + PolicyConstraints { + require_explicit_policy: Option, + inhibit_policy_mapping: Option, + }, ExtendedKeyUsage { // A list of all possible extended key usage in OID diff --git a/src/main.rs b/src/main.rs index 2ab6a63..6314447 100644 --- a/src/main.rs +++ b/src/main.rs @@ -120,7 +120,7 @@ const CA_SIGNED_CERT: [u8; 0x0356] = "308203523082023a02146048517ee55aabd1e8f2bd7db1d91e679708e644300d06092a864886f70d01010b05003067310b30090603550406130255533113301106035504080c0a536f6d652d53746174653121301f060355040a0c18496e7465726e6574205769646769747320507479204c74643120301e06035504030c176578616d706c65732e63612e756c666865696d2e6e6574301e170d3230313130363034323035305a170d3230313230363034323035305a3064310b30090603550406130255533113301106035504080c0a536f6d652d53746174653121301f060355040a0c18496e7465726e6574205769646769747320507479204c7464311d301b06035504030c146578616d706c65732e756c666865696d2e6e657430820122300d06092a864886f70d01010105000382010f003082010a0282010100b2940671bfe7ace7416ba9d34018c229588e9d4eed8bd6623e44ab1239e8f1f0de9050b2f485a98e63f5b483330fb0b5abaeb33d11889033b0b684bf34696d28206bb361782c4b106a8d47874cbbdf971b5ab887bca508bccf250a1a811cee078464638e441941347d4c8885ac9b59d9fc9636276912b04d9e3ab29bd8ad319572ae54f0b6145c4d675a78607dcc4793a4d432f1c2a41ea29dd4f7262b6fe472dfaea51aca992b4624e73fa9901fa364fc5b721052ef3187e659d58d2706770d365380a7ebab6caac5b23271c01531fdf95368ee48af5383035f249be7c18f50ce9e52877558efe4b2e29f61328396e2a3b5e71309ad13d93d6ba3d5c3eb2b650203010001300d06092a864886f70d01010b0500038201010063c9ab0f5d2e164513e8e74b656ae4f48dd004c3ead9f1026b7741cbf02bb0efcf19e0fbf8a788dae059a2393167f016bafc0e3efd5c5b4c43079b6506eb67f17f44f9591503c7d1fdb77bf631894817393ea82610ad5106d23ec6bf1a6d96d749f05c0136cd71256617a51fe862529aee4a37d5f456dc7da8b220ff10ede4e87bc63e4589b3f81133a7f82ab900419e8a2d802d59e99cfbbd268702efd17616168b45b5211da0e644c29dcb92dbbf32b43586bbab05deb0261771605c52836363bd28ff9853d44436349f5ba11f2640bc9c42688e0d5eb6cac9f3f5e5f98652fa4f4ba52604371ec45f09d678e31d463285a4b3734f587f35a339920544f476" ); -const SELF_SIGNED_WITH_SAN: [u8; 0x065B] = +const SELF_SIGNED_WITH_SAN: [u8; 0x046C] = hex_literal::hex!( - "308206573082043fa00302010202144d8eb521b65348ff483311fbd343303b15590153300d06092a864886f70d01010b05003073310b3009060355040613025553310b300906035504080c0256413114301206035504070c0b416e6f746865724369747931123010060355040a0c094d79436f6d70616e7931133011060355040b0c0a4d794469766973696f6e3118301606035504030c0f7777772e636f6d70616e792e636f6d301e170d3230313130393034353735315a170d3231313130393034353735315a3073310b3009060355040613025553310b300906035504080c0256413114301206035504070c0b416e6f746865724369747931123010060355040a0c094d79436f6d70616e7931133011060355040b0c0a4d794469766973696f6e3118301606035504030c0f7777772e636f6d70616e792e636f6d30820222300d06092a864886f70d01010105000382020f003082020a0282020100ac61268194dfd6fbeab3e05b6ac55568dd676701ddca6c6784e18c2fae2af11625a945aa3b22674e81dd52810994b6cef1317b99e2e6a9e9e92c4e63890a59f4837264425e44876db170e1cc403da9db7eef99ce406bd41099ff9ebd73c37cda1dbe0737f78b98fa6a5f3054c37a01dcf99cf6247675b40e85f990928ef064fc4f7b9770c93ea9f038a7e9fbdb1f93ffb5a01fabafdd1ec2f4c73dc84297b804b383bb5f6e9f0d9f1f437e8a26dd181778b64db6b2caa420a17b57590e5747008311e583497ce922b9f162bca1ecba1547280e2343fd2b8dabc014bef0490f651f9a676d49656bfc747a60d423702f28c0dfa7d0ee17049b10ddaf1e8c45f29debe34d23b5d6715a67e27faa238ac9f10a00ab3f93966bdbcc917b2b905e1e01c39d34c3b20fa6b2918e0272f0e3a77af45e0adb5380a752c35701b8c8081ed57ac42dbf875bfef09f01542ef58914387492697cafba758516c10522d3d7315b993cc096c3b1e6c072123a508b8aed5455067c99b69b88be39e6b65e668a65a515f0e84ead532061f22a51dac83280bb3c5decaa634f7d334de19aef1623642ea72e8817d4e09330d2507ff5a10805329b6296cd029b792cb42ab8499bc57a28f1480d04c84a4f5b8a11b8c60c053d643ca6bae999139a8cd84ef8decb413f9d6de1733bc477ab86aead3e1a1f208b1abfb9e62c63730c1f47e5d97c1c1ce8eb0203010001a381e23081df300b0603551d0f04040302043030130603551d25040c300a06082b0601050507030130760603551d11046f306d820f7777772e636f6d70616e792e6e6574820b636f6d70616e792e636f6d820b636f6d70616e792e6e6574810b626f6240636f6d70616e79810d616c69636540636f6d70616e79a01e06032a0304a0170c15736f6d65206f74686572206964656e7469666965728704c0a801c830430603551d1e043c303aa01c300e820c2e6578616d706c652e636f6d300a8708c0a80001ffffff00a11a300a8708c0aa0001ffffff00300c820a2e6c6f63616c686f7374300d06092a864886f70d01010b050003820201003b6b5e5aeb8a598a4400fb3f2afc4f3604b3a2319d56ed7e9b4bee20cf8f141326dd0db406b0462240cdbf64730ab2d3a4bdc17dc54554dd834ef929b672da638a4f2d9e9c8221f4b7140121b124a47d773d50e86aa3d111c824a449f704c1e79312fa2b558dfb7260f7ac9e450ebfb321e9d7554a802c824937403e3266f4d3743d848196b855663020c0e1f529fc4ca822b52a70e9a5987bcd3c62c2bf3f80915cb5060c1f077580ffd8f532ad0e807662f030ba6168fc91ccc8e61f78df9a0c7b5090c6ddc01213323cc5a651dea96f67c4b466e17cd522fabc5a7f80e88d4933eef79e2b24d735eab2e5618e14960c10acdc58d928b514a6f2221a9fc71e67f52816713abbe6b8dabcdfa06c83499ece822bdead9a1695d13f2da7391463177a4162fa7cf8aeae3e9182def0789c4fa63ddc6c75cccf23c7a1438569488e131205892cabb482902dd117cfcbcb9fdbb963d11d3de702c51f6ef9772756e9d4c94c1925a0752b0fa45dcb7fc07f1cfe7685ec6c52011c1a98c246cb774dec66ebfcd4d0f52ed3a24d86df593eb7e318edc4e8946e8100825eb363b00507f585f8ee6770f66f1b638c9129cbd9a3f8f862771b4bf0d7c1da2a8cfd592d30b3345fad00a3f5c20d6bd4e365a26f3f4091f8de03aa9900a2da36dde5098aa7689827f50197d42b4aebe3745a509c698f5b8cfe9635b00491a1c9218c7cd05758" + "3082046830820350a00302010202145525dc68e0e749158bf5bee8fe12c71675127510300d06092a864886f70d01010b05003073310b3009060355040613025553310b300906035504080c0256413114301206035504070c0b416e6f746865724369747931123010060355040a0c094d79436f6d70616e7931133011060355040b0c0a4d794469766973696f6e3118301606035504030c0f7777772e636f6d70616e792e636f6d301e170d3230313130393035353034385a170d3231313130393035353034385a3073310b3009060355040613025553310b300906035504080c0256413114301206035504070c0b416e6f746865724369747931123010060355040a0c094d79436f6d70616e7931133011060355040b0c0a4d794469766973696f6e3118301606035504030c0f7777772e636f6d70616e792e636f6d30820122300d06092a864886f70d01010105000382010f003082010a0282010100a03fec3ee07c6dce516520ab9166b7b5d95c13196328cb443e8419d748889ebc99a22b8032240a816b585e2affaea7383e4b655aacc06ee67814693fc440f6a27939e793a85280334a4662da460802123d191dccfe029997cc7f94f7ff9aef7a2d662f01fb9cd153e14c6b21ab28b209f09082d635b705bca4553efaeaddc21d7fc73366130a54270175c608cff2f2336fbf13896ebfc44016259032c81f2499f19ce14da840f25b0339fae72926b231ec6f14b7ff66716be0d3471f6485b6657270834fc4058e93625d4df95eb3a95c2eec48c3f060cdd740e373fc240e3c76d5c3c3f11cb53dab471d8af6b46e0294b3c7a75c82edcbab24af0448237310b90203010001a381f33081f0300b0603551d0f04040302043030130603551d25040c300a06082b0601050507030130760603551d11046f306d820f7777772e636f6d70616e792e6e6574820b636f6d70616e792e636f6d820b636f6d70616e792e6e6574810b626f6240636f6d70616e79810d616c69636540636f6d70616e79a01e06032a0304a0170c15736f6d65206f74686572206964656e7469666965728704c0a801c830430603551d1e043c303aa01c300e820c2e6578616d706c652e636f6d300a8708c0a80001ffffff00a11a300a8708c0aa0001ffffff00300c820a2e6c6f63616c686f7374300f0603551d2404083006800103810105300d06092a864886f70d01010b0500038201010079721d5b3b0f17edc425ac5f6faa5d085760eba5ebd0bc2a5010a0893e46c68656fecc0b66c7216fb093a07aaad88819011b9daa0407aef06778c7c04dbc2f5dd20d3156cccd3a36298b26d8d548d2ae4564b55f1e00fe49d6a7c53f05eecd0b63d7716d51a59c71141f9b223cbf79c57d46a7a240d2bc851f38c7da45c9ec7ad99381605247e46309541ea89fe9e175f8fd4c296b7ea667dafb8d4310e577c79be50a1d3b48c4d012a01aaf605816f37f5a00f1cb86dee776b31f8a444a79f48313502b23d001d1f3a641f9ec05960180cfa0440b8002afc3b49c3c1779adfd7f9ecef38fb14c2b3902323314990928f764c70a579e02453fec03bc78f98914" ); diff --git a/src/oid.rs b/src/oid.rs index ec4d4c5..3d7a109 100644 --- a/src/oid.rs +++ b/src/oid.rs @@ -74,6 +74,7 @@ pub const CERT_EXT_KEY_USAGE: &'static [u8] = &[85, 29, 37]; pub const CERT_INHIBIT_ANY_POLICY: &'static [u8] = &[85, 29, 54]; // 2.5.29.54 pub const CERT_SUBJECTALTNAME: &'static [u8] = &[85, 29, 17]; // 2.5.29.17 pub const CERT_NAME_CONSTRAINTS: &'static [u8] = &[85, 29, 30]; // 2.5.29.30 +pub const CERT_POLICY_CONSTRAINTS: &'static [u8] = &[85, 29, 36]; // 2.5.29.36 // 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 diff --git a/src/parse.rs b/src/parse.rs index cb674b5..49e1545 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -7,6 +7,11 @@ use nom::combinator::opt; use nom::sequence::preceded; use nom::sequence::tuple; use nom::error::ErrorKind; +use nom::character::complete::digit0; +use nom::character::is_digit; + +use chrono::{DateTime, FixedOffset, TimeZone}; +use heapless::{String, consts::*}; use byteorder::{ByteOrder, NetworkEndian}; @@ -26,7 +31,7 @@ use crate::certificate::{ TBSCertificate as Asn1DerTBSCertificate, Name as Asn1DerName, AttributeTypeAndValue as Asn1DerAttribute, - GeneralName as Asn1DerGeneralName + GeneralName as Asn1DerGeneralName, }; use crate::oid; @@ -876,26 +881,121 @@ pub fn parse_asn1_der_validity(bytes: &[u8]) -> IResult<&[u8], Asn1DerValidity> } // Parser for Time Representation (0x17: UTCTime, 0x18: GeneralizedTime) -pub fn parse_ans1_der_time(bytes: &[u8]) -> IResult<&[u8], Asn1DerTime> { +pub fn parse_ans1_der_time(bytes: &[u8]) -> IResult<&[u8], DateTime> { let (rest, (tag_val, _, value)) = parse_asn1_der_object(bytes)?; // Handle UTCTime, Gen.Time and Invalid Tag values match tag_val { 0x17 => { + let (_, date_time) = complete( + parse_asn1_der_utc_time + )(value)?; Ok(( rest, - Asn1DerTime::UTCTime(value) + date_time )) }, 0x18 => { + // TODO: Not implemented + let (_, date_time) = complete( + parse_asn1_der_generalized_time + )(value)?; Ok(( rest, - Asn1DerTime::GeneralizedTime(value) + date_time )) }, _ => Err(nom::Err::Failure((&[], ErrorKind::Verify))) } } +// Parser for UTCTime +pub fn parse_asn1_der_utc_time(bytes: &[u8]) -> IResult<&[u8], DateTime> { + + // Buffer for building string + let mut string: String = String::new(); + + // Decide the appropriate century (1950 to 2049) + let year_tag: u8 = core::str::from_utf8(&bytes[..2]).unwrap().parse().unwrap(); + if year_tag < 50 { + string.push_str("20"); + } else { + string.push_str("19"); + } + + // Take out YYMMDDhhmm first + let (rest, first_part) = take(10_usize)(bytes)?; + string.push_str(core::str::from_utf8(first_part).unwrap()).unwrap(); + let (rest, _) = if u8::is_ascii_digit(&rest[0]) { + let (rest, seconds) = take(2_usize)(rest)?; + string.push_str(core::str::from_utf8(seconds).unwrap()).unwrap(); + (rest, seconds) + } else { + string.push_str("00").unwrap(); + // The second parameter will not be used anymore + (rest, rest) + }; + match rest[0] as char { + 'Z' => { + string.push_str("+0000") + }, + _ => { + string.push_str(core::str::from_utf8(rest).unwrap()) + } + }; + + Ok(( + &[], + DateTime::parse_from_str( + &string, "%Y%m%d%H%M%S%z" + ).unwrap() + )) +} + +// Parser for GeneralizedTime +pub fn parse_asn1_der_generalized_time(bytes: &[u8]) -> IResult<&[u8], DateTime> { + + // Buffer for building string + let mut string: String = String::new(); + + // Find the first non-digit byte + let mut first_non_digit_index = 0; + while first_non_digit_index < bytes.len() { + if !u8::is_ascii_digit(&bytes[first_non_digit_index]) { + break; + } + first_non_digit_index += 1; + } + + string.push_str(core::str::from_utf8( + &bytes[..first_non_digit_index]).unwrap() + ).unwrap(); + + match first_non_digit_index { + 10 => string.push_str("0000.000").unwrap(), + 12 => string.push_str("00.000").unwrap(), + 14 => string.push_str(".000").unwrap(), + 18 => {}, + _ => return Err(nom::Err::Failure((&[], ErrorKind::Verify))) + }; + + match bytes.len() - first_non_digit_index { + // Local time, without relative time diff to UTC time + // Assume UTC + 0 | 1 => string.push_str("+0000").unwrap(), + 5 => string.push_str(core::str::from_utf8( + &bytes[first_non_digit_index..]).unwrap() + ).unwrap(), + _ => return Err(nom::Err::Failure((&[], ErrorKind::Verify))) + }; + + Ok(( + &[], + DateTime::parse_from_str( + &string, "%Y%m%d%H%M%S%.3f%z" + ).unwrap() + )) +} + // Parser for SubjectKeyPublicInfo (Sequence: 0x30) pub fn parse_asn1_der_subject_key_public_info(bytes: &[u8]) -> IResult<&[u8], Asn1DerSubjectPublicKeyInfo> { let (rest, (tag_val, _, value)) = parse_asn1_der_object(bytes)?; @@ -1004,12 +1104,18 @@ pub fn parse_asn1_der_extension(bytes: &[u8]) -> IResult<&[u8], Asn1DerExtension parse_asn1_der_subject_alternative_name )(rem_ext_data)?; extension_value - } + }, oid::CERT_NAME_CONSTRAINTS => { let (_, extension_value) = complete( parse_asn1_der_name_constraints )(rem_ext_data)?; extension_value + }, + oid::CERT_POLICY_CONSTRAINTS => { + let (_, extension_value) = complete( + parse_asn1_der_policy_constraints + )(rem_ext_data)?; + extension_value } // TODO: Parse extension value for recognized extensions _ => Asn1DerExtensionValue::Unrecognized @@ -1224,6 +1330,63 @@ pub fn parse_asn1_der_name_constraints(bytes: &[u8]) -> IResult<&[u8], Asn1DerEx )) } +// Parser for policy constraints +pub fn parse_asn1_der_policy_constraints(bytes: &[u8]) -> IResult<&[u8], Asn1DerExtensionValue> { + // Strip sequence + let (_, constraint_seq) = complete( + parse_asn1_der_sequence + )(bytes)?; + + // Init policy constraints + let mut require_explicit_policy = None; + let mut inhibit_policy_mapping = None; + + let (rest, (mut tag_val, _, mut policy)) = parse_asn1_der_object(constraint_seq)?; + if tag_val == 0x80 { + let temp = if policy.len() > 1 { + // The maximum acceptable cert chain length would probably be less than 10 + 128 + } else { + policy[0] + }; + require_explicit_policy.replace(temp); + + if rest.len() == 0 { + return Ok(( + &[], + Asn1DerExtensionValue::PolicyConstraints { + require_explicit_policy, + inhibit_policy_mapping + } + )) + } + + let (_, (second_tag_val, _, second_policy)) = complete( + parse_asn1_der_object + )(rest)?; + tag_val = second_tag_val; + policy = second_policy; + } + + if tag_val == 0x81 { + let temp = if policy.len() > 1 { + // The maximum acceptable cert chain length would probably be less than 10 + 128 + } else { + policy[0] + }; + inhibit_policy_mapping.replace(temp); + } + + Ok(( + &[], + Asn1DerExtensionValue::PolicyConstraints { + require_explicit_policy, + inhibit_policy_mapping + } + )) +} + // Parser for CertificatePolicies Extension (sequence: 0x30) pub fn parse_asn1_der_certificate_policies(bytes: &[u8]) -> IResult<&[u8], Asn1DerExtensionValue> { let (rest, (tag_val, _, mut value)) = parse_asn1_der_object(bytes)?;