core_crypto_ffi/generic/context/
mod.rs

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