#![deny(rustdoc::broken_intra_doc_links)]
#![deny(missing_docs)]
#![allow(clippy::redundant_field_names)]
#![forbid(unsafe_code)]
use std::io::{Read, Seek};
use der::asn1::{Any, ObjectIdentifier, OctetString, OctetStringRef, Uint};
use der::{Decode, Enumerated, Sequence};
use itertools::Itertools;
use pkcs7::{ContentInfo, ContentType};
#[cfg(feature = "serde")]
use serde::ser::SerializeStruct;
#[cfg(feature = "serde")]
use serde::{ser, Serialize};
use spki::AlgorithmIdentifier;
use thiserror::Error;
use x509_cert::attr::Attributes;
use x509_cert::ext::pkix::ExtendedKeyUsage;
use x509_cert::time::Time;
pub const MS_CERT_TRUST_LIST_OID: ObjectIdentifier =
ObjectIdentifier::new_unwrap("1.3.6.1.4.1.311.10.1");
pub const MS_CERT_PROP_ID_METAEKUS_OID: ObjectIdentifier =
ObjectIdentifier::new_unwrap("1.3.6.1.4.1.311.10.11.9");
#[derive(Debug, Error)]
pub enum CtlError {
#[error("I/O error")]
Io(#[from] std::io::Error),
#[error("bad DER encoding")]
Der(#[from] der::Error),
#[error("bad PKCS#7 content-type: expected SignedData, got {0:?}")]
ContentType(ContentType),
#[error("missing SignedData encapsulated content")]
MissingSignedData,
#[error("bad SignedData ContentType: expected {MS_CERT_TRUST_LIST_OID}, got {0}")]
Content(ObjectIdentifier),
#[error("missing SignedData inner content")]
MissingSignedDataContent,
}
pub type SubjectIdentifier = OctetString;
pub type MetaEku = Vec<ObjectIdentifier>;
#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
pub struct TrustedSubject {
identifier: SubjectIdentifier,
pub attributes: Option<Attributes>,
}
impl TrustedSubject {
pub fn cert_id(&self) -> &[u8] {
self.identifier.as_bytes()
}
pub fn extended_key_usages(
&self,
) -> impl Iterator<Item = Result<ObjectIdentifier, der::Error>> + '_ {
self.attributes
.iter()
.flat_map(|attrs| attrs.iter())
.filter(|attr| attr.oid == MS_CERT_PROP_ID_METAEKUS_OID)
.flat_map(|attr| attr.values.iter())
.flat_map(|value| {
value
.decode_as::<OctetStringRef>()
.map(|o| MetaEku::from_der(o.as_bytes()))
})
.flatten_ok()
}
}
#[cfg(feature = "serde")]
impl Serialize for TrustedSubject {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let eku_oids = self
.extended_key_usages()
.collect::<Result<Vec<_>, _>>()
.map_err(|e| ser::Error::custom(format!("EKU collection failed: {e}")))?
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>();
let mut s = serializer.serialize_struct("TrustedSubject", 2)?;
s.serialize_field("identifier", &hex::encode(self.identifier.as_bytes()))?;
s.serialize_field("ekus", &eku_oids)?;
s.end()
}
}
#[derive(Clone, Debug, Copy, PartialEq, Eq, Enumerated)]
#[asn1(type = "INTEGER")]
#[repr(u8)]
#[derive(Default)]
pub enum CtlVersion {
#[default]
V1 = 0,
}
pub type SubjectUsage = ExtendedKeyUsage;
pub type ListIdentifier = OctetString;
pub type TrustedSubjects = Vec<TrustedSubject>;
#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
pub struct CertificateTrustList {
#[asn1(default = "Default::default")]
pub version: CtlVersion,
pub subject_usage: SubjectUsage,
pub list_identifier: Option<ListIdentifier>,
pub sequence_number: Option<Uint>,
pub this_update: Time,
pub next_update: Option<Time>,
pub subject_algorithm: AlgorithmIdentifier<Any>,
pub trusted_subjects: Option<TrustedSubjects>,
#[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")]
pub ctl_extensions: Option<Any>,
}
impl CertificateTrustList {
pub fn from_der<R: Read + Seek>(mut source: R) -> Result<Self, CtlError> {
let mut der = vec![];
source.read_to_end(&mut der)?;
let body = ContentInfo::from_der(&der)?;
let signed_data = match body {
ContentInfo::SignedData(signed_data) => signed_data,
_ => return Err(CtlError::ContentType(body.content_type())),
};
if signed_data.encap_content_info.e_content_type != MS_CERT_TRUST_LIST_OID {
return Err(CtlError::Content(
signed_data.encap_content_info.e_content_type,
));
}
let Some(content) = signed_data.encap_content_info.e_content else {
return Err(CtlError::MissingSignedDataContent);
};
Ok(content.decode_as()?)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_metaeku() {
let metaeku = b"\x30\x1E\x06\x08\x2B\x06\x01\x05\x05\x07\x03\x02\x06\x08\x2B\x06\x01\x05\x05\x07\x03\x04\x06\x08\x2B\x06\x01\x05\x05\x07\x03\x01";
let res = MetaEku::from_der(metaeku).unwrap();
assert_eq!(res.len(), 3);
assert_eq!(res[0], ObjectIdentifier::new_unwrap("1.3.6.1.5.5.7.3.2"));
assert_eq!(res[1], ObjectIdentifier::new_unwrap("1.3.6.1.5.5.7.3.4"));
assert_eq!(res[2], ObjectIdentifier::new_unwrap("1.3.6.1.5.5.7.3.1"));
}
}