Skip to main content

openmls/group/mls_group/
application.rs

1use openmls_traits::signatures::Signer;
2
3use crate::storage::OpenMlsProvider;
4
5#[cfg(feature = "virtual-clients-draft")]
6use crate::tree::secret_tree::SecretType;
7
8#[cfg(feature = "virtual-clients-draft")]
9use super::errors::ConfirmMessageError;
10use super::{errors::CreateMessageError, *};
11
12/// The result of [`MlsGroup::create_unconfirmed_message`]: the encrypted
13/// message together with the bookkeeping a virtual client needs to coordinate
14/// the send with the DS.
15#[cfg(feature = "virtual-clients-draft")]
16#[derive(Debug, Clone)]
17pub struct UnconfirmedMessage {
18    /// The encrypted application message to fan out.
19    pub message: MlsMessageOut,
20    /// The ratchet generation used for encryption. Pass it back to
21    /// [`MlsGroup::confirm_message`] once the DS has accepted the message, to
22    /// delete the retained encryption secret.
23    pub generation: u32,
24    /// The [`GenerationId`] to attach to the fanned-out message, present when
25    /// the group is bound to an emulation epoch and `None` otherwise. A
26    /// strongly-consistent DS compares it across siblings to detect generation
27    /// collisions.
28    ///
29    /// [`GenerationId`]: crate::components::vc_derivation_info::GenerationId
30    pub generation_id: Option<crate::components::vc_derivation_info::GenerationId>,
31}
32
33impl MlsGroup {
34    // === Application messages ===
35
36    /// Creates an application message. Returns
37    /// `CreateMessageError::MlsGroupStateError::UseAfterEviction` if the member
38    /// is no longer part of the group. Returns
39    /// `CreateMessageError::MlsGroupStateError::PendingProposal` if pending
40    /// proposals exist. In that case `.process_pending_proposals()` must be
41    /// called first and incoming messages from the DS must be processed
42    /// afterwards.
43    #[cfg(not(feature = "virtual-clients-draft"))]
44    pub fn create_message<Provider: OpenMlsProvider>(
45        &mut self,
46        provider: &Provider,
47        signer: &impl Signer,
48        message: &[u8],
49    ) -> Result<MlsMessageOut, CreateMessageError> {
50        let (_, output) =
51            self.create_message_internal::<_, CreateMessageError>(provider, signer, message)?;
52        Ok(output)
53    }
54
55    /// Creates an application message. Returns
56    /// `CreateMessageError::MlsGroupStateError::UseAfterEviction` if the member
57    /// is no longer part of the group. Returns
58    /// `CreateMessageError::MlsGroupStateError::PendingProposal` if pending
59    /// proposals exist. In that case `.process_pending_proposals()` must be
60    /// called first and incoming messages from the DS must be processed
61    /// afterwards.
62    #[cfg(all(feature = "virtual-clients-draft", any(feature = "test-utils", test)))]
63    pub fn create_message<Provider: OpenMlsProvider>(
64        &mut self,
65        provider: &Provider,
66        signer: &impl Signer,
67        message: &[u8],
68    ) -> Result<MlsMessageOut, CreateMessageError<Provider::StorageError>> {
69        let (generation, _generation_id, output) =
70            self.create_message_internal(provider, signer, message)?;
71        self.confirm_message(provider.storage(), generation)?;
72        Ok(output)
73    }
74
75    #[cfg(not(feature = "virtual-clients-draft"))]
76    fn create_message_internal<Provider: OpenMlsProvider, E>(
77        &mut self,
78        provider: &Provider,
79        signer: &impl Signer,
80        message: &[u8],
81    ) -> Result<(u32, MlsMessageOut), E>
82    where
83        E: From<LibraryError> + From<MlsGroupStateError>,
84    {
85        if !self.is_active() {
86            return Err(MlsGroupStateError::UseAfterEviction.into());
87        }
88        if !self.proposal_store().is_empty() {
89            return Err(MlsGroupStateError::PendingProposal.into());
90        }
91
92        let aad = self.outgoing_authenticated_data()?;
93        let authenticated_content = AuthenticatedContent::new_application(
94            self.own_leaf_index(),
95            &aad,
96            message,
97            self.context(),
98            signer,
99        )?;
100        let EncryptionOutput {
101            generation,
102            private_message,
103        } = self
104            .encrypt(authenticated_content, provider)
105            // We know the application message is wellformed and we have the key material of the current epoch
106            .map_err(|_| LibraryError::custom("Malformed plaintext"))?;
107
108        let output = MlsMessageOut::from_private_message(private_message, self.version());
109        self.reset_aad();
110        Ok((generation, output))
111    }
112
113    #[cfg(feature = "virtual-clients-draft")]
114    fn create_message_internal<Provider: OpenMlsProvider>(
115        &mut self,
116        provider: &Provider,
117        signer: &impl Signer,
118        message: &[u8],
119    ) -> Result<
120        (
121            u32,
122            Option<crate::components::vc_derivation_info::GenerationId>,
123            MlsMessageOut,
124        ),
125        CreateMessageError<Provider::StorageError>,
126    > {
127        if !self.is_active() {
128            return Err(MlsGroupStateError::UseAfterEviction.into());
129        }
130        if !self.proposal_store().is_empty() {
131            return Err(MlsGroupStateError::PendingProposal.into());
132        }
133
134        let aad = self.outgoing_authenticated_data()?;
135        let authenticated_content = AuthenticatedContent::new_application(
136            self.own_leaf_index(),
137            &aad,
138            message,
139            self.context(),
140            signer,
141        )?;
142        let EncryptionOutput {
143            generation,
144            private_message,
145            generation_id,
146        } = self.encrypt(authenticated_content, provider)?;
147
148        let output = MlsMessageOut::from_private_message(private_message, self.version());
149        self.reset_aad();
150        Ok((generation, generation_id, output))
151    }
152
153    /// Creates an application message. Encryption secrets are only deleted
154    /// after the message has been confirmed via `confirm_message()`.
155    ///
156    /// Returns the ratchet `generation` used for encryption, an optional
157    /// [`GenerationId`], and the encrypted message. The `generation` is passed
158    /// back to `confirm_message` to delete the retained encryption secret once
159    /// the DS has accepted the message. The [`GenerationId`] is present when
160    /// the group is bound to an emulation epoch and `None` otherwise. When
161    /// present, the application attaches it to the fanned-out message so a
162    /// strongly-consistent DS can detect generation collisions between
163    /// siblings.
164    ///
165    /// Returns `CreateMessageError::MlsGroupStateError::UseAfterEviction` if
166    /// the member is no longer part of the group. Returns
167    /// `CreateMessageError::MlsGroupStateError::PendingProposal` if pending
168    /// proposals exist. In that case `.process_pending_proposals()` must be
169    /// called first and incoming messages from the DS must be processed
170    /// afterwards.
171    ///
172    /// [`GenerationId`]: crate::components::vc_derivation_info::GenerationId
173    #[cfg(feature = "virtual-clients-draft")]
174    pub fn create_unconfirmed_message<Provider: OpenMlsProvider>(
175        &mut self,
176        provider: &Provider,
177        signer: &impl Signer,
178        message: &[u8],
179    ) -> Result<UnconfirmedMessage, CreateMessageError<Provider::StorageError>> {
180        let (generation, generation_id, message) =
181            self.create_message_internal(provider, signer, message)?;
182        Ok(UnconfirmedMessage {
183            message,
184            generation,
185            generation_id,
186        })
187    }
188
189    /// Confirms that a message has been successfully sent without a generation
190    /// collision. This deletes the encryption secrets for the given generation.
191    #[cfg(feature = "virtual-clients-draft")]
192    pub fn confirm_message<Storage: StorageProvider>(
193        &mut self,
194        storage: &Storage,
195        generation: u32,
196    ) -> Result<(), ConfirmMessageError<Storage::Error>> {
197        // For now we only support application secrets.
198        let secret_type = SecretType::ApplicationSecret;
199        self.message_secrets_store
200            .message_secrets_mut()
201            .secret_tree_mut()
202            .delete_own_secret_for_generation(secret_type, generation)?;
203        storage
204            .write_message_secrets(self.group_id(), &self.message_secrets_store)
205            .map_err(ConfirmMessageError::StorageError)?;
206        Ok(())
207    }
208}