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::{X509Crl, X509IntermediateCert, X509TrustAnchor},
18 traits::FetchFromDatabase,
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("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 .filter_map(|choice| match choice.decoded_ta {
144 TrustAnchorChoice::Certificate(ref cert) => Some(cert.clone()),
145 _ => None,
146 })
147 .collect()
148 }
149
150 pub fn hooks(&self) -> Arc<dyn PkiEnvironmentHooks> {
152 self.hooks.clone()
153 }
154
155 pub fn database(&self) -> &Database {
157 &self.database
158 }
159
160 pub fn database_arc(&self) -> Arc<Database> {
164 self.database.clone()
165 }
166
167 pub async fn add_trust_anchor(&self, tx: &Transaction, cert: Certificate) -> Result<()> {
172 self.rjt_pki_env.lock().await.validate_trust_anchor_cert(&cert)?;
174
175 let fingerprint = cert
176 .tbs_certificate
177 .subject_public_key_info
178 .fingerprint_bytes()
179 .map_err(Error::Spki)?
180 .to_vec();
181
182 if tx.get::<X509TrustAnchor>(&fingerprint).await?.is_some() {
184 return Err(Error::TrustAnchorAlreadyExists);
185 }
186
187 let cert_data = X509TrustAnchor {
188 fingerprint,
189 content: cert.to_der()?,
190 };
191
192 tx.save(cert_data).await?;
193
194 let mut trust_anchors = TaSource::new();
195 trust_anchors.push(certval::CertFile {
196 filename: "".to_string(),
197 bytes: cert.to_der()?,
198 });
199 trust_anchors.initialize().map_err(Error::Certval)?;
200 self.rjt_pki_env
201 .lock()
202 .await
203 .add_trust_anchor_source(Box::new(trust_anchors));
204 Ok(())
205 }
206
207 pub async fn remove_trust_anchor(&self, tx: &Transaction, fingerprint: &[u8]) -> Result<()> {
212 tx.remove_borrowed::<X509TrustAnchor>(fingerprint).await?;
213
214 let anchors = tx.load_all::<X509TrustAnchor>().await?;
215
216 let mut guard = self.rjt_pki_env.lock().await;
217 guard.clear_trust_anchor_sources();
218
219 let mut source = TaSource::new();
220 for anchor in anchors {
221 let mut anchor = Arc::unwrap_or_clone(anchor);
222 source.push(certval::CertFile {
223 filename: "".to_string(),
224 bytes: std::mem::take(&mut anchor.content),
225 });
226 }
227
228 source.initialize().map_err(Error::Certval)?;
229 guard.add_trust_anchor_source(Box::new(source));
230
231 Ok(())
232 }
233
234 pub async fn add_intermediate_cert(&self, tx: &Transaction, cert: Certificate) -> Result<()> {
242 let (ski, aki) = RjtPkiEnvironment::extract_ski_aki_from_cert(&cert)?;
244 let ski_aki_pair = format!("{ski}:{}", aki.unwrap_or_default());
245 let cert_der = RjtPkiEnvironment::encode_cert_to_der(&cert)?;
246 let intermediate_cert = X509IntermediateCert {
247 content: cert_der,
248 ski_aki_pair,
249 };
250
251 tx.save(intermediate_cert).await?;
252
253 let dps: Vec<String> = extract_crl_uris(&cert)?.iter().flatten().cloned().collect();
255 let crls = self.fetch_crls(dps.iter().map(AsRef::as_ref)).await?;
256
257 for (distribution_point, crl) in &crls {
259 self.save_crl(tx, distribution_point, crl).await?;
260 }
261
262 let mut cps = CertificationPathSettings::new();
263 certval::set_time_of_interest(&mut cps, now()?);
264 let mut cert_source = CertSource::new();
265 cert_source.push(certval::CertFile {
266 filename: "".to_string(),
267 bytes: cert.to_der()?,
268 });
269
270 let mut guard = self.rjt_pki_env.lock().await;
271 cert_source.initialize(&cps).map_err(Error::Certval)?;
272 cert_source.find_all_partial_paths(&guard, &cps);
273 guard.add_certificate_source(Box::new(cert_source));
274
275 Ok(())
276 }
277
278 pub async fn validate_cert(&self, cert: &x509_cert::Certificate) -> RustyX509CheckResult<()> {
285 self.rjt_pki_env.lock().await.validate_cert_and_revocation(cert)
286 }
287
288 pub async fn validate_credential<'a>(&'a self, credential: CredentialRef<'a>) -> CredentialAuthenticationStatus {
294 let CredentialRef::X509 { certificates } = credential else {
295 panic!("this function can only be called with an X509 credential");
296 };
297
298 let Some(cert) = certificates
299 .first()
300 .and_then(|cert_raw| x509_cert::Certificate::from_der(cert_raw).ok())
301 else {
302 return CredentialAuthenticationStatus::Invalid;
303 };
304
305 match self.rjt_pki_env.lock().await.validate_cert_and_revocation(&cert) {
306 Err(RustyX509CheckError::CertValError(CertvalError::PathValidation(
307 PathValidationStatus::CertificateRevoked
308 | PathValidationStatus::CertificateRevokedEndEntity
309 | PathValidationStatus::CertificateRevokedIntermediateCa,
310 ))) => {
311 CredentialAuthenticationStatus::Valid
314 }
315 Err(RustyX509CheckError::CertValError(CertvalError::PathValidation(
316 PathValidationStatus::InvalidNotAfterDate,
317 ))) => {
318 CredentialAuthenticationStatus::Valid
321 }
322 Err(RustyX509CheckError::CertValError(CertvalError::PathValidation(_))) => {
323 CredentialAuthenticationStatus::Invalid
324 }
325 Err(_) => CredentialAuthenticationStatus::Unknown,
326 Ok(_) => CredentialAuthenticationStatus::Valid,
327 }
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use spki::der::DecodePem as _;
334
335 use super::*;
336
337 const EXAMPLE_CERT_PEM: &str = "
338-----BEGIN CERTIFICATE-----
339MIIBkzCCAUWgAwIBAgIUHFYIFRkm33GKIOb4xLeNtkjl3TIwBQYDK2VwMDcxFTAT
340BgNVBAMMDFRlc3QgUm9vdCBDQTERMA8GA1UECgwIVGVzdCBPcmcxCzAJBgNVBAYT
341AlVTMB4XDTI2MDUyODE1MzA0NFoXDTM2MDUyNTE1MzA0NFowNzEVMBMGA1UEAwwM
342VGVzdCBSb290IENBMREwDwYDVQQKDAhUZXN0IE9yZzELMAkGA1UEBhMCVVMwKjAF
343BgMrZXADIQDa0nMgIgBZeNM2ysNUVp80zwjZNqPJt7HYK3GX7GPp9aNjMGEwHQYD
344VR0OBBYEFHA0MmaaNGOTuBvdo3zzQoKFJ3p5MB8GA1UdIwQYMBaAFHA0MmaaNGOT
345uBvdo3zzQoKFJ3p5MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAUG
346AytlcANBAJffPzL50OWnmEBo9mGBQfPVzKRIfFc8EaXox1D5VF9cC1r8nRa0hUq+
347LOVS/gxNk618+PKA2bYq67MZQXCYGgk=
348-----END CERTIFICATE-----
349";
350
351 #[tokio::test]
352 async fn can_add_trust_anchor() {
353 let db = Database::open_in_memory().unwrap();
354 let tx = db.new_transaction().await.unwrap();
355 let pki_env = PkiEnvironment::with_dummy_hooks(db).await.unwrap();
356 let cert = x509_cert::Certificate::from_pem(EXAMPLE_CERT_PEM).unwrap();
357 assert!(pki_env.add_trust_anchor(&tx, cert.clone()).await.is_ok());
358 assert!(matches!(
359 pki_env.add_trust_anchor(&tx, cert).await,
360 Err(Error::TrustAnchorAlreadyExists)
361 ));
362 }
363
364 #[tokio::test]
365 async fn can_remove_trust_anchor() {
366 let db = Database::open_in_memory().unwrap();
367 let tx = db.new_transaction().await.unwrap();
368 let pki_env = PkiEnvironment::with_dummy_hooks(db).await.unwrap();
369 let cert = x509_cert::Certificate::from_pem(EXAMPLE_CERT_PEM).unwrap();
370 pki_env.add_trust_anchor(&tx, cert.clone()).await.unwrap();
371
372 let certs = pki_env.get_trust_anchors().await;
373 assert_eq!(certs.len(), 1);
374
375 pki_env
376 .remove_trust_anchor(
377 &tx,
378 &certs[0]
379 .tbs_certificate
380 .subject_public_key_info
381 .fingerprint_bytes()
382 .expect("Getting fingerprint of subject plublic key info"),
383 )
384 .await
385 .unwrap();
386 assert_eq!(pki_env.get_trust_anchors().await.len(), 0);
387 }
388
389 #[tokio::test]
390 async fn can_add_intermediate_cert() {
391 let db = Database::open_in_memory().unwrap();
392 let tx = db.new_transaction().await.unwrap();
393 let pki_env = PkiEnvironment::with_dummy_hooks(db).await.unwrap();
394 let cert = x509_cert::Certificate::from_pem(EXAMPLE_CERT_PEM).unwrap();
395 assert!(pki_env.add_intermediate_cert(&tx, cert).await.is_ok());
396 }
397}