Skip to main content

wire_e2e_identity/validation/
mod.rs

1use std::time::Duration;
2
3use certval::{
4    CertSource, CertVector, CertificationPath, CertificationPathResults, CertificationPathSettings, DeferDecodeSigned,
5    EXTS_OF_INTEREST, ExtensionProcessing as _, PDVTrustAnchorChoice, PathValidationStatus, TaSource, TimeOfInterest,
6    check_revocation, validate_path_rfc5280,
7    validator::{PDVCertificate, path_validator::check_validity},
8    verify_signatures,
9};
10use const_oid::AssociatedOid;
11use x509_cert::{
12    certificate::Raw,
13    der::{Decode, Encode},
14};
15
16mod cache;
17mod crl_store;
18mod misc;
19
20use cache::RevocationCache;
21use crl_store::CrlStore;
22
23#[derive(Debug, thiserror::Error)]
24pub enum Error {
25    /// Failed mapping a DER certificate
26    #[error(transparent)]
27    DerError(#[from] x509_cert::der::Error),
28    /// PEM de/serialization error
29    #[error("PEM en/decoding error: {0}")]
30    PemError(#[from] x509_cert::der::pem::Error),
31    /// Poisoned lock error
32    #[error("A lock has been poisoned and cannot be recovered from.")]
33    LockPoisonError,
34    /// Error for when the current UNIX epoch time cannot be determined.
35    #[error("Cannot determine current UNIX epoch")]
36    CannotDetermineCurrentTime,
37    /// Certificate / revocation validation error
38    #[error("Certificate validation error: {0}")]
39    CertValError(certval::Error),
40    /// Error when we have no idea what the cert status is
41    #[error("Something went wrong, we cannot determine if this certificate is OK. You might want to ignore this")]
42    CannotDetermineVerificationStatus,
43    /// Required 'Subject Key Identifier' extension is missing
44    #[error("Required 'Subject Key Identifier' extension is missing")]
45    MissingSki,
46    /// Implementation error
47    #[error("Implementation error")]
48    ImplementationError,
49}
50
51impl From<certval::Error> for Error {
52    fn from(value: certval::Error) -> Self {
53        Error::CertValError(value)
54    }
55}
56
57pub type Result<T> = core::result::Result<T, Error>;
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum IdentityStatus {
61    /// All is fine
62    Valid,
63    /// The Certificate is expired
64    Expired,
65    /// The Certificate is revoked
66    Revoked,
67}
68
69impl IdentityStatus {
70    pub async fn from_cert(cert: &x509_cert::Certificate, env: &crate::pki_env::PkiEnvironment) -> Self {
71        match env.validate_cert(cert).await {
72            Err(Error::CertValError(certval::Error::PathValidation(e))) => match e {
73                PathValidationStatus::InvalidNotAfterDate => IdentityStatus::Expired,
74                PathValidationStatus::CertificateRevoked
75                | PathValidationStatus::CertificateRevokedEndEntity
76                | PathValidationStatus::NoPathsFound
77                | PathValidationStatus::CertificateRevokedIntermediateCa => IdentityStatus::Revoked,
78                _ => IdentityStatus::Valid,
79            },
80            _ => IdentityStatus::Valid,
81        }
82    }
83}
84
85/// Extracts the CRL Distribution points that are FullName URIs from the Certificate
86pub fn extract_crl_uris(cert: &x509_cert::Certificate) -> Result<Option<std::collections::HashSet<String>>> {
87    use certval::validator::{PDVCertificate, PDVExtension};
88    use x509_cert::ext::pkix::name::{DistributionPointName, GeneralName};
89
90    Ok(PDVCertificate::try_from(cert.clone())?
91        .get_extension(&const_oid::db::rfc5280::ID_CE_CRL_DISTRIBUTION_POINTS)?
92        .and_then(|ext| {
93            let PDVExtension::CrlDistributionPoints(crl_distribution_points) = ext else {
94                return None;
95            };
96
97            Some(crl_distribution_points.0.iter().fold(
98                Default::default(),
99                |mut set: std::collections::HashSet<String>, dp| {
100                    if let Some(DistributionPointName::FullName(dp_full_names)) = dp.distribution_point.as_ref() {
101                        for gn in dp_full_names.iter() {
102                            if let GeneralName::UniformResourceIdentifier(uri) = gn {
103                                set.insert(uri.to_string());
104                            }
105                        }
106                    }
107
108                    set
109                },
110            ))
111        }))
112}
113
114fn check_cpr(cpr: CertificationPathResults) -> Result<()> {
115    if let Some(validation_status) = cpr.get_validation_status() {
116        match validation_status {
117            certval::PathValidationStatus::Valid => Ok(()),
118            // No CRL is available, this is fine
119            certval::PathValidationStatus::RevocationStatusNotDetermined
120            | certval::PathValidationStatus::RevocationStatusNotAvailable => Ok(()),
121            validation_status => Err(Error::CertValError(certval::Error::PathValidation(validation_status))),
122        }
123    } else {
124        Err(Error::CannotDetermineVerificationStatus)
125    }
126}
127
128pub(crate) fn now() -> Result<u64> {
129    Ok(web_time::SystemTime::now()
130        .duration_since(web_time::SystemTime::UNIX_EPOCH)
131        .map_err(|_| Error::CannotDetermineCurrentTime)?
132        .as_secs())
133}
134
135/// Initializes a certval PkiEnvironment using the provided params
136pub(crate) fn prepare_environment(
137    trust_roots: &[x509_cert::anchor::TrustAnchorChoice],
138    intermediates: &[x509_cert::Certificate],
139    crls: &[x509_cert::crl::CertificateList<Raw>],
140) -> Result<certval::environment::PkiEnvironment> {
141    let toi = TimeOfInterest::from_unix_secs(now()?)?;
142
143    let mut cps = CertificationPathSettings::new();
144    cps.set_time_of_interest(toi);
145
146    // Make a Certificate source for intermediate CA certs
147    let mut cert_source = CertSource::new();
148    for (i, cert) in intermediates.iter().enumerate() {
149        cert_source.push(certval::CertFile {
150            filename: format!("Intermediate CA #{i} [{}]", cert.tbs_certificate().subject()),
151            bytes: cert.to_der()?,
152        });
153    }
154
155    cert_source.initialize(&cps)?;
156
157    // Make a TrustAnchor source
158    let mut trust_anchors = TaSource::new();
159    for (i, root) in trust_roots.iter().enumerate() {
160        trust_anchors.push(certval::CertFile {
161            filename: format!("TrustAnchor #{i}"),
162            bytes: root.to_der()?,
163        });
164    }
165
166    trust_anchors.initialize()?;
167
168    let revocation_cache = RevocationCache::default();
169
170    // Make a CRL source
171    let crl_source = CrlStore::from(crls);
172    crl_source.index_crls(toi)?;
173
174    let mut pe = certval::environment::PkiEnvironment::default();
175    pe.populate_5280_pki_environment();
176    pe.add_trust_anchor_source(Box::new(trust_anchors));
177    pe.add_crl_source(Box::new(crl_source));
178    pe.add_revocation_cache(Box::new(revocation_cache));
179
180    cert_source.find_all_partial_paths(&pe, &cps);
181
182    pe.add_certificate_source(Box::new(cert_source));
183    Ok(pe)
184}
185
186pub(crate) fn validate_trust_anchor_cert(
187    pe: &certval::environment::PkiEnvironment,
188    cert: &x509_cert::Certificate,
189) -> Result<()> {
190    let toi = TimeOfInterest::from_unix_secs(now()?)?;
191
192    let mut cps = CertificationPathSettings::default();
193    cps.set_time_of_interest(toi);
194
195    let mut cert = PDVCertificate::try_from(cert.clone())?;
196    cert.parse_extensions(EXTS_OF_INTEREST);
197
198    let ta = PDVTrustAnchorChoice::try_from(x509_cert::anchor::TrustAnchorChoice::Certificate(
199        cert.decoded().clone(),
200    ))?;
201    let mut certification_path = CertificationPath::new(ta, vec![], cert);
202
203    check_validity(pe, &cps, &mut certification_path, &mut CertificationPathResults::new())?;
204    verify_signatures(pe, &cps, &mut certification_path, &mut CertificationPathResults::new())?;
205
206    Ok(())
207}
208
209pub(crate) fn validate_cert(
210    pe: &certval::environment::PkiEnvironment,
211    end_identity_cert: &x509_cert::Certificate,
212    perform_revocation_check: bool,
213) -> Result<()> {
214    let toi = TimeOfInterest::from_unix_secs(now()?)?;
215
216    let mut cps = CertificationPathSettings::default();
217    cps.set_time_of_interest(toi);
218    cps.set_require_ta_store(true);
219    cps.set_forbid_self_signed_ee(true);
220
221    let mut end_identity_cert = PDVCertificate::try_from(end_identity_cert.clone())?;
222    end_identity_cert.parse_extensions(EXTS_OF_INTEREST);
223
224    let mut paths = vec![];
225    pe.get_paths_for_target(&end_identity_cert, &mut paths, 0, toi)?;
226
227    if paths.is_empty() {
228        return Err(Error::CertValError(certval::Error::PathValidation(
229            certval::PathValidationStatus::NoPathsFound,
230        )));
231    }
232
233    let mut result = Ok(());
234
235    let any_path_validates = paths.into_iter().any(|mut path| {
236        let mut cpr = CertificationPathResults::new();
237        let _ = validate_path_rfc5280(pe, &cps, &mut path, &mut cpr);
238        let r = check_cpr(cpr);
239        if r.is_err() {
240            result = r;
241            return false;
242        }
243
244        if perform_revocation_check {
245            cps.set_check_crls(true);
246            cps.set_revocation_max_age(Duration::from_hours(24));
247            let mut cpr = CertificationPathResults::new();
248            let _ = check_revocation(pe, &cps, &mut path, &mut cpr);
249            let r = check_cpr(cpr);
250            if r.is_err() {
251                result = r;
252                return false;
253            }
254        }
255
256        true
257    });
258
259    if any_path_validates { Ok(()) } else { result }
260}
261
262pub(crate) fn validate_crl(
263    pe: &certval::environment::PkiEnvironment,
264    crl_raw: &[u8],
265) -> Result<x509_cert::crl::CertificateList<Raw>> {
266    let crl = x509_cert::crl::CertificateList::from_der(crl_raw)?;
267
268    let mut spki_list = vec![];
269    if let Some(aki) = crl.tbs_cert_list.crl_extensions.as_ref().and_then(|extensions| {
270        extensions
271            .iter()
272            .find(|ext| ext.extn_id == x509_cert::ext::pkix::AuthorityKeyIdentifier::OID)
273    }) {
274        let akid = aki.extn_value.as_bytes();
275        if let Ok(ta) = pe.get_trust_anchor(akid) {
276            spki_list.push(certval::source::ta_source::get_subject_public_key_info_from_trust_anchor(&ta.decoded_ta));
277        } else if let Ok(intermediates) = pe.get_intermediates_by_skid(akid) {
278            spki_list.extend(
279                intermediates
280                    .into_iter()
281                    .map(|c| c.decoded().tbs_certificate().subject_public_key_info()),
282            );
283        }
284    }
285
286    if let Ok(ta) = pe.get_trust_anchor_by_name(&crl.tbs_cert_list.issuer) {
287        let spki = certval::source::ta_source::get_subject_public_key_info_from_trust_anchor(&ta.decoded_ta);
288        if !spki_list.contains(&spki) {
289            spki_list.push(spki);
290        }
291    }
292
293    spki_list.extend(
294        pe.get_cert_by_name(&crl.tbs_cert_list.issuer)
295            .into_iter()
296            .map(|c| c.decoded().tbs_certificate().subject_public_key_info()),
297    );
298
299    spki_list.dedup();
300
301    let crl_defer = DeferDecodeSigned::from_der(crl_raw)?;
302
303    let any_spki_verifies = spki_list.into_iter().any(|spki| {
304        pe.verify_signature_message(
305            pe,
306            &crl_defer.tbs_field,
307            crl.signature.raw_bytes(),
308            &crl.signature_algorithm,
309            spki,
310        )
311        .is_ok()
312    });
313
314    if any_spki_verifies {
315        Ok(crl)
316    } else {
317        Err(Error::CertValError(certval::Error::PathValidation(
318            certval::PathValidationStatus::SignatureVerificationFailure,
319        )))
320    }
321}