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(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) 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("finding the existing row of an evicted conversation"))?
84 .ok_or(Error::MlsGroupInvalidState(
89 "an evicted conversation must already have been persisted",
90 ))?;
91 (persisted.credential_id, persisted.credential_type)
92 };
93
94 PersistedMlsGroup {
95 id: id.as_slice().into(),
96 state: core_crypto_keystore::ser(group).map_err(KeystoreError::wrap("serializing group state"))?,
97 epoch: group.epoch().as_u64(),
98 ciphersuite: group.ciphersuite() as u16,
99 credential_id,
100 credential_type,
101 own_leaf_index: group.own_leaf_index().u32(),
102 is_pending: false,
107 tnt_tx_counter: self.tnt_message_tx_counter.into(),
108 }
109 .save(tx)
110 .map_err(KeystoreError::wrap("persisting mls group"))?;
111
112 Ok(())
113 }
114}
115
116#[derive(Debug, derive_more::Constructor)]
118pub struct Conversation {
119 pub(in crate::mls::conversation) id: ConversationId,
120 pub(in crate::mls::conversation) group: RwLock<MlsGroupState>,
121 pub(in crate::mls::conversation) configuration: ConversationConfiguration,
122 pub(in crate::mls::conversation) session: Session,
123}
124
125impl Conversation {
126 pub fn id(&self) -> &ConversationIdRef {
128 self.id.as_ref()
129 }
130
131 pub(crate) async fn group(&self) -> RwLockReadGuard<'_, MlsGroupState> {
133 self.group.read().await
134 }
135
136 pub fn configuration(&self) -> &ConversationConfiguration {
138 &self.configuration
139 }
140
141 pub async fn epoch(&self) -> u64 {
143 self.group().await.epoch().as_u64()
144 }
145
146 pub fn cipher_suite(&self) -> CipherSuite {
148 self.configuration.cipher_suite
149 }
150
151 pub async fn credential_ref(&self) -> Result<CredentialRef> {
153 let credential = self
154 .find_current_credential()
155 .await
156 .map_err(|_| Error::IdentityInitializationError)?;
157 Ok(CredentialRef::from_credential(&credential))
158 }
159
160 pub async fn export_secret_key(&self, key_length: usize) -> Result<SecretKey> {
169 const EXPORTER_LABEL: &str = "exporter";
170 const EXPORTER_CONTEXT: &[u8] = &[];
171 self.group()
172 .await
173 .export_secret(
174 &self.session.crypto_provider,
175 EXPORTER_LABEL,
176 EXPORTER_CONTEXT,
177 key_length,
178 )
179 .map(Into::into)
180 .map_err(OpenMlsError::wrap("exporting secret key"))
181 .map_err(Into::into)
182 }
183
184 pub async fn get_external_sender(&self) -> Result<ExternalSender> {
188 let group = self.group().await;
189 let ext_senders = group
190 .group_context_extensions()
191 .external_senders()
192 .ok_or(Error::MissingExternalSenderExtension)?;
193 let ext_sender = ext_senders.first().ok_or(Error::MissingExternalSenderExtension)?;
194 Ok(ext_sender.clone().into())
195 }
196}
197
198#[cfg(test)]
199mod test_utils {
200 use openmls::prelude::SignaturePublicKey;
201
202 use super::*;
203
204 impl Conversation {
205 pub async fn signature_keys(&self) -> Vec<SignaturePublicKey> {
206 let group = self.group().await;
207 group
208 .members()
209 .map(|m| m.signature_key)
210 .map(|mpk| SignaturePublicKey::from(mpk.as_slice()))
211 .collect()
212 }
213
214 pub async fn encryption_keys(&self) -> Vec<Vec<u8>> {
215 let group = self.group().await;
216 group.members().map(|m| m.encryption_key).collect()
217 }
218
219 pub async fn extensions(&self) -> openmls::prelude::Extensions {
220 let group = self.group().await;
221 group.export_group_context().extensions().to_owned()
222 }
223 }
224}