core_crypto/mls/conversation/immutable/
mod.rs1mod 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::{
11 Transaction,
12 ancillary::ConversationIdRef as KeystoreConversationIdRef,
13 entities::PersistedMlsGroup,
14 traits::{EntityDatabaseMutation as _, FetchFromDatabase},
15};
16use openmls::group::{InnerState, MlsGroup};
17
18use super::{ConversationIdRef, Error, Result, SecretKey, group_metadata};
19use crate::{
20 CipherSuite, ConversationConfiguration, ConversationId, CredentialRef, ExternalSender, KeystoreError, OpenMlsError,
21 Session, mls::TntMessageCounter,
22};
23
24#[derive(derive_more::Constructor, derive_more::Deref, derive_more::DerefMut, derive_more::Debug)]
25pub(crate) struct MlsGroupState {
26 #[deref]
27 #[deref_mut]
28 group: MlsGroup,
29 tnt_message_tx_counter: TntMessageCounter,
37}
38
39impl MlsGroupState {
40 pub(crate) fn mls_group(&self) -> &MlsGroup {
41 &self.group
42 }
43
44 pub(in crate::mls::conversation) fn mls_group_mut(&mut self) -> &mut MlsGroup {
45 &mut self.group
46 }
47
48 pub(in crate::mls::conversation) fn obtain_tnt_message_tx_counter(&mut self) -> Result<TntMessageCounter> {
50 self.tnt_message_tx_counter.increment()?;
51 self.group.set_state(InnerState::Changed);
52
53 Ok(self.tnt_message_tx_counter)
54 }
55
56 pub(in crate::mls::conversation) fn reset_tnt_message_tx_counter(&mut self) {
57 self.tnt_message_tx_counter = Default::default();
58 self.group.set_state(InnerState::Changed);
59 }
60
61 pub(crate) async fn persist(&mut self, tx: &Transaction) -> Result<()> {
62 self.mls_group_mut().set_state(InnerState::Persisted);
64 let id = self.group.group_id();
65 let group = self.mls_group();
66
67 let (credential_id, credential_type) = if group.is_active() {
77 let current_credential = group_metadata::current_credential_pk(group, tx).await?;
78 (current_credential.public_key_hash, current_credential.credential_type)
79 } else {
80 let persisted = tx
81 .get_borrowed::<PersistedMlsGroup>(KeystoreConversationIdRef::new(id.as_slice()))
82 .await
83 .map_err(KeystoreError::wrap(
84 "finding the existing row of an evicted conversation",
85 ))?
86 .ok_or(Error::MlsGroupInvalidState(
91 "an evicted conversation must already have been persisted",
92 ))?;
93 (persisted.credential_id, persisted.credential_type)
94 };
95
96 PersistedMlsGroup {
97 id: id.as_slice().into(),
98 state: core_crypto_keystore::ser(group).map_err(KeystoreError::wrap("serializing group state"))?,
99 epoch: group.epoch().as_u64(),
100 ciphersuite: group.ciphersuite() as u16,
101 credential_id,
102 credential_type,
103 own_leaf_index: group.own_leaf_index().u32(),
104 is_pending: false,
109 tnt_tx_counter: self.tnt_message_tx_counter.into(),
110 }
111 .save(tx)
112 .map_err(KeystoreError::wrap("persisting mls group"))?;
113
114 Ok(())
115 }
116}
117
118#[derive(Debug, derive_more::Constructor)]
120pub struct Conversation {
121 pub(in crate::mls::conversation) id: ConversationId,
122 pub(in crate::mls::conversation) group: RwLock<MlsGroupState>,
123 pub(in crate::mls::conversation) configuration: ConversationConfiguration,
124 pub(in crate::mls::conversation) session: Session,
125}
126
127impl Conversation {
128 pub fn id(&self) -> &ConversationIdRef {
130 self.id.as_ref()
131 }
132
133 pub(crate) async fn group(&self) -> RwLockReadGuard<'_, MlsGroupState> {
135 self.group.read().await
136 }
137
138 pub fn configuration(&self) -> &ConversationConfiguration {
140 &self.configuration
141 }
142
143 pub async fn epoch(&self) -> u64 {
145 self.group().await.epoch().as_u64()
146 }
147
148 pub fn cipher_suite(&self) -> CipherSuite {
150 self.configuration.cipher_suite
151 }
152
153 pub async fn credential_ref(&self) -> Result<CredentialRef> {
155 let credential = self
156 .find_current_credential()
157 .await
158 .map_err(|_| Error::IdentityInitializationError)?;
159 Ok(CredentialRef::from_credential(&credential))
160 }
161
162 pub async fn export_secret_key(&self, key_length: usize) -> Result<SecretKey> {
171 const EXPORTER_LABEL: &str = "exporter";
172 const EXPORTER_CONTEXT: &[u8] = &[];
173 self.group()
174 .await
175 .export_secret(
176 &self.session.crypto_provider,
177 EXPORTER_LABEL,
178 EXPORTER_CONTEXT,
179 key_length,
180 )
181 .map(Into::into)
182 .map_err(OpenMlsError::wrap("exporting secret key"))
183 .map_err(Into::into)
184 }
185
186 pub async fn get_external_sender(&self) -> Result<ExternalSender> {
190 let group = self.group().await;
191 let ext_senders = group
192 .group_context_extensions()
193 .external_senders()
194 .ok_or(Error::MissingExternalSenderExtension)?;
195 let ext_sender = ext_senders.first().ok_or(Error::MissingExternalSenderExtension)?;
196 Ok(ext_sender.clone().into())
197 }
198}
199
200#[cfg(test)]
201mod test_utils {
202 use openmls::prelude::SignaturePublicKey;
203
204 use super::*;
205
206 impl Conversation {
207 pub async fn signature_keys(&self) -> Vec<SignaturePublicKey> {
208 let group = self.group().await;
209 group
210 .members()
211 .map(|m| m.signature_key)
212 .map(|mpk| SignaturePublicKey::from(mpk.as_slice()))
213 .collect()
214 }
215
216 pub async fn encryption_keys(&self) -> Vec<Vec<u8>> {
217 let group = self.group().await;
218 group.members().map(|m| m.encryption_key).collect()
219 }
220
221 pub async fn extensions(&self) -> openmls::prelude::Extensions {
222 let group = self.group().await;
223 group.export_group_context().extensions().to_owned()
224 }
225 }
226}