core_crypto/e2e_identity/
init_certificates.rs

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