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}