core_crypto/e2e_identity/
stash.rs

1use openmls_traits::{random::OpenMlsRand, OpenMlsCryptoProvider};
2
3use crate::context::CentralContext;
4use crate::prelude::{CryptoError, CryptoResult, E2eiEnrollment};
5use core_crypto_keystore::CryptoKeystoreMls;
6use mls_crypto_provider::MlsCryptoProvider;
7
8/// A unique identifier for an enrollment a consumer can use to fetch it from the keystore when he
9/// wants to resume the process
10pub(crate) type EnrollmentHandle = Vec<u8>;
11
12impl E2eiEnrollment {
13    pub(crate) async fn stash(self, backend: &MlsCryptoProvider) -> CryptoResult<EnrollmentHandle> {
14        // should be enough to prevent collisions
15        const HANDLE_SIZE: usize = 32;
16
17        let content = serde_json::to_vec(&self)?;
18        let handle = backend.crypto().random_vec(HANDLE_SIZE).map_err(CryptoError::from)?;
19        backend
20            .key_store()
21            .save_e2ei_enrollment(&handle, &content)
22            .await
23            .map_err(CryptoError::from)?;
24        Ok(handle)
25    }
26
27    pub(crate) async fn stash_pop(backend: &MlsCryptoProvider, handle: EnrollmentHandle) -> CryptoResult<Self> {
28        let content = backend
29            .key_store()
30            .pop_e2ei_enrollment(&handle)
31            .await
32            .map_err(CryptoError::from)?;
33        Ok(serde_json::from_slice(&content)?)
34    }
35}
36
37impl CentralContext {
38    /// Allows persisting an active enrollment (for example while redirecting the user during OAuth)
39    /// in order to resume it later with [CentralContext::e2ei_enrollment_stash_pop]
40    ///
41    /// # Arguments
42    /// * `enrollment` - the enrollment instance to persist
43    ///
44    /// # Returns
45    /// A handle for retrieving the enrollment later on
46    pub async fn e2ei_enrollment_stash(&self, enrollment: E2eiEnrollment) -> CryptoResult<EnrollmentHandle> {
47        enrollment.stash(&self.mls_provider().await?).await
48    }
49
50    /// Fetches the persisted enrollment and deletes it from the keystore
51    ///
52    /// # Arguments
53    /// * `handle` - returned by [CentralContext::e2ei_enrollment_stash]
54    pub async fn e2ei_enrollment_stash_pop(&self, handle: EnrollmentHandle) -> CryptoResult<E2eiEnrollment> {
55        E2eiEnrollment::stash_pop(&self.mls_provider().await?, handle).await
56    }
57}
58
59#[cfg(test)]
60mod tests {
61
62    use mls_crypto_provider::MlsCryptoProvider;
63    use wasm_bindgen_test::*;
64
65    use crate::{
66        e2e_identity::id::WireQualifiedClientId,
67        e2e_identity::tests::*,
68        prelude::{E2eiEnrollment, INITIAL_KEYING_MATERIAL_COUNT},
69        test_utils::{x509::X509TestChain, *},
70    };
71
72    wasm_bindgen_test_configure!(run_in_browser);
73
74    #[apply(all_cred_cipher)]
75    #[wasm_bindgen_test]
76    async fn stash_and_pop_should_not_abort_enrollment(case: TestCase) {
77        run_test_wo_clients(case.clone(), move |mut cc| {
78            Box::pin(async move {
79                let x509_test_chain = X509TestChain::init_empty(case.signature_scheme());
80
81                let is_renewal = false;
82                let (mut enrollment, cert) = e2ei_enrollment(
83                    &mut cc,
84                    &case,
85                    &x509_test_chain,
86                    Some(E2EI_CLIENT_ID_URI),
87                    is_renewal,
88                    init_enrollment,
89                    |e, cc| {
90                        Box::pin(async move {
91                            let handle = cc.e2ei_enrollment_stash(e).await.unwrap();
92                            cc.e2ei_enrollment_stash_pop(handle).await.unwrap()
93                        })
94                    },
95                )
96                .await
97                .unwrap();
98
99                assert!(cc
100                    .context
101                    .e2ei_mls_init_only(&mut enrollment, cert, Some(INITIAL_KEYING_MATERIAL_COUNT))
102                    .await
103                    .is_ok());
104            })
105        })
106        .await
107    }
108
109    // this ensures the nominal test does its job
110    #[apply(all_cred_cipher)]
111    #[wasm_bindgen_test]
112    async fn should_fail_when_restoring_invalid(case: TestCase) {
113        run_test_wo_clients(case.clone(), move |mut cc| {
114            Box::pin(async move {
115                let x509_test_chain = X509TestChain::init_empty(case.signature_scheme());
116
117                let is_renewal = false;
118                let result = e2ei_enrollment(
119                    &mut cc,
120                    &case,
121                    &x509_test_chain,
122                    Some(E2EI_CLIENT_ID_URI),
123                    is_renewal,
124                    init_enrollment,
125                    move |e, _cc| {
126                        Box::pin(async move {
127                            // this restore recreates a partial enrollment
128                            let backend = MlsCryptoProvider::try_new_in_memory("new").await.unwrap();
129                            backend.new_transaction().await.unwrap();
130                            let client_id = e.client_id.parse::<WireQualifiedClientId>().unwrap();
131                            E2eiEnrollment::try_new(
132                                client_id.into(),
133                                e.display_name,
134                                e.handle,
135                                e.team,
136                                1,
137                                &backend,
138                                e.ciphersuite,
139                                None,
140                                #[cfg(not(target_family = "wasm"))]
141                                None,
142                            )
143                            .unwrap()
144                        })
145                    },
146                )
147                .await;
148                assert!(result.is_err());
149            })
150        })
151        .await
152    }
153}