core_crypto/transaction_context/e2e_identity/
init_certificates.rs

1use super::{Error, Result};
2use crate::e2e_identity::E2eiDumpedPkiEnv;
3use crate::{
4    KeystoreError, MlsError, RecursiveError,
5    e2e_identity::{CrlRegistration, NewCrlDistributionPoints, restore_pki_env},
6    transaction_context::TransactionContext,
7};
8use core_crypto_keystore::{
9    connection::FetchFromDatabase,
10    entities::{E2eiAcmeCA, E2eiCrl, E2eiIntermediateCert},
11};
12use openmls_traits::OpenMlsCryptoProvider;
13use wire_e2e_identity::prelude::x509::{
14    extract_crl_uris, extract_expiration_from_crl,
15    revocation::{PkiEnvironment, PkiEnvironmentParams},
16};
17use x509_cert::der::Decode;
18
19impl TransactionContext {
20    /// See [Client::e2ei_is_pki_env_setup].
21    /// Unlike [Client::e2ei_is_pki_env_setup], this function returns a result.
22    pub async fn e2ei_is_pki_env_setup(&self) -> Result<bool> {
23        Ok(self
24            .mls_provider()
25            .await
26            .map_err(RecursiveError::transaction("getting mls provider"))?
27            .authentication_service()
28            .is_env_setup()
29            .await)
30    }
31
32    /// See [Client::e2ei_dump_pki_env].
33    pub async fn e2ei_dump_pki_env(&self) -> Result<Option<E2eiDumpedPkiEnv>> {
34        if !self.e2ei_is_pki_env_setup().await? {
35            return Ok(None);
36        }
37        let mls_provider = self
38            .mls_provider()
39            .await
40            .map_err(RecursiveError::transaction("getting mls provider"))?;
41        let Some(pki_env) = &*mls_provider.authentication_service().borrow().await else {
42            return Ok(None);
43        };
44        E2eiDumpedPkiEnv::from_pki_env(pki_env)
45            .await
46            .map_err(RecursiveError::e2e_identity("dumping pki env"))
47            .map_err(Into::into)
48    }
49
50    /// Registers a Root Trust Anchor CA for the use in E2EI processing.
51    ///
52    /// Please note that without a Root Trust Anchor, all validations *will* fail;
53    /// So this is the first step to perform after initializing your E2EI client
54    ///
55    /// # Parameters
56    /// * `trust_anchor_pem` - PEM certificate to anchor as a Trust Root
57    pub async fn e2ei_register_acme_ca(&self, trust_anchor_pem: String) -> Result<()> {
58        {
59            if self
60                .mls_provider()
61                .await
62                .map_err(RecursiveError::transaction("getting mls provider"))?
63                .keystore()
64                .find_unique::<E2eiAcmeCA>()
65                .await
66                .is_ok()
67            {
68                return Err(Error::TrustAnchorAlreadyRegistered);
69            }
70        }
71
72        let pki_env = PkiEnvironment::init(PkiEnvironmentParams {
73            intermediates: Default::default(),
74            trust_roots: Default::default(),
75            crls: Default::default(),
76            time_of_interest: Default::default(),
77        })?;
78
79        // Parse/decode PEM cert
80        let root_cert = PkiEnvironment::decode_pem_cert(trust_anchor_pem)?;
81
82        // Validate it (expiration & signature only)
83        pki_env.validate_trust_anchor_cert(&root_cert)?;
84
85        // Save DER repr in keystore
86        let cert_der = PkiEnvironment::encode_cert_to_der(&root_cert)?;
87        let acme_ca = E2eiAcmeCA { content: cert_der };
88        self.mls_provider()
89            .await
90            .map_err(RecursiveError::transaction("getting mls provider"))?
91            .keystore()
92            .save(acme_ca)
93            .await
94            .map_err(KeystoreError::wrap("saving acme ca"))?;
95
96        // To do that, tear down and recreate the pki env
97        self.init_pki_env().await?;
98
99        Ok(())
100    }
101
102    pub(crate) async fn init_pki_env(&self) -> Result<()> {
103        if let Some(pki_env) = restore_pki_env(
104            &self
105                .mls_provider()
106                .await
107                .map_err(RecursiveError::transaction("getting mls provider"))?
108                .keystore(),
109        )
110        .await
111        .map_err(RecursiveError::e2e_identity("restoring pki env"))?
112        {
113            let provider = self
114                .mls_provider()
115                .await
116                .map_err(RecursiveError::transaction("getting mls provider"))?;
117            provider
118                .authentication_service()
119                .update_env(pki_env)
120                .await
121                .map_err(MlsError::wrap("updating authentication service env"))?;
122        }
123
124        Ok(())
125    }
126
127    /// Registers an Intermediate CA for the use in E2EI processing.
128    ///
129    /// Please note that a Root Trust Anchor CA is needed to validate Intermediate CAs;
130    /// You **need** to have a Root CA registered before calling this
131    ///
132    /// # Parameters
133    /// * `cert_pem` - PEM certificate to register as an Intermediate CA
134    pub async fn e2ei_register_intermediate_ca_pem(&self, cert_pem: String) -> Result<NewCrlDistributionPoints> {
135        // Parse/decode PEM cert
136        let inter_ca = PkiEnvironment::decode_pem_cert(cert_pem)?;
137        self.e2ei_register_intermediate_ca(inter_ca).await
138    }
139
140    pub(crate) async fn e2ei_register_intermediate_ca_der(&self, cert_der: &[u8]) -> Result<NewCrlDistributionPoints> {
141        let inter_ca = x509_cert::Certificate::from_der(cert_der)?;
142        self.e2ei_register_intermediate_ca(inter_ca).await
143    }
144
145    async fn e2ei_register_intermediate_ca(
146        &self,
147        inter_ca: x509_cert::Certificate,
148    ) -> Result<NewCrlDistributionPoints> {
149        // TrustAnchor must have been registered at this point
150        let keystore = self
151            .keystore()
152            .await
153            .map_err(RecursiveError::transaction("getting keystore"))?;
154        let trust_anchor = keystore
155            .find_unique::<E2eiAcmeCA>()
156            .await
157            .map_err(KeystoreError::wrap("finding acme ca"))?;
158        let trust_anchor = x509_cert::Certificate::from_der(&trust_anchor.content)?;
159
160        // the `/federation` endpoint from smallstep repeats the root CA
161        // so we filter it out here so that clients don't have to do it
162        if inter_ca == trust_anchor {
163            return Ok(None.into());
164        }
165
166        let intermediate_crl = extract_crl_uris(&inter_ca)?.map(|s| s.into_iter().collect());
167
168        let (ski, aki) = PkiEnvironment::extract_ski_aki_from_cert(&inter_ca)?;
169
170        let ski_aki_pair = format!("{ski}:{}", aki.unwrap_or_default());
171
172        // Validate it
173        {
174            let provider = self
175                .mls_provider()
176                .await
177                .map_err(RecursiveError::transaction("getting mls provider"))?;
178            let auth_service_arc = provider.authentication_service().borrow().await;
179            let Some(pki_env) = auth_service_arc.as_ref() else {
180                return Err(Error::PkiEnvironmentUnset);
181            };
182            pki_env.validate_cert_and_revocation(&inter_ca)?;
183        }
184
185        // Save DER repr in keystore
186        let cert_der = PkiEnvironment::encode_cert_to_der(&inter_ca)?;
187        let intermediate_ca = E2eiIntermediateCert {
188            content: cert_der,
189            ski_aki_pair,
190        };
191        keystore
192            .save(intermediate_ca)
193            .await
194            .map_err(KeystoreError::wrap("saving intermediate ca"))?;
195
196        self.init_pki_env().await?;
197
198        Ok(intermediate_crl.into())
199    }
200
201    /// Registers a CRL for the use in E2EI processing.
202    ///
203    /// Please note that a Root Trust Anchor CA is needed to validate CRLs;
204    /// You **need** to have a Root CA registered before calling this
205    ///
206    /// # Parameters
207    /// * `crl_dp` - CRL Distribution Point; Basically the URL you fetched it from
208    /// * `crl_der` - DER representation of the CRL
209    ///
210    /// # Returns
211    /// A [CrlRegistration] with the dirty state of the new CRL (see struct) and its expiration timestamp
212    pub async fn e2ei_register_crl(&self, crl_dp: String, crl_der: Vec<u8>) -> Result<CrlRegistration> {
213        // Parse & Validate CRL
214        let crl = {
215            let provider = self
216                .mls_provider()
217                .await
218                .map_err(RecursiveError::transaction("getting mls provider"))?;
219            let auth_service_arc = provider.authentication_service().borrow().await;
220            let Some(pki_env) = auth_service_arc.as_ref() else {
221                return Err(Error::PkiEnvironmentUnset);
222            };
223            pki_env.validate_crl_with_raw(&crl_der)?
224        };
225
226        let expiration = extract_expiration_from_crl(&crl);
227
228        let ks = self
229            .keystore()
230            .await
231            .map_err(RecursiveError::transaction("getting keystore"))?;
232
233        let dirty = ks
234            .find::<E2eiCrl>(crl_dp.as_bytes())
235            .await
236            .ok()
237            .flatten()
238            .map(|existing_crl| {
239                PkiEnvironment::decode_der_crl(existing_crl.content.clone())
240                    .map(|old_crl| old_crl.tbs_cert_list.revoked_certificates != crl.tbs_cert_list.revoked_certificates)
241            })
242            .transpose()?
243            .unwrap_or_default();
244
245        // Save DER repr in keystore
246        let crl_data = E2eiCrl {
247            content: PkiEnvironment::encode_crl_to_der(&crl)?,
248            distribution_point: crl_dp,
249        };
250        ks.save(crl_data).await.map_err(KeystoreError::wrap("saving crl"))?;
251
252        self.init_pki_env().await?;
253
254        Ok(CrlRegistration { expiration, dirty })
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use wasm_bindgen_test::*;
261    use x509_cert::der::EncodePem;
262
263    use crate::test_utils::*;
264
265    use super::super::Error;
266
267    wasm_bindgen_test_configure!(run_in_browser);
268
269    #[apply(all_cred_cipher)]
270    #[wasm_bindgen_test]
271    async fn register_acme_ca_should_fail_when_already_set(case: TestCase) {
272        use x509_cert::der::pem::LineEnding;
273
274        if !case.is_x509() {
275            return;
276        }
277        run_test_with_client_ids(case.clone(), ["alice"], move |[alice_central]| {
278            Box::pin(async move {
279                let alice_test_chain = alice_central.x509_test_chain.as_ref().as_ref().unwrap();
280                let alice_ta = alice_test_chain
281                    .trust_anchor
282                    .certificate
283                    .to_pem(LineEnding::CRLF)
284                    .unwrap();
285
286                assert!(matches!(
287                    alice_central.context.e2ei_register_acme_ca(alice_ta).await.unwrap_err(),
288                    Error::TrustAnchorAlreadyRegistered
289                ));
290            })
291        })
292        .await;
293    }
294
295    #[apply(all_cred_cipher)]
296    #[wasm_bindgen_test]
297    async fn x509_restore_should_not_happen_if_basic(case: TestCase) {
298        if case.is_x509() {
299            return;
300        }
301        run_test_with_client_ids(case.clone(), ["alice"], move |[alice_ctx]| {
302            Box::pin(async move {
303                let SessionContext {
304                    context,
305                    x509_test_chain,
306                    ..
307                } = alice_ctx;
308
309                assert!(x509_test_chain.is_none());
310                assert!(!context.e2ei_is_pki_env_setup().await.unwrap());
311
312                // mls_central.restore_from_disk().await.unwrap();
313
314                assert!(x509_test_chain.is_none());
315                // assert!(!mls_central.mls_backend.is_pki_env_setup().await);
316            })
317        })
318        .await;
319    }
320}