Skip to main content

wire_e2e_identity/pki_env/
mod.rs

1//! PKI Environment API
2
3mod 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/// New Certificate Revocation List distribution points.
61#[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/// The PKI environment which can be initialized independently from a CoreCrypto session.
113#[derive(Debug)]
114pub struct PkiEnvironment {
115    /// Implemented by the clients and used by us to make external calls during e2e flow
116    hooks: Arc<dyn PkiEnvironmentHooks>,
117    /// The database in which X509 Credentials are stored.
118    database: Database,
119    rjt_pki_env: Mutex<RjtPkiEnvironment>,
120}
121
122impl PkiEnvironment {
123    /// Create a new PKI Environment
124    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    /// Return certificates that are used as trust anchors.
134    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    /// Get the hooks.
148    pub fn hooks(&self) -> Arc<dyn PkiEnvironmentHooks> {
149        self.hooks.clone()
150    }
151
152    /// Get the database.
153    pub fn database(&self) -> &Database {
154        &self.database
155    }
156
157    /// Wrap an operation which requires a transaction.
158    ///
159    /// If a transaction does not already exist, creates one.
160    ///
161    /// After the operation finishes, if we created a transaction, then either
162    /// commit or rollback the operation according to the operation's success.
163    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    /// Adds the certificate as a trust anchor to the PKI environment.
184    ///
185    /// The certificate is saved to the database, and included in the PKI environment for
186    /// future validation.
187    ///
188    /// # Caution
189    ///
190    /// Adding a trust anchor will replace any existing trust anchor. This limitation
191    /// will be relaxed in the future.
192    pub async fn add_trust_anchor(&self, cert: Certificate) -> Result<()> {
193        // Validate it (expiration & signature only)
194        self.rjt_pki_env.lock().await.validate_trust_anchor_cert(&cert)?;
195
196        // Save cert's DER representation to the database
197        // TODO: make this work for multiple trust anchors, see WPB-25632
198        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    /// Adds the certificate to the PKI environment.
219    ///
220    /// The certificate is saved to the database, and included in the PKI environment for
221    /// future validation.
222    ///
223    /// CRL (Certificate Revocation List) distribution points are extracted from the certificate and
224    /// an attempt is made to fetch a CRL from each one.
225    pub async fn add_intermediate_cert(&self, cert: Certificate) -> Result<()> {
226        // Save cert's DER representation to the database
227        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            // Get CRL distribution points and CRLs
239            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            // Save all CRLs to the database
243            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    /// Validate an end-entity X509 certificate.
268    ///
269    /// Performs validation of the provided certificate in the context
270    /// defined by the set of trust anchors and intermediate certificates
271    /// contained in this PKI environment. Revocation check is performed
272    /// and time of interest is set to the time of the call.
273    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    /// Validate an X509 credential.
278    ///
279    /// # Panics
280    ///
281    /// Panics if the provided credential is not of type X509.
282    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                // ? Revoked credentials are A-OK. They still degrade conversations though.
301                // TODO: update this after WPB-25524
302                CredentialAuthenticationStatus::Valid
303            }
304            Err(RustyX509CheckError::CertValError(CertvalError::PathValidation(
305                PathValidationStatus::InvalidNotAfterDate,
306            ))) => {
307                // ? Expired credentials are A-OK. They still degrade conversations though.
308                // TODO: update this after WPB-25524
309                CredentialAuthenticationStatus::Valid
310            }
311            Err(RustyX509CheckError::CertValError(CertvalError::PathValidation(_))) => {
312                CredentialAuthenticationStatus::Invalid
313            }
314            Err(_) => CredentialAuthenticationStatus::Unknown,
315            Ok(_) => CredentialAuthenticationStatus::Valid,
316        }
317    }
318}