wire_e2e_identity/pki_env/
mod.rs1mod crl;
4pub mod hooks;
5
6#[cfg(test)]
7mod dummy;
8
9use std::{collections::HashSet, sync::Arc};
10
11use async_lock::Mutex;
12use certval::{
13 CertSource, CertVector as _, CertificationPathSettings, Error as CertvalError, PathValidationStatus, TaSource,
14};
15use core_crypto_keystore::{
16 CryptoKeystoreError, Database,
17 entities::{E2eiAcmeCA, E2eiCrl, E2eiIntermediateCert},
18 traits::{FetchFromDatabase, UniqueEntity},
19};
20use openmls_traits::authentication_service::{CredentialAuthenticationStatus, CredentialRef};
21use x509_cert::{
22 Certificate,
23 anchor::TrustAnchorChoice,
24 der::{Decode as _, Encode as _},
25};
26
27use crate::{
28 pki_env::hooks::PkiEnvironmentHooks,
29 x509_check::{
30 RustyX509CheckError, RustyX509CheckResult, extract_crl_uris,
31 revocation::{PkiEnvironment as RjtPkiEnvironment, PkiEnvironmentParams, now},
32 },
33};
34
35pub type Result<T> = core::result::Result<T, Error>;
36
37#[derive(Debug, thiserror::Error)]
38pub enum Error {
39 #[error("The trust anchor certificate couldn't be loaded from the database.")]
40 NoTrustAnchor,
41 #[error("Failed to fetch CRL from '{uri}': HTTP {status}")]
42 CrlFetchUnsuccessful { uri: String, status: u16 },
43 #[error(transparent)]
44 HooksError(#[from] hooks::PkiEnvironmentHooksError),
45 #[error(transparent)]
46 X509Error(#[from] RustyX509CheckError),
47 #[error(transparent)]
48 UrlError(#[from] url::ParseError),
49 #[error(transparent)]
50 JsonError(#[from] serde_json::Error),
51 #[error(transparent)]
52 X509CertDerError(#[from] x509_cert::der::Error),
53 #[error(transparent)]
54 KeystoreError(#[from] core_crypto_keystore::CryptoKeystoreError),
55 #[error("certval error: {0}")]
56 Certval(certval::Error),
57}
58
59#[derive(Debug, Clone, derive_more::From, derive_more::Into, derive_more::Deref, derive_more::DerefMut)]
61pub struct NewCrlDistributionPoints(Option<HashSet<String>>);
62
63impl From<NewCrlDistributionPoints> for Option<Vec<String>> {
64 fn from(mut dp: NewCrlDistributionPoints) -> Self {
65 dp.take().map(|d| d.into_iter().collect())
66 }
67}
68
69impl IntoIterator for NewCrlDistributionPoints {
70 type Item = String;
71
72 type IntoIter = std::collections::hash_set::IntoIter<String>;
73
74 fn into_iter(self) -> Self::IntoIter {
75 let items = self.0.unwrap_or_default();
76 items.into_iter()
77 }
78}
79
80async fn restore_pki_env(data_provider: &impl FetchFromDatabase) -> Result<RjtPkiEnvironment> {
81 let mut trust_roots = vec![];
82 if let Ok(Some(ta_raw)) = data_provider.get_unique::<E2eiAcmeCA>().await {
83 trust_roots.push(
84 x509_cert::Certificate::from_der(&ta_raw.content).map(x509_cert::anchor::TrustAnchorChoice::Certificate)?,
85 );
86 }
87
88 let intermediates = data_provider
89 .load_all::<E2eiIntermediateCert>()
90 .await?
91 .into_iter()
92 .map(|inter| x509_cert::Certificate::from_der(&inter.content))
93 .collect::<core::result::Result<Vec<_>, _>>()?;
94
95 let crls = data_provider
96 .load_all::<E2eiCrl>()
97 .await?
98 .into_iter()
99 .map(|crl| x509_cert::crl::CertificateList::from_der(&crl.content))
100 .collect::<core::result::Result<Vec<_>, _>>()?;
101
102 let params = PkiEnvironmentParams {
103 trust_roots: &trust_roots,
104 intermediates: &intermediates,
105 crls: &crls,
106 };
107
108 Ok(RjtPkiEnvironment::init(params)?)
109}
110
111#[derive(Debug)]
113pub struct PkiEnvironment {
114 hooks: Arc<dyn PkiEnvironmentHooks>,
116 database: Arc<Database>,
118 rjt_pki_env: Mutex<RjtPkiEnvironment>,
119}
120
121impl PkiEnvironment {
122 pub async fn new(hooks: Arc<dyn PkiEnvironmentHooks>, database: Arc<Database>) -> Result<PkiEnvironment> {
124 let rjt_pki_env = restore_pki_env(&*database).await?;
125 Ok(Self {
126 hooks,
127 database,
128 rjt_pki_env: Mutex::new(rjt_pki_env),
129 })
130 }
131
132 pub async fn get_trust_anchors(&self) -> Vec<Certificate> {
134 self.rjt_pki_env
135 .lock()
136 .await
137 .get_trust_anchors()
138 .iter()
139 .filter_map(|choice| match choice.decoded_ta {
140 TrustAnchorChoice::Certificate(ref cert) => Some(cert.clone()),
141 _ => None,
142 })
143 .collect()
144 }
145
146 pub fn hooks(&self) -> Arc<dyn PkiEnvironmentHooks> {
148 self.hooks.clone()
149 }
150
151 pub fn database(&self) -> &Database {
153 &self.database
154 }
155
156 pub fn database_arc(&self) -> Arc<Database> {
160 self.database.clone()
161 }
162
163 async fn transactionally<T, E>(&self, operation: impl AsyncFnOnce() -> std::result::Result<T, E>) -> Result<T>
170 where
171 E: Into<Error>,
172 {
173 let created_transaction = match self.database.try_new_immediate_transaction().await {
174 Ok(()) => true,
175 Err(CryptoKeystoreError::TransactionInProgress) => false,
176 Err(err) => return Err(err.into()),
177 };
178 let operation_outcome = operation().await;
179 if created_transaction {
180 if operation_outcome.is_ok() {
181 self.database.commit_transaction().await?;
182 } else {
183 self.database.rollback_transaction().await?;
184 }
185 }
186 operation_outcome.map_err(Into::into)
187 }
188
189 pub async fn add_trust_anchor(&self, cert: Certificate) -> Result<()> {
199 self.rjt_pki_env.lock().await.validate_trust_anchor_cert(&cert)?;
201
202 let cert_data = E2eiAcmeCA {
205 content: cert.to_der()?,
206 };
207
208 self.transactionally(async || self.database.save(cert_data).await)
209 .await?;
210
211 let mut trust_anchors = TaSource::new();
212 trust_anchors.push(certval::CertFile {
213 filename: "".to_string(),
214 bytes: cert.to_der()?,
215 });
216 trust_anchors.initialize().map_err(Error::Certval)?;
217 self.rjt_pki_env
218 .lock()
219 .await
220 .add_trust_anchor_source(Box::new(trust_anchors));
221 Ok(())
222 }
223
224 pub async fn remove_trust_anchor(&self, serial_number: &[u8]) -> Result<()> {
229 let mut guard = self.rjt_pki_env.lock().await;
230
231 let certs: Vec<_> = guard
232 .get_trust_anchors()
233 .iter()
234 .filter_map(|choice| match choice.decoded_ta {
235 TrustAnchorChoice::Certificate(ref cert)
236 if cert.tbs_certificate.serial_number.as_bytes() != serial_number =>
237 {
238 Some(cert.clone())
239 }
240 _ => None,
241 })
242 .collect();
243
244 guard.clear_trust_anchor_sources();
245
246 let mut trust_anchors = TaSource::new();
247 for cert in certs {
248 trust_anchors.push(certval::CertFile {
249 filename: "".to_string(),
250 bytes: cert.to_der()?,
251 });
252 }
253 trust_anchors.initialize().map_err(Error::Certval)?;
254 guard.add_trust_anchor_source(Box::new(trust_anchors));
255
256 self.transactionally(async || {
258 self.database
259 .remove::<E2eiAcmeCA>(&<E2eiAcmeCA as UniqueEntity>::KEY)
260 .await
261 })
262 .await?;
263
264 Ok(())
265 }
266
267 pub async fn add_intermediate_cert(&self, cert: Certificate) -> Result<()> {
275 let (ski, aki) = RjtPkiEnvironment::extract_ski_aki_from_cert(&cert)?;
277 let ski_aki_pair = format!("{ski}:{}", aki.unwrap_or_default());
278 let cert_der = RjtPkiEnvironment::encode_cert_to_der(&cert)?;
279 let intermediate_cert = E2eiIntermediateCert {
280 content: cert_der,
281 ski_aki_pair,
282 };
283
284 self.transactionally(async || {
285 self.database.save(intermediate_cert).await?;
286
287 let dps: Vec<String> = extract_crl_uris(&cert)?.iter().flatten().cloned().collect();
289 let crls = self.fetch_crls(dps.iter().map(AsRef::as_ref)).await?;
290
291 for (distribution_point, crl) in &crls {
293 self.save_crl(distribution_point, crl).await?;
294 }
295
296 Result::Ok(())
297 })
298 .await?;
299
300 let mut cps = CertificationPathSettings::new();
301 certval::set_time_of_interest(&mut cps, now()?);
302 let mut cert_source = CertSource::new();
303 cert_source.push(certval::CertFile {
304 filename: "".to_string(),
305 bytes: cert.to_der()?,
306 });
307
308 let mut guard = self.rjt_pki_env.lock().await;
309 cert_source.initialize(&cps).map_err(Error::Certval)?;
310 cert_source.find_all_partial_paths(&guard, &cps);
311 guard.add_certificate_source(Box::new(cert_source));
312
313 Ok(())
314 }
315
316 pub async fn validate_cert(&self, cert: &x509_cert::Certificate) -> RustyX509CheckResult<()> {
323 self.rjt_pki_env.lock().await.validate_cert_and_revocation(cert)
324 }
325
326 pub async fn validate_credential<'a>(&'a self, credential: CredentialRef<'a>) -> CredentialAuthenticationStatus {
332 let CredentialRef::X509 { certificates } = credential else {
333 panic!("this function can only be called with an X509 credential");
334 };
335
336 let Some(cert) = certificates
337 .first()
338 .and_then(|cert_raw| x509_cert::Certificate::from_der(cert_raw).ok())
339 else {
340 return CredentialAuthenticationStatus::Invalid;
341 };
342
343 match self.rjt_pki_env.lock().await.validate_cert_and_revocation(&cert) {
344 Err(RustyX509CheckError::CertValError(CertvalError::PathValidation(
345 PathValidationStatus::CertificateRevoked
346 | PathValidationStatus::CertificateRevokedEndEntity
347 | PathValidationStatus::CertificateRevokedIntermediateCa,
348 ))) => {
349 CredentialAuthenticationStatus::Valid
352 }
353 Err(RustyX509CheckError::CertValError(CertvalError::PathValidation(
354 PathValidationStatus::InvalidNotAfterDate,
355 ))) => {
356 CredentialAuthenticationStatus::Valid
359 }
360 Err(RustyX509CheckError::CertValError(CertvalError::PathValidation(_))) => {
361 CredentialAuthenticationStatus::Invalid
362 }
363 Err(_) => CredentialAuthenticationStatus::Unknown,
364 Ok(_) => CredentialAuthenticationStatus::Valid,
365 }
366 }
367}
368
369#[cfg(test)]
370mod tests {
371 use spki::der::DecodePem as _;
372
373 use super::*;
374
375 const EXAMPLE_CERT_PEM: &str = "
376-----BEGIN CERTIFICATE-----
377MIIBkzCCAUWgAwIBAgIUHFYIFRkm33GKIOb4xLeNtkjl3TIwBQYDK2VwMDcxFTAT
378BgNVBAMMDFRlc3QgUm9vdCBDQTERMA8GA1UECgwIVGVzdCBPcmcxCzAJBgNVBAYT
379AlVTMB4XDTI2MDUyODE1MzA0NFoXDTM2MDUyNTE1MzA0NFowNzEVMBMGA1UEAwwM
380VGVzdCBSb290IENBMREwDwYDVQQKDAhUZXN0IE9yZzELMAkGA1UEBhMCVVMwKjAF
381BgMrZXADIQDa0nMgIgBZeNM2ysNUVp80zwjZNqPJt7HYK3GX7GPp9aNjMGEwHQYD
382VR0OBBYEFHA0MmaaNGOTuBvdo3zzQoKFJ3p5MB8GA1UdIwQYMBaAFHA0MmaaNGOT
383uBvdo3zzQoKFJ3p5MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAUG
384AytlcANBAJffPzL50OWnmEBo9mGBQfPVzKRIfFc8EaXox1D5VF9cC1r8nRa0hUq+
385LOVS/gxNk618+PKA2bYq67MZQXCYGgk=
386-----END CERTIFICATE-----
387";
388
389 #[tokio::test]
390 async fn can_add_trust_anchor() {
391 let db = Database::open_in_memory().unwrap();
392 let pki_env = PkiEnvironment::with_dummy_hooks(db).await.unwrap();
393 let cert = x509_cert::Certificate::from_pem(EXAMPLE_CERT_PEM).unwrap();
394 assert!(pki_env.add_trust_anchor(cert).await.is_ok());
395 }
396
397 #[tokio::test]
398 async fn can_remove_trust_anchor() {
399 let db = Database::open_in_memory().unwrap();
400 let pki_env = PkiEnvironment::with_dummy_hooks(db).await.unwrap();
401 let cert = x509_cert::Certificate::from_pem(EXAMPLE_CERT_PEM).unwrap();
402 pki_env.add_trust_anchor(cert.clone()).await.unwrap();
403
404 let certs = pki_env.get_trust_anchors().await;
405 assert_eq!(certs.len(), 1);
406
407 pki_env
408 .remove_trust_anchor(certs[0].tbs_certificate.serial_number.as_bytes())
409 .await
410 .unwrap();
411 assert_eq!(pki_env.get_trust_anchors().await.len(), 0);
412 }
413
414 #[tokio::test]
415 async fn can_add_intermediate_cert() {
416 let db = Database::open_in_memory().unwrap();
417 let pki_env = PkiEnvironment::with_dummy_hooks(db).await.unwrap();
418 let cert = x509_cert::Certificate::from_pem(EXAMPLE_CERT_PEM).unwrap();
419 assert!(pki_env.add_intermediate_cert(cert).await.is_ok());
420 }
421}