core_crypto/e2e_identity/
init_certificates.rs

1use super::{Error, Result};
2use crate::{MlsError, RecursiveError, context::CentralContext, e2e_identity::CrlRegistration, prelude::MlsCentral};
3use core_crypto_keystore::{
4    connection::FetchFromDatabase,
5    entities::{E2eiAcmeCA, E2eiCrl, E2eiIntermediateCert},
6};
7use openmls_traits::OpenMlsCryptoProvider;
8use std::collections::HashSet;
9use wire_e2e_identity::prelude::x509::{
10    extract_crl_uris, extract_expiration_from_crl,
11    revocation::{PkiEnvironment, PkiEnvironmentParams},
12};
13use x509_cert::der::{Decode, EncodePem, pem::LineEnding};
14
15#[derive(Debug, Clone, derive_more::From, derive_more::Into, derive_more::Deref, derive_more::DerefMut)]
16pub struct NewCrlDistributionPoint(Option<HashSet<String>>);
17
18impl From<NewCrlDistributionPoint> for Option<Vec<String>> {
19    fn from(mut dp: NewCrlDistributionPoint) -> Self {
20        dp.take().map(|d| d.into_iter().collect())
21    }
22}
23
24#[derive(Debug, Clone)]
25/// Dump of the PKI environemnt as PEM
26pub struct E2eiDumpedPkiEnv {
27    /// Root CA in use (i.e. Trust Anchor)
28    pub root_ca: String,
29    /// Intermediate CAs that are loaded
30    pub intermediates: Vec<String>,
31    /// CRLs registered in the PKI env
32    pub crls: Vec<String>,
33}
34
35impl CentralContext {
36    /// See [MlsCentral::e2ei_is_pki_env_setup].
37    /// Unlike [MlsCentral::e2ei_is_pki_env_setup], this function returns a result.
38    pub async fn e2ei_is_pki_env_setup(&self) -> Result<bool> {
39        Ok(self
40            .mls_provider()
41            .await
42            .map_err(RecursiveError::root("getting mls provider"))?
43            .authentication_service()
44            .is_env_setup()
45            .await)
46    }
47
48    /// See [MlsCentral::e2ei_dump_pki_env].
49    pub async fn e2ei_dump_pki_env(&self) -> Result<Option<E2eiDumpedPkiEnv>> {
50        if !self.e2ei_is_pki_env_setup().await? {
51            return Ok(None);
52        }
53        let mls_provider = self
54            .mls_provider()
55            .await
56            .map_err(RecursiveError::root("getting mls provider"))?;
57        let Some(pki_env) = &*mls_provider.authentication_service().borrow().await else {
58            return Ok(None);
59        };
60        E2eiDumpedPkiEnv::from_pki_env(pki_env).await
61    }
62
63    /// Registers a Root Trust Anchor CA for the use in E2EI processing.
64    ///
65    /// Please note that without a Root Trust Anchor, all validations *will* fail;
66    /// So this is the first step to perform after initializing your E2EI client
67    ///
68    /// # Parameters
69    /// * `trust_anchor_pem` - PEM certificate to anchor as a Trust Root
70    pub async fn e2ei_register_acme_ca(&self, trust_anchor_pem: String) -> Result<()> {
71        {
72            if self
73                .mls_provider()
74                .await
75                .map_err(RecursiveError::root("getting mls provider"))?
76                .keystore()
77                .find_unique::<E2eiAcmeCA>()
78                .await
79                .is_ok()
80            {
81                return Err(Error::TrustAnchorAlreadyRegistered);
82            }
83        }
84
85        let pki_env = PkiEnvironment::init(PkiEnvironmentParams {
86            intermediates: Default::default(),
87            trust_roots: Default::default(),
88            crls: Default::default(),
89            time_of_interest: Default::default(),
90        })?;
91
92        // Parse/decode PEM cert
93        let root_cert = PkiEnvironment::decode_pem_cert(trust_anchor_pem)?;
94
95        // Validate it (expiration & signature only)
96        pki_env.validate_trust_anchor_cert(&root_cert)?;
97
98        // Save DER repr in keystore
99        let cert_der = PkiEnvironment::encode_cert_to_der(&root_cert)?;
100        let acme_ca = E2eiAcmeCA { content: cert_der };
101        self.mls_provider()
102            .await
103            .map_err(RecursiveError::root("getting mls provider"))?
104            .keystore()
105            .save(acme_ca)
106            .await?;
107
108        // To do that, tear down and recreate the pki env
109        self.init_pki_env().await?;
110
111        Ok(())
112    }
113
114    pub(crate) async fn init_pki_env(&self) -> Result<()> {
115        if let Some(pki_env) = restore_pki_env(
116            &self
117                .mls_provider()
118                .await
119                .map_err(RecursiveError::root("getting mls provider"))?
120                .keystore(),
121        )
122        .await?
123        {
124            let provider = self
125                .mls_provider()
126                .await
127                .map_err(RecursiveError::root("getting mls provider"))?;
128            provider
129                .authentication_service()
130                .update_env(pki_env)
131                .await
132                .map_err(MlsError::wrap("updating authentication service env"))?;
133        }
134
135        Ok(())
136    }
137
138    /// Registers an Intermediate CA for the use in E2EI processing.
139    ///
140    /// Please note that a Root Trust Anchor CA is needed to validate Intermediate CAs;
141    /// You **need** to have a Root CA registered before calling this
142    ///
143    /// # Parameters
144    /// * `cert_pem` - PEM certificate to register as an Intermediate CA
145    pub async fn e2ei_register_intermediate_ca_pem(&self, cert_pem: String) -> Result<NewCrlDistributionPoint> {
146        // Parse/decode PEM cert
147        let inter_ca = PkiEnvironment::decode_pem_cert(cert_pem)?;
148        self.e2ei_register_intermediate_ca(inter_ca).await
149    }
150
151    pub(crate) async fn e2ei_register_intermediate_ca_der(&self, cert_der: &[u8]) -> Result<NewCrlDistributionPoint> {
152        let inter_ca = x509_cert::Certificate::from_der(cert_der)?;
153        self.e2ei_register_intermediate_ca(inter_ca).await
154    }
155
156    async fn e2ei_register_intermediate_ca(&self, inter_ca: x509_cert::Certificate) -> Result<NewCrlDistributionPoint> {
157        // TrustAnchor must have been registered at this point
158        let keystore = self
159            .keystore()
160            .await
161            .map_err(RecursiveError::root("getting keystore"))?;
162        let trust_anchor = keystore.find_unique::<E2eiAcmeCA>().await?;
163        let trust_anchor = x509_cert::Certificate::from_der(&trust_anchor.content)?;
164
165        // the `/federation` endpoint from smallstep repeats the root CA
166        // so we filter it out here so that clients don't have to do it
167        if inter_ca == trust_anchor {
168            return Ok(None.into());
169        }
170
171        let intermediate_crl = extract_crl_uris(&inter_ca)?.map(|s| s.into_iter().collect());
172
173        let (ski, aki) = PkiEnvironment::extract_ski_aki_from_cert(&inter_ca)?;
174
175        let ski_aki_pair = format!("{ski}:{}", aki.unwrap_or_default());
176
177        // Validate it
178        {
179            let provider = self
180                .mls_provider()
181                .await
182                .map_err(RecursiveError::root("getting mls provider"))?;
183            let auth_service_arc = provider.authentication_service().borrow().await;
184            let Some(pki_env) = auth_service_arc.as_ref() else {
185                return Err(Error::PkiEnvironmentUnset);
186            };
187            pki_env.validate_cert_and_revocation(&inter_ca)?;
188        }
189
190        // Save DER repr in keystore
191        let cert_der = PkiEnvironment::encode_cert_to_der(&inter_ca)?;
192        let intermediate_ca = E2eiIntermediateCert {
193            content: cert_der,
194            ski_aki_pair,
195        };
196        keystore.save(intermediate_ca).await?;
197
198        self.init_pki_env().await?;
199
200        Ok(intermediate_crl.into())
201    }
202
203    /// Registers a CRL for the use in E2EI processing.
204    ///
205    /// Please note that a Root Trust Anchor CA is needed to validate CRLs;
206    /// You **need** to have a Root CA registered before calling this
207    ///
208    /// # Parameters
209    /// * `crl_dp` - CRL Distribution Point; Basically the URL you fetched it from
210    /// * `crl_der` - DER representation of the CRL
211    ///
212    /// # Returns
213    /// A [CrlRegistration] with the dirty state of the new CRL (see struct) and its expiration timestamp
214    pub async fn e2ei_register_crl(&self, crl_dp: String, crl_der: Vec<u8>) -> Result<CrlRegistration> {
215        // Parse & Validate CRL
216        let crl = {
217            let provider = self
218                .mls_provider()
219                .await
220                .map_err(RecursiveError::root("getting mls provider"))?;
221            let auth_service_arc = provider.authentication_service().borrow().await;
222            let Some(pki_env) = auth_service_arc.as_ref() else {
223                return Err(Error::PkiEnvironmentUnset);
224            };
225            pki_env.validate_crl_with_raw(&crl_der)?
226        };
227
228        let expiration = extract_expiration_from_crl(&crl);
229
230        let ks = self
231            .keystore()
232            .await
233            .map_err(RecursiveError::root("getting keystore"))?;
234
235        let dirty = ks
236            .find::<E2eiCrl>(crl_dp.as_bytes())
237            .await
238            .ok()
239            .flatten()
240            .map(|existing_crl| {
241                PkiEnvironment::decode_der_crl(existing_crl.content.clone())
242                    .map(|old_crl| old_crl.tbs_cert_list.revoked_certificates != crl.tbs_cert_list.revoked_certificates)
243            })
244            .transpose()?
245            .unwrap_or_default();
246
247        // Save DER repr in keystore
248        let crl_data = E2eiCrl {
249            content: PkiEnvironment::encode_crl_to_der(&crl)?,
250            distribution_point: crl_dp,
251        };
252        ks.save(crl_data).await?;
253
254        self.init_pki_env().await?;
255
256        Ok(CrlRegistration { expiration, dirty })
257    }
258}
259
260impl MlsCentral {
261    /// Returns whether the E2EI PKI environment is setup (i.e. Root CA, Intermediates, CRLs)
262    pub async fn e2ei_is_pki_env_setup(&self) -> bool {
263        self.mls_backend.is_pki_env_setup().await
264    }
265
266    /// Dumps the PKI environment as PEM
267    pub async fn e2ei_dump_pki_env(&self) -> Result<Option<E2eiDumpedPkiEnv>> {
268        if !self.e2ei_is_pki_env_setup().await {
269            return Ok(None);
270        }
271        let pki_env_lock = self.mls_backend.authentication_service().borrow().await;
272        let Some(pki_env) = &*pki_env_lock else {
273            return Ok(None);
274        };
275        E2eiDumpedPkiEnv::from_pki_env(pki_env).await
276    }
277}
278
279impl E2eiDumpedPkiEnv {
280    async fn from_pki_env(pki_env: &PkiEnvironment) -> Result<Option<E2eiDumpedPkiEnv>> {
281        let Some(root) = pki_env
282            .get_trust_anchors()
283            .map_err(Error::certificate_validation("getting pki trust anchors"))?
284            .pop()
285        else {
286            return Ok(None);
287        };
288
289        let x509_cert::anchor::TrustAnchorChoice::Certificate(root) = &root.decoded_ta else {
290            return Ok(None);
291        };
292
293        let root_ca = root.to_pem(LineEnding::LF)?;
294
295        let inner_intermediates = pki_env
296            .get_intermediates()
297            .map_err(Error::certificate_validation("getting pki intermediates"))?;
298
299        let mut intermediates = Vec::with_capacity(inner_intermediates.len());
300
301        for inter in inner_intermediates {
302            let pem_inter = inter.decoded_cert.to_pem(LineEnding::LF)?;
303            intermediates.push(pem_inter);
304        }
305
306        let inner_crls: Vec<Vec<u8>> = pki_env
307            .get_all_crls()
308            .map_err(Error::certificate_validation("getting all crls"))?;
309
310        let mut crls = Vec::with_capacity(inner_crls.len());
311        for crl in inner_crls.into_iter() {
312            let crl_pem = x509_cert::der::pem::encode_string("X509 CRL", LineEnding::LF, &crl)
313                .map_err(Error::certificate_validation("encoding crl title to pem"))?;
314            crls.push(crl_pem);
315        }
316
317        Ok(Some(E2eiDumpedPkiEnv {
318            root_ca,
319            intermediates,
320            crls,
321        }))
322    }
323}
324
325pub(crate) async fn restore_pki_env(data_provider: &impl FetchFromDatabase) -> Result<Option<PkiEnvironment>> {
326    let mut trust_roots = vec![];
327    let Ok(ta_raw) = data_provider.find_unique::<E2eiAcmeCA>().await else {
328        return Ok(None);
329    };
330
331    trust_roots.push(
332        x509_cert::Certificate::from_der(&ta_raw.content).map(x509_cert::anchor::TrustAnchorChoice::Certificate)?,
333    );
334
335    let intermediates = data_provider
336        .find_all::<E2eiIntermediateCert>(Default::default())
337        .await?
338        .into_iter()
339        .map(|inter| x509_cert::Certificate::from_der(&inter.content))
340        .collect::<Result<Vec<_>, _>>()?;
341
342    let crls = data_provider
343        .find_all::<E2eiCrl>(Default::default())
344        .await?
345        .into_iter()
346        .map(|crl| x509_cert::crl::CertificateList::from_der(&crl.content))
347        .collect::<Result<Vec<_>, _>>()?;
348
349    let params = PkiEnvironmentParams {
350        trust_roots: &trust_roots,
351        intermediates: &intermediates,
352        crls: &crls,
353        time_of_interest: None,
354    };
355
356    Ok(Some(PkiEnvironment::init(params)?))
357}
358
359#[cfg(test)]
360mod tests {
361    use wasm_bindgen_test::*;
362    use x509_cert::der::EncodePem;
363
364    use crate::test_utils::*;
365
366    use super::super::Error;
367
368    wasm_bindgen_test_configure!(run_in_browser);
369
370    #[apply(all_cred_cipher)]
371    #[wasm_bindgen_test]
372    async fn register_acme_ca_should_fail_when_already_set(case: TestCase) {
373        use x509_cert::der::pem::LineEnding;
374
375        if !case.is_x509() {
376            return;
377        }
378        run_test_with_client_ids(case.clone(), ["alice"], move |[alice_central]| {
379            Box::pin(async move {
380                let alice_test_chain = alice_central.x509_test_chain.as_ref().as_ref().unwrap();
381                let alice_ta = alice_test_chain
382                    .trust_anchor
383                    .certificate
384                    .to_pem(LineEnding::CRLF)
385                    .unwrap();
386
387                assert!(matches!(
388                    alice_central.context.e2ei_register_acme_ca(alice_ta).await.unwrap_err(),
389                    Error::TrustAnchorAlreadyRegistered
390                ));
391            })
392        })
393        .await;
394    }
395
396    #[apply(all_cred_cipher)]
397    #[wasm_bindgen_test]
398    async fn x509_restore_should_not_happen_if_basic(case: TestCase) {
399        if case.is_x509() {
400            return;
401        }
402        run_test_with_client_ids(case.clone(), ["alice"], move |[alice_ctx]| {
403            Box::pin(async move {
404                let ClientContext {
405                    context,
406                    x509_test_chain,
407                    ..
408                } = alice_ctx;
409
410                assert!(x509_test_chain.is_none());
411                assert!(!context.e2ei_is_pki_env_setup().await.unwrap());
412
413                // mls_central.restore_from_disk().await.unwrap();
414
415                assert!(x509_test_chain.is_none());
416                // assert!(!mls_central.mls_backend.is_pki_env_setup().await);
417            })
418        })
419        .await;
420    }
421}