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,
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},
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: Database,
119 rjt_pki_env: Mutex<RjtPkiEnvironment>,
120}
121
122impl PkiEnvironment {
123 pub async fn new(hooks: Arc<dyn PkiEnvironmentHooks>, database: 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 async fn transactionally<T, E>(&self, operation: impl AsyncFnOnce() -> std::result::Result<T, E>) -> Result<T>
164 where
165 E: Into<Error>,
166 {
167 let created_transaction = match self.database.try_new_immediate_transaction().await {
168 Ok(()) => true,
169 Err(CryptoKeystoreError::TransactionInProgress) => false,
170 Err(err) => return Err(err.into()),
171 };
172 let operation_outcome = operation().await;
173 if created_transaction {
174 if operation_outcome.is_ok() {
175 self.database.commit_transaction().await?;
176 } else {
177 self.database.rollback_transaction().await?;
178 }
179 }
180 operation_outcome.map_err(Into::into)
181 }
182
183 pub async fn add_trust_anchor(&self, cert: Certificate) -> Result<()> {
193 self.rjt_pki_env.lock().await.validate_trust_anchor_cert(&cert)?;
195
196 let cert_data = E2eiAcmeCA {
199 content: cert.to_der()?,
200 };
201
202 self.transactionally(async || self.database.save(cert_data).await)
203 .await?;
204
205 let mut trust_anchors = TaSource::new();
206 trust_anchors.push(certval::CertFile {
207 filename: "".to_string(),
208 bytes: cert.to_der()?,
209 });
210 trust_anchors.initialize().map_err(Error::Certval)?;
211 self.rjt_pki_env
212 .lock()
213 .await
214 .add_trust_anchor_source(Box::new(trust_anchors));
215 Ok(())
216 }
217
218 pub async fn add_intermediate_cert(&self, cert: Certificate) -> Result<()> {
226 let (ski, aki) = RjtPkiEnvironment::extract_ski_aki_from_cert(&cert)?;
228 let ski_aki_pair = format!("{ski}:{}", aki.unwrap_or_default());
229 let cert_der = RjtPkiEnvironment::encode_cert_to_der(&cert)?;
230 let intermediate_cert = E2eiIntermediateCert {
231 content: cert_der,
232 ski_aki_pair,
233 };
234
235 self.transactionally(async || {
236 self.database.save(intermediate_cert).await?;
237
238 let dps: Vec<String> = extract_crl_uris(&cert)?.iter().flatten().cloned().collect();
240 let crls = self.fetch_crls(dps.iter().map(AsRef::as_ref)).await?;
241
242 for (distribution_point, crl) in &crls {
244 self.save_crl(distribution_point, crl).await?;
245 }
246
247 Result::Ok(())
248 })
249 .await?;
250
251 let cps = CertificationPathSettings::new();
252 let mut cert_source = CertSource::new();
253 cert_source.push(certval::CertFile {
254 filename: "".to_string(),
255 bytes: cert.to_der()?,
256 });
257
258 cert_source.initialize(&cps).map_err(Error::Certval)?;
259 self.rjt_pki_env
260 .lock()
261 .await
262 .add_certificate_source(Box::new(cert_source));
263
264 Ok(())
265 }
266
267 pub async fn validate_cert(&self, cert: &x509_cert::Certificate) -> RustyX509CheckResult<()> {
274 self.rjt_pki_env.lock().await.validate_cert_and_revocation(cert)
275 }
276
277 pub async fn validate_credential<'a>(&'a self, credential: CredentialRef<'a>) -> CredentialAuthenticationStatus {
283 let CredentialRef::X509 { certificates } = credential else {
284 panic!("this function can only be called with an X509 credential");
285 };
286
287 let Some(cert) = certificates
288 .first()
289 .and_then(|cert_raw| x509_cert::Certificate::from_der(cert_raw).ok())
290 else {
291 return CredentialAuthenticationStatus::Invalid;
292 };
293
294 match self.rjt_pki_env.lock().await.validate_cert_and_revocation(&cert) {
295 Err(RustyX509CheckError::CertValError(CertvalError::PathValidation(
296 PathValidationStatus::CertificateRevoked
297 | PathValidationStatus::CertificateRevokedEndEntity
298 | PathValidationStatus::CertificateRevokedIntermediateCa,
299 ))) => {
300 CredentialAuthenticationStatus::Valid
303 }
304 Err(RustyX509CheckError::CertValError(CertvalError::PathValidation(
305 PathValidationStatus::InvalidNotAfterDate,
306 ))) => {
307 CredentialAuthenticationStatus::Valid
310 }
311 Err(RustyX509CheckError::CertValError(CertvalError::PathValidation(_))) => {
312 CredentialAuthenticationStatus::Invalid
313 }
314 Err(_) => CredentialAuthenticationStatus::Unknown,
315 Ok(_) => CredentialAuthenticationStatus::Valid,
316 }
317 }
318}