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 TimeOfInterest,
15};
16use core_crypto_keystore::{
17 Database, Transaction,
18 entities::{X509Crl, X509IntermediateCert, X509TrustAnchor},
19 traits::{EntityDatabaseMutation as _, EntityDeleteBorrowed, FetchFromDatabase},
20};
21use openmls_traits::authentication_service::{CredentialAuthenticationStatus, CredentialRef};
22use x509_cert::{
23 Certificate,
24 der::{Decode as _, Encode as _},
25};
26
27use crate::{
28 pki_env::hooks::PkiEnvironmentHooks,
29 validation,
30 validation::{extract_crl_uris, now, prepare_environment, validate_cert, validate_trust_anchor_cert},
31};
32
33pub type Result<T> = core::result::Result<T, Error>;
34
35#[derive(Debug, thiserror::Error)]
36pub enum Error {
37 #[error("The trust anchor certificate couldn't be loaded from the database.")]
38 NoTrustAnchor,
39 #[error("The trust anchor certificate already exists in the database.")]
40 TrustAnchorAlreadyExists,
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 Validation(#[from] validation::Error),
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 #[error("spki error: {0}")]
58 Spki(spki::Error),
59}
60
61#[derive(Debug, Clone, derive_more::From, derive_more::Into, derive_more::Deref, derive_more::DerefMut)]
63pub struct NewCrlDistributionPoints(Option<HashSet<String>>);
64
65impl From<NewCrlDistributionPoints> for Option<Vec<String>> {
66 fn from(mut dp: NewCrlDistributionPoints) -> Self {
67 dp.take().map(|d| d.into_iter().collect())
68 }
69}
70
71impl IntoIterator for NewCrlDistributionPoints {
72 type Item = String;
73
74 type IntoIter = std::collections::hash_set::IntoIter<String>;
75
76 fn into_iter(self) -> Self::IntoIter {
77 let items = self.0.unwrap_or_default();
78 items.into_iter()
79 }
80}
81
82async fn restore_pki_env(data_provider: &impl FetchFromDatabase) -> Result<certval::environment::PkiEnvironment> {
83 let mut trust_roots = vec![];
84 for ta_raw in data_provider.load_all::<X509TrustAnchor>().await? {
85 trust_roots.push(
86 x509_cert::Certificate::from_der(&ta_raw.content).map(x509_cert::anchor::TrustAnchorChoice::Certificate)?,
87 );
88 }
89
90 let intermediates = data_provider
91 .load_all::<X509IntermediateCert>()
92 .await?
93 .into_iter()
94 .map(|inter| x509_cert::Certificate::from_der(&inter.content))
95 .collect::<core::result::Result<Vec<_>, _>>()?;
96
97 let crls = data_provider
98 .load_all::<X509Crl>()
99 .await?
100 .into_iter()
101 .map(|crl| x509_cert::crl::CertificateList::from_der(&crl.content))
102 .collect::<core::result::Result<Vec<_>, _>>()?;
103
104 Ok(prepare_environment(&trust_roots, &intermediates, &crls)?)
105}
106
107pub struct PkiEnvironment {
109 hooks: Arc<dyn PkiEnvironmentHooks>,
111 database: Arc<Database>,
113 env: Mutex<certval::environment::PkiEnvironment>,
114}
115
116impl std::fmt::Debug for PkiEnvironment {
117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118 f.debug_struct("PkiEnvironment")
119 .field("hooks", &self.hooks)
120 .field("database", &self.database)
121 .field("env", &format!("{:p}", &self.env))
122 .finish()
123 }
124}
125
126impl PkiEnvironment {
127 pub async fn new(hooks: Arc<dyn PkiEnvironmentHooks>, database: Arc<Database>) -> Result<PkiEnvironment> {
129 let env = restore_pki_env(&*database).await?;
130 Ok(Self {
131 hooks,
132 database,
133 env: Mutex::new(env),
134 })
135 }
136
137 pub async fn get_trust_anchors(&self) -> Vec<Certificate> {
139 self.env
140 .lock()
141 .await
142 .get_trust_anchors()
143 .iter()
144 .map(|choice| Certificate::from_der(&choice.encoded_ta).expect("valid DER"))
145 .collect()
146 }
147
148 pub fn hooks(&self) -> Arc<dyn PkiEnvironmentHooks> {
150 self.hooks.clone()
151 }
152
153 pub fn database(&self) -> &Database {
155 &self.database
156 }
157
158 pub fn database_arc(&self) -> Arc<Database> {
162 self.database.clone()
163 }
164
165 pub async fn add_trust_anchor(&self, tx: &Transaction, cert: Certificate) -> Result<()> {
170 validate_trust_anchor_cert(&*self.env.lock().await, &cert)?;
172
173 let fingerprint = cert
174 .tbs_certificate()
175 .subject_public_key_info()
176 .fingerprint_bytes()
177 .map_err(Error::Spki)?
178 .to_vec();
179
180 if tx.get::<X509TrustAnchor>(&fingerprint).await?.is_some() {
182 return Err(Error::TrustAnchorAlreadyExists);
183 }
184
185 let cert_data = X509TrustAnchor {
186 fingerprint,
187 content: cert.to_der()?,
188 };
189
190 cert_data.save(tx)?;
191
192 let mut trust_anchors = TaSource::new();
193 trust_anchors.push(certval::CertFile {
194 filename: "".to_string(),
195 bytes: cert.to_der()?,
196 });
197 trust_anchors.initialize().map_err(Error::Certval)?;
198 self.env.lock().await.add_trust_anchor_source(Box::new(trust_anchors));
199 Ok(())
200 }
201
202 pub async fn remove_trust_anchor(&self, tx: &Transaction, fingerprint: &[u8]) -> Result<()> {
207 X509TrustAnchor::delete_borrowed(tx, fingerprint)?;
208
209 let anchors = tx.load_all::<X509TrustAnchor>().await?;
210
211 let mut guard = self.env.lock().await;
212 guard.clear_trust_anchor_sources();
213
214 let mut source = TaSource::new();
215 for anchor in anchors {
216 let mut anchor = Arc::unwrap_or_clone(anchor);
217 source.push(certval::CertFile {
218 filename: "".to_string(),
219 bytes: std::mem::take(&mut anchor.content),
220 });
221 }
222
223 source.initialize().map_err(Error::Certval)?;
224 guard.add_trust_anchor_source(Box::new(source));
225
226 Ok(())
227 }
228
229 pub async fn add_intermediate_cert(&self, tx: &Transaction, cert: Certificate) -> Result<()> {
237 let toi = TimeOfInterest::from_unix_secs(now()?)?;
238
239 let (ski, aki) = crate::utils::extract_ski_aki_from_cert(&cert)?;
241 let ski_aki_pair = format!("{ski}:{}", aki.unwrap_or_default());
242 let cert_der = cert.to_der()?;
243 let intermediate_cert = X509IntermediateCert {
244 content: cert_der,
245 ski_aki_pair,
246 };
247
248 intermediate_cert.save(tx)?;
249
250 let dps: Vec<String> = extract_crl_uris(&cert)?.iter().flatten().cloned().collect();
252 let crls = self.fetch_crls(dps.iter().map(AsRef::as_ref)).await?;
253
254 for (distribution_point, crl) in &crls {
256 self.save_crl(tx, distribution_point, crl).await?;
257 }
258
259 let mut cps = CertificationPathSettings::new();
260 cps.set_time_of_interest(toi);
261 let mut cert_source = CertSource::new();
262 cert_source.push(certval::CertFile {
263 filename: "".to_string(),
264 bytes: cert.to_der()?,
265 });
266
267 let mut guard = self.env.lock().await;
268 cert_source.initialize(&cps).map_err(Error::Certval)?;
269 cert_source.find_all_partial_paths(&guard, &cps);
270 guard.add_certificate_source(Box::new(cert_source));
271
272 Ok(())
273 }
274
275 pub async fn validate_cert(&self, cert: &x509_cert::Certificate) -> validation::Result<()> {
282 validate_cert(&*self.env.lock().await, cert, true)
283 }
284
285 pub async fn validate_credential<'a>(&'a self, credential: CredentialRef<'a>) -> CredentialAuthenticationStatus {
291 let CredentialRef::X509 { certificates } = credential else {
292 panic!("this function can only be called with an X509 credential");
293 };
294
295 let Some(cert) = certificates
296 .first()
297 .and_then(|cert_raw| x509_cert::Certificate::from_der(cert_raw).ok())
298 else {
299 return CredentialAuthenticationStatus::Invalid;
300 };
301
302 match validate_cert(&*self.env.lock().await, &cert, true) {
303 Err(validation::Error::CertValError(CertvalError::PathValidation(
304 PathValidationStatus::CertificateRevoked
305 | PathValidationStatus::CertificateRevokedEndEntity
306 | PathValidationStatus::CertificateRevokedIntermediateCa,
307 ))) => CredentialAuthenticationStatus::Revoked,
308 Err(validation::Error::CertValError(CertvalError::PathValidation(
309 PathValidationStatus::InvalidNotAfterDate,
310 ))) => CredentialAuthenticationStatus::Expired,
311 Err(validation::Error::CertValError(CertvalError::PathValidation(_))) => {
312 CredentialAuthenticationStatus::Invalid
313 }
314 Err(_) => CredentialAuthenticationStatus::Unknown,
315 Ok(_) => CredentialAuthenticationStatus::Valid,
316 }
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use spki::der::DecodePem as _;
323
324 use super::*;
325
326 const EXAMPLE_CERT_PEM: &str = "
327-----BEGIN CERTIFICATE-----
328MIIBkzCCAUWgAwIBAgIUHFYIFRkm33GKIOb4xLeNtkjl3TIwBQYDK2VwMDcxFTAT
329BgNVBAMMDFRlc3QgUm9vdCBDQTERMA8GA1UECgwIVGVzdCBPcmcxCzAJBgNVBAYT
330AlVTMB4XDTI2MDUyODE1MzA0NFoXDTM2MDUyNTE1MzA0NFowNzEVMBMGA1UEAwwM
331VGVzdCBSb290IENBMREwDwYDVQQKDAhUZXN0IE9yZzELMAkGA1UEBhMCVVMwKjAF
332BgMrZXADIQDa0nMgIgBZeNM2ysNUVp80zwjZNqPJt7HYK3GX7GPp9aNjMGEwHQYD
333VR0OBBYEFHA0MmaaNGOTuBvdo3zzQoKFJ3p5MB8GA1UdIwQYMBaAFHA0MmaaNGOT
334uBvdo3zzQoKFJ3p5MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAUG
335AytlcANBAJffPzL50OWnmEBo9mGBQfPVzKRIfFc8EaXox1D5VF9cC1r8nRa0hUq+
336LOVS/gxNk618+PKA2bYq67MZQXCYGgk=
337-----END CERTIFICATE-----
338";
339
340 #[tokio::test]
341 async fn can_add_trust_anchor() {
342 let db = Database::open_in_memory().unwrap();
343 let tx = db.new_transaction().await.unwrap();
344 let pki_env = PkiEnvironment::with_dummy_hooks(db).await.unwrap();
345 let cert = x509_cert::Certificate::from_pem(EXAMPLE_CERT_PEM).unwrap();
346 assert!(pki_env.add_trust_anchor(&tx, cert.clone()).await.is_ok());
347 assert!(matches!(
348 pki_env.add_trust_anchor(&tx, cert).await,
349 Err(Error::TrustAnchorAlreadyExists)
350 ));
351 }
352
353 #[tokio::test]
354 async fn can_remove_trust_anchor() {
355 let db = Database::open_in_memory().unwrap();
356 let tx = db.new_transaction().await.unwrap();
357 let pki_env = PkiEnvironment::with_dummy_hooks(db).await.unwrap();
358 let cert = x509_cert::Certificate::from_pem(EXAMPLE_CERT_PEM).unwrap();
359 pki_env.add_trust_anchor(&tx, cert.clone()).await.unwrap();
360
361 let certs = pki_env.get_trust_anchors().await;
362 assert_eq!(certs.len(), 1);
363
364 pki_env
365 .remove_trust_anchor(
366 &tx,
367 &certs[0]
368 .tbs_certificate()
369 .subject_public_key_info()
370 .fingerprint_bytes()
371 .expect("Getting fingerprint of subject plublic key info"),
372 )
373 .await
374 .unwrap();
375 assert_eq!(pki_env.get_trust_anchors().await.len(), 0);
376 }
377
378 #[tokio::test]
379 async fn can_add_intermediate_cert() {
380 let db = Database::open_in_memory().unwrap();
381 let tx = db.new_transaction().await.unwrap();
382 let pki_env = PkiEnvironment::with_dummy_hooks(db).await.unwrap();
383 let cert = x509_cert::Certificate::from_pem(EXAMPLE_CERT_PEM).unwrap();
384 assert!(pki_env.add_intermediate_cert(&tx, cert).await.is_ok());
385 }
386}