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 openmls::group::MlsGroup;
11
12use super::{ConversationIdRef, Error, Result, SecretKey};
13use crate::{
14    CipherSuite, ConversationConfiguration, ConversationId, CredentialRef, ExternalSender, OpenMlsError, Session,
15};
16
17/// A Conversation exposes the read-only interface of an MLS conversation.
18#[derive(Debug, derive_more::Constructor)]
19pub struct Conversation {
20    pub(in crate::mls::conversation) id: ConversationId,
21    pub(in crate::mls::conversation) group: RwLock<MlsGroup>,
22    pub(in crate::mls::conversation) configuration: ConversationConfiguration,
23    session: Session,
24}
25
26impl Conversation {
27    /// Returns the conversation's ID
28    pub fn id(&self) -> &ConversationIdRef {
29        ConversationIdRef::new(&self.id)
30    }
31
32    /// Returns an immutable guard over the underlying MLS group
33    pub async fn group(&self) -> RwLockReadGuard<'_, MlsGroup> {
34        self.group.read().await
35    }
36
37    /// Returns the conversation's configuration
38    pub fn configuration(&self) -> &ConversationConfiguration {
39        &self.configuration
40    }
41
42    /// Returns current epoch of the MLS group
43    pub async fn epoch(&self) -> u64 {
44        self.group().await.epoch().as_u64()
45    }
46
47    /// Returns this conversation's cipher suite
48    pub fn cipher_suite(&self) -> CipherSuite {
49        self.configuration.cipher_suite
50    }
51
52    /// Returns a reference to the credential used in this conversation
53    pub async fn credential_ref(&self) -> Result<CredentialRef> {
54        let credential = self
55            .find_current_credential()
56            .await
57            .map_err(|_| Error::IdentityInitializationError)?;
58        Ok(CredentialRef::from_credential(&credential))
59    }
60
61    /// Derives a new key from the one in the group, to be used elsewhere.
62    ///
63    /// # Arguments
64    /// * `key_length` - the length of the key to be derived. If the value is higher than the bounds of `u16` or the
65    ///   context hash * 255, an error will be returned
66    ///
67    /// # Errors
68    /// OpenMls secret generation error
69    pub async fn export_secret_key(&self, key_length: usize) -> Result<SecretKey> {
70        const EXPORTER_LABEL: &str = "exporter";
71        const EXPORTER_CONTEXT: &[u8] = &[];
72        self.group()
73            .await
74            .export_secret(
75                &self.session.crypto_provider,
76                EXPORTER_LABEL,
77                EXPORTER_CONTEXT,
78                key_length,
79            )
80            .map(Into::into)
81            .map_err(OpenMlsError::wrap("exporting secret key"))
82            .map_err(Into::into)
83    }
84
85    /// Returns the first external sender present in this group.
86    ///
87    /// This should be used to initialize a subconversation
88    pub async fn get_external_sender(&self) -> Result<ExternalSender> {
89        let group = self.group().await;
90        let ext_senders = group
91            .group_context_extensions()
92            .external_senders()
93            .ok_or(Error::MissingExternalSenderExtension)?;
94        let ext_sender = ext_senders.first().ok_or(Error::MissingExternalSenderExtension)?;
95        Ok(ext_sender.clone().into())
96    }
97}
98
99#[cfg(test)]
100mod test_utils {
101    use openmls::prelude::SignaturePublicKey;
102
103    use super::*;
104
105    impl Conversation {
106        pub async fn signature_keys(&self) -> Vec<SignaturePublicKey> {
107            let group = self.group().await;
108            group
109                .members()
110                .map(|m| m.signature_key)
111                .map(|mpk| SignaturePublicKey::from(mpk.as_slice()))
112                .collect()
113        }
114
115        pub async fn encryption_keys(&self) -> Vec<Vec<u8>> {
116            let group = self.group().await;
117            group.members().map(|m| m.encryption_key).collect()
118        }
119
120        pub async fn extensions(&self) -> openmls::prelude::Extensions {
121            let group = self.group().await;
122            group.export_group_context().extensions().to_owned()
123        }
124    }
125}