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::CredentialWithKey,
10    error::LibraryError,
11    framing::{
12        mls_content::FramedContentBody, ApplicationMessage, DecryptedMessage, ProcessedMessage,
13        ProcessedMessageContent, ProtocolMessage, Sender, SenderContext, UnverifiedMessage,
14    },
15    group::{
16        errors::ValidationError, mls_group::errors::ProcessMessageError,
17        past_secrets::MessageSecretsStore, proposal_store::QueuedProposal,
18    },
19    messages::proposals::Proposal,
20};
21
22use super::PublicGroup;
23
24impl PublicGroup {
25    /// This function is used to parse messages from the DS.
26    /// It checks for syntactic errors and makes some semantic checks as well.
27    /// If the input is a [PrivateMessage] message, it will be decrypted.
28    /// Returns an [UnverifiedMessage] that can be inspected and later processed in
29    /// [Self::process_unverified_message()].
30    /// Checks the following semantic validation:
31    ///  - ValSem002
32    ///  - ValSem003
33    ///  - ValSem004
34    ///  - ValSem005
35    ///  - ValSem006
36    ///  - ValSem007
37    ///  - ValSem009
38    ///  - ValSem112
39    ///  - ValSem245
40    pub(crate) fn parse_message<'a>(
41        &self,
42        decrypted_message: DecryptedMessage,
43        message_secrets_store_option: impl Into<Option<&'a MessageSecretsStore>>,
44    ) -> Result<UnverifiedMessage, ValidationError> {
45        let message_secrets_store_option = message_secrets_store_option.into();
46        let verifiable_content = decrypted_message.verifiable_content();
47
48        // Checks the following semantic validation:
49        //  - ValSem004
50        //  - ValSem005
51        //  - ValSem009
52        self.validate_verifiable_content(verifiable_content, message_secrets_store_option)?;
53
54        let message_epoch = verifiable_content.epoch();
55
56        // Depending on the epoch of the message, use the correct set of leaf nodes for getting the
57        // credential and signature key for the member with given index.
58        let look_up_credential_with_key = |leaf_node_index| {
59            if message_epoch == self.group_context().epoch() {
60                self.treesync()
61                    .leaf(leaf_node_index)
62                    .map(CredentialWithKey::from)
63            } else if let Some(store) = message_secrets_store_option {
64                store
65                    .leaves_for_epoch(message_epoch)
66                    .get(leaf_node_index.u32() as usize)
67                    .map(CredentialWithKey::from)
68            } else {
69                None
70            }
71        };
72
73        // Extract the credential if the sender is a member or a new member.
74        // Checks the following semantic validation:
75        //  - ValSem112
76        //  - ValSem245
77        //  - Prepares ValSem246 by setting the right credential. The remainder
78        //    of ValSem246 is validated as part of ValSem010.
79        // External senders are not supported yet #106/#151.
80        let CredentialWithKey {
81            credential,
82            signature_key,
83        } = decrypted_message.credential(
84            look_up_credential_with_key,
85            self.group_context().extensions().external_senders(),
86        )?;
87        let signature_public_key = OpenMlsSignaturePublicKey::from_signature_key(
88            signature_key,
89            self.ciphersuite().signature_algorithm(),
90        );
91
92        // For commit messages, we need to check if the sender is a member or a
93        // new member and set the tree position accordingly.
94        let sender_context = match decrypted_message.sender() {
95            Sender::Member(leaf_index) => Some(SenderContext::Member((
96                self.group_id().clone(),
97                *leaf_index,
98            ))),
99            Sender::NewMemberCommit => Some(SenderContext::ExternalCommit {
100                group_id: self.group_id().clone(),
101                leftmost_blank_index: self.treesync().free_leaf_index(),
102                self_removes_in_store: self.proposal_store.self_removes(),
103            }),
104            Sender::External(_) | Sender::NewMemberProposal => None,
105        };
106
107        Ok(UnverifiedMessage::from_decrypted_message(
108            decrypted_message,
109            credential,
110            signature_public_key,
111            sender_context,
112        ))
113    }
114
115    /// This function is used to parse messages from the DS. It checks for
116    /// syntactic errors and does semantic validation as well. It returns a
117    /// [ProcessedMessage] enum. Checks the following semantic validation:
118    ///  - ValSem002
119    ///  - ValSem003
120    ///  - ValSem004
121    ///  - ValSem005
122    ///  - ValSem006
123    ///  - ValSem007
124    ///  - ValSem008
125    ///  - ValSem009
126    ///  - ValSem010
127    ///  - ValSem101
128    ///  - ValSem102
129    ///  - ValSem104
130    ///  - ValSem106
131    ///  - ValSem107
132    ///  - ValSem108
133    ///  - ValSem110
134    ///  - ValSem111
135    ///  - ValSem112
136    ///  - ValSem200
137    ///  - ValSem201
138    ///  - ValSem202: Path must be the right length
139    ///  - ValSem203: Path secrets must decrypt correctly
140    ///  - ValSem204: Public keys from Path must be verified and match the
141    ///    private keys from the direct path
142    ///  - ValSem205
143    ///  - ValSem240
144    ///  - ValSem241
145    ///  - ValSem242
146    ///  - ValSem244
147    ///  - ValSem245
148    ///  - ValSem246 (as part of ValSem010)
149    pub fn process_message(
150        &self,
151        crypto: &impl OpenMlsCrypto,
152        message: impl Into<ProtocolMessage>,
153    ) -> Result<ProcessedMessage, ProcessMessageError> {
154        let protocol_message = message.into();
155        // Checks the following semantic validation:
156        //  - ValSem002
157        //  - ValSem003
158        self.validate_framing(&protocol_message)?;
159
160        let decrypted_message = match protocol_message {
161            ProtocolMessage::PrivateMessage(_) => {
162                return Err(ProcessMessageError::IncompatibleWireFormat)
163            }
164            ProtocolMessage::PublicMessage(public_message) => {
165                DecryptedMessage::from_inbound_public_message(
166                    *public_message,
167                    None,
168                    self.group_context()
169                        .tls_serialize_detached()
170                        .map_err(LibraryError::missing_bound_check)?,
171                    crypto,
172                    self.ciphersuite(),
173                )?
174            }
175        };
176
177        let unverified_message = self
178            .parse_message(decrypted_message, None)
179            .map_err(ProcessMessageError::from)?;
180        self.process_unverified_message(crypto, unverified_message)
181    }
182}
183
184impl PublicGroup {
185    /// This processing function does most of the semantic verifications.
186    /// It returns a [ProcessedMessage] enum.
187    /// Checks the following semantic validation:
188    ///  - ValSem008
189    ///  - ValSem010
190    ///  - ValSem101
191    ///  - ValSem102
192    ///  - ValSem104
193    ///  - ValSem106
194    ///  - ValSem107
195    ///  - ValSem108
196    ///  - ValSem110
197    ///  - ValSem111
198    ///  - ValSem112
199    ///  - ValSem200
200    ///  - ValSem201
201    ///  - ValSem202: Path must be the right length
202    ///  - ValSem203: Path secrets must decrypt correctly
203    ///  - ValSem204: Public keys from Path must be verified and match the
204    ///    private keys from the direct path
205    ///  - ValSem205
206    ///  - ValSem240
207    ///  - ValSem241
208    ///  - ValSem242
209    ///  - ValSem244
210    ///  - ValSem246 (as part of ValSem010)
211    pub(crate) fn process_unverified_message(
212        &self,
213        crypto: &impl OpenMlsCrypto,
214        unverified_message: UnverifiedMessage,
215    ) -> Result<ProcessedMessage, ProcessMessageError> {
216        // Checks the following semantic validation:
217        //  - ValSem010
218        //  - ValSem246 (as part of ValSem010)
219        //  - https://validation.openmls.tech/#valn1203
220        let (content, credential) =
221            unverified_message.verify(self.ciphersuite(), crypto, self.version())?;
222
223        match content.sender() {
224            Sender::Member(_) | Sender::NewMemberCommit | Sender::NewMemberProposal => {
225                let sender = content.sender().clone();
226                let authenticated_data = content.authenticated_data().to_owned();
227
228                let content = match content.content() {
229                    FramedContentBody::Application(application_message) => {
230                        ProcessedMessageContent::ApplicationMessage(ApplicationMessage::new(
231                            application_message.as_slice().to_owned(),
232                        ))
233                    }
234                    FramedContentBody::Proposal(_) => {
235                        let proposal = Box::new(QueuedProposal::from_authenticated_content_by_ref(
236                            self.ciphersuite(),
237                            crypto,
238                            content,
239                        )?);
240                        if matches!(sender, Sender::NewMemberProposal) {
241                            ProcessedMessageContent::ExternalJoinProposalMessage(proposal)
242                        } else {
243                            ProcessedMessageContent::ProposalMessage(proposal)
244                        }
245                    }
246                    FramedContentBody::Commit(_) => {
247                        let staged_commit = self.stage_commit(&content, crypto)?;
248                        ProcessedMessageContent::StagedCommitMessage(Box::new(staged_commit))
249                    }
250                };
251
252                Ok(ProcessedMessage::new(
253                    self.group_id().clone(),
254                    self.group_context().epoch(),
255                    sender,
256                    authenticated_data,
257                    content,
258                    credential,
259                ))
260            }
261            Sender::External(_) => {
262                let sender = content.sender().clone();
263                let data = content.authenticated_data().to_owned();
264                // https://validation.openmls.tech/#valn1501
265                match content.content() {
266                    FramedContentBody::Application(_) => {
267                        Err(ProcessMessageError::UnauthorizedExternalApplicationMessage)
268                    }
269                    // TODO: https://validation.openmls.tech/#valn1502
270                    FramedContentBody::Proposal(Proposal::GroupContextExtensions(_)) => {
271                        let content = ProcessedMessageContent::ProposalMessage(Box::new(
272                            QueuedProposal::from_authenticated_content_by_ref(
273                                self.ciphersuite(),
274                                crypto,
275                                content,
276                            )?,
277                        ));
278                        Ok(ProcessedMessage::new(
279                            self.group_id().clone(),
280                            self.group_context().epoch(),
281                            sender,
282                            data,
283                            content,
284                            credential,
285                        ))
286                    }
287
288                    FramedContentBody::Proposal(Proposal::Remove(_)) => {
289                        let content = ProcessedMessageContent::ProposalMessage(Box::new(
290                            QueuedProposal::from_authenticated_content_by_ref(
291                                self.ciphersuite(),
292                                crypto,
293                                content,
294                            )?,
295                        ));
296                        Ok(ProcessedMessage::new(
297                            self.group_id().clone(),
298                            self.group_context().epoch(),
299                            sender,
300                            data,
301                            content,
302                            credential,
303                        ))
304                    }
305                    FramedContentBody::Proposal(Proposal::Add(_)) => {
306                        let content = ProcessedMessageContent::ProposalMessage(Box::new(
307                            QueuedProposal::from_authenticated_content_by_ref(
308                                self.ciphersuite(),
309                                crypto,
310                                content,
311                            )?,
312                        ));
313                        Ok(ProcessedMessage::new(
314                            self.group_id().clone(),
315                            self.group_context().epoch(),
316                            sender,
317                            data,
318                            content,
319                            credential,
320                        ))
321                    }
322                    // TODO #151/#106
323                    FramedContentBody::Proposal(_) => {
324                        Err(ProcessMessageError::UnsupportedProposalType)
325                    }
326                    FramedContentBody::Commit(_) => {
327                        Err(ProcessMessageError::UnauthorizedExternalCommitMessage)
328                    }
329                }
330            }
331        }
332    }
333}