wire_e2e_identity/validation/
mod.rs1use 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 #[error(transparent)]
27 DerError(#[from] x509_cert::der::Error),
28 #[error("PEM en/decoding error: {0}")]
30 PemError(#[from] x509_cert::der::pem::Error),
31 #[error("A lock has been poisoned and cannot be recovered from.")]
33 LockPoisonError,
34 #[error("Cannot determine current UNIX epoch")]
36 CannotDetermineCurrentTime,
37 #[error("Certificate validation error: {0}")]
39 CertValError(certval::Error),
40 #[error("Something went wrong, we cannot determine if this certificate is OK. You might want to ignore this")]
42 CannotDetermineVerificationStatus,
43 #[error("Required 'Subject Key Identifier' extension is missing")]
45 MissingSki,
46 #[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 Valid,
63 Expired,
65 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
85pub 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 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
135pub(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 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 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 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}