openmls/group/public_group/
process.rs1use 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")]
24use crate::{
25 group::{
26 mls_group::processing::{committed_app_data_update_proposals, UnresolvedAppDataCommit},
27 ResolveAppDataCommitError, StageCommitError, StagedCommit,
28 },
29 prelude::processing::{AppDataDictionaryUpdater, AppDataUpdates},
30};
31
32use super::PublicGroup;
33
34impl PublicGroup {
35 pub(crate) fn parse_message<'a>(
51 &self,
52 decrypted_message: DecryptedMessage,
53 message_secrets_store_option: impl Into<Option<&'a MessageSecretsStore>>,
54 ) -> Result<UnverifiedMessage, ValidationError> {
55 let message_secrets_store_option = message_secrets_store_option.into();
56 let verifiable_content = decrypted_message.verifiable_content();
57
58 self.validate_verifiable_content(verifiable_content, message_secrets_store_option)?;
63
64 let message_epoch = verifiable_content.epoch();
65
66 let look_up_credential_with_key = |leaf_node_index| {
69 if message_epoch == self.group_context().epoch() {
70 self.treesync()
71 .leaf(leaf_node_index)
72 .map(CredentialWithKey::from)
73 } else if let Some(store) = message_secrets_store_option {
74 store
78 .leaves_for_epoch(message_epoch)
79 .get(&leaf_node_index)
80 .map(|&member| CredentialWithKey::from(member))
81 } else {
82 None
83 }
84 };
85
86 let CredentialWithKey {
94 credential,
95 signature_key,
96 } = decrypted_message.credential(
97 look_up_credential_with_key,
98 self.group_context().extensions().external_senders(),
99 )?;
100 let signature_public_key = OpenMlsSignaturePublicKey::from_signature_key(
101 signature_key,
102 self.ciphersuite().signature_algorithm(),
103 );
104
105 let sender_context = match decrypted_message.sender() {
108 Sender::Member(leaf_index) => Some(SenderContext::Member((
109 self.group_id().clone(),
110 *leaf_index,
111 ))),
112 Sender::NewMemberCommit => Some(SenderContext::ExternalCommit {
113 group_id: self.group_id().clone(),
114 leftmost_blank_index: self.treesync().free_leaf_index(),
115 self_removes_in_store: self.proposal_store.self_removes(),
116 }),
117 Sender::External(_) | Sender::NewMemberProposal => None,
118 };
119
120 Ok(UnverifiedMessage::from_decrypted_message(
121 decrypted_message,
122 credential,
123 signature_public_key,
124 sender_context,
125 ))
126 }
127
128 #[cfg_attr(
164 feature = "extensions-draft",
165 doc = "A commit covering AppDataUpdate proposals is returned as\n\
166 [`ProcessedMessageContent::UnresolvedAppDataCommit`], since the\n\
167 application has to interpret the proposals before the commit can be\n\
168 staged via [`PublicGroup::stage_app_data_commit()`]."
169 )]
170 pub fn process_message(
171 &self,
172 crypto: &impl OpenMlsCrypto,
173 message: impl Into<ProtocolMessage>,
174 ) -> Result<ProcessedMessage, PublicProcessMessageError> {
175 let protocol_message = message.into();
176 self.validate_framing(&protocol_message)?;
180
181 let decrypted_message = match protocol_message {
182 ProtocolMessage::PrivateMessage(_) => {
183 return Err(PublicProcessMessageError::IncompatibleWireFormat)
184 }
185 ProtocolMessage::PublicMessage(public_message) => {
186 DecryptedMessage::from_inbound_public_message(
187 *public_message,
188 None,
189 self.group_context()
190 .tls_serialize_detached()
191 .map_err(LibraryError::missing_bound_check)?,
192 crypto,
193 self.ciphersuite(),
194 )?
195 }
196 };
197
198 let unverified_message = self
199 .parse_message(decrypted_message, None)
200 .map_err(PublicProcessMessageError::from)?;
201 self.process_unverified_message(crypto, unverified_message)
202 }
203
204 #[cfg(feature = "extensions-draft")]
205 pub fn app_data_dictionary_updater(&self) -> AppDataDictionaryUpdater<'_> {
207 AppDataDictionaryUpdater::new(self.group_context().app_data_dict())
208 }
209
210 #[cfg(feature = "extensions-draft")]
217 pub fn stage_app_data_commit(
218 &self,
219 crypto: &impl OpenMlsCrypto,
220 unresolved_commit: UnresolvedAppDataCommit,
221 app_data_dict_updates: Option<AppDataUpdates>,
222 ) -> Result<StagedCommit, StageCommitError> {
223 self.stage_commit_with_app_data_updates(
224 &unresolved_commit.into_content(),
225 crypto,
226 app_data_dict_updates,
227 )
228 }
229
230 #[cfg(feature = "extensions-draft")]
245 pub fn resolve_app_data_commit(
246 &self,
247 crypto: &impl OpenMlsCrypto,
248 processed_message: ProcessedMessage,
249 app_data_dict_updates: Option<AppDataUpdates>,
250 ) -> Result<ProcessedMessage, ResolveAppDataCommitError> {
251 processed_message.resolve_app_data_commit(|unresolved_commit| {
252 self.stage_app_data_commit(crypto, unresolved_commit, app_data_dict_updates)
253 })
254 }
255}
256
257impl PublicGroup {
258 pub(crate) fn process_unverified_message(
285 &self,
286 crypto: &impl OpenMlsCrypto,
287 unverified_message: UnverifiedMessage,
288 ) -> Result<ProcessedMessage, PublicProcessMessageError> {
289 let verified = unverified_message.verify(self.ciphersuite(), crypto, self.version())?;
294 let content = verified.content;
295 let credential = verified.credential;
296
297 #[cfg_attr(not(feature = "extensions-draft"), allow(unused_mut))]
298 let mut processed = match content.sender() {
299 Sender::Member(_) | Sender::NewMemberCommit | Sender::NewMemberProposal => {
300 self.process_internal_authenticated_content(crypto, content, credential)?
301 }
302 Sender::External(_) => {
303 self.process_external_authenticated_content(crypto, content, credential)?
304 }
305 };
306 #[cfg(feature = "extensions-draft")]
307 if self.group_context().safe_aad_required() {
308 processed
309 .try_attach_safe_aad()
310 .map_err(|_| PublicProcessMessageError::MalformedSafeAad)?;
311 }
312 Ok(processed)
313 }
314
315 fn process_internal_authenticated_content(
316 &self,
317 crypto: &impl OpenMlsCrypto,
318 content: AuthenticatedContent,
319 credential: Credential,
320 ) -> Result<ProcessedMessage, PublicProcessMessageError> {
321 let sender = content.sender().clone();
322 let authenticated_data = content.authenticated_data().to_owned();
323
324 let content = match content.content() {
325 FramedContentBody::Application(application_message) => {
326 ProcessedMessageContent::ApplicationMessage(ApplicationMessage::new(
327 application_message.as_slice().to_owned(),
328 ))
329 }
330 FramedContentBody::Proposal(_) => {
331 let proposal = Box::new(QueuedProposal::from_authenticated_content_by_ref(
332 self.ciphersuite(),
333 crypto,
334 content,
335 )?);
336 if matches!(sender, Sender::NewMemberProposal) {
337 ProcessedMessageContent::ExternalJoinProposalMessage(proposal)
338 } else {
339 ProcessedMessageContent::ProposalMessage(proposal)
340 }
341 }
342 FramedContentBody::Commit(commit) => {
343 #[cfg(feature = "extensions-draft")]
350 {
351 let app_data_update_proposals =
352 committed_app_data_update_proposals(commit, &self.proposal_store);
353 if !app_data_update_proposals.is_empty() {
354 let unresolved_commit =
355 UnresolvedAppDataCommit::new(content, app_data_update_proposals);
356 return Ok(ProcessedMessage::new(
357 self.group_id().clone(),
358 self.group_context().epoch(),
359 sender,
360 authenticated_data,
361 ProcessedMessageContent::UnresolvedAppDataCommit(Box::new(
362 unresolved_commit,
363 )),
364 credential,
365 #[cfg(feature = "virtual-clients-draft")]
366 None,
367 ));
368 }
369 }
370 #[cfg(not(feature = "extensions-draft"))]
371 let _ = commit;
372
373 let staged_commit = self.stage_commit(&content, crypto)?;
374 ProcessedMessageContent::StagedCommitMessage(Box::new(staged_commit))
375 }
376 };
377
378 Ok(ProcessedMessage::new(
379 self.group_id().clone(),
380 self.group_context().epoch(),
381 sender,
382 authenticated_data,
383 content,
384 credential,
385 #[cfg(feature = "virtual-clients-draft")]
386 None,
387 ))
388 }
389
390 fn process_external_authenticated_content(
391 &self,
392 crypto: &impl OpenMlsCrypto,
393 content: AuthenticatedContent,
394 credential: Credential,
395 ) -> Result<ProcessedMessage, PublicProcessMessageError> {
396 let sender = content.sender().clone();
397 let data = content.authenticated_data().to_owned();
398
399 debug_assert!(matches!(sender, Sender::External(_)));
400
401 match content.content() {
403 FramedContentBody::Application(_) => {
404 Err(PublicProcessMessageError::UnauthorizedExternalApplicationMessage)
405 }
406 FramedContentBody::Proposal(Proposal::GroupContextExtensions(_)) => {
408 let content = ProcessedMessageContent::ProposalMessage(Box::new(
409 QueuedProposal::from_authenticated_content_by_ref(
410 self.ciphersuite(),
411 crypto,
412 content,
413 )?,
414 ));
415 Ok(ProcessedMessage::new(
416 self.group_id().clone(),
417 self.group_context().epoch(),
418 sender,
419 data,
420 content,
421 credential,
422 #[cfg(feature = "virtual-clients-draft")]
423 None,
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 #[cfg(feature = "virtual-clients-draft")]
443 None,
444 ))
445 }
446 FramedContentBody::Proposal(Proposal::Add(_)) => {
447 let content = ProcessedMessageContent::ProposalMessage(Box::new(
448 QueuedProposal::from_authenticated_content_by_ref(
449 self.ciphersuite(),
450 crypto,
451 content,
452 )?,
453 ));
454 Ok(ProcessedMessage::new(
455 self.group_id().clone(),
456 self.group_context().epoch(),
457 sender,
458 data,
459 content,
460 credential,
461 #[cfg(feature = "virtual-clients-draft")]
462 None,
463 ))
464 }
465 FramedContentBody::Proposal(_) => {
467 Err(PublicProcessMessageError::UnsupportedProposalType)
468 }
469 FramedContentBody::Commit(_) => {
470 Err(PublicProcessMessageError::UnauthorizedExternalCommitMessage)
471 }
472 }
473 }
474}