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