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