core_crypto/mls/conversation/
export.rs1use crate::context::CentralContext;
20use crate::mls::{
21 client::id::ClientId, ConversationId, CryptoError, CryptoResult, MlsCentral, MlsConversation, MlsError,
22};
23
24impl MlsConversation {
25 const EXPORTER_LABEL: &'static str = "exporter";
26 const EXPORTER_CONTEXT: &'static [u8] = &[];
27
28 pub fn export_secret_key(
30 &self,
31 backend: &impl openmls_traits::OpenMlsCryptoProvider,
32 key_length: usize,
33 ) -> CryptoResult<Vec<u8>> {
34 self.group
35 .export_secret(backend, Self::EXPORTER_LABEL, Self::EXPORTER_CONTEXT, key_length)
36 .map_err(MlsError::from)
37 .map_err(CryptoError::from)
38 }
39
40 pub fn get_client_ids(&self) -> Vec<ClientId> {
42 self.group
43 .members()
44 .map(|kp| ClientId::from(kp.credential.identity()))
45 .collect()
46 }
47}
48
49impl CentralContext {
50 #[cfg_attr(test, crate::idempotent)]
52 pub async fn export_secret_key(
53 &self,
54 conversation_id: &ConversationId,
55 key_length: usize,
56 ) -> CryptoResult<Vec<u8>> {
57 self.get_conversation(conversation_id)
58 .await?
59 .read()
60 .await
61 .export_secret_key(&self.mls_provider().await?, key_length)
62 }
63
64 #[cfg_attr(test, crate::idempotent)]
66 pub async fn get_client_ids(&self, conversation_id: &ConversationId) -> CryptoResult<Vec<ClientId>> {
67 Ok(self
68 .get_conversation(conversation_id)
69 .await?
70 .read()
71 .await
72 .get_client_ids())
73 }
74}
75
76impl MlsCentral {
77 #[cfg_attr(test, crate::idempotent)]
87 pub async fn export_secret_key(
88 &self,
89 conversation_id: &ConversationId,
90 key_length: usize,
91 ) -> CryptoResult<Vec<u8>> {
92 self.get_conversation(conversation_id)
93 .await?
94 .ok_or_else(|| CryptoError::ConversationNotFound(conversation_id.clone()))?
95 .export_secret_key(&self.mls_backend, key_length)
96 }
97
98 #[cfg_attr(test, crate::idempotent)]
106 pub async fn get_client_ids(&self, conversation_id: &ConversationId) -> CryptoResult<Vec<ClientId>> {
107 Ok(self
108 .get_conversation(conversation_id)
109 .await?
110 .ok_or_else(|| CryptoError::ConversationNotFound(conversation_id.clone()))?
111 .get_client_ids())
112 }
113}
114
115#[cfg(test)]
116mod tests {
117
118 use crate::{
119 prelude::{CryptoError, MlsError},
120 test_utils::*,
121 };
122 use openmls::prelude::ExportSecretError;
123
124 use wasm_bindgen_test::*;
125
126 wasm_bindgen_test_configure!(run_in_browser);
127
128 mod export_secret {
129 use super::*;
130
131 #[apply(all_cred_cipher)]
132 #[wasm_bindgen_test]
133 pub async fn can_export_secret_key(case: TestCase) {
134 run_test_with_client_ids(case.clone(), ["alice"], move |[alice_central]| {
135 Box::pin(async move {
136 let id = conversation_id();
137 alice_central
138 .context
139 .new_conversation(&id, case.credential_type, case.cfg.clone())
140 .await
141 .unwrap();
142
143 let key_length = 128;
144 let result = alice_central.context.export_secret_key(&id, key_length).await;
145 assert!(result.is_ok());
146 assert_eq!(result.unwrap().len(), key_length);
147 })
148 })
149 .await
150 }
151
152 #[apply(all_cred_cipher)]
153 #[wasm_bindgen_test]
154 pub async fn cannot_export_secret_key_invalid_length(case: TestCase) {
155 run_test_with_client_ids(case.clone(), ["alice"], move |[alice_central]| {
156 Box::pin(async move {
157 let id = conversation_id();
158 alice_central
159 .context
160 .new_conversation(&id, case.credential_type, case.cfg.clone())
161 .await
162 .unwrap();
163
164 let result = alice_central.context.export_secret_key(&id, usize::MAX).await;
165 assert!(matches!(
166 result.unwrap_err(),
167 CryptoError::MlsError(MlsError::MlsExportSecretError(ExportSecretError::KeyLengthTooLong))
168 ));
169 })
170 })
171 .await
172 }
173
174 #[apply(all_cred_cipher)]
175 #[wasm_bindgen_test]
176 pub async fn cannot_export_secret_key_not_found(case: TestCase) {
177 run_test_with_client_ids(case.clone(), ["alice"], move |[alice_central]| {
178 Box::pin(async move {
179 let id = conversation_id();
180 alice_central
181 .context
182 .new_conversation(&id, case.credential_type, case.cfg.clone())
183 .await
184 .unwrap();
185
186 let unknown_id = b"not_found".to_vec();
187 let error = alice_central.context.get_client_ids(&unknown_id).await.unwrap_err();
188 assert!(matches!(error, CryptoError::ConversationNotFound(c) if c == unknown_id));
189 })
190 })
191 .await
192 }
193 }
194
195 mod get_client_ids {
196 use super::*;
197
198 #[apply(all_cred_cipher)]
199 #[wasm_bindgen_test]
200 pub async fn can_get_client_ids(case: TestCase) {
201 run_test_with_client_ids(case.clone(), ["alice", "bob"], move |[alice_central, bob_central]| {
202 Box::pin(async move {
203 let id = conversation_id();
204 alice_central
205 .context
206 .new_conversation(&id, case.credential_type, case.cfg.clone())
207 .await
208 .unwrap();
209
210 assert_eq!(alice_central.context.get_client_ids(&id).await.unwrap().len(), 1);
211
212 alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
213 assert_eq!(alice_central.context.get_client_ids(&id).await.unwrap().len(), 2);
214 })
215 })
216 .await
217 }
218
219 #[apply(all_cred_cipher)]
220 #[wasm_bindgen_test]
221 pub async fn cannot_get_client_ids_not_found(case: TestCase) {
222 run_test_with_client_ids(case.clone(), ["alice"], move |[alice_central]| {
223 Box::pin(async move {
224 let id = conversation_id();
225 alice_central
226 .context
227 .new_conversation(&id, case.credential_type, case.cfg.clone())
228 .await
229 .unwrap();
230
231 let unknown_id = b"not_found".to_vec();
232 let error = alice_central.context.get_client_ids(&unknown_id).await.unwrap_err();
233 assert!(matches!(error, CryptoError::ConversationNotFound(c) if c == unknown_id));
234 })
235 })
236 .await
237 }
238 }
239}