1#![deny(rustdoc::broken_intra_doc_links)]
7#![deny(missing_docs)]
8#![allow(clippy::redundant_field_names)]
9#![forbid(unsafe_code)]
10
11use std::io::{Read, Seek};
12
13use der::asn1::{Any, ObjectIdentifier, OctetString, OctetStringRef, Uint};
14use der::{Decode, Enumerated, Sequence};
15use itertools::Itertools;
16use pkcs7::{ContentInfo, ContentType};
17#[cfg(feature = "serde")]
18use serde::ser::SerializeStruct;
19#[cfg(feature = "serde")]
20use serde::{ser, Serialize};
21use spki::AlgorithmIdentifier;
22use thiserror::Error;
23use x509_cert::attr::Attributes;
24use x509_cert::ext::pkix::ExtendedKeyUsage;
25use x509_cert::time::Time;
26
27pub const MS_CERT_TRUST_LIST_OID: ObjectIdentifier =
29 ObjectIdentifier::new_unwrap("1.3.6.1.4.1.311.10.1");
30
31pub const MS_CERT_PROP_ID_METAEKUS_OID: ObjectIdentifier =
33 ObjectIdentifier::new_unwrap("1.3.6.1.4.1.311.10.11.9");
34
35#[derive(Debug, Error)]
37pub enum CtlError {
38 #[error("I/O error")]
40 Io(#[from] std::io::Error),
41
42 #[error("bad DER encoding")]
44 Der(#[from] der::Error),
45
46 #[error("bad PKCS#7 content-type: expected SignedData, got {0:?}")]
48 ContentType(ContentType),
49
50 #[error("missing SignedData encapsulated content")]
52 MissingSignedData,
53
54 #[error("bad SignedData ContentType: expected {MS_CERT_TRUST_LIST_OID}, got {0}")]
56 Content(ObjectIdentifier),
57
58 #[error("missing SignedData inner content")]
60 MissingSignedDataContent,
61}
62
63pub type SubjectIdentifier = OctetString;
67
68pub type MetaEku = Vec<ObjectIdentifier>;
76
77#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
88pub struct TrustedSubject {
89 identifier: SubjectIdentifier,
90 pub attributes: Option<Attributes>,
92}
93
94impl TrustedSubject {
95 pub fn cert_id(&self) -> &[u8] {
97 self.identifier.as_bytes()
98 }
99
100 pub fn extended_key_usages(
103 &self,
104 ) -> impl Iterator<Item = Result<ObjectIdentifier, der::Error>> + '_ {
105 self.attributes
113 .iter()
114 .flat_map(|attrs| attrs.iter())
115 .filter(|attr| attr.oid == MS_CERT_PROP_ID_METAEKUS_OID)
116 .flat_map(|attr| attr.values.iter())
117 .flat_map(|value| {
118 value
119 .decode_as::<OctetStringRef>()
120 .map(|o| MetaEku::from_der(o.as_bytes()))
121 })
122 .flatten_ok()
123 }
124}
125
126#[cfg(feature = "serde")]
127impl Serialize for TrustedSubject {
128 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
129 where
130 S: serde::Serializer,
131 {
132 let eku_oids = self
133 .extended_key_usages()
134 .collect::<Result<Vec<_>, _>>()
135 .map_err(|e| ser::Error::custom(format!("EKU collection failed: {e}")))?
136 .iter()
137 .map(ToString::to_string)
138 .collect::<Vec<_>>();
139
140 let mut s = serializer.serialize_struct("TrustedSubject", 2)?;
141 s.serialize_field("identifier", &hex::encode(self.identifier.as_bytes()))?;
142 s.serialize_field("ekus", &eku_oids)?;
143 s.end()
144 }
145}
146
147#[derive(Clone, Debug, Copy, PartialEq, Eq, Enumerated)]
153#[asn1(type = "INTEGER")]
154#[repr(u8)]
155#[derive(Default)]
156pub enum CtlVersion {
157 #[default]
159 V1 = 0,
160}
161
162pub type SubjectUsage = ExtendedKeyUsage;
168
169pub type ListIdentifier = OctetString;
173
174pub type TrustedSubjects = Vec<TrustedSubject>;
178
179#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
199pub struct CertificateTrustList {
200 #[asn1(default = "Default::default")]
202 pub version: CtlVersion,
203
204 pub subject_usage: SubjectUsage,
206
207 pub list_identifier: Option<ListIdentifier>,
209
210 pub sequence_number: Option<Uint>,
212
213 pub this_update: Time,
217
218 pub next_update: Option<Time>,
220
221 pub subject_algorithm: AlgorithmIdentifier<Any>,
223
224 pub trusted_subjects: Option<TrustedSubjects>,
226
227 #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")]
231 pub ctl_extensions: Option<Any>,
232}
233
234impl CertificateTrustList {
235 pub fn from_der<R: Read + Seek>(mut source: R) -> Result<Self, CtlError> {
238 let mut der = vec![];
241 source.read_to_end(&mut der)?;
242
243 let body = ContentInfo::from_der(&der)?;
244 let signed_data = match body {
245 ContentInfo::SignedData(signed_data) => signed_data,
246 _ => return Err(CtlError::ContentType(body.content_type())),
247 };
248
249 if signed_data.encap_content_info.e_content_type != MS_CERT_TRUST_LIST_OID {
251 return Err(CtlError::Content(
252 signed_data.encap_content_info.e_content_type,
253 ));
254 }
255
256 let Some(content) = signed_data.encap_content_info.e_content else {
257 return Err(CtlError::MissingSignedDataContent);
258 };
259
260 Ok(content.decode_as()?)
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[test]
269 fn test_metaeku() {
270 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";
273 let res = MetaEku::from_der(metaeku).unwrap();
274
275 assert_eq!(res.len(), 3);
276 assert_eq!(res[0], ObjectIdentifier::new_unwrap("1.3.6.1.5.5.7.3.2"));
277 assert_eq!(res[1], ObjectIdentifier::new_unwrap("1.3.6.1.5.5.7.3.4"));
278 assert_eq!(res[2], ObjectIdentifier::new_unwrap("1.3.6.1.5.5.7.3.1"));
279 }
280}