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