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 Database, Transaction,
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 pub async fn add_trust_anchor(&self, tx: &Transaction, cert: Certificate) -> Result<()> {
173 self.rjt_pki_env.lock().await.validate_trust_anchor_cert(&cert)?;
175
176 let cert_data = E2eiAcmeCA {
179 content: cert.to_der()?,
180 };
181
182 tx.save(cert_data).await?;
183
184 let mut trust_anchors = TaSource::new();
185 trust_anchors.push(certval::CertFile {
186 filename: "".to_string(),
187 bytes: cert.to_der()?,
188 });
189 trust_anchors.initialize().map_err(Error::Certval)?;
190 self.rjt_pki_env
191 .lock()
192 .await
193 .add_trust_anchor_source(Box::new(trust_anchors));
194 Ok(())
195 }
196
197 pub async fn remove_trust_anchor(&self, tx: &Transaction, serial_number: &[u8]) -> Result<()> {
202 let mut guard = self.rjt_pki_env.lock().await;
203
204 let certs: Vec<_> = guard
205 .get_trust_anchors()
206 .iter()
207 .filter_map(|choice| match choice.decoded_ta {
208 TrustAnchorChoice::Certificate(ref cert)
209 if cert.tbs_certificate.serial_number.as_bytes() != serial_number =>
210 {
211 Some(cert.clone())
212 }
213 _ => None,
214 })
215 .collect();
216
217 guard.clear_trust_anchor_sources();
218
219 let mut trust_anchors = TaSource::new();
220 for cert in certs {
221 trust_anchors.push(certval::CertFile {
222 filename: "".to_string(),
223 bytes: cert.to_der()?,
224 });
225 }
226 trust_anchors.initialize().map_err(Error::Certval)?;
227 guard.add_trust_anchor_source(Box::new(trust_anchors));
228
229 tx.remove::<E2eiAcmeCA>(&<E2eiAcmeCA as UniqueEntity>::KEY).await?;
231
232 Ok(())
233 }
234
235 pub async fn add_intermediate_cert(&self, tx: &Transaction, cert: Certificate) -> Result<()> {
243 let (ski, aki) = RjtPkiEnvironment::extract_ski_aki_from_cert(&cert)?;
245 let ski_aki_pair = format!("{ski}:{}", aki.unwrap_or_default());
246 let cert_der = RjtPkiEnvironment::encode_cert_to_der(&cert)?;
247 let intermediate_cert = E2eiIntermediateCert {
248 content: cert_der,
249 ski_aki_pair,
250 };
251
252 tx.save(intermediate_cert).await?;
253
254 let dps: Vec<String> = extract_crl_uris(&cert)?.iter().flatten().cloned().collect();
256 let crls = self.fetch_crls(dps.iter().map(AsRef::as_ref)).await?;
257
258 for (distribution_point, crl) in &crls {
260 self.save_crl(tx, distribution_point, crl).await?;
261 }
262
263 let mut cps = CertificationPathSettings::new();
264 certval::set_time_of_interest(&mut cps, now()?);
265 let mut cert_source = CertSource::new();
266 cert_source.push(certval::CertFile {
267 filename: "".to_string(),
268 bytes: cert.to_der()?,
269 });
270
271 let mut guard = self.rjt_pki_env.lock().await;
272 cert_source.initialize(&cps).map_err(Error::Certval)?;
273 cert_source.find_all_partial_paths(&guard, &cps);
274 guard.add_certificate_source(Box::new(cert_source));
275
276 Ok(())
277 }
278
279 pub async fn validate_cert(&self, cert: &x509_cert::Certificate) -> RustyX509CheckResult<()> {
286 self.rjt_pki_env.lock().await.validate_cert_and_revocation(cert)
287 }
288
289 pub async fn validate_credential<'a>(&'a self, credential: CredentialRef<'a>) -> CredentialAuthenticationStatus {
295 let CredentialRef::X509 { certificates } = credential else {
296 panic!("this function can only be called with an X509 credential");
297 };
298
299 let Some(cert) = certificates
300 .first()
301 .and_then(|cert_raw| x509_cert::Certificate::from_der(cert_raw).ok())
302 else {
303 return CredentialAuthenticationStatus::Invalid;
304 };
305
306 match self.rjt_pki_env.lock().await.validate_cert_and_revocation(&cert) {
307 Err(RustyX509CheckError::CertValError(CertvalError::PathValidation(
308 PathValidationStatus::CertificateRevoked
309 | PathValidationStatus::CertificateRevokedEndEntity
310 | PathValidationStatus::CertificateRevokedIntermediateCa,
311 ))) => {
312 CredentialAuthenticationStatus::Valid
315 }
316 Err(RustyX509CheckError::CertValError(CertvalError::PathValidation(
317 PathValidationStatus::InvalidNotAfterDate,
318 ))) => {
319 CredentialAuthenticationStatus::Valid
322 }
323 Err(RustyX509CheckError::CertValError(CertvalError::PathValidation(_))) => {
324 CredentialAuthenticationStatus::Invalid
325 }
326 Err(_) => CredentialAuthenticationStatus::Unknown,
327 Ok(_) => CredentialAuthenticationStatus::Valid,
328 }
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use spki::der::DecodePem as _;
335
336 use super::*;
337
338 const EXAMPLE_CERT_PEM: &str = "
339-----BEGIN CERTIFICATE-----
340MIIBkzCCAUWgAwIBAgIUHFYIFRkm33GKIOb4xLeNtkjl3TIwBQYDK2VwMDcxFTAT
341BgNVBAMMDFRlc3QgUm9vdCBDQTERMA8GA1UECgwIVGVzdCBPcmcxCzAJBgNVBAYT
342AlVTMB4XDTI2MDUyODE1MzA0NFoXDTM2MDUyNTE1MzA0NFowNzEVMBMGA1UEAwwM
343VGVzdCBSb290IENBMREwDwYDVQQKDAhUZXN0IE9yZzELMAkGA1UEBhMCVVMwKjAF
344BgMrZXADIQDa0nMgIgBZeNM2ysNUVp80zwjZNqPJt7HYK3GX7GPp9aNjMGEwHQYD
345VR0OBBYEFHA0MmaaNGOTuBvdo3zzQoKFJ3p5MB8GA1UdIwQYMBaAFHA0MmaaNGOT
346uBvdo3zzQoKFJ3p5MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAUG
347AytlcANBAJffPzL50OWnmEBo9mGBQfPVzKRIfFc8EaXox1D5VF9cC1r8nRa0hUq+
348LOVS/gxNk618+PKA2bYq67MZQXCYGgk=
349-----END CERTIFICATE-----
350";
351
352 #[tokio::test]
353 async fn can_add_trust_anchor() {
354 let db = Database::open_in_memory().unwrap();
355 let tx = db.new_transaction().await.unwrap();
356 let pki_env = PkiEnvironment::with_dummy_hooks(db).await.unwrap();
357 let cert = x509_cert::Certificate::from_pem(EXAMPLE_CERT_PEM).unwrap();
358 assert!(pki_env.add_trust_anchor(&tx, cert).await.is_ok());
359 }
360
361 #[tokio::test]
362 async fn can_remove_trust_anchor() {
363 let db = Database::open_in_memory().unwrap();
364 let tx = db.new_transaction().await.unwrap();
365 let pki_env = PkiEnvironment::with_dummy_hooks(db).await.unwrap();
366 let cert = x509_cert::Certificate::from_pem(EXAMPLE_CERT_PEM).unwrap();
367 pki_env.add_trust_anchor(&tx, cert.clone()).await.unwrap();
368
369 let certs = pki_env.get_trust_anchors().await;
370 assert_eq!(certs.len(), 1);
371
372 pki_env
373 .remove_trust_anchor(&tx, certs[0].tbs_certificate.serial_number.as_bytes())
374 .await
375 .unwrap();
376 assert_eq!(pki_env.get_trust_anchors().await.len(), 0);
377 }
378
379 #[tokio::test]
380 async fn can_add_intermediate_cert() {
381 let db = Database::open_in_memory().unwrap();
382 let tx = db.new_transaction().await.unwrap();
383 let pki_env = PkiEnvironment::with_dummy_hooks(db).await.unwrap();
384 let cert = x509_cert::Certificate::from_pem(EXAMPLE_CERT_PEM).unwrap();
385 assert!(pki_env.add_intermediate_cert(&tx, cert).await.is_ok());
386 }
387}