core_crypto/mls/conversation/
wipe.rs

1use crate::context::CentralContext;
2use crate::prelude::{ConversationId, CryptoResult, MlsConversation, MlsError};
3use core_crypto_keystore::CryptoKeystoreMls;
4use mls_crypto_provider::MlsCryptoProvider;
5use openmls_traits::OpenMlsCryptoProvider;
6
7impl CentralContext {
8    /// Destroys a group locally
9    ///
10    /// # Errors
11    /// KeyStore errors, such as IO
12    #[cfg_attr(test, crate::dispotent)]
13    pub async fn wipe_conversation(&self, id: &ConversationId) -> CryptoResult<()> {
14        let provider = self.mls_provider().await?;
15        self.get_conversation(id)
16            .await?
17            .write()
18            .await
19            .wipe_associated_entities(&provider)
20            .await?;
21        provider.key_store().mls_group_delete(id).await?;
22        let _ = self.mls_groups().await?.remove(id);
23        Ok(())
24    }
25}
26
27impl MlsConversation {
28    async fn wipe_associated_entities(&mut self, backend: &MlsCryptoProvider) -> CryptoResult<()> {
29        // the own client may or may not have generated an epoch keypair in the previous epoch
30        // Since it is a terminal operation, ignoring the error is fine here.
31        let _ = self.group.delete_previous_epoch_keypairs(backend).await;
32
33        let pending_proposals = self.group.pending_proposals().cloned().collect::<Vec<_>>();
34        for proposal in pending_proposals {
35            // Update proposals rekey the own leaf node. Hence the associated encryption keypair has to be cleared
36            self.group
37                .remove_pending_proposal(backend.key_store(), proposal.proposal_reference())
38                .await
39                .map_err(MlsError::from)?;
40        }
41
42        Ok(())
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use wasm_bindgen_test::*;
49
50    use crate::{prelude::CryptoError, test_utils::*};
51
52    wasm_bindgen_test_configure!(run_in_browser);
53
54    #[apply(all_cred_cipher)]
55    #[wasm_bindgen_test]
56    async fn can_wipe_group(case: TestCase) {
57        run_test_with_central(case.clone(), move |[central]| {
58            Box::pin(async move {
59                let id = conversation_id();
60                central
61                    .context
62                    .new_conversation(&id, case.credential_type, case.cfg.clone())
63                    .await
64                    .unwrap();
65                assert!(central.get_conversation_unchecked(&id).await.group.is_active());
66
67                central.context.wipe_conversation(&id).await.unwrap();
68                assert!(!central.context.conversation_exists(&id).await.unwrap());
69            })
70        })
71        .await;
72    }
73
74    #[apply(all_cred_cipher)]
75    #[wasm_bindgen_test]
76    async fn cannot_wipe_group_non_existent(case: TestCase) {
77        run_test_with_central(case.clone(), move |[central]| {
78            Box::pin(async move {
79                let id = conversation_id();
80                let err = central.context.wipe_conversation(&id).await.unwrap_err();
81                assert!(matches!(err, CryptoError::ConversationNotFound(conv_id) if conv_id == id));
82            })
83        })
84        .await;
85    }
86
87    // should delete anything related to this conversation
88    #[apply(all_cred_cipher)]
89    #[wasm_bindgen_test]
90    async fn should_cascade_deletion(case: TestCase) {
91        run_test_with_client_ids(case.clone(), ["alice"], move |[cc]| {
92            Box::pin(async move {
93                let id = conversation_id();
94                cc.context
95                    .new_conversation(&id, case.credential_type, case.cfg.clone())
96                    .await
97                    .unwrap();
98                let initial_count = cc.context.count_entities().await;
99
100                cc.context.new_update_proposal(&id).await.unwrap();
101                let post_proposal_count = cc.context.count_entities().await;
102                assert_eq!(
103                    post_proposal_count.encryption_keypair,
104                    initial_count.encryption_keypair + 1
105                );
106
107                cc.context.wipe_conversation(&id).await.unwrap();
108
109                let final_count = cc.context.count_entities().await;
110                assert_eq!(final_count.group, 0);
111                assert_eq!(final_count.encryption_keypair, final_count.key_package);
112                assert_eq!(final_count.epoch_encryption_keypair, 0);
113            })
114        })
115        .await
116    }
117}