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, TntMessageTxCounter},
14 traits::{EntityDatabaseMutation as _, EntityDeleteBorrowed 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(in crate::mls::conversation) 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) async fn obtain_tnt_message_tx_counter(
53 &mut self,
54 database: &impl FetchFromDatabase,
55 ) -> Result<TntMessageCounter> {
56 let mut counter = self.tnt_message_tx_counter;
57
58 if counter.is_zero() {
59 counter = database
60 .get_borrowed::<TntMessageTxCounter>(KeystoreConversationIdRef::new(self.group_id().as_slice()))
61 .await
62 .map_err(KeystoreError::wrap("searching for tnt message counters for group"))?
63 .map(|counter| counter.count)
64 .unwrap_or_default()
65 .into();
66 }
67
68 counter.increment()?;
69 self.tnt_message_tx_counter = counter;
70 self.group.set_state(InnerState::Changed);
71
72 Ok(counter)
73 }
74
75 pub(in crate::mls::conversation) async fn reset_tnt_message_tx_counter(&mut self, tx: &Transaction) -> Result<()> {
76 self.tnt_message_tx_counter = Default::default();
77 let id = KeystoreConversationIdRef::new(self.group.group_id().as_slice());
78 TntMessageTxCounter::delete_borrowed(tx, id)
79 .map_err(KeystoreError::wrap("removing transient message tx counter"))?;
80 Ok(())
81 }
82
83 pub(crate) async fn persist(&mut self, tx: &Transaction) -> Result<()> {
84 self.mls_group_mut().set_state(InnerState::Persisted);
86 let id = self.group.group_id();
87 let group = self.mls_group();
88
89 let (credential_id, credential_type) = if group.is_active() {
99 let current_credential = group_metadata::current_credential_pk(group, tx).await?;
100 (current_credential.public_key_hash, current_credential.credential_type)
101 } else {
102 let persisted = tx
103 .get_borrowed::<PersistedMlsGroup>(KeystoreConversationIdRef::new(id.as_slice()))
104 .await
105 .map_err(KeystoreError::wrap("finding the existing row of an evicted conversation"))?
106 .ok_or(Error::MlsGroupInvalidState(
111 "an evicted conversation must already have been persisted",
112 ))?;
113 (persisted.credential_id, persisted.credential_type)
114 };
115
116 PersistedMlsGroup {
117 id: id.as_slice().into(),
118 state: core_crypto_keystore::ser(group).map_err(KeystoreError::wrap("serializing group state"))?,
119 epoch: group.epoch().as_u64(),
120 ciphersuite: group.ciphersuite() as u16,
121 credential_id,
122 credential_type,
123 own_leaf_index: group.own_leaf_index().u32(),
124 is_pending: false,
129 }
130 .save(tx)
131 .map_err(KeystoreError::wrap("persisting mls group"))?;
132
133 TntMessageTxCounter {
134 conversation_id: id.as_slice().into(),
135 count: self.tnt_message_tx_counter.into(),
136 }
137 .save(tx)
138 .map_err(KeystoreError::wrap("saving transient message tx counter"))?;
139
140 Ok(())
141 }
142}
143
144#[derive(Debug, derive_more::Constructor)]
146pub struct Conversation {
147 pub(in crate::mls::conversation) id: ConversationId,
148 pub(in crate::mls::conversation) group: RwLock<MlsGroupState>,
149 pub(in crate::mls::conversation) configuration: ConversationConfiguration,
150 session: Session,
151}
152
153impl Conversation {
154 pub fn id(&self) -> &ConversationIdRef {
156 self.id.as_ref()
157 }
158
159 pub(crate) async fn group(&self) -> RwLockReadGuard<'_, MlsGroupState> {
161 self.group.read().await
162 }
163
164 pub fn configuration(&self) -> &ConversationConfiguration {
166 &self.configuration
167 }
168
169 pub async fn epoch(&self) -> u64 {
171 self.group().await.epoch().as_u64()
172 }
173
174 pub fn cipher_suite(&self) -> CipherSuite {
176 self.configuration.cipher_suite
177 }
178
179 pub async fn credential_ref(&self) -> Result<CredentialRef> {
181 let credential = self
182 .find_current_credential()
183 .await
184 .map_err(|_| Error::IdentityInitializationError)?;
185 Ok(CredentialRef::from_credential(&credential))
186 }
187
188 pub async fn export_secret_key(&self, key_length: usize) -> Result<SecretKey> {
197 const EXPORTER_LABEL: &str = "exporter";
198 const EXPORTER_CONTEXT: &[u8] = &[];
199 self.group()
200 .await
201 .export_secret(
202 &self.session.crypto_provider,
203 EXPORTER_LABEL,
204 EXPORTER_CONTEXT,
205 key_length,
206 )
207 .map(Into::into)
208 .map_err(OpenMlsError::wrap("exporting secret key"))
209 .map_err(Into::into)
210 }
211
212 pub async fn get_external_sender(&self) -> Result<ExternalSender> {
216 let group = self.group().await;
217 let ext_senders = group
218 .group_context_extensions()
219 .external_senders()
220 .ok_or(Error::MissingExternalSenderExtension)?;
221 let ext_sender = ext_senders.first().ok_or(Error::MissingExternalSenderExtension)?;
222 Ok(ext_sender.clone().into())
223 }
224}
225
226#[cfg(test)]
227mod test_utils {
228 use openmls::prelude::SignaturePublicKey;
229
230 use super::*;
231
232 impl Conversation {
233 pub async fn signature_keys(&self) -> Vec<SignaturePublicKey> {
234 let group = self.group().await;
235 group
236 .members()
237 .map(|m| m.signature_key)
238 .map(|mpk| SignaturePublicKey::from(mpk.as_slice()))
239 .collect()
240 }
241
242 pub async fn encryption_keys(&self) -> Vec<Vec<u8>> {
243 let group = self.group().await;
244 group.members().map(|m| m.encryption_key).collect()
245 }
246
247 pub async fn extensions(&self) -> openmls::prelude::Extensions {
248 let group = self.group().await;
249 group.export_group_context().extensions().to_owned()
250 }
251 }
252}