diff --git a/src/xpi/signatures.rs b/src/xpi/signatures.rs index ba2932c..0998ab3 100644 --- a/src/xpi/signatures.rs +++ b/src/xpi/signatures.rs @@ -1,10 +1,11 @@ use super::cose_ish::CoseSign; use cms::cert::{ + x509, x509::{ attr::AttributeTypeAndValue, certificate::TbsCertificateInner, der::{ - asn1::{PrintableStringRef, TeletexStringRef, Utf8StringRef}, + asn1::{GeneralizedTime, PrintableStringRef, TeletexStringRef, UtcTime, Utf8StringRef}, Decode, Encode, Tag, Tagged, }, Certificate, @@ -17,16 +18,57 @@ use const_oid::db::{ rfc4519::{COMMON_NAME, ORGANIZATIONAL_UNIT_NAME}, rfc5912::{ID_SHA_1, ID_SHA_256}, }; -use serde::Serialize; +use serde::{Serialize, Serializer}; use std::convert::{From, TryInto}; -use std::{fmt, io, io::Read}; +use std::{fmt, io, io::Read, time::Duration}; use zip::ZipArchive; +#[derive(Debug, PartialEq)] +/// Represents a date in a certificate. +pub struct Date(x509::time::Time); + +impl Date { + pub fn utc_time_from_duration(duration: Duration) -> Self { + Date(x509::time::Time::from( + UtcTime::from_unix_duration(duration).expect("failed to make UtcTime"), + )) + } + + pub fn generalized_time_from_duration(duration: Duration) -> Self { + Date(x509::time::Time::from( + GeneralizedTime::from_unix_duration(duration).expect("failed to make GeneralizedTime"), + )) + } +} + +impl Default for Date { + fn default() -> Self { + Date::generalized_time_from_duration(Duration::ZERO) + } +} + +impl Serialize for Date { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = format!("{}", self.0); + serializer.serialize_str(&s) + } +} + +impl fmt::Display for Date { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + #[derive(Default, Serialize)] /// Represents some of the information found in a certificate. pub struct CertificateInfo { pub common_name: String, pub organizational_unit: String, + pub end_date: Date, } impl CertificateInfo { @@ -73,6 +115,7 @@ impl From<&TbsCertificateInner> for CertificateInfo { CertificateInfo { common_name, organizational_unit, + end_date: Date(tbs_cert.validity.not_after), } } } @@ -81,8 +124,10 @@ impl fmt::Display for CertificateInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "Common Name (CN): {}\n Organizational Unit (OU): {}", - self.common_name, self.organizational_unit + "Common Name (CN): {}\n \ + Organizational Unit (OU): {}\n \ + End Date : {}", + self.common_name, self.organizational_unit, self.end_date ) } } diff --git a/tests/fixtures/line-staging-cas-cur.xpi b/tests/fixtures/line-staging-cas-cur.xpi new file mode 100644 index 0000000..f09bcd9 Binary files /dev/null and b/tests/fixtures/line-staging-cas-cur.xpi differ diff --git a/tests/xpi_test.rs b/tests/xpi_test.rs index 90d9352..3bdfa60 100644 --- a/tests/xpi_test.rs +++ b/tests/xpi_test.rs @@ -1,5 +1,6 @@ use std::io::Cursor; -use xpidump::{RecommendationState, Signature, SignatureKind, XPI}; +use std::time::Duration; +use xpidump::{Date, RecommendationState, Signature, SignatureKind, XPI}; use zip::ZipArchive; fn assert_signature(signature: &Signature, kind: SignatureKind, is_staging: bool, algorithm: &str) { @@ -45,7 +46,16 @@ fn test_prod_regular_addon() { false, "SHA-1", ); + assert_eq!( + Date::utc_time_from_duration(Duration::from_secs(1743724800)), + xpi.signatures.pkcs7.certificates[0].end_date + ); + assert_signature(&xpi.signatures.cose, SignatureKind::Regular, false, "ES256"); + assert_eq!( + Date::utc_time_from_duration(Duration::from_secs(1743724800)), + xpi.signatures.cose.certificates[0].end_date + ); } #[test] @@ -72,6 +82,10 @@ fn test_prod_old_regular_addon() { "{6AC85730-7D0F-4de0-B3FA-21142DD85326}", xpi.signatures.pkcs7.certificates[1].common_name ); + assert_eq!( + Date::utc_time_from_duration(Duration::from_secs(1741996362)), + xpi.signatures.pkcs7.certificates[0].end_date + ); assert!(!xpi.signatures.cose.exists()); } @@ -229,3 +243,43 @@ fn test_unsigned_addon() { assert!(!xpi.is_recommended()); assert!(!xpi.signatures.has_signatures()); } + +#[test] +fn test_staging_line_extension() { + let bytes = include_bytes!("fixtures/line-staging-cas-cur.xpi"); + let reader = Cursor::new(bytes); + let mut archive = ZipArchive::new(reader).unwrap(); + + let xpi = XPI::new(&mut archive); + + assert!(xpi.manifest.exists()); + assert!(xpi.is_recommended()); + assert_eq!( + "{0cdc308b-4c2a-497d-916a-164d602ed358}", + xpi.manifest.id.expect("expect add-on ID") + ); + assert_eq!( + "109.2", + xpi.manifest.version.expect("expect add-on version") + ); + + assert_signature(&xpi.signatures.pkcs7, SignatureKind::Regular, true, "SHA-1"); + assert_eq!( + Date::utc_time_from_duration(Duration::from_secs(1741910400)), + xpi.signatures.pkcs7.certificates[0].end_date + ); + assert_eq!( + Date::utc_time_from_duration(Duration::from_secs(2026818980)), + xpi.signatures.pkcs7.certificates[1].end_date + ); + + assert_signature(&xpi.signatures.cose, SignatureKind::Regular, true, "ES256"); + assert_eq!( + Date::utc_time_from_duration(Duration::from_secs(1741910400)), + xpi.signatures.cose.certificates[0].end_date + ); + assert_eq!( + Date::utc_time_from_duration(Duration::from_secs(2026818980)), + xpi.signatures.cose.certificates[1].end_date + ); +}