1use 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::{
25 component::ComponentData,
26 messages::{
27 proposals::AppDataUpdateOperation,
28 proposals_in::{ProposalIn, ProposalOrRefIn},
29 },
30 prelude::processing::{AppDataDictionaryUpdater, AppDataUpdates},
31};
32
33use super::PublicGroup;
34
35impl PublicGroup {
36 pub(crate) fn parse_message<'a>(
52 &self,
53 decrypted_message: DecryptedMessage,
54 message_secrets_store_option: impl Into<Option<&'a MessageSecretsStore>>,
55 ) -> Result<UnverifiedMessage, ValidationError> {
56 let message_secrets_store_option = message_secrets_store_option.into();
57 let verifiable_content = decrypted_message.verifiable_content();
58
59 self.validate_verifiable_content(verifiable_content, message_secrets_store_option)?;
64
65 let message_epoch = verifiable_content.epoch();
66
67 let look_up_credential_with_key = |leaf_node_index| {
70 if message_epoch == self.group_context().epoch() {
71 self.treesync()
72 .leaf(leaf_node_index)
73 .map(CredentialWithKey::from)
74 } else if let Some(store) = message_secrets_store_option {
75 store
79 .leaves_for_epoch(message_epoch)
80 .get(&leaf_node_index)
81 .map(|&member| CredentialWithKey::from(member))
82 } else {
83 None
84 }
85 };
86
87 let CredentialWithKey {
95 credential,
96 signature_key,
97 } = decrypted_message.credential(
98 look_up_credential_with_key,
99 self.group_context().extensions().external_senders(),
100 )?;
101 let signature_public_key = OpenMlsSignaturePublicKey::from_signature_key(
102 signature_key,
103 self.ciphersuite().signature_algorithm(),
104 );
105
106 let sender_context = match decrypted_message.sender() {
109 Sender::Member(leaf_index) => Some(SenderContext::Member((
110 self.group_id().clone(),
111 *leaf_index,
112 ))),
113 Sender::NewMemberCommit => Some(SenderContext::ExternalCommit {
114 group_id: self.group_id().clone(),
115 leftmost_blank_index: self.treesync().free_leaf_index(),
116 self_removes_in_store: self.proposal_store.self_removes(),
117 }),
118 Sender::External(_) | Sender::NewMemberProposal => None,
119 };
120
121 Ok(UnverifiedMessage::from_decrypted_message(
122 decrypted_message,
123 credential,
124 signature_public_key,
125 sender_context,
126 ))
127 }
128
129 pub fn process_message(
164 &self,
165 crypto: &impl OpenMlsCrypto,
166 message: impl Into<ProtocolMessage>,
167 ) -> Result<ProcessedMessage, PublicProcessMessageError> {
168 let protocol_message = message.into();
169 self.validate_framing(&protocol_message)?;
173
174 let decrypted_message = match protocol_message {
175 ProtocolMessage::PrivateMessage(_) => {
176 return Err(PublicProcessMessageError::IncompatibleWireFormat)
177 }
178 ProtocolMessage::PublicMessage(public_message) => {
179 DecryptedMessage::from_inbound_public_message(
180 *public_message,
181 None,
182 self.group_context()
183 .tls_serialize_detached()
184 .map_err(LibraryError::missing_bound_check)?,
185 crypto,
186 self.ciphersuite(),
187 )?
188 }
189 };
190
191 let unverified_message = self
192 .parse_message(decrypted_message, None)
193 .map_err(PublicProcessMessageError::from)?;
194 self.process_unverified_message(crypto, unverified_message)
195 }
196
197 #[cfg(feature = "extensions-draft-08")]
203 pub fn process_message_with_app_data_updates(
204 &self,
205 crypto: &impl OpenMlsCrypto,
206 message: impl Into<ProtocolMessage>,
207 ) -> Result<ProcessedMessage, PublicProcessMessageError> {
208 let protocol_message = message.into();
209 self.validate_framing(&protocol_message)?;
210
211 let decrypted_message = match protocol_message {
212 ProtocolMessage::PrivateMessage(_) => {
213 return Err(PublicProcessMessageError::IncompatibleWireFormat)
214 }
215 ProtocolMessage::PublicMessage(public_message) => {
216 DecryptedMessage::from_inbound_public_message(
217 *public_message,
218 None,
219 self.group_context()
220 .tls_serialize_detached()
221 .map_err(LibraryError::missing_bound_check)?,
222 crypto,
223 self.ciphersuite(),
224 )?
225 }
226 };
227
228 let unverified_message = self
229 .parse_message(decrypted_message, None)
230 .map_err(PublicProcessMessageError::from)?;
231 let app_data_updates = self.extract_app_data_updates(&unverified_message);
232 self.process_unverified_message_with_app_data_updates(
233 crypto,
234 unverified_message,
235 app_data_updates,
236 )
237 }
238
239 #[cfg(feature = "extensions-draft-08")]
240 fn extract_app_data_updates(&self, unverified: &UnverifiedMessage) -> Option<AppDataUpdates> {
241 let mut updater = AppDataDictionaryUpdater::new(self.group_context().app_data_dict());
242 let mut updated = false;
243 for proposal in unverified.committed_proposals()? {
244 if let ProposalOrRefIn::Proposal(proposal) = proposal {
245 if let ProposalIn::AppDataUpdate(app_data_update) = &**proposal {
246 match app_data_update.operation() {
247 AppDataUpdateOperation::Update(data) => {
248 updater.set(ComponentData::from_parts(
249 app_data_update.component_id(),
250 data.clone(),
251 ));
252 }
253 AppDataUpdateOperation::Remove => {
254 updater.remove(&app_data_update.component_id());
255 }
256 }
257 updated = true;
258 }
259 }
260 }
261 updated.then(|| updater.changes()).flatten()
262 }
263}
264
265impl PublicGroup {
266 pub(crate) fn process_unverified_message(
293 &self,
294 crypto: &impl OpenMlsCrypto,
295 unverified_message: UnverifiedMessage,
296 ) -> Result<ProcessedMessage, PublicProcessMessageError> {
297 let verified = unverified_message.verify(self.ciphersuite(), crypto, self.version())?;
302 let content = verified.content;
303 let credential = verified.credential;
304
305 #[cfg_attr(not(feature = "extensions-draft-08"), allow(unused_mut))]
306 let mut processed = match content.sender() {
307 Sender::Member(_) | Sender::NewMemberCommit | Sender::NewMemberProposal => {
308 self.process_internal_authenticated_content(crypto, content, credential)?
309 }
310 Sender::External(_) => {
311 self.process_external_authenticated_content(crypto, content, credential)?
312 }
313 };
314 #[cfg(feature = "extensions-draft-08")]
315 if self.group_context().safe_aad_required() {
316 processed
317 .try_attach_safe_aad()
318 .map_err(|_| PublicProcessMessageError::MalformedSafeAad)?;
319 }
320 Ok(processed)
321 }
322
323 #[cfg(feature = "extensions-draft-08")]
350 pub fn process_unverified_message_with_app_data_updates(
351 &self,
352 crypto: &impl OpenMlsCrypto,
353 unverified_message: UnverifiedMessage,
354 app_data_dict_updates: Option<AppDataUpdates>,
355 ) -> Result<ProcessedMessage, PublicProcessMessageError> {
356 let verified = unverified_message.verify(self.ciphersuite(), crypto, self.version())?;
361 let content = verified.content;
362 let credential = verified.credential;
363
364 #[cfg_attr(not(feature = "extensions-draft-08"), allow(unused_mut))]
365 let mut processed = match content.sender() {
366 Sender::Member(_) | Sender::NewMemberCommit | Sender::NewMemberProposal => self
367 .process_internal_authenticated_content_with_app_data_updates(
368 crypto,
369 content,
370 credential,
371 app_data_dict_updates,
372 )?,
373 Sender::External(_) => {
374 self.process_external_authenticated_content(crypto, content, credential)?
375 }
376 };
377 #[cfg(feature = "extensions-draft-08")]
378 if self.group_context().safe_aad_required() {
379 processed
380 .try_attach_safe_aad()
381 .map_err(|_| PublicProcessMessageError::MalformedSafeAad)?;
382 }
383 Ok(processed)
384 }
385
386 fn process_internal_authenticated_content(
387 &self,
388 crypto: &impl OpenMlsCrypto,
389 content: AuthenticatedContent,
390 credential: Credential,
391 ) -> Result<ProcessedMessage, PublicProcessMessageError> {
392 let sender = content.sender().clone();
393 let authenticated_data = content.authenticated_data().to_owned();
394
395 let content = match content.content() {
396 FramedContentBody::Application(application_message) => {
397 ProcessedMessageContent::ApplicationMessage(ApplicationMessage::new(
398 application_message.as_slice().to_owned(),
399 ))
400 }
401 FramedContentBody::Proposal(_) => {
402 let proposal = Box::new(QueuedProposal::from_authenticated_content_by_ref(
403 self.ciphersuite(),
404 crypto,
405 content,
406 )?);
407 if matches!(sender, Sender::NewMemberProposal) {
408 ProcessedMessageContent::ExternalJoinProposalMessage(proposal)
409 } else {
410 ProcessedMessageContent::ProposalMessage(proposal)
411 }
412 }
413 FramedContentBody::Commit(_) => {
414 let staged_commit = self.stage_commit(&content, crypto)?;
415 ProcessedMessageContent::StagedCommitMessage(Box::new(staged_commit))
416 }
417 };
418
419 Ok(ProcessedMessage::new(
420 self.group_id().clone(),
421 self.group_context().epoch(),
422 sender,
423 authenticated_data,
424 content,
425 credential,
426 #[cfg(feature = "virtual-clients-draft")]
427 None,
428 ))
429 }
430
431 #[cfg(feature = "extensions-draft-08")]
432 fn process_internal_authenticated_content_with_app_data_updates(
433 &self,
434 crypto: &impl OpenMlsCrypto,
435 content: AuthenticatedContent,
436 credential: Credential,
437 app_data_dict_updates: Option<AppDataUpdates>,
438 ) -> Result<ProcessedMessage, PublicProcessMessageError> {
439 let sender = content.sender().clone();
440 let authenticated_data = content.authenticated_data().to_owned();
441
442 debug_assert!(matches!(
443 sender,
444 Sender::Member(_) | Sender::NewMemberCommit | Sender::NewMemberProposal
445 ));
446
447 let content = match content.content() {
448 FramedContentBody::Application(application_message) => {
449 ProcessedMessageContent::ApplicationMessage(ApplicationMessage::new(
450 application_message.as_slice().to_owned(),
451 ))
452 }
453 FramedContentBody::Proposal(_) => {
454 let proposal = Box::new(QueuedProposal::from_authenticated_content_by_ref(
455 self.ciphersuite(),
456 crypto,
457 content,
458 )?);
459 if matches!(sender, Sender::NewMemberProposal) {
460 ProcessedMessageContent::ExternalJoinProposalMessage(proposal)
461 } else {
462 ProcessedMessageContent::ProposalMessage(proposal)
463 }
464 }
465 FramedContentBody::Commit(_) => {
466 let staged_commit = self.stage_commit_with_app_data_updates(
467 &content,
468 crypto,
469 app_data_dict_updates,
470 )?;
471
472 ProcessedMessageContent::StagedCommitMessage(Box::new(staged_commit))
473 }
474 };
475
476 Ok(ProcessedMessage::new(
477 self.group_id().clone(),
478 self.group_context().epoch(),
479 sender,
480 authenticated_data,
481 content,
482 credential,
483 #[cfg(feature = "virtual-clients-draft")]
484 None,
485 ))
486 }
487
488 fn process_external_authenticated_content(
489 &self,
490 crypto: &impl OpenMlsCrypto,
491 content: AuthenticatedContent,
492 credential: Credential,
493 ) -> Result<ProcessedMessage, PublicProcessMessageError> {
494 let sender = content.sender().clone();
495 let data = content.authenticated_data().to_owned();
496
497 debug_assert!(matches!(sender, Sender::External(_)));
498
499 match content.content() {
501 FramedContentBody::Application(_) => {
502 Err(PublicProcessMessageError::UnauthorizedExternalApplicationMessage)
503 }
504 FramedContentBody::Proposal(Proposal::GroupContextExtensions(_)) => {
506 let content = ProcessedMessageContent::ProposalMessage(Box::new(
507 QueuedProposal::from_authenticated_content_by_ref(
508 self.ciphersuite(),
509 crypto,
510 content,
511 )?,
512 ));
513 Ok(ProcessedMessage::new(
514 self.group_id().clone(),
515 self.group_context().epoch(),
516 sender,
517 data,
518 content,
519 credential,
520 #[cfg(feature = "virtual-clients-draft")]
521 None,
522 ))
523 }
524
525 FramedContentBody::Proposal(Proposal::Remove(_)) => {
526 let content = ProcessedMessageContent::ProposalMessage(Box::new(
527 QueuedProposal::from_authenticated_content_by_ref(
528 self.ciphersuite(),
529 crypto,
530 content,
531 )?,
532 ));
533 Ok(ProcessedMessage::new(
534 self.group_id().clone(),
535 self.group_context().epoch(),
536 sender,
537 data,
538 content,
539 credential,
540 #[cfg(feature = "virtual-clients-draft")]
541 None,
542 ))
543 }
544 FramedContentBody::Proposal(Proposal::Add(_)) => {
545 let content = ProcessedMessageContent::ProposalMessage(Box::new(
546 QueuedProposal::from_authenticated_content_by_ref(
547 self.ciphersuite(),
548 crypto,
549 content,
550 )?,
551 ));
552 Ok(ProcessedMessage::new(
553 self.group_id().clone(),
554 self.group_context().epoch(),
555 sender,
556 data,
557 content,
558 credential,
559 #[cfg(feature = "virtual-clients-draft")]
560 None,
561 ))
562 }
563 FramedContentBody::Proposal(_) => {
565 Err(PublicProcessMessageError::UnsupportedProposalType)
566 }
567 FramedContentBody::Commit(_) => {
568 Err(PublicProcessMessageError::UnauthorizedExternalCommitMessage)
569 }
570 }
571 }
572}