Skip to main content

openmls/group/public_group/
process.rs

1//! This module contains the implementation of the processing functions for
2//! public groups.
3
4use openmls_traits::crypto::OpenMlsCrypto;
5use tls_codec::Serialize;
6
7use crate::{
8    ciphersuite::OpenMlsSignaturePublicKey,
9    credentials::{Credential, CredentialWithKey},
10    error::LibraryError,
11    framing::{
12        mls_auth_content::AuthenticatedContent, mls_content::FramedContentBody, ApplicationMessage,
13        DecryptedMessage, ProcessedMessage, ProcessedMessageContent, ProtocolMessage, Sender,
14        SenderContext, UnverifiedMessage,
15    },
16    group::{
17        errors::ValidationError, past_secrets::MessageSecretsStore, proposal_store::QueuedProposal,
18        PublicProcessMessageError,
19    },
20    messages::proposals::Proposal,
21};
22
23#[cfg(feature = "extensions-draft-08")]
24use crate::prelude::processing::AppDataUpdates;
25
26use super::PublicGroup;
27
28impl PublicGroup {
29    /// This function is used to parse messages from the DS.
30    /// It checks for syntactic errors and makes some semantic checks as well.
31    /// If the input is a [PrivateMessage] message, it will be decrypted.
32    /// Returns an [UnverifiedMessage] that can be inspected and later processed in
33    /// [Self::process_unverified_message()].
34    /// Checks the following semantic validation:
35    ///  - ValSem002
36    ///  - ValSem003
37    ///  - ValSem004
38    ///  - ValSem005
39    ///  - ValSem006
40    ///  - ValSem007
41    ///  - ValSem009
42    ///  - ValSem112
43    ///  - ValSem245
44    pub(crate) fn parse_message<'a>(
45        &self,
46        decrypted_message: DecryptedMessage,
47        message_secrets_store_option: impl Into<Option<&'a MessageSecretsStore>>,
48    ) -> Result<UnverifiedMessage, ValidationError> {
49        let message_secrets_store_option = message_secrets_store_option.into();
50        let verifiable_content = decrypted_message.verifiable_content();
51
52        // Checks the following semantic validation:
53        //  - ValSem004
54        //  - ValSem005
55        //  - ValSem009
56        self.validate_verifiable_content(verifiable_content, message_secrets_store_option)?;
57
58        let message_epoch = verifiable_content.epoch();
59
60        // Depending on the epoch of the message, use the correct set of leaf nodes for getting the
61        // credential and signature key for the member with given index.
62        let look_up_credential_with_key = |leaf_node_index| {
63            if message_epoch == self.group_context().epoch() {
64                self.treesync()
65                    .leaf(leaf_node_index)
66                    .map(CredentialWithKey::from)
67            } else if let Some(store) = message_secrets_store_option {
68                // The message is from a past epoch, look up the member in the
69                // past secrets store based on the epoch and sender's leaf
70                // index.
71                store
72                    .leaves_for_epoch(message_epoch)
73                    .get(&leaf_node_index)
74                    .map(|&member| CredentialWithKey::from(member))
75            } else {
76                None
77            }
78        };
79
80        // Extract the credential if the sender is a member or a new member.
81        // Checks the following semantic validation:
82        //  - ValSem112
83        //  - ValSem245
84        //  - Prepares ValSem246 by setting the right credential. The remainder
85        //    of ValSem246 is validated as part of ValSem010.
86        // External senders are not supported yet #106/#151.
87        let CredentialWithKey {
88            credential,
89            signature_key,
90        } = decrypted_message.credential(
91            look_up_credential_with_key,
92            self.group_context().extensions().external_senders(),
93        )?;
94        let signature_public_key = OpenMlsSignaturePublicKey::from_signature_key(
95            signature_key,
96            self.ciphersuite().signature_algorithm(),
97        );
98
99        // For commit messages, we need to check if the sender is a member or a
100        // new member and set the tree position accordingly.
101        let sender_context = match decrypted_message.sender() {
102            Sender::Member(leaf_index) => Some(SenderContext::Member((
103                self.group_id().clone(),
104                *leaf_index,
105            ))),
106            Sender::NewMemberCommit => Some(SenderContext::ExternalCommit {
107                group_id: self.group_id().clone(),
108                leftmost_blank_index: self.treesync().free_leaf_index(),
109                self_removes_in_store: self.proposal_store.self_removes(),
110            }),
111            Sender::External(_) | Sender::NewMemberProposal => None,
112        };
113
114        Ok(UnverifiedMessage::from_decrypted_message(
115            decrypted_message,
116            credential,
117            signature_public_key,
118            sender_context,
119        ))
120    }
121
122    /// This function is used to parse messages from the DS. It checks for
123    /// syntactic errors and does semantic validation as well. It returns a
124    /// [ProcessedMessage] enum. Checks the following semantic validation:
125    ///  - ValSem002
126    ///  - ValSem003
127    ///  - ValSem004
128    ///  - ValSem005
129    ///  - ValSem006
130    ///  - ValSem007
131    ///  - ValSem008
132    ///  - ValSem009
133    ///  - ValSem010
134    ///  - ValSem101
135    ///  - ValSem102
136    ///  - ValSem104
137    ///  - ValSem106
138    ///  - ValSem107
139    ///  - ValSem108
140    ///  - ValSem110
141    ///  - ValSem111
142    ///  - ValSem112
143    ///  - ValSem200
144    ///  - ValSem201
145    ///  - ValSem202: Path must be the right length
146    ///  - ValSem203: Path secrets must decrypt correctly
147    ///  - ValSem204: Public keys from Path must be verified and match the
148    ///    private keys from the direct path
149    ///  - ValSem205
150    ///  - ValSem240
151    ///  - ValSem241
152    ///  - ValSem242
153    ///  - ValSem244
154    ///  - ValSem245
155    ///  - ValSem246 (as part of ValSem010)
156    pub fn process_message(
157        &self,
158        crypto: &impl OpenMlsCrypto,
159        message: impl Into<ProtocolMessage>,
160    ) -> Result<ProcessedMessage, PublicProcessMessageError> {
161        let protocol_message = message.into();
162        // Checks the following semantic validation:
163        //  - ValSem002
164        //  - ValSem003
165        self.validate_framing(&protocol_message)?;
166
167        let decrypted_message = match protocol_message {
168            ProtocolMessage::PrivateMessage(_) => {
169                return Err(PublicProcessMessageError::IncompatibleWireFormat)
170            }
171            ProtocolMessage::PublicMessage(public_message) => {
172                DecryptedMessage::from_inbound_public_message(
173                    *public_message,
174                    None,
175                    self.group_context()
176                        .tls_serialize_detached()
177                        .map_err(LibraryError::missing_bound_check)?,
178                    crypto,
179                    self.ciphersuite(),
180                )?
181            }
182        };
183
184        let unverified_message = self
185            .parse_message(decrypted_message, None)
186            .map_err(PublicProcessMessageError::from)?;
187        self.process_unverified_message(crypto, unverified_message)
188    }
189}
190
191impl PublicGroup {
192    /// This processing function does most of the semantic verifications.
193    /// It returns a [ProcessedMessage] enum.
194    /// Checks the following semantic validation:
195    ///  - ValSem008
196    ///  - ValSem010
197    ///  - ValSem101
198    ///  - ValSem102
199    ///  - ValSem104
200    ///  - ValSem106
201    ///  - ValSem107
202    ///  - ValSem108
203    ///  - ValSem110
204    ///  - ValSem111
205    ///  - ValSem112
206    ///  - ValSem200
207    ///  - ValSem201
208    ///  - ValSem202: Path must be the right length
209    ///  - ValSem203: Path secrets must decrypt correctly
210    ///  - ValSem204: Public keys from Path must be verified and match the
211    ///    private keys from the direct path
212    ///  - ValSem205
213    ///  - ValSem240
214    ///  - ValSem241
215    ///  - ValSem242
216    ///  - ValSem244
217    ///  - ValSem246 (as part of ValSem010)
218    pub(crate) fn process_unverified_message(
219        &self,
220        crypto: &impl OpenMlsCrypto,
221        unverified_message: UnverifiedMessage,
222    ) -> Result<ProcessedMessage, PublicProcessMessageError> {
223        // Checks the following semantic validation:
224        //  - ValSem010
225        //  - ValSem246 (as part of ValSem010)
226        //  - https://validation.openmls.tech/#valn1203
227        let (content, credential) =
228            unverified_message.verify(self.ciphersuite(), crypto, self.version())?;
229
230        match content.sender() {
231            Sender::Member(_) | Sender::NewMemberCommit | Sender::NewMemberProposal => {
232                self.process_internal_authenticated_content(crypto, content, credential)
233            }
234            Sender::External(_) => {
235                self.process_external_authenticated_content(crypto, content, credential)
236            }
237        }
238    }
239
240    /// This processing function does most of the semantic verifications.
241    /// It returns a [ProcessedMessage] enum.
242    /// Checks the following semantic validation:
243    ///  - ValSem008
244    ///  - ValSem010
245    ///  - ValSem101
246    ///  - ValSem102
247    ///  - ValSem104
248    ///  - ValSem106
249    ///  - ValSem107
250    ///  - ValSem108
251    ///  - ValSem110
252    ///  - ValSem111
253    ///  - ValSem112
254    ///  - ValSem200
255    ///  - ValSem201
256    ///  - ValSem202: Path must be the right length
257    ///  - ValSem203: Path secrets must decrypt correctly
258    ///  - ValSem204: Public keys from Path must be verified and match the
259    ///    private keys from the direct path
260    ///  - ValSem205
261    ///  - ValSem240
262    ///  - ValSem241
263    ///  - ValSem242
264    ///  - ValSem244
265    ///  - ValSem246 (as part of ValSem010)
266    #[cfg(feature = "extensions-draft-08")]
267    pub fn process_unverified_message_with_app_data_updates(
268        &self,
269        crypto: &impl OpenMlsCrypto,
270        unverified_message: UnverifiedMessage,
271        app_data_dict_updates: Option<AppDataUpdates>,
272    ) -> Result<ProcessedMessage, PublicProcessMessageError> {
273        // Checks the following semantic validation:
274        //  - ValSem010
275        //  - ValSem246 (as part of ValSem010)
276        //  - https://validation.openmls.tech/#valn1203
277        let (content, credential) =
278            unverified_message.verify(self.ciphersuite(), crypto, self.version())?;
279
280        match content.sender() {
281            Sender::Member(_) | Sender::NewMemberCommit | Sender::NewMemberProposal => self
282                .process_internal_authenticated_content_with_app_data_updates(
283                    crypto,
284                    content,
285                    credential,
286                    app_data_dict_updates,
287                ),
288            Sender::External(_) => {
289                self.process_external_authenticated_content(crypto, content, credential)
290            }
291        }
292    }
293
294    fn process_internal_authenticated_content(
295        &self,
296        crypto: &impl OpenMlsCrypto,
297        content: AuthenticatedContent,
298        credential: Credential,
299    ) -> Result<ProcessedMessage, PublicProcessMessageError> {
300        let sender = content.sender().clone();
301        let authenticated_data = content.authenticated_data().to_owned();
302
303        let content = match content.content() {
304            FramedContentBody::Application(application_message) => {
305                ProcessedMessageContent::ApplicationMessage(ApplicationMessage::new(
306                    application_message.as_slice().to_owned(),
307                ))
308            }
309            FramedContentBody::Proposal(_) => {
310                let proposal = Box::new(QueuedProposal::from_authenticated_content_by_ref(
311                    self.ciphersuite(),
312                    crypto,
313                    content,
314                )?);
315                if matches!(sender, Sender::NewMemberProposal) {
316                    ProcessedMessageContent::ExternalJoinProposalMessage(proposal)
317                } else {
318                    ProcessedMessageContent::ProposalMessage(proposal)
319                }
320            }
321            FramedContentBody::Commit(_) => {
322                let staged_commit = self.stage_commit(&content, crypto)?;
323                ProcessedMessageContent::StagedCommitMessage(Box::new(staged_commit))
324            }
325        };
326
327        Ok(ProcessedMessage::new(
328            self.group_id().clone(),
329            self.group_context().epoch(),
330            sender,
331            authenticated_data,
332            content,
333            credential,
334        ))
335    }
336
337    #[cfg(feature = "extensions-draft-08")]
338    fn process_internal_authenticated_content_with_app_data_updates(
339        &self,
340        crypto: &impl OpenMlsCrypto,
341        content: AuthenticatedContent,
342        credential: Credential,
343        app_data_dict_updates: Option<AppDataUpdates>,
344    ) -> Result<ProcessedMessage, PublicProcessMessageError> {
345        let sender = content.sender().clone();
346        let authenticated_data = content.authenticated_data().to_owned();
347
348        debug_assert!(matches!(
349            sender,
350            Sender::Member(_) | Sender::NewMemberCommit | Sender::NewMemberProposal
351        ));
352
353        let content = match content.content() {
354            FramedContentBody::Application(application_message) => {
355                ProcessedMessageContent::ApplicationMessage(ApplicationMessage::new(
356                    application_message.as_slice().to_owned(),
357                ))
358            }
359            FramedContentBody::Proposal(_) => {
360                let proposal = Box::new(QueuedProposal::from_authenticated_content_by_ref(
361                    self.ciphersuite(),
362                    crypto,
363                    content,
364                )?);
365                if matches!(sender, Sender::NewMemberProposal) {
366                    ProcessedMessageContent::ExternalJoinProposalMessage(proposal)
367                } else {
368                    ProcessedMessageContent::ProposalMessage(proposal)
369                }
370            }
371            FramedContentBody::Commit(_) => {
372                let staged_commit = self.stage_commit_with_app_data_updates(
373                    &content,
374                    crypto,
375                    app_data_dict_updates,
376                )?;
377
378                ProcessedMessageContent::StagedCommitMessage(Box::new(staged_commit))
379            }
380        };
381
382        Ok(ProcessedMessage::new(
383            self.group_id().clone(),
384            self.group_context().epoch(),
385            sender,
386            authenticated_data,
387            content,
388            credential,
389        ))
390    }
391
392    fn process_external_authenticated_content(
393        &self,
394        crypto: &impl OpenMlsCrypto,
395        content: AuthenticatedContent,
396        credential: Credential,
397    ) -> Result<ProcessedMessage, PublicProcessMessageError> {
398        let sender = content.sender().clone();
399        let data = content.authenticated_data().to_owned();
400
401        debug_assert!(matches!(sender, Sender::External(_)));
402
403        // https://validation.openmls.tech/#valn1501
404        match content.content() {
405            FramedContentBody::Application(_) => {
406                Err(PublicProcessMessageError::UnauthorizedExternalApplicationMessage)
407            }
408            // TODO: https://validation.openmls.tech/#valn1502
409            FramedContentBody::Proposal(Proposal::GroupContextExtensions(_)) => {
410                let content = ProcessedMessageContent::ProposalMessage(Box::new(
411                    QueuedProposal::from_authenticated_content_by_ref(
412                        self.ciphersuite(),
413                        crypto,
414                        content,
415                    )?,
416                ));
417                Ok(ProcessedMessage::new(
418                    self.group_id().clone(),
419                    self.group_context().epoch(),
420                    sender,
421                    data,
422                    content,
423                    credential,
424                ))
425            }
426
427            FramedContentBody::Proposal(Proposal::Remove(_)) => {
428                let content = ProcessedMessageContent::ProposalMessage(Box::new(
429                    QueuedProposal::from_authenticated_content_by_ref(
430                        self.ciphersuite(),
431                        crypto,
432                        content,
433                    )?,
434                ));
435                Ok(ProcessedMessage::new(
436                    self.group_id().clone(),
437                    self.group_context().epoch(),
438                    sender,
439                    data,
440                    content,
441                    credential,
442                ))
443            }
444            FramedContentBody::Proposal(Proposal::Add(_)) => {
445                let content = ProcessedMessageContent::ProposalMessage(Box::new(
446                    QueuedProposal::from_authenticated_content_by_ref(
447                        self.ciphersuite(),
448                        crypto,
449                        content,
450                    )?,
451                ));
452                Ok(ProcessedMessage::new(
453                    self.group_id().clone(),
454                    self.group_context().epoch(),
455                    sender,
456                    data,
457                    content,
458                    credential,
459                ))
460            }
461            // TODO #151/#106
462            FramedContentBody::Proposal(_) => {
463                Err(PublicProcessMessageError::UnsupportedProposalType)
464            }
465            FramedContentBody::Commit(_) => {
466                Err(PublicProcessMessageError::UnauthorizedExternalCommitMessage)
467            }
468        }
469    }
470}