1#[cfg(all(
6 feature = "by_ref_proposal",
7 feature = "custom_proposal",
8 feature = "self_remove_proposal"
9))]
10use super::SelfRemoveProposal;
11use super::{
12 commit_sender,
13 confirmation_tag::ConfirmationTag,
14 framing::{
15 ApplicationData, Content, ContentType, MlsMessage, MlsMessagePayload, PublicMessage, Sender,
16 },
17 message_signature::AuthenticatedContent,
18 mls_rules::{CommitDirection, MlsRules},
19 proposal_filter::ProposalBundle,
20 state::GroupState,
21 transcript_hash::InterimTranscriptHash,
22 transcript_hashes, validate_group_info_member, GroupContext, GroupInfo, ReInitProposal,
23 RemoveProposal, Welcome,
24};
25use crate::{
26 client::MlsError,
27 group::proposal_filter::path_update_required,
28 key_package::validate_key_package_properties,
29 time::MlsTime,
30 tree_kem::{
31 leaf_node_validator::{LeafNodeValidator, ValidationContext},
32 node::LeafIndex,
33 path_secret::PathSecret,
34 validate_update_path, TreeKemPrivate, TreeKemPublic, ValidatedUpdatePath,
35 },
36 CipherSuiteProvider, KeyPackage,
37};
38use itertools::Itertools;
39use mls_rs_codec::{MlsDecode, MlsEncode, MlsSize};
40
41use alloc::boxed::Box;
42use alloc::vec::Vec;
43use core::fmt::{self, Debug};
44use mls_rs_core::{
45 identity::{IdentityProvider, MemberValidationContext},
46 protocol_version::ProtocolVersion,
47 psk::PreSharedKeyStorage,
48};
49
50#[cfg(feature = "by_ref_proposal")]
51use super::proposal_ref::ProposalRef;
52
53#[cfg(not(feature = "by_ref_proposal"))]
54use crate::group::proposal_cache::resolve_for_commit;
55
56use super::proposal::Proposal;
57use super::proposal_filter::ProposalInfo;
58
59#[cfg(feature = "private_message")]
60use crate::group::framing::PrivateMessage;
61
62#[derive(Debug)]
63pub(crate) struct ProvisionalState {
64 pub(crate) public_tree: TreeKemPublic,
65 pub(crate) applied_proposals: ProposalBundle,
66 pub(crate) group_context: GroupContext,
67 pub(crate) external_init_index: Option<LeafIndex>,
68 pub(crate) indexes_of_added_kpkgs: Vec<LeafIndex>,
69 pub(crate) unused_proposals: Vec<ProposalInfo<Proposal>>,
70}
71
72#[derive(Clone, Debug, PartialEq, MlsSize, MlsEncode, MlsDecode)]
73#[non_exhaustive]
74pub struct NewEpoch {
75 pub epoch: u64,
76 pub prior_state: GroupState,
77 pub applied_proposals: Vec<ProposalInfo<Proposal>>,
78 pub unused_proposals: Vec<ProposalInfo<Proposal>>,
79}
80
81impl NewEpoch {
82 pub(crate) fn new(prior_state: GroupState, provisional_state: &ProvisionalState) -> NewEpoch {
83 NewEpoch {
84 epoch: provisional_state.group_context.epoch,
85 prior_state,
86 unused_proposals: provisional_state.unused_proposals.clone(),
87 applied_proposals: provisional_state
88 .applied_proposals
89 .clone()
90 .into_proposals()
91 .collect_vec(),
92 }
93 }
94}
95
96impl NewEpoch {
97 pub fn epoch(&self) -> u64 {
98 self.epoch
99 }
100
101 pub fn prior_state(&self) -> &GroupState {
102 &self.prior_state
103 }
104
105 pub fn applied_proposals(&self) -> &[ProposalInfo<Proposal>] {
106 &self.applied_proposals
107 }
108
109 pub fn unused_proposals(&self) -> &[ProposalInfo<Proposal>] {
110 &self.unused_proposals
111 }
112}
113
114#[derive(Clone, Debug, PartialEq)]
115pub enum CommitEffect {
116 NewEpoch(Box<NewEpoch>),
117 Removed {
118 new_epoch: Box<NewEpoch>,
119 remover: Sender,
120 },
121 ReInit(ProposalInfo<ReInitProposal>),
122}
123
124impl MlsSize for CommitEffect {
125 fn mls_encoded_len(&self) -> usize {
126 0u8.mls_encoded_len()
127 + match self {
128 Self::NewEpoch(e) => e.mls_encoded_len(),
129 Self::Removed { new_epoch, remover } => {
130 new_epoch.mls_encoded_len() + remover.mls_encoded_len()
131 }
132 Self::ReInit(r) => r.mls_encoded_len(),
133 }
134 }
135}
136
137impl MlsEncode for CommitEffect {
138 fn mls_encode(&self, writer: &mut Vec<u8>) -> Result<(), mls_rs_codec::Error> {
139 match self {
140 Self::NewEpoch(e) => {
141 1u8.mls_encode(writer)?;
142 e.mls_encode(writer)?;
143 }
144 Self::Removed { new_epoch, remover } => {
145 2u8.mls_encode(writer)?;
146 new_epoch.mls_encode(writer)?;
147 remover.mls_encode(writer)?;
148 }
149 Self::ReInit(r) => {
150 3u8.mls_encode(writer)?;
151 r.mls_encode(writer)?;
152 }
153 }
154
155 Ok(())
156 }
157}
158
159impl MlsDecode for CommitEffect {
160 fn mls_decode(reader: &mut &[u8]) -> Result<Self, mls_rs_codec::Error> {
161 match u8::mls_decode(reader)? {
162 1u8 => Ok(Self::NewEpoch(NewEpoch::mls_decode(reader)?.into())),
163 2u8 => Ok(Self::Removed {
164 new_epoch: NewEpoch::mls_decode(reader)?.into(),
165 remover: Sender::mls_decode(reader)?,
166 }),
167 3u8 => Ok(Self::ReInit(ProposalInfo::mls_decode(reader)?)),
168 _ => Err(mls_rs_codec::Error::UnsupportedEnumDiscriminant),
169 }
170 }
171}
172
173#[derive(Debug, Clone)]
174#[allow(clippy::large_enum_variant)]
175pub enum ReceivedMessage {
178 ApplicationMessage(ApplicationMessageDescription),
180 Commit(CommitMessageDescription),
182 Proposal(ProposalMessageDescription),
184 GroupInfo(GroupInfo),
186 Welcome,
188 KeyPackage(KeyPackage),
190}
191
192impl TryFrom<ApplicationMessageDescription> for ReceivedMessage {
193 type Error = MlsError;
194
195 fn try_from(value: ApplicationMessageDescription) -> Result<Self, Self::Error> {
196 Ok(ReceivedMessage::ApplicationMessage(value))
197 }
198}
199
200impl From<CommitMessageDescription> for ReceivedMessage {
201 fn from(value: CommitMessageDescription) -> Self {
202 ReceivedMessage::Commit(value)
203 }
204}
205
206impl From<ProposalMessageDescription> for ReceivedMessage {
207 fn from(value: ProposalMessageDescription) -> Self {
208 ReceivedMessage::Proposal(value)
209 }
210}
211
212impl From<GroupInfo> for ReceivedMessage {
213 fn from(value: GroupInfo) -> Self {
214 ReceivedMessage::GroupInfo(value)
215 }
216}
217
218impl From<Welcome> for ReceivedMessage {
219 fn from(_: Welcome) -> Self {
220 ReceivedMessage::Welcome
221 }
222}
223
224impl From<KeyPackage> for ReceivedMessage {
225 fn from(value: KeyPackage) -> Self {
226 ReceivedMessage::KeyPackage(value)
227 }
228}
229
230#[derive(Clone, PartialEq, Eq)]
231pub struct ApplicationMessageDescription {
233 pub sender_index: u32,
235 data: ApplicationData,
237 pub authenticated_data: Vec<u8>,
239 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
242 pub unauthenticated_key_generation: Option<u32>,
243}
244
245impl Debug for ApplicationMessageDescription {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 let mut res = f.debug_struct("ApplicationMessageDescription");
248 res.field("sender_index", &self.sender_index)
249 .field("data", &self.data)
250 .field(
251 "authenticated_data",
252 &mls_rs_core::debug::pretty_bytes(&self.authenticated_data),
253 );
254 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
255 res.field(
256 "unauthenticated_key_generation",
257 &self.unauthenticated_key_generation,
258 );
259 res.finish()
260 }
261}
262
263impl ApplicationMessageDescription {
264 pub fn data(&self) -> &[u8] {
265 self.data.as_bytes()
266 }
267}
268
269#[derive(Clone, PartialEq, MlsSize, MlsEncode, MlsDecode)]
270#[non_exhaustive]
271pub struct CommitMessageDescription {
273 pub is_external: bool,
275 pub committer: u32,
277 pub effect: CommitEffect,
279 #[mls_codec(with = "mls_rs_codec::byte_vec")]
281 pub authenticated_data: Vec<u8>,
282}
283
284impl Debug for CommitMessageDescription {
285 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286 f.debug_struct("CommitMessageDescription")
287 .field("is_external", &self.is_external)
288 .field("committer", &self.committer)
289 .field("effect", &self.effect)
290 .field(
291 "authenticated_data",
292 &mls_rs_core::debug::pretty_bytes(&self.authenticated_data),
293 )
294 .finish()
295 }
296}
297
298#[derive(Debug, Clone, Copy, PartialEq, Eq, MlsEncode, MlsDecode, MlsSize)]
299#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
300#[repr(u8)]
301pub enum ProposalSender {
303 Member(u32) = 1u8,
305 External(u32) = 2u8,
308 NewMember = 3u8,
310}
311
312impl TryFrom<Sender> for ProposalSender {
313 type Error = MlsError;
314
315 fn try_from(value: Sender) -> Result<Self, Self::Error> {
316 match value {
317 Sender::Member(index) => Ok(Self::Member(index)),
318 #[cfg(feature = "by_ref_proposal")]
319 Sender::External(index) => Ok(Self::External(index)),
320 #[cfg(feature = "by_ref_proposal")]
321 Sender::NewMemberProposal => Ok(Self::NewMember),
322 Sender::NewMemberCommit => Err(MlsError::InvalidSender),
323 }
324 }
325}
326
327#[cfg(feature = "by_ref_proposal")]
328#[derive(Clone, MlsEncode, MlsDecode, MlsSize, PartialEq)]
329#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
330#[non_exhaustive]
331pub struct ProposalMessageDescription {
333 pub sender: ProposalSender,
335 pub proposal: Proposal,
337 pub authenticated_data: Vec<u8>,
339 pub proposal_ref: ProposalRef,
341}
342
343#[cfg(feature = "by_ref_proposal")]
344impl Debug for ProposalMessageDescription {
345 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346 f.debug_struct("ProposalMessageDescription")
347 .field("sender", &self.sender)
348 .field("proposal", &self.proposal)
349 .field(
350 "authenticated_data",
351 &mls_rs_core::debug::pretty_bytes(&self.authenticated_data),
352 )
353 .field("proposal_ref", &self.proposal_ref)
354 .finish()
355 }
356}
357
358#[cfg(feature = "by_ref_proposal")]
359#[derive(MlsSize, MlsEncode, MlsDecode)]
360pub struct CachedProposal {
361 pub(crate) proposal: Proposal,
362 pub(crate) proposal_ref: ProposalRef,
363 pub(crate) sender: Sender,
364}
365
366#[cfg(feature = "by_ref_proposal")]
367impl CachedProposal {
368 pub fn from_bytes(bytes: &[u8]) -> Result<Self, MlsError> {
370 Ok(Self::mls_decode(&mut &*bytes)?)
371 }
372
373 pub fn to_bytes(&self) -> Result<Vec<u8>, MlsError> {
375 Ok(self.mls_encode_to_vec()?)
376 }
377
378 pub fn proposal(&self) -> &Proposal {
380 &self.proposal
381 }
382
383 pub fn proposal_ref(&self) -> &ProposalRef {
385 &self.proposal_ref
386 }
387
388 pub fn sender(&self) -> &Sender {
390 &self.sender
391 }
392}
393
394#[cfg(feature = "by_ref_proposal")]
395impl ProposalMessageDescription {
396 pub fn cached_proposal(self) -> CachedProposal {
397 let sender = match self.sender {
398 ProposalSender::Member(i) => Sender::Member(i),
399 ProposalSender::External(i) => Sender::External(i),
400 ProposalSender::NewMember => Sender::NewMemberProposal,
401 };
402
403 CachedProposal {
404 proposal: self.proposal,
405 proposal_ref: self.proposal_ref,
406 sender,
407 }
408 }
409
410 pub fn proposal_ref(&self) -> Vec<u8> {
411 self.proposal_ref.to_vec()
412 }
413
414 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
415 pub(crate) async fn new<C: CipherSuiteProvider>(
416 cs: &C,
417 content: &AuthenticatedContent,
418 proposal: Proposal,
419 ) -> Result<Self, MlsError> {
420 Ok(ProposalMessageDescription {
421 authenticated_data: content.content.authenticated_data.clone(),
422 proposal,
423 sender: content.content.sender.try_into()?,
424 proposal_ref: ProposalRef::from_content(cs, content).await?,
425 })
426 }
427}
428
429#[cfg(not(feature = "by_ref_proposal"))]
430#[derive(Debug, Clone)]
431pub struct ProposalMessageDescription {}
433
434#[allow(clippy::large_enum_variant)]
435pub(crate) enum EventOrContent<E> {
436 #[cfg_attr(
437 not(all(feature = "private_message", feature = "external_client")),
438 allow(dead_code)
439 )]
440 Event(E),
441 Content(AuthenticatedContent),
442}
443
444#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
445#[cfg_attr(all(target_arch = "wasm32", mls_build_async), maybe_async::must_be_async(?Send))]
446#[cfg_attr(
447 all(not(target_arch = "wasm32"), mls_build_async),
448 maybe_async::must_be_async
449)]
450pub(crate) trait MessageProcessor: Send + Sync {
451 type OutputType: TryFrom<ApplicationMessageDescription, Error = MlsError>
452 + From<CommitMessageDescription>
453 + From<ProposalMessageDescription>
454 + From<GroupInfo>
455 + From<Welcome>
456 + From<KeyPackage>
457 + Send;
458
459 type MlsRules: MlsRules;
460 type IdentityProvider: IdentityProvider;
461 type CipherSuiteProvider: CipherSuiteProvider;
462 type PreSharedKeyStorage: PreSharedKeyStorage;
463
464 async fn process_incoming_message(
465 &mut self,
466 message: MlsMessage,
467 #[cfg(feature = "by_ref_proposal")] cache_proposal: bool,
468 ) -> Result<Self::OutputType, MlsError> {
469 self.process_incoming_message_with_time(
470 message,
471 #[cfg(feature = "by_ref_proposal")]
472 cache_proposal,
473 None,
474 )
475 .await
476 }
477
478 async fn process_incoming_message_with_time(
479 &mut self,
480 message: MlsMessage,
481 #[cfg(feature = "by_ref_proposal")] cache_proposal: bool,
482 time_sent: Option<MlsTime>,
483 ) -> Result<Self::OutputType, MlsError> {
484 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
485 let unauthn_key_gen_in_app_msg: Option<u32> = match message.payload {
491 MlsMessagePayload::Cipher(ref cipher_text) => self
492 .get_unauthenticated_key_generation_from_sender_data(cipher_text)
493 .unwrap_or_default(),
494 _ => None,
495 };
496
497 let event_or_content = self
498 .get_event_from_incoming_message(message, time_sent)
499 .await?;
500
501 self.process_event_or_content(
502 event_or_content,
503 #[cfg(feature = "by_ref_proposal")]
504 cache_proposal,
505 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
506 unauthn_key_gen_in_app_msg,
507 time_sent,
508 )
509 .await
510 }
511
512 async fn get_event_from_incoming_message(
513 &mut self,
514 message: MlsMessage,
515 time: Option<MlsTime>,
516 ) -> Result<EventOrContent<Self::OutputType>, MlsError> {
517 self.check_metadata(&message)?;
518
519 match message.payload {
520 MlsMessagePayload::Plain(plaintext) => {
521 self.verify_plaintext_authentication(plaintext).await
522 }
523 #[cfg(feature = "private_message")]
524 MlsMessagePayload::Cipher(cipher_text) => self.process_ciphertext(&cipher_text).await,
525 MlsMessagePayload::GroupInfo(group_info) => {
526 validate_group_info_member(
527 self.group_state(),
528 message.version,
529 &group_info,
530 self.cipher_suite_provider(),
531 )
532 .await?;
533
534 Ok(EventOrContent::Event(group_info.into()))
535 }
536 MlsMessagePayload::Welcome(welcome) => {
537 self.validate_welcome(&welcome, message.version)?;
538
539 Ok(EventOrContent::Event(welcome.into()))
540 }
541 MlsMessagePayload::KeyPackage(key_package) => {
542 self.validate_key_package(&key_package, message.version, time)
543 .await?;
544
545 Ok(EventOrContent::Event(key_package.into()))
546 }
547 }
548 }
549
550 async fn process_event_or_content(
551 &mut self,
552 event_or_content: EventOrContent<Self::OutputType>,
553 #[cfg(feature = "by_ref_proposal")] cache_proposal: bool,
554 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
555 unauthn_key_gen_in_app_msg: Option<u32>,
556 time_sent: Option<MlsTime>,
557 ) -> Result<Self::OutputType, MlsError> {
558 let msg = match event_or_content {
559 EventOrContent::Event(event) => event,
560 EventOrContent::Content(content) => {
561 self.process_auth_content(
562 content,
563 #[cfg(feature = "by_ref_proposal")]
564 cache_proposal,
565 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
566 unauthn_key_gen_in_app_msg,
567 time_sent,
568 )
569 .await?
570 }
571 };
572
573 Ok(msg)
574 }
575
576 async fn process_auth_content(
577 &mut self,
578 auth_content: AuthenticatedContent,
579 #[cfg(feature = "by_ref_proposal")] cache_proposal: bool,
580 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
581 unauthn_key_gen_in_app_msg: Option<u32>,
582 time_sent: Option<MlsTime>,
583 ) -> Result<Self::OutputType, MlsError> {
584 let event = match auth_content.content.content {
585 #[cfg(feature = "private_message")]
586 Content::Application(data) => {
587 let authenticated_data = auth_content.content.authenticated_data;
588 let sender = auth_content.content.sender;
589
590 self.process_application_message(
591 data,
592 sender,
593 authenticated_data,
594 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
595 unauthn_key_gen_in_app_msg,
596 )
597 .and_then(Self::OutputType::try_from)
598 }
599 Content::Commit(_) => self
600 .process_commit(auth_content, time_sent)
601 .await
602 .map(Self::OutputType::from),
603 #[cfg(feature = "by_ref_proposal")]
604 Content::Proposal(ref proposal) => self
605 .process_proposal(&auth_content, proposal, cache_proposal)
606 .await
607 .map(Self::OutputType::from),
608 }?;
609
610 Ok(event)
611 }
612
613 #[cfg(feature = "private_message")]
614 fn process_application_message(
615 &self,
616 data: ApplicationData,
617 sender: Sender,
618 authenticated_data: Vec<u8>,
619 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
620 unauthenticated_key_generation: Option<u32>,
621 ) -> Result<ApplicationMessageDescription, MlsError> {
622 let Sender::Member(sender_index) = sender else {
623 return Err(MlsError::InvalidSender);
624 };
625
626 Ok(ApplicationMessageDescription {
627 authenticated_data,
628 sender_index,
629 data,
630 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
631 unauthenticated_key_generation,
632 })
633 }
634
635 #[cfg(feature = "by_ref_proposal")]
636 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
637 async fn process_proposal(
638 &mut self,
639 auth_content: &AuthenticatedContent,
640 proposal: &Proposal,
641 cache_proposal: bool,
642 ) -> Result<ProposalMessageDescription, MlsError> {
643 let proposal = ProposalMessageDescription::new(
644 self.cipher_suite_provider(),
645 auth_content,
646 proposal.clone(),
647 )
648 .await?;
649
650 let group_state = self.group_state_mut();
651
652 if cache_proposal {
653 group_state.proposals.insert(
654 proposal.proposal_ref.clone(),
655 proposal.proposal.clone(),
656 auth_content.content.sender,
657 );
658 }
659
660 Ok(proposal)
661 }
662
663 async fn process_commit(
664 &mut self,
665 auth_content: AuthenticatedContent,
666 time_sent: Option<MlsTime>,
667 ) -> Result<CommitMessageDescription, MlsError> {
668 if self.group_state().pending_reinit.is_some() {
669 return Err(MlsError::GroupUsedAfterReInit);
670 }
671
672 let (interim_transcript_hash, confirmed_transcript_hash) = transcript_hashes(
674 self.cipher_suite_provider(),
675 &self.group_state().interim_transcript_hash,
676 &auth_content,
677 )
678 .await?;
679
680 #[cfg(any(feature = "private_message", feature = "by_ref_proposal"))]
681 let commit = match auth_content.content.content {
682 Content::Commit(commit) => Ok(commit),
683 _ => Err(MlsError::UnexpectedMessageType),
684 }?;
685
686 #[cfg(not(any(feature = "private_message", feature = "by_ref_proposal")))]
687 let Content::Commit(commit) = auth_content.content.content;
688
689 let group_state = self.group_state();
690 let id_provider = self.identity_provider();
691
692 #[cfg(feature = "by_ref_proposal")]
693 let proposals = group_state
694 .proposals
695 .resolve_for_commit(auth_content.content.sender, commit.proposals)?;
696
697 #[cfg(not(feature = "by_ref_proposal"))]
698 let proposals = resolve_for_commit(auth_content.content.sender, commit.proposals)?;
699
700 let mut provisional_state = group_state
701 .apply_resolved(
702 auth_content.content.sender,
703 proposals,
704 commit.path.as_ref().map(|path| &path.leaf_node),
705 &id_provider,
706 self.cipher_suite_provider(),
707 &self.psk_storage(),
708 &self.mls_rules(),
709 time_sent,
710 CommitDirection::Receive,
711 )
712 .await?;
713
714 let sender = commit_sender(&auth_content.content.sender, &provisional_state)?;
715
716 if path_update_required(&provisional_state.applied_proposals, &self.mls_rules())
719 && commit.path.is_none()
720 {
721 return Err(MlsError::CommitMissingPath);
722 }
723
724 let self_removed = self.removal_proposal(&provisional_state);
725 #[cfg(all(
726 feature = "by_ref_proposal",
727 feature = "custom_proposal",
728 feature = "self_remove_proposal"
729 ))]
730 let self_removed_by_self = self.self_removal_proposal(&provisional_state);
731
732 let is_self_removed = self_removed.is_some();
733 #[cfg(all(
734 feature = "by_ref_proposal",
735 feature = "custom_proposal",
736 feature = "self_remove_proposal"
737 ))]
738 let is_self_removed = is_self_removed || self_removed_by_self.is_some();
739
740 let update_path = match commit.path {
741 Some(update_path) => Some(
742 validate_update_path(
743 &self.identity_provider(),
744 self.cipher_suite_provider(),
745 update_path,
746 &provisional_state,
747 sender,
748 time_sent,
749 &group_state.context,
750 )
751 .await?,
752 ),
753 None => None,
754 };
755
756 let commit_effect =
757 if let Some(reinit) = provisional_state.applied_proposals.reinitializations.pop() {
758 self.group_state_mut().pending_reinit = Some(reinit.proposal.clone());
759 CommitEffect::ReInit(reinit)
760 } else if let Some(remove_proposal) = self_removed {
761 let new_epoch = NewEpoch::new(self.group_state().clone(), &provisional_state);
762 CommitEffect::Removed {
763 remover: remove_proposal.sender,
764 new_epoch: Box::new(new_epoch),
765 }
766 } else {
767 CommitEffect::NewEpoch(Box::new(NewEpoch::new(
768 self.group_state().clone(),
769 &provisional_state,
770 )))
771 };
772
773 #[cfg(all(
774 feature = "by_ref_proposal",
775 feature = "custom_proposal",
776 feature = "self_remove_proposal"
777 ))]
778 let commit_effect = if let Some(self_remove_proposal) = self_removed_by_self {
779 let new_epoch = NewEpoch::new(self.group_state().clone(), &provisional_state);
780 CommitEffect::Removed {
781 remover: self_remove_proposal.sender,
782 new_epoch: Box::new(new_epoch),
783 }
784 } else {
785 commit_effect
786 };
787
788 let new_secrets = match update_path {
789 Some(update_path) if !is_self_removed => {
790 self.apply_update_path(sender, &update_path, &mut provisional_state)
791 .await
792 }
793 _ => Ok(None),
794 }?;
795
796 provisional_state.group_context.confirmed_transcript_hash = confirmed_transcript_hash;
798
799 provisional_state
801 .public_tree
802 .update_hashes(&[sender], self.cipher_suite_provider())
803 .await?;
804
805 provisional_state.group_context.tree_hash = provisional_state
807 .public_tree
808 .tree_hash(self.cipher_suite_provider())
809 .await?;
810
811 if let Some(confirmation_tag) = &auth_content.auth.confirmation_tag {
812 if !is_self_removed {
813 self.update_key_schedule(
815 new_secrets,
816 interim_transcript_hash,
817 confirmation_tag,
818 provisional_state,
819 )
820 .await?;
821 }
822 Ok(CommitMessageDescription {
823 is_external: matches!(auth_content.content.sender, Sender::NewMemberCommit),
824 authenticated_data: auth_content.content.authenticated_data,
825 committer: *sender,
826 effect: commit_effect,
827 })
828 } else {
829 Err(MlsError::InvalidConfirmationTag)
830 }
831 }
832
833 fn group_state(&self) -> &GroupState;
834 fn group_state_mut(&mut self) -> &mut GroupState;
835 fn mls_rules(&self) -> Self::MlsRules;
836 fn identity_provider(&self) -> Self::IdentityProvider;
837 fn cipher_suite_provider(&self) -> &Self::CipherSuiteProvider;
838 fn psk_storage(&self) -> Self::PreSharedKeyStorage;
839
840 fn removal_proposal(
841 &self,
842 provisional_state: &ProvisionalState,
843 ) -> Option<ProposalInfo<RemoveProposal>>;
844
845 #[cfg(all(
846 feature = "by_ref_proposal",
847 feature = "custom_proposal",
848 feature = "self_remove_proposal"
849 ))]
850 fn self_removal_proposal(
851 &self,
852 provisional_state: &ProvisionalState,
853 ) -> Option<ProposalInfo<SelfRemoveProposal>>;
854
855 #[cfg(feature = "private_message")]
856 fn min_epoch_available(&self) -> Option<u64>;
857
858 fn check_metadata(&self, message: &MlsMessage) -> Result<(), MlsError> {
859 let context = &self.group_state().context;
860
861 if message.version != context.protocol_version {
862 return Err(MlsError::ProtocolVersionMismatch);
863 }
864
865 if let Some((group_id, epoch, content_type)) = match &message.payload {
866 MlsMessagePayload::Plain(plaintext) => Some((
867 &plaintext.content.group_id,
868 plaintext.content.epoch,
869 plaintext.content.content_type(),
870 )),
871 #[cfg(feature = "private_message")]
872 MlsMessagePayload::Cipher(ciphertext) => Some((
873 &ciphertext.group_id,
874 ciphertext.epoch,
875 ciphertext.content_type,
876 )),
877 _ => None,
878 } {
879 if group_id != &context.group_id {
880 return Err(MlsError::GroupIdMismatch);
881 }
882
883 match content_type {
884 ContentType::Commit => {
885 if context.epoch != epoch {
886 Err(MlsError::InvalidEpoch)
887 } else {
888 Ok(())
889 }
890 }
891 #[cfg(feature = "by_ref_proposal")]
892 ContentType::Proposal => {
893 if context.epoch != epoch {
894 Err(MlsError::InvalidEpoch)
895 } else {
896 Ok(())
897 }
898 }
899 #[cfg(feature = "private_message")]
900 ContentType::Application => {
901 if let Some(min) = self.min_epoch_available() {
902 if epoch < min {
903 Err(MlsError::InvalidEpoch)
904 } else {
905 Ok(())
906 }
907 } else {
908 Ok(())
909 }
910 }
911 }?;
912
913 let check_epoch = content_type == ContentType::Commit;
915
916 #[cfg(feature = "by_ref_proposal")]
917 let check_epoch = check_epoch || content_type == ContentType::Proposal;
918
919 if check_epoch && epoch != context.epoch {
920 return Err(MlsError::InvalidEpoch);
921 }
922
923 #[cfg(feature = "private_message")]
925 if !matches!(&message.payload, MlsMessagePayload::Cipher(_))
926 && content_type == ContentType::Application
927 {
928 return Err(MlsError::UnencryptedApplicationMessage);
929 }
930 }
931
932 Ok(())
933 }
934
935 fn validate_welcome(
936 &self,
937 welcome: &Welcome,
938 version: ProtocolVersion,
939 ) -> Result<(), MlsError> {
940 let state = self.group_state();
941
942 (welcome.cipher_suite == state.context.cipher_suite
943 && version == state.context.protocol_version)
944 .then_some(())
945 .ok_or(MlsError::InvalidWelcomeMessage)
946 }
947
948 async fn validate_key_package(
949 &self,
950 key_package: &KeyPackage,
951 version: ProtocolVersion,
952 time: Option<MlsTime>,
953 ) -> Result<(), MlsError> {
954 let cs = self.cipher_suite_provider();
955 let id = self.identity_provider();
956
957 validate_key_package(key_package, version, cs, &id, time).await
958 }
959
960 #[cfg(feature = "private_message")]
961 async fn process_ciphertext(
962 &mut self,
963 cipher_text: &PrivateMessage,
964 ) -> Result<EventOrContent<Self::OutputType>, MlsError>;
965
966 async fn verify_plaintext_authentication(
967 &self,
968 message: PublicMessage,
969 ) -> Result<EventOrContent<Self::OutputType>, MlsError>;
970
971 #[cfg(all(feature = "export_key_generation", feature = "private_message"))]
972 async fn get_unauthenticated_key_generation_from_sender_data(
974 &mut self,
975 cipher_text: &PrivateMessage,
976 ) -> Result<Option<u32>, MlsError>;
977
978 async fn apply_update_path(
979 &mut self,
980 sender: LeafIndex,
981 update_path: &ValidatedUpdatePath,
982 provisional_state: &mut ProvisionalState,
983 ) -> Result<Option<(TreeKemPrivate, PathSecret)>, MlsError> {
984 provisional_state
985 .public_tree
986 .apply_update_path(
987 sender,
988 update_path,
989 &provisional_state.group_context.extensions,
990 self.identity_provider(),
991 self.cipher_suite_provider(),
992 )
993 .await
994 .map(|_| None)
995 }
996
997 async fn update_key_schedule(
998 &mut self,
999 secrets: Option<(TreeKemPrivate, PathSecret)>,
1000 interim_transcript_hash: InterimTranscriptHash,
1001 confirmation_tag: &ConfirmationTag,
1002 provisional_public_state: ProvisionalState,
1003 ) -> Result<(), MlsError>;
1004}
1005
1006#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1007pub(crate) async fn validate_key_package<C: CipherSuiteProvider, I: IdentityProvider>(
1008 key_package: &KeyPackage,
1009 version: ProtocolVersion,
1010 cs: &C,
1011 id: &I,
1012 time: Option<MlsTime>,
1013) -> Result<(), MlsError> {
1014 let validator = LeafNodeValidator::new(cs, id, MemberValidationContext::None);
1015
1016 #[cfg(feature = "std")]
1017 let context = Some(MlsTime::now());
1018
1019 #[cfg(not(feature = "std"))]
1020 let context = None;
1021
1022 let context = if time.is_some() { time } else { context };
1023
1024 let context = ValidationContext::Add(context);
1025
1026 validator
1027 .check_if_valid(&key_package.leaf_node, context)
1028 .await?;
1029
1030 validate_key_package_properties(key_package, version, cs).await?;
1031
1032 Ok(())
1033}
1034
1035#[cfg(test)]
1036mod tests {
1037 use alloc::{vec, vec::Vec};
1038 use mls_rs_codec::{MlsDecode, MlsEncode};
1039
1040 use crate::{
1041 client::test_utils::TEST_PROTOCOL_VERSION,
1042 group::{test_utils::get_test_group_context, GroupState, Sender},
1043 };
1044
1045 use super::{CommitEffect, NewEpoch};
1046
1047 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
1048 async fn commit_effect_codec() {
1049 let epoch = NewEpoch {
1050 epoch: 7,
1051 prior_state: GroupState {
1052 #[cfg(feature = "by_ref_proposal")]
1053 proposals: crate::group::ProposalCache::new(TEST_PROTOCOL_VERSION, vec![]),
1054 context: get_test_group_context(7, 7.into()).await,
1055 public_tree: Default::default(),
1056 interim_transcript_hash: vec![].into(),
1057 pending_reinit: None,
1058 confirmation_tag: Default::default(),
1059 },
1060 applied_proposals: vec![],
1061 unused_proposals: vec![],
1062 };
1063
1064 let effects = vec![
1065 CommitEffect::NewEpoch(epoch.clone().into()),
1066 CommitEffect::Removed {
1067 new_epoch: epoch.into(),
1068 remover: Sender::Member(0),
1069 },
1070 ];
1071
1072 let bytes = effects.mls_encode_to_vec().unwrap();
1073
1074 assert_eq!(
1075 effects,
1076 Vec::<CommitEffect>::mls_decode(&mut &*bytes).unwrap()
1077 );
1078 }
1079}