core_crypto_ffi/generic/context/
mod.rs

1use super::{
2    Ciphersuite, Ciphersuites, ClientId, ConversationConfiguration, CoreCrypto, CoreCryptoError, CoreCryptoResult,
3    CustomConfiguration, DecryptedMessage, MlsCredentialType, WelcomeBundle,
4};
5use crate::NewCrlDistributionPoints;
6use async_lock::{Mutex, OnceCell};
7use core_crypto::mls::conversation::Conversation as _;
8use core_crypto::mls::conversation::Error as ConversationError;
9use core_crypto::{
10    RecursiveError,
11    context::CentralContext,
12    prelude::{
13        ClientIdentifier, ConversationId, KeyPackageIn, KeyPackageRef, MlsConversationConfiguration,
14        VerifiableGroupInfo,
15    },
16};
17use std::{future::Future, ops::Deref, sync::Arc};
18use tls_codec::{Deserialize, Serialize};
19
20pub mod e2ei;
21pub mod proteus;
22
23#[derive(uniffi::Object)]
24pub struct CoreCryptoContext {
25    pub(super) context: Arc<CentralContext>,
26}
27
28impl Deref for CoreCryptoContext {
29    type Target = CentralContext;
30
31    fn deref(&self) -> &Self::Target {
32        self.context.as_ref()
33    }
34}
35
36#[uniffi::export(with_foreign)]
37#[async_trait::async_trait]
38pub trait CoreCryptoCommand: Send + Sync {
39    /// Will be called inside a transaction in CoreCrypto
40    async fn execute(&self, context: Arc<CoreCryptoContext>) -> CoreCryptoResult<()>;
41}
42
43#[async_trait::async_trait]
44impl<F, Fut> CoreCryptoCommand for F
45where
46    F: Fn(Arc<CoreCryptoContext>) -> Fut + Send + Sync,
47    Fut: Future<Output = CoreCryptoResult<()>> + Send,
48{
49    async fn execute(&self, context: Arc<CoreCryptoContext>) -> CoreCryptoResult<()> {
50        self(context).await
51    }
52}
53
54/// Helper for working with the new transasction interface.
55///
56/// This helper serves two purposes: to present a `FnOnce` interface for transactions,
57/// and to allow the extraction of data from within transactions.
58///
59/// ## Extracting Data
60///
61/// The `CoreCryptoCommand` interface requires some kind of interior mutability to extract
62/// any data: it takes an immutable reference to the implementing item, and returns the unit struct
63/// in the success case.
64///
65/// That pattern is relatively arcane and verbose, particularly when we just want to smuggle out
66/// some data from within the transaction. This helper is intended to ease and automate
67/// that process.
68///
69/// Use it like this (pseudocode):
70///
71/// ```ignore
72/// // an extractor is always `Arc`-wrapped
73/// let extractor: Arc<_> = TransactionHelper::new(move |context| async move {
74///     // return whatever you need from the transaction here
75/// });
76/// core_crypto.transaction(extractor.clone()).await?;
77/// let return_value = extractor.into_return_value();
78/// ```
79///
80/// ## Panics
81///
82/// `TransactionHelper` is a one-shot item. Attempting to use the
83/// same extractor in two different transactions will cause a panic.
84pub struct TransactionHelper<T, F> {
85    func: Mutex<Option<F>>,
86    return_value: OnceCell<T>,
87}
88
89impl<T, F, Fut> TransactionHelper<T, F>
90where
91    F: FnOnce(Arc<CoreCryptoContext>) -> Fut + Send + Sync,
92    Fut: Future<Output = CoreCryptoResult<T>> + Send,
93    T: Send + Sync,
94{
95    pub fn new(func: F) -> Arc<Self> {
96        Arc::new(Self {
97            func: Mutex::new(Some(func)),
98            return_value: OnceCell::new(),
99        })
100    }
101
102    /// Get the return value from the internal function.
103    ///
104    /// ## Panics
105    ///
106    /// - If there exists more than one strong reference to this extractor
107    /// - If the inner function was never called
108    /// - If the inner function returned an `Err` variant
109    ///
110    /// In general if you call this after a call like
111    ///
112    /// ```ignore
113    /// core_crypto.transaction(extractor.clone())?;
114    /// ```
115    ///
116    /// then this will be fine.
117    pub fn into_return_value(self: Arc<Self>) -> T {
118        Arc::into_inner(self)
119            .expect("there should exist exactly one strong ref right now")
120            .return_value
121            .into_inner()
122            .expect("return value should be initialized")
123    }
124
125    /// Safely get the return value from the internal function.
126    ///
127    /// If there exists more than one strong reference to this item, or
128    /// the inner function was never called or returned an `Err` variant,
129    /// this will return `None`.
130    pub fn try_into_return_value(self: Arc<Self>) -> Option<T> {
131        Arc::into_inner(self)?.return_value.into_inner()
132    }
133}
134
135#[async_trait::async_trait]
136impl<T, F, Fut> CoreCryptoCommand for TransactionHelper<T, F>
137where
138    F: FnOnce(Arc<CoreCryptoContext>) -> Fut + Send + Sync,
139    Fut: Future<Output = CoreCryptoResult<T>> + Send,
140    T: Send + Sync,
141{
142    async fn execute(&self, context: Arc<CoreCryptoContext>) -> CoreCryptoResult<()> {
143        let func = self
144            .func
145            .lock()
146            .await
147            .take()
148            .expect("inner function must only be called once");
149        let return_value = func(context).await?;
150        let set_result = self.return_value.set(return_value).await;
151        if set_result.is_err() {
152            // can't just `.expect()` here because `T` is not `Debug`
153            // though TBH this would be a really weird case; we should already have
154            // paniced getting `func` above
155            panic!("return value was previously set");
156        }
157        Ok(())
158    }
159}
160
161#[uniffi::export]
162impl CoreCrypto {
163    /// Starts a new transaction in Core Crypto. If the callback succeeds, it will be committed,
164    /// otherwise, every operation performed with the context will be discarded.
165    ///
166    /// When calling this function from within Rust, async functions accepting a context
167    /// implement `CoreCryptoCommand`, so operations can be defined inline as follows:
168    ///
169    /// ```ignore
170    /// core_crypto.transaction(Arc::new(|context| async {
171    ///     // your implementation here
172    ///     Ok(())
173    /// }))?;
174    /// ```
175    pub async fn transaction(&self, command: Arc<dyn CoreCryptoCommand>) -> CoreCryptoResult<()> {
176        let context = Arc::new(CoreCryptoContext {
177            context: Arc::new(self.central.new_transaction().await?),
178        });
179
180        let result = command.execute(context.clone()).await;
181        match result {
182            Ok(result) => {
183                context.context.finish().await?;
184                Ok(result)
185            }
186            Err(err) => {
187                context.context.abort().await?;
188                Err(err)
189            }
190        }
191    }
192}
193
194#[uniffi::export]
195impl CoreCryptoContext {
196    /// See [core_crypto::context::CentralContext::set_data].
197    pub async fn set_data(&self, data: Vec<u8>) -> CoreCryptoResult<()> {
198        self.context.set_data(data).await.map_err(Into::into)
199    }
200
201    /// See [core_crypto::context::CentralContext::get_data].
202    pub async fn get_data(&self) -> CoreCryptoResult<Option<Vec<u8>>> {
203        self.context.get_data().await.map_err(Into::into)
204    }
205
206    /// See [core_crypto::context::CentralContext::mls_init]
207    pub async fn mls_init(
208        &self,
209        client_id: ClientId,
210        ciphersuites: Ciphersuites,
211        nb_key_package: Option<u32>,
212    ) -> CoreCryptoResult<()> {
213        let nb_key_package = nb_key_package
214            .map(usize::try_from)
215            .transpose()
216            .map_err(CoreCryptoError::generic())?;
217        self.context
218            .mls_init(
219                ClientIdentifier::Basic(client_id.0),
220                (&ciphersuites).into(),
221                nb_key_package,
222            )
223            .await?;
224        Ok(())
225    }
226
227    /// See [core_crypto::context::CentralContext::mls_generate_keypairs]
228    pub async fn mls_generate_keypairs(&self, ciphersuites: Ciphersuites) -> CoreCryptoResult<Vec<ClientId>> {
229        Ok(self
230            .context
231            .mls_generate_keypairs((&ciphersuites).into())
232            .await
233            .map(|cids| cids.into_iter().map(ClientId).collect())?)
234    }
235
236    /// See [core_crypto::context::CentralContext::mls_init_with_client_id]
237    pub async fn mls_init_with_client_id(
238        &self,
239        client_id: ClientId,
240        tmp_client_ids: Vec<ClientId>,
241        ciphersuites: Ciphersuites,
242    ) -> CoreCryptoResult<()> {
243        Ok(self
244            .context
245            .mls_init_with_client_id(
246                client_id.0,
247                tmp_client_ids.into_iter().map(|cid| cid.0).collect(),
248                (&ciphersuites).into(),
249            )
250            .await?)
251    }
252
253    /// See [core_crypto::mls::MlsCentral::client_public_key]
254    pub async fn client_public_key(
255        &self,
256        ciphersuite: Ciphersuite,
257        credential_type: MlsCredentialType,
258    ) -> CoreCryptoResult<Vec<u8>> {
259        Ok(self
260            .context
261            .client_public_key(ciphersuite.into(), credential_type.into())
262            .await?)
263    }
264
265    /// See [core_crypto::mls::conversation::ConversationGuard::epoch]
266    pub async fn conversation_epoch(&self, conversation_id: Vec<u8>) -> CoreCryptoResult<u64> {
267        let conversation = self.context.conversation(&conversation_id).await?;
268        Ok(conversation.epoch().await)
269    }
270
271    /// See [core_crypto::mls::conversation::ConversationGuard::ciphersuite]
272    pub async fn conversation_ciphersuite(&self, conversation_id: &ConversationId) -> CoreCryptoResult<Ciphersuite> {
273        let cs = self.context.conversation(conversation_id).await?.ciphersuite().await;
274        Ok(Ciphersuite::from(core_crypto::prelude::CiphersuiteName::from(cs)))
275    }
276
277    /// See [core_crypto::mls::MlsCentral::conversation_exists]
278    pub async fn conversation_exists(&self, conversation_id: Vec<u8>) -> CoreCryptoResult<bool> {
279        Ok(self.context.conversation_exists(&conversation_id).await?)
280    }
281
282    /// See [core_crypto::mls::conversation::ImmutableConversation::get_client_ids]
283    pub async fn get_client_ids(&self, conversation_id: Vec<u8>) -> CoreCryptoResult<Vec<ClientId>> {
284        let client_ids = self
285            .context
286            .conversation(&conversation_id)
287            .await?
288            .get_client_ids()
289            .await
290            .into_iter()
291            .map(ClientId)
292            .collect();
293        Ok(client_ids)
294    }
295
296    /// See [core_crypto::mls::conversation::ImmutableConversation::export_secret_key]
297    pub async fn export_secret_key(&self, conversation_id: Vec<u8>, key_length: u32) -> CoreCryptoResult<Vec<u8>> {
298        self.context
299            .conversation(&conversation_id)
300            .await?
301            .export_secret_key(key_length as usize)
302            .await
303            .map_err(Into::into)
304    }
305
306    /// See [core_crypto::mls::conversation::ImmutableConversation::get_external_sender]
307    pub async fn get_external_sender(&self, conversation_id: Vec<u8>) -> CoreCryptoResult<Vec<u8>> {
308        self.context
309            .conversation(&conversation_id)
310            .await?
311            .get_external_sender()
312            .await
313            .map_err(Into::into)
314    }
315
316    /// See [core_crypto::context::CentralContext::get_or_create_client_keypackages]
317    pub async fn client_keypackages(
318        &self,
319        ciphersuite: Ciphersuite,
320        credential_type: MlsCredentialType,
321        amount_requested: u32,
322    ) -> CoreCryptoResult<Vec<Vec<u8>>> {
323        let kps = self
324            .context
325            .get_or_create_client_keypackages(ciphersuite.into(), credential_type.into(), amount_requested as usize)
326            .await
327            .map_err(RecursiveError::mls_client("getting or creating client keypackages"))?;
328
329        kps.into_iter()
330            .map(|kp| {
331                kp.tls_serialize_detached()
332                    .map_err(core_crypto::mls::conversation::Error::tls_serialize("keypackage"))
333                    .map_err(RecursiveError::mls_conversation("serializing keypackage"))
334                    .map_err(Into::into)
335            })
336            .collect::<CoreCryptoResult<Vec<Vec<u8>>>>()
337    }
338
339    /// See [core_crypto::context::CentralContext::client_valid_key_packages_count]
340    pub async fn client_valid_keypackages_count(
341        &self,
342        ciphersuite: Ciphersuite,
343        credential_type: MlsCredentialType,
344    ) -> CoreCryptoResult<u64> {
345        let count = self
346            .context
347            .client_valid_key_packages_count(ciphersuite.into(), credential_type.into())
348            .await
349            .map_err(RecursiveError::mls_client("counting client valid keypackages"))?;
350
351        Ok(count.try_into().unwrap_or(0))
352    }
353
354    /// See [core_crypto::context::CentralContext::delete_keypackages]
355    pub async fn delete_keypackages(&self, refs: Vec<Vec<u8>>) -> CoreCryptoResult<()> {
356        let refs = refs
357            .into_iter()
358            .map(|r| KeyPackageRef::from_slice(&r))
359            .collect::<Vec<_>>();
360
361        self.context
362            .delete_keypackages(&refs[..])
363            .await
364            .map_err(RecursiveError::mls_client("deleting keypackages"))?;
365        Ok(())
366    }
367
368    /// See [core_crypto::context::CentralContext::new_conversation]
369    pub async fn create_conversation(
370        &self,
371        conversation_id: Vec<u8>,
372        creator_credential_type: MlsCredentialType,
373        config: ConversationConfiguration,
374    ) -> CoreCryptoResult<()> {
375        let mut lower_cfg = MlsConversationConfiguration {
376            custom: config.custom.into(),
377            ciphersuite: config.ciphersuite.into(),
378            ..Default::default()
379        };
380
381        self.context
382            .set_raw_external_senders(&mut lower_cfg, config.external_senders)
383            .await?;
384
385        self.context
386            .new_conversation(&conversation_id, creator_credential_type.into(), lower_cfg)
387            .await?;
388        Ok(())
389    }
390
391    /// See [core_crypto::context::CentralContext::process_raw_welcome_message]
392    pub async fn process_welcome_message(
393        &self,
394        welcome_message: Vec<u8>,
395        custom_configuration: CustomConfiguration,
396    ) -> CoreCryptoResult<WelcomeBundle> {
397        let result = self
398            .context
399            .process_raw_welcome_message(welcome_message, custom_configuration.into())
400            .await?
401            .into();
402        Ok(result)
403    }
404
405    /// See [core_crypto::mls::conversation::conversation_guard::ConversationGuard::add_members]
406    pub async fn add_clients_to_conversation(
407        &self,
408        conversation_id: Vec<u8>,
409        key_packages: Vec<Vec<u8>>,
410    ) -> CoreCryptoResult<NewCrlDistributionPoints> {
411        let key_packages = key_packages
412            .into_iter()
413            .map(|kp| {
414                KeyPackageIn::tls_deserialize(&mut kp.as_slice())
415                    .map_err(core_crypto::mls::conversation::Error::tls_deserialize("keypackage"))
416                    .map_err(RecursiveError::mls_conversation("adding members to conversation"))
417                    .map_err(Into::into)
418            })
419            .collect::<CoreCryptoResult<Vec<_>>>()?;
420
421        let distribution_points: Option<Vec<_>> = self
422            .context
423            .conversation(&conversation_id)
424            .await?
425            .add_members(key_packages)
426            .await?
427            .into();
428        Ok(distribution_points.into())
429    }
430
431    /// See [core_crypto::context::CentralContext::remove_members_from_conversation]
432    pub async fn remove_clients_from_conversation(
433        &self,
434        conversation_id: Vec<u8>,
435        clients: Vec<ClientId>,
436    ) -> CoreCryptoResult<()> {
437        let clients: Vec<core_crypto::prelude::ClientId> = clients.into_iter().map(|c| c.0).collect();
438        self.context
439            .conversation(&conversation_id)
440            .await?
441            .remove_members(&clients)
442            .await
443            .map_err(Into::into)
444    }
445
446    /// See [core_crypto::mls::conversation::ConversationGuard::mark_as_child_of]
447    pub async fn mark_conversation_as_child_of(&self, child_id: Vec<u8>, parent_id: Vec<u8>) -> CoreCryptoResult<()> {
448        self.context
449            .conversation(&child_id)
450            .await?
451            .mark_as_child_of(&parent_id)
452            .await
453            .map_err(Into::into)
454    }
455
456    /// See [core_crypto::context::CentralContext::update_keying_material]
457    pub async fn update_keying_material(&self, conversation_id: Vec<u8>) -> CoreCryptoResult<()> {
458        self.context
459            .conversation(&conversation_id)
460            .await?
461            .update_key_material()
462            .await
463            .map_err(Into::into)
464    }
465
466    /// See [core_crypto::mls::conversation::conversation_guard::ConversationGuard::commit_pending_proposals]
467    pub async fn commit_pending_proposals(&self, conversation_id: Vec<u8>) -> CoreCryptoResult<()> {
468        self.context
469            .conversation(&conversation_id)
470            .await?
471            .commit_pending_proposals()
472            .await
473            .map_err(Into::into)
474    }
475
476    /// see [core_crypto::context::CentralContext::wipe_conversation]
477    pub async fn wipe_conversation(&self, conversation_id: Vec<u8>) -> CoreCryptoResult<()> {
478        self.context.wipe_conversation(&conversation_id).await?;
479        Ok(())
480    }
481
482    /// See [core_crypto::mls::conversation::conversation_guard::ConversationGuard::decrypt_message]
483    pub async fn decrypt_message(
484        &self,
485        conversation_id: Vec<u8>,
486        payload: Vec<u8>,
487    ) -> CoreCryptoResult<DecryptedMessage> {
488        let result = self
489            .context
490            .conversation(&conversation_id)
491            .await?
492            .decrypt_message(&payload)
493            .await;
494        let decrypted_message = if let Err(ConversationError::PendingConversation(mut pending)) = result {
495            pending.try_process_own_join_commit(&payload).await
496        } else {
497            result
498        }?;
499
500        decrypted_message.try_into()
501    }
502
503    /// See [core_crypto::mls::conversation::conversation_guard::ConversationGuard::encrypt_message]
504    pub async fn encrypt_message(&self, conversation_id: Vec<u8>, message: Vec<u8>) -> CoreCryptoResult<Vec<u8>> {
505        self.context
506            .conversation(&conversation_id)
507            .await?
508            .encrypt_message(message)
509            .await
510            .map_err(Into::into)
511    }
512
513    /// See [core_crypto::context::CentralContext::join_by_external_commit]
514    pub async fn join_by_external_commit(
515        &self,
516        group_info: Vec<u8>,
517        custom_configuration: CustomConfiguration,
518        credential_type: MlsCredentialType,
519    ) -> CoreCryptoResult<WelcomeBundle> {
520        let group_info = VerifiableGroupInfo::tls_deserialize(&mut group_info.as_slice())
521            .map_err(core_crypto::mls::conversation::Error::tls_deserialize(
522                "verifiable group info",
523            ))
524            .map_err(RecursiveError::mls_conversation("joining by external commmit"))?;
525        Ok(self
526            .context
527            .join_by_external_commit(group_info, custom_configuration.into(), credential_type.into())
528            .await?
529            .into())
530    }
531}