diff --git a/src/data_model/common.rs b/src/data_model/common.rs index b555d13..3cc185c 100644 --- a/src/data_model/common.rs +++ b/src/data_model/common.rs @@ -1,6 +1,8 @@ use x509_parser::asn1_rs::Tag; use x509_parser::x509::AlgorithmIdentifier; +pub type UtcTime = time::OffsetDateTime; + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Asn1TimeEncoding { UtcTime, @@ -9,7 +11,7 @@ pub enum Asn1TimeEncoding { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Asn1TimeUtc { - pub utc: time::OffsetDateTime, + pub utc: UtcTime, pub encoding: Asn1TimeEncoding, } diff --git a/src/data_model/manifest.rs b/src/data_model/manifest.rs index 9b992e4..2daf619 100644 --- a/src/data_model/manifest.rs +++ b/src/data_model/manifest.rs @@ -1,4 +1,4 @@ -use crate::data_model::common::BigUnsigned; +use crate::data_model::common::{BigUnsigned, UtcTime}; use crate::data_model::oid::{OID_CT_RPKI_MANIFEST, OID_SHA256}; use crate::data_model::rc::ResourceCertificate; use crate::data_model::signed_object::{ @@ -6,7 +6,6 @@ use crate::data_model::signed_object::{ }; use der_parser::ber::BerObjectContent; use der_parser::der::{DerObject, Tag, parse_der}; -use time::OffsetDateTime; #[derive(Clone, Debug, PartialEq, Eq)] pub struct ManifestObject { @@ -26,8 +25,8 @@ pub struct ManifestObjectParsed { pub struct ManifestEContent { pub version: u32, pub manifest_number: BigUnsigned, - pub this_update: OffsetDateTime, - pub next_update: OffsetDateTime, + pub this_update: UtcTime, + pub next_update: UtcTime, pub file_hash_alg: String, pub files: Vec, } @@ -378,7 +377,7 @@ fn parse_manifest_number(obj: &DerObject<'_>) -> Result, err: ManifestProfileError, -) -> Result { +) -> Result { match &obj.content { BerObjectContent::GeneralizedTime(dt) => dt .to_datetime() diff --git a/src/data_model/rc.rs b/src/data_model/rc.rs index 6d8cdbf..1306704 100644 --- a/src/data_model/rc.rs +++ b/src/data_model/rc.rs @@ -1,12 +1,14 @@ use der_parser::ber::{BerObjectContent, Class}; use der_parser::der::{DerObject, Tag, parse_der}; use der_parser::num_bigint::BigUint; -use time::OffsetDateTime; use url::Url; use x509_parser::asn1_rs::{Class as Asn1Class, Tag as Asn1Tag}; use x509_parser::extensions::ParsedExtension; use x509_parser::prelude::{FromDer, X509Certificate, X509Extension, X509Version}; +use crate::data_model::common::{ + Asn1TimeUtc, InvalidTimeEncodingError, UtcTime, asn1_time_to_model, +}; use crate::data_model::oid::{ OID_AD_SIGNED_OBJECT, OID_AUTONOMOUS_SYS_IDS, OID_CP_IPADDR_ASNUMBER, OID_IP_ADDR_BLOCKS, OID_SHA256_WITH_RSA_ENCRYPTION, OID_SUBJECT_INFO_ACCESS, OID_SUBJECT_KEY_IDENTIFIER, @@ -41,8 +43,8 @@ pub struct RpkixTbsCertificate { pub signature_algorithm: String, pub issuer_dn: String, pub subject_dn: String, - pub validity_not_before: OffsetDateTime, - pub validity_not_after: OffsetDateTime, + pub validity_not_before: UtcTime, + pub validity_not_after: UtcTime, /// DER encoding of SubjectPublicKeyInfo. pub subject_public_key_info: Vec, pub extensions: RcExtensions, @@ -68,8 +70,8 @@ pub struct ResourceCertificateParsed { pub tbs_signature_algorithm: AlgorithmIdentifierValue, pub issuer_dn: String, pub subject_dn: String, - pub validity_not_before: OffsetDateTime, - pub validity_not_after: OffsetDateTime, + pub validity_not_before: Asn1TimeUtc, + pub validity_not_after: Asn1TimeUtc, /// DER encoding of SubjectPublicKeyInfo. pub subject_public_key_info: Vec, pub extensions: RcExtensionsParsed, @@ -326,6 +328,9 @@ pub enum ResourceCertificateParseError { #[derive(Debug, thiserror::Error)] pub enum ResourceCertificateProfileError { + #[error("{0}")] + InvalidTimeEncoding(#[from] InvalidTimeEncodingError), + #[error("certificate version must be v3 (RFC 5280 §4.1; RFC 6487 §4)")] InvalidVersion, @@ -394,8 +399,8 @@ impl ResourceCertificate { return Err(ResourceCertificateParseError::TrailingBytes(rem.len())); } - let validity_not_before = cert.validity().not_before.to_datetime(); - let validity_not_after = cert.validity().not_after.to_datetime(); + let validity_not_before = asn1_time_to_model(cert.validity().not_before); + let validity_not_after = asn1_time_to_model(cert.validity().not_after); let subject_public_key_info = cert.tbs_certificate.subject_pki.raw.to_vec(); @@ -444,6 +449,11 @@ impl ResourceCertificateParsed { _ => return Err(ResourceCertificateProfileError::InvalidVersion), }; + self.validity_not_before + .validate_encoding_rfc5280("notBefore")?; + self.validity_not_after + .validate_encoding_rfc5280("notAfter")?; + if self.signature_algorithm != self.tbs_signature_algorithm { return Err(ResourceCertificateProfileError::SignatureAlgorithmMismatch); } @@ -469,8 +479,8 @@ impl ResourceCertificateParsed { signature_algorithm: self.signature_algorithm.oid, issuer_dn: self.issuer_dn, subject_dn: self.subject_dn, - validity_not_before: self.validity_not_before, - validity_not_after: self.validity_not_after, + validity_not_before: self.validity_not_before.utc, + validity_not_after: self.validity_not_after.utc, subject_public_key_info: self.subject_public_key_info, extensions, }, diff --git a/tests/test_rc_time_encoding.rs b/tests/test_rc_time_encoding.rs new file mode 100644 index 0000000..5809c79 --- /dev/null +++ b/tests/test_rc_time_encoding.rs @@ -0,0 +1,24 @@ +use rpki::data_model::common::Asn1TimeEncoding; +use rpki::data_model::rc::{ResourceCertificate, ResourceCertificateProfileError}; + +#[test] +fn rc_validity_time_encoding_is_validated() { + let der = std::fs::read( + "tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer", + ) + .expect("read RC fixture"); + + let mut parsed = ResourceCertificate::parse_der(&der).expect("parse RC fixture"); + parsed.validity_not_before.encoding = match parsed.validity_not_before.encoding { + Asn1TimeEncoding::UtcTime => Asn1TimeEncoding::GeneralizedTime, + Asn1TimeEncoding::GeneralizedTime => Asn1TimeEncoding::UtcTime, + }; + + match parsed.validate_profile() { + Ok(_rc) => panic!("expected time-encoding validation error"), + Err(ResourceCertificateProfileError::InvalidTimeEncoding(e)) => { + assert_eq!(e.field, "notBefore"); + } + Err(e) => panic!("unexpected error: {e}"), + } +}