Skip to main content

x509_info/extensions/
locations.rs

1use crate::{
2    extensions::general_name, names::inspect_attribute, GeneralName, NameAttribute, OidNames,
3};
4use x509_parser::extensions as backend;
5
6/// Authority key identifier components, as asserted by the certificate.
7/// Matching these fields does not establish an issuer relationship or trust.
8#[derive(Clone, Debug, PartialEq, Eq)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize))]
10#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
11#[non_exhaustive]
12pub struct AuthorityKeyIdentifier {
13    /// Key identifier octets in lowercase hex, if present.
14    pub key_identifier_hex: Option<String>,
15    /// Issuer GeneralNames, if present, in encoded order.
16    pub authority_cert_issuer: Option<Vec<GeneralName>>,
17    /// Serial INTEGER content in lowercase hex, preserving sign padding.
18    pub authority_cert_serial_hex: Option<String>,
19}
20
21/// An AIA/SIA access method and its location. This library never retrieves it.
22#[derive(Clone, Debug, PartialEq, Eq)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize))]
24#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
25#[non_exhaustive]
26pub struct AccessDescription {
27    /// Dotted-decimal method OID, including unrecognized methods.
28    pub method_oid: String,
29    /// Presentation label from the caller's OID table, when available.
30    pub method_name: Option<String>,
31    /// Encoded access location; not necessarily a URI or safe to fetch.
32    pub location: GeneralName,
33}
34
35/// A CRL distribution-point name; relative RDNs are not resolved automatically.
36#[derive(Clone, Debug, PartialEq, Eq)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize))]
38#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
39#[cfg_attr(
40    feature = "serde",
41    serde(tag = "kind", content = "value", rename_all = "snake_case")
42)]
43#[non_exhaustive]
44pub enum DistributionPointName {
45    /// Full GeneralNames in encoded order.
46    FullName(Vec<GeneralName>),
47    /// One RDN relative to the CRL issuer, retaining all attributes.
48    RelativeName(Vec<NameAttribute>),
49}
50
51/// A CRL/freshest-CRL distribution point, without revocation processing or I/O.
52#[derive(Clone, Debug, PartialEq, Eq)]
53#[cfg_attr(feature = "serde", derive(serde::Serialize))]
54#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
55#[non_exhaustive]
56pub struct DistributionPoint {
57    /// Encoded distribution-point name, when present.
58    pub name: Option<DistributionPointName>,
59    /// ReasonFlags as a bit mask: bit 1 keyCompromise, 2 cACompromise,
60    /// 3 affiliationChanged, 4 superseded, 5 cessationOfOperation,
61    /// 6 certificateHold, 7 privilegeWithdrawn, 8 aACompromise; bit 0 is unused.
62    /// None means no restriction was encoded. No revocation policy is inferred.
63    pub reason_flags: Option<u16>,
64    /// CRL issuer GeneralNames, if present.
65    pub crl_issuer: Option<Vec<GeneralName>>,
66}
67
68pub(crate) fn access(
69    items: &[backend::AccessDescription<'_>],
70    names: &OidNames,
71) -> Vec<AccessDescription> {
72    items
73        .iter()
74        .map(|item| {
75            let method_oid = item.access_method.to_id_string();
76            AccessDescription {
77                method_name: names.get(&method_oid).map(str::to_owned),
78                method_oid,
79                location: general_name(&item.access_location, names),
80            }
81        })
82        .collect()
83}
84
85pub(crate) fn distribution(bytes: &[u8], names: &OidNames) -> Option<Vec<DistributionPoint>> {
86    use x509_cert::{
87        der::{Decode, Encode},
88        ext::pkix::{name::DistributionPointName as B, CrlDistributionPoints},
89    };
90    use x509_parser::prelude::FromDer;
91    let points = CrlDistributionPoints::from_der(bytes).ok()?;
92    if points.0.is_empty() {
93        return None;
94    }
95    points
96        .0
97        .iter()
98        .map(|p| {
99            let name = match p.distribution_point.as_ref() {
100                Some(B::FullName(list)) => {
101                    Some(DistributionPointName::FullName(crypto_names(list, names)?))
102                }
103                Some(B::NameRelativeToCRLIssuer(rdn)) => {
104                    let der = rdn.to_der().ok()?;
105                    let (rest, rdn) =
106                        x509_parser::x509::RelativeDistinguishedName::from_der(&der).ok()?;
107                    if !rest.is_empty() {
108                        return None;
109                    }
110                    Some(DistributionPointName::RelativeName(
111                        rdn.iter().map(|a| inspect_attribute(a, names)).collect(),
112                    ))
113                }
114                None => None,
115            };
116            Some(DistributionPoint {
117                name,
118                reason_flags: p.reasons.map(|r| r.bits()),
119                crl_issuer: match &p.crl_issuer {
120                    Some(list) => Some(crypto_names(list, names)?),
121                    None => None,
122                },
123            })
124        })
125        .collect()
126}
127
128fn crypto_names(
129    list: &[x509_cert::ext::pkix::name::GeneralName],
130    names: &OidNames,
131) -> Option<Vec<GeneralName>> {
132    use x509_cert::der::Encode;
133    use x509_parser::prelude::FromDer;
134    list.iter()
135        .map(|item| {
136            // Reuse the same GeneralName projection for both format libraries. Encoding
137            // canonical in-memory values avoids duplicating string and IP conversion rules.
138            let der = item.to_der().ok()?;
139            let (rest, name) = backend::GeneralName::from_der(&der).ok()?;
140            if !rest.is_empty() {
141                return None;
142            }
143            Some(general_name(&name, names))
144        })
145        .collect()
146}