Skip to main content

core_crypto/mls/conversation/immutable/
mod.rs

1mod clients;
2mod commit_delay;
3mod credential;
4mod duplicate;
5mod e2ei;
6mod history_sharing;
7mod persistence;
8
9use async_lock::{RwLock, RwLockReadGuard};
10use core_crypto_keystore::{Transaction, entities::PersistedMlsGroup, traits::EntityDatabaseMutation as _};
11use openmls::group::{InnerState, MlsGroup};
12
13use super::{ConversationIdRef, Error, Result, SecretKey};
14use crate::{
15    CipherSuite, ConversationConfiguration, ConversationId, CredentialRef, ExternalSender, KeystoreError, OpenMlsError,
16    Session, mls::TntMessageCounter,
17};
18
19#[derive(derive_more::Constructor, derive_more::Deref, derive_more::DerefMut, derive_more::Debug)]
20pub(crate) struct MlsGroupState {
21    #[deref]
22    #[deref_mut]
23    group: MlsGroup,
24    #[debug(skip)]
25    tnt_message_counter: TntMessageCounter,
26}
27
28impl MlsGroupState {
29    pub(in crate::mls::conversation) fn mls_group(&self) -> &MlsGroup {
30        &self.group
31    }
32
33    pub(in crate::mls::conversation) fn mls_group_mut(&mut self) -> &mut MlsGroup {
34        &mut self.group
35    }
36
37    #[expect(unused)]
38    pub(in crate::mls::conversation) fn tnt_message_counter(&self) -> TntMessageCounter {
39        self.tnt_message_counter
40    }
41
42    /// Get the tnt message counter bound to this conversation after incrementing it.
43    pub(in crate::mls::conversation) fn obtain_tnt_message_counter(&mut self) -> Result<TntMessageCounter> {
44        self.tnt_message_counter.increment()?;
45        self.group.set_state(InnerState::Changed);
46        Ok(self.tnt_message_counter)
47    }
48
49    pub(in crate::mls::conversation) fn reset_tnt_message_counter(&mut self) {
50        self.tnt_message_counter = Default::default()
51    }
52
53    pub(crate) async fn persist(&mut self, tx: &Transaction) -> Result<()> {
54        // We must change the mls group persisted state before persisting, otherwise it will never reach the DB.
55        self.mls_group_mut().set_state(InnerState::Persisted);
56        let id = self.group.group_id();
57
58        PersistedMlsGroup {
59            id: id.to_vec(),
60            state: core_crypto_keystore::ser(self.mls_group())
61                .map_err(KeystoreError::wrap("serializing group state"))?,
62        }
63        .save(tx)
64        .map_err(KeystoreError::wrap("persisting mls group"))?;
65
66        Ok(())
67    }
68}
69
70/// A Conversation exposes the read-only interface of an MLS conversation.
71#[derive(Debug, derive_more::Constructor)]
72pub struct Conversation {
73    pub(in crate::mls::conversation) id: ConversationId,
74    pub(in crate::mls::conversation) group: RwLock<MlsGroupState>,
75    pub(in crate::mls::conversation) configuration: ConversationConfiguration,
76    session: Session,
77}
78
79impl Conversation {
80    /// Returns the conversation's ID
81    pub fn id(&self) -> &ConversationIdRef {
82        ConversationIdRef::new(&self.id)
83    }
84
85    /// Returns an immutable guard over the underlying MLS group
86    pub(crate) async fn group(&self) -> RwLockReadGuard<'_, MlsGroupState> {
87        self.group.read().await
88    }
89
90    /// Returns the conversation's configuration
91    pub fn configuration(&self) -> &ConversationConfiguration {
92        &self.configuration
93    }
94
95    /// Returns current epoch of the MLS group
96    pub async fn epoch(&self) -> u64 {
97        self.group().await.epoch().as_u64()
98    }
99
100    /// Returns this conversation's cipher suite
101    pub fn cipher_suite(&self) -> CipherSuite {
102        self.configuration.cipher_suite
103    }
104
105    /// Returns a reference to the credential used in this conversation
106    pub async fn credential_ref(&self) -> Result<CredentialRef> {
107        let credential = self
108            .find_current_credential()
109            .await
110            .map_err(|_| Error::IdentityInitializationError)?;
111        Ok(CredentialRef::from_credential(&credential))
112    }
113
114    /// Derives a new key from the one in the group, to be used elsewhere.
115    ///
116    /// # Arguments
117    /// * `key_length` - the length of the key to be derived. If the value is higher than the bounds of `u16` or the
118    ///   context hash * 255, an error will be returned
119    ///
120    /// # Errors
121    /// OpenMls secret generation error
122    pub async fn export_secret_key(&self, key_length: usize) -> Result<SecretKey> {
123        const EXPORTER_LABEL: &str = "exporter";
124        const EXPORTER_CONTEXT: &[u8] = &[];
125        self.group()
126            .await
127            .export_secret(
128                &self.session.crypto_provider,
129                EXPORTER_LABEL,
130                EXPORTER_CONTEXT,
131                key_length,
132            )
133            .map(Into::into)
134            .map_err(OpenMlsError::wrap("exporting secret key"))
135            .map_err(Into::into)
136    }
137
138    /// Returns the first external sender present in this group.
139    ///
140    /// This should be used to initialize a subconversation
141    pub async fn get_external_sender(&self) -> Result<ExternalSender> {
142        let group = self.group().await;
143        let ext_senders = group
144            .group_context_extensions()
145            .external_senders()
146            .ok_or(Error::MissingExternalSenderExtension)?;
147        let ext_sender = ext_senders.first().ok_or(Error::MissingExternalSenderExtension)?;
148        Ok(ext_sender.clone().into())
149    }
150}
151
152#[cfg(test)]
153mod test_utils {
154    use openmls::prelude::SignaturePublicKey;
155
156    use super::*;
157
158    impl Conversation {
159        pub async fn signature_keys(&self) -> Vec<SignaturePublicKey> {
160            let group = self.group().await;
161            group
162                .members()
163                .map(|m| m.signature_key)
164                .map(|mpk| SignaturePublicKey::from(mpk.as_slice()))
165                .collect()
166        }
167
168        pub async fn encryption_keys(&self) -> Vec<Vec<u8>> {
169            let group = self.group().await;
170            group.members().map(|m| m.encryption_key).collect()
171        }
172
173        pub async fn extensions(&self) -> openmls::prelude::Extensions {
174            let group = self.group().await;
175            group.export_group_context().extensions().to_owned()
176        }
177    }
178}