core_crypto/mls/conversation/immutable/
mod.rs1mod clients;
2mod commit_delay;
3mod credential;
4mod duplicate;
5mod e2ei;
6mod history_sharing;
7mod persistence;
8
9use std::sync::{Arc, Weak};
10
11use async_lock::{RwLock, RwLockReadGuard};
12use core_crypto_keystore::{
13 Transaction,
14 ancillary::ConversationIdRef as KeystoreConversationIdRef,
15 entities::PersistedMlsGroup,
16 traits::{EntityDatabaseMutation as _, FetchFromDatabase},
17};
18use openmls::group::{InnerState, MlsGroup};
19
20use super::{ConversationIdRef, Error, Result, SecretKey, group_metadata};
21use crate::{
22 CipherSuite, ConversationConfiguration, ConversationId, CredentialRef, ExternalSender, KeystoreError, OpenMlsError,
23 Session, mls::TntMessageCounter,
24};
25
26#[derive(derive_more::Constructor, derive_more::Deref, derive_more::DerefMut, derive_more::Debug)]
27pub(crate) struct MlsGroupState {
28 #[deref]
29 #[deref_mut]
30 group: MlsGroup,
31 tnt_message_tx_counter: TntMessageCounter,
39}
40
41impl MlsGroupState {
42 pub(crate) fn mls_group(&self) -> &MlsGroup {
43 &self.group
44 }
45
46 pub(in crate::mls::conversation) fn mls_group_mut(&mut self) -> &mut MlsGroup {
47 &mut self.group
48 }
49
50 pub(in crate::mls::conversation) fn obtain_tnt_message_tx_counter(&mut self) -> Result<TntMessageCounter> {
52 self.tnt_message_tx_counter.increment()?;
53 self.group.set_state(InnerState::Changed);
54
55 Ok(self.tnt_message_tx_counter)
56 }
57
58 pub(in crate::mls::conversation) fn reset_tnt_message_tx_counter(&mut self) {
59 self.tnt_message_tx_counter = Default::default();
60 self.group.set_state(InnerState::Changed);
61 }
62
63 pub(crate) async fn persist(&mut self, tx: &Transaction) -> Result<()> {
64 self.mls_group_mut().set_state(InnerState::Persisted);
66 let id = self.group.group_id();
67 let group = self.mls_group();
68
69 let (credential_id, credential_type) = if group.is_active() {
79 let current_credential = group_metadata::current_credential_pk(group, tx).await?;
80 (current_credential.public_key_hash, current_credential.credential_type)
81 } else {
82 let persisted = tx
83 .get_borrowed::<PersistedMlsGroup>(KeystoreConversationIdRef::new(id.as_slice()))
84 .await
85 .map_err(KeystoreError::wrap(
86 "finding the existing row of an evicted conversation",
87 ))?
88 .ok_or(Error::MlsGroupInvalidState(
93 "an evicted conversation must already have been persisted",
94 ))?;
95 (persisted.credential_id, persisted.credential_type)
96 };
97
98 PersistedMlsGroup {
99 id: id.as_slice().into(),
100 state: core_crypto_keystore::ser(group).map_err(KeystoreError::wrap("serializing group state"))?,
101 epoch: group.epoch().as_u64(),
102 ciphersuite: group.ciphersuite() as u16,
103 credential_id,
104 credential_type,
105 own_leaf_index: group.own_leaf_index().u32(),
106 is_pending: false,
111 tnt_tx_counter: self.tnt_message_tx_counter.into(),
112 }
113 .save(tx)
114 .map_err(KeystoreError::wrap("persisting mls group"))?;
115
116 Ok(())
117 }
118}
119
120#[derive(Debug, derive_more::Constructor)]
122pub struct Conversation {
123 pub(in crate::mls::conversation) id: ConversationId,
124 pub(in crate::mls::conversation) group: RwLock<MlsGroupState>,
125 pub(in crate::mls::conversation) configuration: ConversationConfiguration,
126 session: Weak<Session>,
127}
128
129impl Conversation {
130 pub fn id(&self) -> &ConversationIdRef {
132 self.id.as_ref()
133 }
134
135 pub(crate) async fn group(&self) -> RwLockReadGuard<'_, MlsGroupState> {
137 self.group.read().await
138 }
139
140 pub fn configuration(&self) -> &ConversationConfiguration {
142 &self.configuration
143 }
144
145 pub async fn epoch(&self) -> u64 {
147 self.group().await.epoch().as_u64()
148 }
149
150 pub fn cipher_suite(&self) -> CipherSuite {
152 self.configuration.cipher_suite
153 }
154
155 pub async fn credential_ref(&self) -> Result<CredentialRef> {
157 let credential = self
158 .find_current_credential()
159 .await
160 .map_err(|_| Error::IdentityInitializationError)?;
161 Ok(CredentialRef::from_credential(&credential))
162 }
163
164 pub(in crate::mls::conversation) fn session(&self) -> Result<Arc<Session>> {
170 self.session.upgrade().ok_or(Error::SessionDropped)
171 }
172
173 pub async fn export_secret_key(&self, key_length: usize) -> Result<SecretKey> {
182 const EXPORTER_LABEL: &str = "exporter";
183 const EXPORTER_CONTEXT: &[u8] = &[];
184 self.group()
185 .await
186 .export_secret(
187 &self.session()?.crypto_provider,
188 EXPORTER_LABEL,
189 EXPORTER_CONTEXT,
190 key_length,
191 )
192 .map(Into::into)
193 .map_err(OpenMlsError::wrap("exporting secret key"))
194 .map_err(Into::into)
195 }
196
197 pub async fn get_external_sender(&self) -> Result<ExternalSender> {
201 let group = self.group().await;
202 let ext_senders = group
203 .group_context_extensions()
204 .external_senders()
205 .ok_or(Error::MissingExternalSenderExtension)?;
206 let ext_sender = ext_senders.first().ok_or(Error::MissingExternalSenderExtension)?;
207 Ok(ext_sender.clone().into())
208 }
209}
210
211#[cfg(test)]
212mod test_utils {
213 use openmls::prelude::SignaturePublicKey;
214
215 use super::*;
216
217 impl Conversation {
218 pub async fn signature_keys(&self) -> Vec<SignaturePublicKey> {
219 let group = self.group().await;
220 group
221 .members()
222 .map(|m| m.signature_key)
223 .map(|mpk| SignaturePublicKey::from(mpk.as_slice()))
224 .collect()
225 }
226
227 pub async fn encryption_keys(&self) -> Vec<Vec<u8>> {
228 let group = self.group().await;
229 group.members().map(|m| m.encryption_key).collect()
230 }
231
232 pub async fn extensions(&self) -> openmls::prelude::Extensions {
233 let group = self.group().await;
234 group.export_group_context().extensions().to_owned()
235 }
236 }
237}