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 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("The trust anchor certificate already exists in the database.")]
42 TrustAnchorAlreadyExists,
43 #[error("Failed to fetch CRL from '{uri}': HTTP {status}")]
44 CrlFetchUnsuccessful { uri: String, status: u16 },
45 #[error(transparent)]
46 HooksError(#[from] hooks::PkiEnvironmentHooksError),
47 #[error(transparent)]
48 X509Error(#[from] RustyX509CheckError),
49 #[error(transparent)]
50 UrlError(#[from] url::ParseError),
51 #[error(transparent)]
52 JsonError(#[from] serde_json::Error),
53 #[error(transparent)]
54 X509CertDerError(#[from] x509_cert::der::Error),
55 #[error(transparent)]
56 KeystoreError(#[from] core_crypto_keystore::CryptoKeystoreError),
57 #[error("certval error: {0}")]
58 Certval(certval::Error),
59 #[error("spki error: {0}")]
60 Spki(spki::Error),
61}
62
63#[derive(Debug, Clone, derive_more::From, derive_more::Into, derive_more::Deref, derive_more::DerefMut)]
65pub struct NewCrlDistributionPoints(Option<HashSet<String>>);
66
67impl From<NewCrlDistributionPoints> for Option<Vec<String>> {
68 fn from(mut dp: NewCrlDistributionPoints) -> Self {
69 dp.take().map(|d| d.into_iter().collect())
70 }
71}
72
73impl IntoIterator for NewCrlDistributionPoints {
74 type Item = String;
75
76 type IntoIter = std::collections::hash_set::IntoIter<String>;
77
78 fn into_iter(self) -> Self::IntoIter {
79 let items = self.0.unwrap_or_default();
80 items.into_iter()
81 }
82}
83
84async fn restore_pki_env(data_provider: &impl FetchFromDatabase) -> Result<RjtPkiEnvironment> {
85 let mut trust_roots = vec![];
86 for ta_raw in data_provider.load_all::<X509TrustAnchor>().await? {
87 trust_roots.push(
88 x509_cert::Certificate::from_der(&ta_raw.content).map(x509_cert::anchor::TrustAnchorChoice::Certificate)?,
89 );
90 }
91
92 let intermediates = data_provider
93 .load_all::<X509IntermediateCert>()
94 .await?
95 .into_iter()
96 .map(|inter| x509_cert::Certificate::from_der(&inter.content))
97 .collect::<core::result::Result<Vec<_>, _>>()?;
98
99 let crls = data_provider
100 .load_all::<X509Crl>()
101 .await?
102 .into_iter()
103 .map(|crl| x509_cert::crl::CertificateList::from_der(&crl.content))
104 .collect::<core::result::Result<Vec<_>, _>>()?;
105
106 let params = PkiEnvironmentParams {
107 trust_roots: &trust_roots,
108 intermediates: &intermediates,
109 crls: &crls,
110 };
111
112 Ok(RjtPkiEnvironment::init(params)?)
113}
114
115#[derive(Debug)]
117pub struct PkiEnvironment {
118 hooks: Arc<dyn PkiEnvironmentHooks>,
120 database: Arc<Database>,
122 rjt_pki_env: Mutex<RjtPkiEnvironment>,
123}
124
125impl PkiEnvironment {
126 pub async fn new(hooks: Arc<dyn PkiEnvironmentHooks>, database: Arc<Database>) -> Result<PkiEnvironment> {
128 let rjt_pki_env = restore_pki_env(&*database).await?;
129 Ok(Self {
130 hooks,
131 database,
132 rjt_pki_env: Mutex::new(rjt_pki_env),
133 })
134 }
135
136 pub async fn get_trust_anchors(&self) -> Vec<Certificate> {
138 self.rjt_pki_env
139 .lock()
140 .await
141 .get_trust_anchors()
142 .iter()
143 .map(|choice| Certificate::from_der(&choice.encoded_ta).expect("valid DER"))
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 pub async fn add_trust_anchor(&self, tx: &Transaction, cert: Certificate) -> Result<()> {
169 self.rjt_pki_env.lock().await.validate_trust_anchor_cert(&cert)?;
171
172 let fingerprint = cert
173 .tbs_certificate()
174 .subject_public_key_info()
175 .fingerprint_bytes()
176 .map_err(Error::Spki)?
177 .to_vec();
178
179 if tx.get::<X509TrustAnchor>(&fingerprint).await?.is_some() {
181 return Err(Error::TrustAnchorAlreadyExists);
182 }
183
184 let cert_data = X509TrustAnchor {
185 fingerprint,
186 content: cert.to_der()?,
187 };
188
189 cert_data.save(tx)?;
190
191 let mut trust_anchors = TaSource::new();
192 trust_anchors.push(certval::CertFile {
193 filename: "".to_string(),
194 bytes: cert.to_der()?,
195 });
196 trust_anchors.initialize().map_err(Error::Certval)?;
197 self.rjt_pki_env
198 .lock()
199 .await
200 .add_trust_anchor_source(Box::new(trust_anchors));
201 Ok(())
202 }
203
204 pub async fn remove_trust_anchor(&self, tx: &Transaction, fingerprint: &[u8]) -> Result<()> {
209 X509TrustAnchor::delete_borrowed(tx, fingerprint)?;
210
211 let anchors = tx.load_all::<X509TrustAnchor>().await?;
212
213 let mut guard = self.rjt_pki_env.lock().await;
214 guard.clear_trust_anchor_sources();
215
216 let mut source = TaSource::new();
217 for anchor in anchors {
218 let mut anchor = Arc::unwrap_or_clone(anchor);
219 source.push(certval::CertFile {
220 filename: "".to_string(),
221 bytes: std::mem::take(&mut anchor.content),
222 });
223 }
224
225 source.initialize().map_err(Error::Certval)?;
226 guard.add_trust_anchor_source(Box::new(source));
227
228 Ok(())
229 }
230
231 pub async fn add_intermediate_cert(&self, tx: &Transaction, cert: Certificate) -> Result<()> {
239 let toi = TimeOfInterest::from_unix_secs(now()?)?;
240
241 let (ski, aki) = RjtPkiEnvironment::extract_ski_aki_from_cert(&cert)?;
243 let ski_aki_pair = format!("{ski}:{}", aki.unwrap_or_default());
244 let cert_der = RjtPkiEnvironment::encode_cert_to_der(&cert)?;
245 let intermediate_cert = X509IntermediateCert {
246 content: cert_der,
247 ski_aki_pair,
248 };
249
250 intermediate_cert.save(tx)?;
251
252 let dps: Vec<String> = extract_crl_uris(&cert)?.iter().flatten().cloned().collect();
254 let crls = self.fetch_crls(dps.iter().map(AsRef::as_ref)).await?;
255
256 for (distribution_point, crl) in &crls {
258 self.save_crl(tx, distribution_point, crl).await?;
259 }
260
261 let mut cps = CertificationPathSettings::new();
262 cps.set_time_of_interest(toi);
263 let mut cert_source = CertSource::new();
264 cert_source.push(certval::CertFile {
265 filename: "".to_string(),
266 bytes: cert.to_der()?,
267 });
268
269 let mut guard = self.rjt_pki_env.lock().await;
270 cert_source.initialize(&cps).map_err(Error::Certval)?;
271 cert_source.find_all_partial_paths(&guard, &cps);
272 guard.add_certificate_source(Box::new(cert_source));
273
274 Ok(())
275 }
276
277 pub async fn validate_cert(&self, cert: &x509_cert::Certificate) -> RustyX509CheckResult<()> {
284 self.rjt_pki_env.lock().await.validate_cert_and_revocation(cert)
285 }
286
287 pub async fn validate_credential<'a>(&'a self, credential: CredentialRef<'a>) -> CredentialAuthenticationStatus {
293 let CredentialRef::X509 { certificates } = credential else {
294 panic!("this function can only be called with an X509 credential");
295 };
296
297 let Some(cert) = certificates
298 .first()
299 .and_then(|cert_raw| x509_cert::Certificate::from_der(cert_raw).ok())
300 else {
301 return CredentialAuthenticationStatus::Invalid;
302 };
303
304 match self.rjt_pki_env.lock().await.validate_cert_and_revocation(&cert) {
305 Err(RustyX509CheckError::CertValError(CertvalError::PathValidation(
306 PathValidationStatus::CertificateRevoked
307 | PathValidationStatus::CertificateRevokedEndEntity
308 | PathValidationStatus::CertificateRevokedIntermediateCa,
309 ))) => CredentialAuthenticationStatus::Revoked,
310 Err(RustyX509CheckError::CertValError(CertvalError::PathValidation(
311 PathValidationStatus::InvalidNotAfterDate,
312 ))) => CredentialAuthenticationStatus::Expired,
313 Err(RustyX509CheckError::CertValError(CertvalError::PathValidation(_))) => {
314 CredentialAuthenticationStatus::Invalid
315 }
316 Err(_) => CredentialAuthenticationStatus::Unknown,
317 Ok(_) => CredentialAuthenticationStatus::Valid,
318 }
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use spki::der::DecodePem as _;
325
326 use super::*;
327
328 const EXAMPLE_CERT_PEM: &str = "
329-----BEGIN CERTIFICATE-----
330MIIBkzCCAUWgAwIBAgIUHFYIFRkm33GKIOb4xLeNtkjl3TIwBQYDK2VwMDcxFTAT
331BgNVBAMMDFRlc3QgUm9vdCBDQTERMA8GA1UECgwIVGVzdCBPcmcxCzAJBgNVBAYT
332AlVTMB4XDTI2MDUyODE1MzA0NFoXDTM2MDUyNTE1MzA0NFowNzEVMBMGA1UEAwwM
333VGVzdCBSb290IENBMREwDwYDVQQKDAhUZXN0IE9yZzELMAkGA1UEBhMCVVMwKjAF
334BgMrZXADIQDa0nMgIgBZeNM2ysNUVp80zwjZNqPJt7HYK3GX7GPp9aNjMGEwHQYD
335VR0OBBYEFHA0MmaaNGOTuBvdo3zzQoKFJ3p5MB8GA1UdIwQYMBaAFHA0MmaaNGOT
336uBvdo3zzQoKFJ3p5MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAUG
337AytlcANBAJffPzL50OWnmEBo9mGBQfPVzKRIfFc8EaXox1D5VF9cC1r8nRa0hUq+
338LOVS/gxNk618+PKA2bYq67MZQXCYGgk=
339-----END CERTIFICATE-----
340";
341
342 #[tokio::test]
343 async fn can_add_trust_anchor() {
344 let db = Database::open_in_memory().unwrap();
345 let tx = db.new_transaction().await.unwrap();
346 let pki_env = PkiEnvironment::with_dummy_hooks(db).await.unwrap();
347 let cert = x509_cert::Certificate::from_pem(EXAMPLE_CERT_PEM).unwrap();
348 assert!(pki_env.add_trust_anchor(&tx, cert.clone()).await.is_ok());
349 assert!(matches!(
350 pki_env.add_trust_anchor(&tx, cert).await,
351 Err(Error::TrustAnchorAlreadyExists)
352 ));
353 }
354
355 #[tokio::test]
356 async fn can_remove_trust_anchor() {
357 let db = Database::open_in_memory().unwrap();
358 let tx = db.new_transaction().await.unwrap();
359 let pki_env = PkiEnvironment::with_dummy_hooks(db).await.unwrap();
360 let cert = x509_cert::Certificate::from_pem(EXAMPLE_CERT_PEM).unwrap();
361 pki_env.add_trust_anchor(&tx, cert.clone()).await.unwrap();
362
363 let certs = pki_env.get_trust_anchors().await;
364 assert_eq!(certs.len(), 1);
365
366 pki_env
367 .remove_trust_anchor(
368 &tx,
369 &certs[0]
370 .tbs_certificate()
371 .subject_public_key_info()
372 .fingerprint_bytes()
373 .expect("Getting fingerprint of subject plublic key info"),
374 )
375 .await
376 .unwrap();
377 assert_eq!(pki_env.get_trust_anchors().await.len(), 0);
378 }
379
380 #[tokio::test]
381 async fn can_add_intermediate_cert() {
382 let db = Database::open_in_memory().unwrap();
383 let tx = db.new_transaction().await.unwrap();
384 let pki_env = PkiEnvironment::with_dummy_hooks(db).await.unwrap();
385 let cert = x509_cert::Certificate::from_pem(EXAMPLE_CERT_PEM).unwrap();
386 assert!(pki_env.add_intermediate_cert(&tx, cert).await.is_ok());
387 }
388}