• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 // Copyright by contributors to this project.
3 // SPDX-License-Identifier: (Apache-2.0 OR MIT)
4 
5 use super::{
6     commit_sender,
7     confirmation_tag::ConfirmationTag,
8     framing::{
9         ApplicationData, Content, ContentType, MlsMessage, MlsMessagePayload, PublicMessage, Sender,
10     },
11     message_signature::AuthenticatedContent,
12     mls_rules::{CommitDirection, MlsRules},
13     proposal_filter::ProposalBundle,
14     state::GroupState,
15     transcript_hash::InterimTranscriptHash,
16     transcript_hashes, validate_group_info_member, GroupContext, GroupInfo, Welcome,
17 };
18 use crate::{
19     client::MlsError,
20     key_package::validate_key_package_properties,
21     time::MlsTime,
22     tree_kem::{
23         leaf_node_validator::{LeafNodeValidator, ValidationContext},
24         node::LeafIndex,
25         path_secret::PathSecret,
26         validate_update_path, TreeKemPrivate, TreeKemPublic, ValidatedUpdatePath,
27     },
28     CipherSuiteProvider, KeyPackage,
29 };
30 use mls_rs_codec::{MlsDecode, MlsEncode, MlsSize};
31 
32 #[cfg(mls_build_async)]
33 use alloc::boxed::Box;
34 use alloc::vec::Vec;
35 use core::fmt::{self, Debug};
36 use mls_rs_core::{
37     identity::IdentityProvider, protocol_version::ProtocolVersion, psk::PreSharedKeyStorage,
38 };
39 
40 #[cfg(feature = "by_ref_proposal")]
41 use super::proposal_ref::ProposalRef;
42 
43 #[cfg(not(feature = "by_ref_proposal"))]
44 use crate::group::proposal_cache::resolve_for_commit;
45 
46 #[cfg(feature = "by_ref_proposal")]
47 use super::proposal::Proposal;
48 
49 #[cfg(feature = "custom_proposal")]
50 use super::proposal_filter::ProposalInfo;
51 
52 #[cfg(feature = "state_update")]
53 use mls_rs_core::{
54     crypto::CipherSuite,
55     group::{MemberUpdate, RosterUpdate},
56 };
57 
58 #[cfg(all(feature = "state_update", feature = "psk"))]
59 use mls_rs_core::psk::ExternalPskId;
60 
61 #[cfg(feature = "state_update")]
62 use crate::tree_kem::UpdatePath;
63 
64 #[cfg(feature = "state_update")]
65 use super::{member_from_key_package, member_from_leaf_node};
66 
67 #[cfg(all(feature = "state_update", feature = "custom_proposal"))]
68 use super::proposal::CustomProposal;
69 
70 #[cfg(feature = "private_message")]
71 use crate::group::framing::PrivateMessage;
72 
73 #[derive(Debug)]
74 pub(crate) struct ProvisionalState {
75     pub(crate) public_tree: TreeKemPublic,
76     pub(crate) applied_proposals: ProposalBundle,
77     pub(crate) group_context: GroupContext,
78     pub(crate) external_init_index: Option<LeafIndex>,
79     pub(crate) indexes_of_added_kpkgs: Vec<LeafIndex>,
80     #[cfg(feature = "by_ref_proposal")]
81     pub(crate) unused_proposals: Vec<crate::mls_rules::ProposalInfo<Proposal>>,
82 }
83 
84 //By default, the path field of a Commit MUST be populated. The path field MAY be omitted if
85 //(a) it covers at least one proposal and (b) none of the proposals covered by the Commit are
86 //of "path required" types. A proposal type requires a path if it cannot change the group
87 //membership in a way that requires the forward secrecy and post-compromise security guarantees
88 //that an UpdatePath provides. The only proposal types defined in this document that do not
89 //require a path are:
90 
91 // add
92 // psk
93 // reinit
path_update_required(proposals: &ProposalBundle) -> bool94 pub(crate) fn path_update_required(proposals: &ProposalBundle) -> bool {
95     let res = proposals.external_init_proposals().first().is_some();
96 
97     #[cfg(feature = "by_ref_proposal")]
98     let res = res || !proposals.update_proposals().is_empty();
99 
100     res || proposals.length() == 0
101         || proposals.group_context_extensions_proposal().is_some()
102         || !proposals.remove_proposals().is_empty()
103 }
104 
105 /// Representation of changes made by a [commit](crate::Group::commit).
106 #[cfg(feature = "state_update")]
107 #[derive(Clone, Debug, PartialEq)]
108 pub struct StateUpdate {
109     pub(crate) roster_update: RosterUpdate,
110     #[cfg(feature = "psk")]
111     pub(crate) added_psks: Vec<ExternalPskId>,
112     pub(crate) pending_reinit: Option<CipherSuite>,
113     pub(crate) active: bool,
114     pub(crate) epoch: u64,
115     #[cfg(feature = "custom_proposal")]
116     pub(crate) custom_proposals: Vec<ProposalInfo<CustomProposal>>,
117     #[cfg(feature = "by_ref_proposal")]
118     pub(crate) unused_proposals: Vec<crate::mls_rules::ProposalInfo<Proposal>>,
119 }
120 
121 #[cfg(not(feature = "state_update"))]
122 #[non_exhaustive]
123 #[derive(Clone, Debug, PartialEq)]
124 pub struct StateUpdate {}
125 
126 #[cfg(feature = "state_update")]
127 impl StateUpdate {
128     /// Changes to the roster as a result of proposals.
roster_update(&self) -> &RosterUpdate129     pub fn roster_update(&self) -> &RosterUpdate {
130         &self.roster_update
131     }
132 
133     #[cfg(feature = "psk")]
134     /// Pre-shared keys that have been added to the group.
added_psks(&self) -> &[ExternalPskId]135     pub fn added_psks(&self) -> &[ExternalPskId] {
136         &self.added_psks
137     }
138 
139     /// Flag to indicate if the group is now pending reinitialization due to
140     /// receiving a [`ReInit`](crate::group::proposal::Proposal::ReInit)
141     /// proposal.
is_pending_reinit(&self) -> bool142     pub fn is_pending_reinit(&self) -> bool {
143         self.pending_reinit.is_some()
144     }
145 
146     /// Flag to indicate the group is still active. This will be false if the
147     /// member processing the commit has been removed from the group.
is_active(&self) -> bool148     pub fn is_active(&self) -> bool {
149         self.active
150     }
151 
152     /// The new epoch of the group state.
new_epoch(&self) -> u64153     pub fn new_epoch(&self) -> u64 {
154         self.epoch
155     }
156 
157     /// Custom proposals that were committed to.
158     #[cfg(feature = "custom_proposal")]
custom_proposals(&self) -> &[ProposalInfo<CustomProposal>]159     pub fn custom_proposals(&self) -> &[ProposalInfo<CustomProposal>] {
160         &self.custom_proposals
161     }
162 
163     /// Proposals that were received in the prior epoch but not committed to.
164     #[cfg(feature = "by_ref_proposal")]
unused_proposals(&self) -> &[crate::mls_rules::ProposalInfo<Proposal>]165     pub fn unused_proposals(&self) -> &[crate::mls_rules::ProposalInfo<Proposal>] {
166         &self.unused_proposals
167     }
168 
pending_reinit_ciphersuite(&self) -> Option<CipherSuite>169     pub fn pending_reinit_ciphersuite(&self) -> Option<CipherSuite> {
170         self.pending_reinit
171     }
172 }
173 
174 #[cfg_attr(
175     all(feature = "ffi", not(test)),
176     safer_ffi_gen::ffi_type(clone, opaque)
177 )]
178 #[derive(Debug, Clone)]
179 #[allow(clippy::large_enum_variant)]
180 /// An event generated as a result of processing a message for a group with
181 /// [`Group::process_incoming_message`](crate::group::Group::process_incoming_message).
182 pub enum ReceivedMessage {
183     /// An application message was decrypted.
184     ApplicationMessage(ApplicationMessageDescription),
185     /// A new commit was processed creating a new group state.
186     Commit(CommitMessageDescription),
187     /// A proposal was received.
188     Proposal(ProposalMessageDescription),
189     /// Validated GroupInfo object
190     GroupInfo(GroupInfo),
191     /// Validated welcome message
192     Welcome,
193     /// Validated key package
194     KeyPackage(KeyPackage),
195 }
196 
197 impl TryFrom<ApplicationMessageDescription> for ReceivedMessage {
198     type Error = MlsError;
199 
try_from(value: ApplicationMessageDescription) -> Result<Self, Self::Error>200     fn try_from(value: ApplicationMessageDescription) -> Result<Self, Self::Error> {
201         Ok(ReceivedMessage::ApplicationMessage(value))
202     }
203 }
204 
205 impl From<CommitMessageDescription> for ReceivedMessage {
from(value: CommitMessageDescription) -> Self206     fn from(value: CommitMessageDescription) -> Self {
207         ReceivedMessage::Commit(value)
208     }
209 }
210 
211 impl From<ProposalMessageDescription> for ReceivedMessage {
from(value: ProposalMessageDescription) -> Self212     fn from(value: ProposalMessageDescription) -> Self {
213         ReceivedMessage::Proposal(value)
214     }
215 }
216 
217 impl From<GroupInfo> for ReceivedMessage {
from(value: GroupInfo) -> Self218     fn from(value: GroupInfo) -> Self {
219         ReceivedMessage::GroupInfo(value)
220     }
221 }
222 
223 impl From<Welcome> for ReceivedMessage {
from(_: Welcome) -> Self224     fn from(_: Welcome) -> Self {
225         ReceivedMessage::Welcome
226     }
227 }
228 
229 impl From<KeyPackage> for ReceivedMessage {
from(value: KeyPackage) -> Self230     fn from(value: KeyPackage) -> Self {
231         ReceivedMessage::KeyPackage(value)
232     }
233 }
234 
235 #[cfg_attr(
236     all(feature = "ffi", not(test)),
237     safer_ffi_gen::ffi_type(clone, opaque)
238 )]
239 #[derive(Clone, PartialEq, Eq)]
240 /// Description of a MLS application message.
241 pub struct ApplicationMessageDescription {
242     /// Index of this user in the group state.
243     pub sender_index: u32,
244     /// Received application data.
245     data: ApplicationData,
246     /// Plaintext authenticated data in the received MLS packet.
247     pub authenticated_data: Vec<u8>,
248 }
249 
250 impl Debug for ApplicationMessageDescription {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result251     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252         f.debug_struct("ApplicationMessageDescription")
253             .field("sender_index", &self.sender_index)
254             .field("data", &self.data)
255             .field(
256                 "authenticated_data",
257                 &mls_rs_core::debug::pretty_bytes(&self.authenticated_data),
258             )
259             .finish()
260     }
261 }
262 
263 #[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::safer_ffi_gen)]
264 impl ApplicationMessageDescription {
data(&self) -> &[u8]265     pub fn data(&self) -> &[u8] {
266         self.data.as_bytes()
267     }
268 }
269 
270 #[cfg_attr(
271     all(feature = "ffi", not(test)),
272     safer_ffi_gen::ffi_type(clone, opaque)
273 )]
274 #[derive(Clone, PartialEq)]
275 #[non_exhaustive]
276 /// Description of a processed MLS commit message.
277 pub struct CommitMessageDescription {
278     /// True if this is the result of an external commit.
279     pub is_external: bool,
280     /// The index in the group state of the member who performed this commit.
281     pub committer: u32,
282     /// A full description of group state changes as a result of this commit.
283     pub state_update: StateUpdate,
284     /// Plaintext authenticated data in the received MLS packet.
285     pub authenticated_data: Vec<u8>,
286 }
287 
288 impl Debug for CommitMessageDescription {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result289     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290         f.debug_struct("CommitMessageDescription")
291             .field("is_external", &self.is_external)
292             .field("committer", &self.committer)
293             .field("state_update", &self.state_update)
294             .field(
295                 "authenticated_data",
296                 &mls_rs_core::debug::pretty_bytes(&self.authenticated_data),
297             )
298             .finish()
299     }
300 }
301 
302 #[derive(Debug, Clone, Copy, PartialEq, Eq, MlsEncode, MlsDecode, MlsSize)]
303 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
304 #[repr(u8)]
305 /// Proposal sender type.
306 pub enum ProposalSender {
307     /// A current member of the group by index in the group state.
308     Member(u32) = 1u8,
309     /// An external entity by index within an
310     /// [`ExternalSendersExt`](crate::extension::built_in::ExternalSendersExt).
311     External(u32) = 2u8,
312     /// A new member proposing their addition to the group.
313     NewMember = 3u8,
314 }
315 
316 impl TryFrom<Sender> for ProposalSender {
317     type Error = MlsError;
318 
try_from(value: Sender) -> Result<Self, Self::Error>319     fn try_from(value: Sender) -> Result<Self, Self::Error> {
320         match value {
321             Sender::Member(index) => Ok(Self::Member(index)),
322             #[cfg(feature = "by_ref_proposal")]
323             Sender::External(index) => Ok(Self::External(index)),
324             #[cfg(feature = "by_ref_proposal")]
325             Sender::NewMemberProposal => Ok(Self::NewMember),
326             Sender::NewMemberCommit => Err(MlsError::InvalidSender),
327         }
328     }
329 }
330 
331 #[cfg(feature = "by_ref_proposal")]
332 #[cfg_attr(
333     all(feature = "ffi", not(test)),
334     safer_ffi_gen::ffi_type(clone, opaque)
335 )]
336 #[derive(Clone, MlsEncode, MlsDecode, MlsSize, PartialEq)]
337 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
338 #[non_exhaustive]
339 /// Description of a processed MLS proposal message.
340 pub struct ProposalMessageDescription {
341     /// Sender of the proposal.
342     pub sender: ProposalSender,
343     /// Proposal content.
344     pub proposal: Proposal,
345     /// Plaintext authenticated data in the received MLS packet.
346     pub authenticated_data: Vec<u8>,
347     /// Proposal reference.
348     pub proposal_ref: ProposalRef,
349 }
350 
351 #[cfg(feature = "by_ref_proposal")]
352 impl Debug for ProposalMessageDescription {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result353     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
354         f.debug_struct("ProposalMessageDescription")
355             .field("sender", &self.sender)
356             .field("proposal", &self.proposal)
357             .field(
358                 "authenticated_data",
359                 &mls_rs_core::debug::pretty_bytes(&self.authenticated_data),
360             )
361             .field("proposal_ref", &self.proposal_ref)
362             .finish()
363     }
364 }
365 
366 #[cfg(feature = "by_ref_proposal")]
367 #[derive(MlsSize, MlsEncode, MlsDecode)]
368 pub struct CachedProposal {
369     pub(crate) proposal: Proposal,
370     pub(crate) proposal_ref: ProposalRef,
371     pub(crate) sender: Sender,
372 }
373 
374 #[cfg(feature = "by_ref_proposal")]
375 impl CachedProposal {
376     /// Deserialize the proposal
from_bytes(bytes: &[u8]) -> Result<Self, MlsError>377     pub fn from_bytes(bytes: &[u8]) -> Result<Self, MlsError> {
378         Ok(Self::mls_decode(&mut &*bytes)?)
379     }
380 
381     /// Serialize the proposal
to_bytes(&self) -> Result<Vec<u8>, MlsError>382     pub fn to_bytes(&self) -> Result<Vec<u8>, MlsError> {
383         Ok(self.mls_encode_to_vec()?)
384     }
385 }
386 
387 #[cfg(feature = "by_ref_proposal")]
388 impl ProposalMessageDescription {
cached_proposal(self) -> CachedProposal389     pub fn cached_proposal(self) -> CachedProposal {
390         let sender = match self.sender {
391             ProposalSender::Member(i) => Sender::Member(i),
392             ProposalSender::External(i) => Sender::External(i),
393             ProposalSender::NewMember => Sender::NewMemberProposal,
394         };
395 
396         CachedProposal {
397             proposal: self.proposal,
398             proposal_ref: self.proposal_ref,
399             sender,
400         }
401     }
402 
proposal_ref(&self) -> Vec<u8>403     pub fn proposal_ref(&self) -> Vec<u8> {
404         self.proposal_ref.to_vec()
405     }
406 
407     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
new<C: CipherSuiteProvider>( cs: &C, content: &AuthenticatedContent, proposal: Proposal, ) -> Result<Self, MlsError>408     pub(crate) async fn new<C: CipherSuiteProvider>(
409         cs: &C,
410         content: &AuthenticatedContent,
411         proposal: Proposal,
412     ) -> Result<Self, MlsError> {
413         Ok(ProposalMessageDescription {
414             authenticated_data: content.content.authenticated_data.clone(),
415             proposal,
416             sender: content.content.sender.try_into()?,
417             proposal_ref: ProposalRef::from_content(cs, content).await?,
418         })
419     }
420 }
421 
422 #[cfg(not(feature = "by_ref_proposal"))]
423 #[cfg_attr(
424     all(feature = "ffi", not(test)),
425     safer_ffi_gen::ffi_type(clone, opaque)
426 )]
427 #[derive(Debug, Clone)]
428 /// Description of a processed MLS proposal message.
429 pub struct ProposalMessageDescription {}
430 
431 #[allow(clippy::large_enum_variant)]
432 pub(crate) enum EventOrContent<E> {
433     #[cfg_attr(
434         not(all(feature = "private_message", feature = "external_client")),
435         allow(dead_code)
436     )]
437     Event(E),
438     Content(AuthenticatedContent),
439 }
440 
441 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
442 #[cfg_attr(all(target_arch = "wasm32", mls_build_async), maybe_async::must_be_async(?Send))]
443 #[cfg_attr(
444     all(not(target_arch = "wasm32"), mls_build_async),
445     maybe_async::must_be_async
446 )]
447 pub(crate) trait MessageProcessor: Send + Sync {
448     type OutputType: TryFrom<ApplicationMessageDescription, Error = MlsError>
449         + From<CommitMessageDescription>
450         + From<ProposalMessageDescription>
451         + From<GroupInfo>
452         + From<Welcome>
453         + From<KeyPackage>
454         + Send;
455 
456     type MlsRules: MlsRules;
457     type IdentityProvider: IdentityProvider;
458     type CipherSuiteProvider: CipherSuiteProvider;
459     type PreSharedKeyStorage: PreSharedKeyStorage;
460 
process_incoming_message( &mut self, message: MlsMessage, #[cfg(feature = "by_ref_proposal")] cache_proposal: bool, ) -> Result<Self::OutputType, MlsError>461     async fn process_incoming_message(
462         &mut self,
463         message: MlsMessage,
464         #[cfg(feature = "by_ref_proposal")] cache_proposal: bool,
465     ) -> Result<Self::OutputType, MlsError> {
466         self.process_incoming_message_with_time(
467             message,
468             #[cfg(feature = "by_ref_proposal")]
469             cache_proposal,
470             None,
471         )
472         .await
473     }
474 
process_incoming_message_with_time( &mut self, message: MlsMessage, #[cfg(feature = "by_ref_proposal")] cache_proposal: bool, time_sent: Option<MlsTime>, ) -> Result<Self::OutputType, MlsError>475     async fn process_incoming_message_with_time(
476         &mut self,
477         message: MlsMessage,
478         #[cfg(feature = "by_ref_proposal")] cache_proposal: bool,
479         time_sent: Option<MlsTime>,
480     ) -> Result<Self::OutputType, MlsError> {
481         let event_or_content = self.get_event_from_incoming_message(message).await?;
482 
483         self.process_event_or_content(
484             event_or_content,
485             #[cfg(feature = "by_ref_proposal")]
486             cache_proposal,
487             time_sent,
488         )
489         .await
490     }
491 
get_event_from_incoming_message( &mut self, message: MlsMessage, ) -> Result<EventOrContent<Self::OutputType>, MlsError>492     async fn get_event_from_incoming_message(
493         &mut self,
494         message: MlsMessage,
495     ) -> Result<EventOrContent<Self::OutputType>, MlsError> {
496         self.check_metadata(&message)?;
497 
498         match message.payload {
499             MlsMessagePayload::Plain(plaintext) => {
500                 self.verify_plaintext_authentication(plaintext).await
501             }
502             #[cfg(feature = "private_message")]
503             MlsMessagePayload::Cipher(cipher_text) => self.process_ciphertext(&cipher_text).await,
504             MlsMessagePayload::GroupInfo(group_info) => {
505                 validate_group_info_member(
506                     self.group_state(),
507                     message.version,
508                     &group_info,
509                     self.cipher_suite_provider(),
510                 )
511                 .await?;
512 
513                 Ok(EventOrContent::Event(group_info.into()))
514             }
515             MlsMessagePayload::Welcome(welcome) => {
516                 self.validate_welcome(&welcome, message.version)?;
517 
518                 Ok(EventOrContent::Event(welcome.into()))
519             }
520             MlsMessagePayload::KeyPackage(key_package) => {
521                 self.validate_key_package(&key_package, message.version)
522                     .await?;
523 
524                 Ok(EventOrContent::Event(key_package.into()))
525             }
526         }
527     }
528 
process_event_or_content( &mut self, event_or_content: EventOrContent<Self::OutputType>, #[cfg(feature = "by_ref_proposal")] cache_proposal: bool, time_sent: Option<MlsTime>, ) -> Result<Self::OutputType, MlsError>529     async fn process_event_or_content(
530         &mut self,
531         event_or_content: EventOrContent<Self::OutputType>,
532         #[cfg(feature = "by_ref_proposal")] cache_proposal: bool,
533         time_sent: Option<MlsTime>,
534     ) -> Result<Self::OutputType, MlsError> {
535         let msg = match event_or_content {
536             EventOrContent::Event(event) => event,
537             EventOrContent::Content(content) => {
538                 self.process_auth_content(
539                     content,
540                     #[cfg(feature = "by_ref_proposal")]
541                     cache_proposal,
542                     time_sent,
543                 )
544                 .await?
545             }
546         };
547 
548         Ok(msg)
549     }
550 
process_auth_content( &mut self, auth_content: AuthenticatedContent, #[cfg(feature = "by_ref_proposal")] cache_proposal: bool, time_sent: Option<MlsTime>, ) -> Result<Self::OutputType, MlsError>551     async fn process_auth_content(
552         &mut self,
553         auth_content: AuthenticatedContent,
554         #[cfg(feature = "by_ref_proposal")] cache_proposal: bool,
555         time_sent: Option<MlsTime>,
556     ) -> Result<Self::OutputType, MlsError> {
557         let event = match auth_content.content.content {
558             #[cfg(feature = "private_message")]
559             Content::Application(data) => {
560                 let authenticated_data = auth_content.content.authenticated_data;
561                 let sender = auth_content.content.sender;
562 
563                 self.process_application_message(data, sender, authenticated_data)
564                     .and_then(Self::OutputType::try_from)
565             }
566             Content::Commit(_) => self
567                 .process_commit(auth_content, time_sent)
568                 .await
569                 .map(Self::OutputType::from),
570             #[cfg(feature = "by_ref_proposal")]
571             Content::Proposal(ref proposal) => self
572                 .process_proposal(&auth_content, proposal, cache_proposal)
573                 .await
574                 .map(Self::OutputType::from),
575         }?;
576 
577         Ok(event)
578     }
579 
580     #[cfg(feature = "private_message")]
process_application_message( &self, data: ApplicationData, sender: Sender, authenticated_data: Vec<u8>, ) -> Result<ApplicationMessageDescription, MlsError>581     fn process_application_message(
582         &self,
583         data: ApplicationData,
584         sender: Sender,
585         authenticated_data: Vec<u8>,
586     ) -> Result<ApplicationMessageDescription, MlsError> {
587         let Sender::Member(sender_index) = sender else {
588             return Err(MlsError::InvalidSender);
589         };
590 
591         Ok(ApplicationMessageDescription {
592             authenticated_data,
593             sender_index,
594             data,
595         })
596     }
597 
598     #[cfg(feature = "by_ref_proposal")]
599     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
process_proposal( &mut self, auth_content: &AuthenticatedContent, proposal: &Proposal, cache_proposal: bool, ) -> Result<ProposalMessageDescription, MlsError>600     async fn process_proposal(
601         &mut self,
602         auth_content: &AuthenticatedContent,
603         proposal: &Proposal,
604         cache_proposal: bool,
605     ) -> Result<ProposalMessageDescription, MlsError> {
606         let proposal = ProposalMessageDescription::new(
607             self.cipher_suite_provider(),
608             auth_content,
609             proposal.clone(),
610         )
611         .await?;
612 
613         let group_state = self.group_state_mut();
614 
615         if cache_proposal {
616             group_state.proposals.insert(
617                 proposal.proposal_ref.clone(),
618                 proposal.proposal.clone(),
619                 auth_content.content.sender,
620             );
621         }
622 
623         Ok(proposal)
624     }
625 
626     #[cfg(feature = "state_update")]
make_state_update( &self, provisional: &ProvisionalState, path: Option<&UpdatePath>, sender: LeafIndex, ) -> Result<StateUpdate, MlsError>627     async fn make_state_update(
628         &self,
629         provisional: &ProvisionalState,
630         path: Option<&UpdatePath>,
631         sender: LeafIndex,
632     ) -> Result<StateUpdate, MlsError> {
633         let added = provisional
634             .applied_proposals
635             .additions
636             .iter()
637             .zip(provisional.indexes_of_added_kpkgs.iter())
638             .map(|(p, index)| member_from_key_package(&p.proposal.key_package, *index))
639             .collect::<Vec<_>>();
640 
641         let mut added = added;
642 
643         let old_tree = &self.group_state().public_tree;
644 
645         let removed = provisional
646             .applied_proposals
647             .removals
648             .iter()
649             .map(|p| {
650                 let index = p.proposal.to_remove;
651                 let node = old_tree.nodes.borrow_as_leaf(index)?;
652                 Ok(member_from_leaf_node(node, index))
653             })
654             .collect::<Result<_, MlsError>>()?;
655 
656         #[cfg(feature = "by_ref_proposal")]
657         let mut updated = provisional
658             .applied_proposals
659             .update_senders
660             .iter()
661             .map(|index| {
662                 let prior = old_tree
663                     .get_leaf_node(*index)
664                     .map(|n| member_from_leaf_node(n, *index))?;
665 
666                 let new = provisional
667                     .public_tree
668                     .get_leaf_node(*index)
669                     .map(|n| member_from_leaf_node(n, *index))?;
670 
671                 Ok::<_, MlsError>(MemberUpdate::new(prior, new))
672             })
673             .collect::<Result<Vec<_>, _>>()?;
674 
675         #[cfg(not(feature = "by_ref_proposal"))]
676         let mut updated = Vec::new();
677 
678         if let Some(path) = path {
679             if !provisional
680                 .applied_proposals
681                 .external_initializations
682                 .is_empty()
683             {
684                 added.push(member_from_leaf_node(&path.leaf_node, sender))
685             } else {
686                 let prior = old_tree
687                     .get_leaf_node(sender)
688                     .map(|n| member_from_leaf_node(n, sender))?;
689 
690                 let new = member_from_leaf_node(&path.leaf_node, sender);
691 
692                 updated.push(MemberUpdate::new(prior, new))
693             }
694         }
695 
696         #[cfg(feature = "psk")]
697         let psks = provisional
698             .applied_proposals
699             .psks
700             .iter()
701             .filter_map(|psk| psk.proposal.external_psk_id().cloned())
702             .collect::<Vec<_>>();
703 
704         let roster_update = RosterUpdate::new(added, removed, updated);
705 
706         let update = StateUpdate {
707             roster_update,
708             #[cfg(feature = "psk")]
709             added_psks: psks,
710             pending_reinit: provisional
711                 .applied_proposals
712                 .reinitializations
713                 .first()
714                 .map(|ri| ri.proposal.new_cipher_suite()),
715             active: true,
716             epoch: provisional.group_context.epoch,
717             #[cfg(feature = "custom_proposal")]
718             custom_proposals: provisional.applied_proposals.custom_proposals.clone(),
719             #[cfg(feature = "by_ref_proposal")]
720             unused_proposals: provisional.unused_proposals.clone(),
721         };
722 
723         Ok(update)
724     }
725 
process_commit( &mut self, auth_content: AuthenticatedContent, time_sent: Option<MlsTime>, ) -> Result<CommitMessageDescription, MlsError>726     async fn process_commit(
727         &mut self,
728         auth_content: AuthenticatedContent,
729         time_sent: Option<MlsTime>,
730     ) -> Result<CommitMessageDescription, MlsError> {
731         if self.group_state().pending_reinit.is_some() {
732             return Err(MlsError::GroupUsedAfterReInit);
733         }
734 
735         // Update the new GroupContext's confirmed and interim transcript hashes using the new Commit.
736         let (interim_transcript_hash, confirmed_transcript_hash) = transcript_hashes(
737             self.cipher_suite_provider(),
738             &self.group_state().interim_transcript_hash,
739             &auth_content,
740         )
741         .await?;
742 
743         #[cfg(any(feature = "private_message", feature = "by_ref_proposal"))]
744         let commit = match auth_content.content.content {
745             Content::Commit(commit) => Ok(commit),
746             _ => Err(MlsError::UnexpectedMessageType),
747         }?;
748 
749         #[cfg(not(any(feature = "private_message", feature = "by_ref_proposal")))]
750         let Content::Commit(commit) = auth_content.content.content;
751 
752         let group_state = self.group_state();
753         let id_provider = self.identity_provider();
754 
755         #[cfg(feature = "by_ref_proposal")]
756         let proposals = group_state
757             .proposals
758             .resolve_for_commit(auth_content.content.sender, commit.proposals)?;
759 
760         #[cfg(not(feature = "by_ref_proposal"))]
761         let proposals = resolve_for_commit(auth_content.content.sender, commit.proposals)?;
762 
763         let mut provisional_state = group_state
764             .apply_resolved(
765                 auth_content.content.sender,
766                 proposals,
767                 commit.path.as_ref().map(|path| &path.leaf_node),
768                 &id_provider,
769                 self.cipher_suite_provider(),
770                 &self.psk_storage(),
771                 &self.mls_rules(),
772                 time_sent,
773                 CommitDirection::Receive,
774             )
775             .await?;
776 
777         let sender = commit_sender(&auth_content.content.sender, &provisional_state)?;
778 
779         #[cfg(feature = "state_update")]
780         let mut state_update = self
781             .make_state_update(&provisional_state, commit.path.as_ref(), sender)
782             .await?;
783 
784         #[cfg(not(feature = "state_update"))]
785         let state_update = StateUpdate {};
786 
787         //Verify that the path value is populated if the proposals vector contains any Update
788         // or Remove proposals, or if it's empty. Otherwise, the path value MAY be omitted.
789         if path_update_required(&provisional_state.applied_proposals) && commit.path.is_none() {
790             return Err(MlsError::CommitMissingPath);
791         }
792 
793         if !self.can_continue_processing(&provisional_state) {
794             #[cfg(feature = "state_update")]
795             {
796                 state_update.active = false;
797             }
798 
799             return Ok(CommitMessageDescription {
800                 is_external: matches!(auth_content.content.sender, Sender::NewMemberCommit),
801                 authenticated_data: auth_content.content.authenticated_data,
802                 committer: *sender,
803                 state_update,
804             });
805         }
806 
807         let update_path = match commit.path {
808             Some(update_path) => Some(
809                 validate_update_path(
810                     &self.identity_provider(),
811                     self.cipher_suite_provider(),
812                     update_path,
813                     &provisional_state,
814                     sender,
815                     time_sent,
816                 )
817                 .await?,
818             ),
819             None => None,
820         };
821 
822         let new_secrets = match update_path {
823             Some(update_path) => {
824                 self.apply_update_path(sender, &update_path, &mut provisional_state)
825                     .await
826             }
827             None => Ok(None),
828         }?;
829 
830         // Update the transcript hash to get the new context.
831         provisional_state.group_context.confirmed_transcript_hash = confirmed_transcript_hash;
832 
833         // Update the parent hashes in the new context
834         provisional_state
835             .public_tree
836             .update_hashes(&[sender], self.cipher_suite_provider())
837             .await?;
838 
839         // Update the tree hash in the new context
840         provisional_state.group_context.tree_hash = provisional_state
841             .public_tree
842             .tree_hash(self.cipher_suite_provider())
843             .await?;
844 
845         if let Some(reinit) = provisional_state.applied_proposals.reinitializations.pop() {
846             self.group_state_mut().pending_reinit = Some(reinit.proposal);
847 
848             #[cfg(feature = "state_update")]
849             {
850                 state_update.active = false;
851             }
852         }
853 
854         if let Some(confirmation_tag) = &auth_content.auth.confirmation_tag {
855             // Update the key schedule to calculate new private keys
856             self.update_key_schedule(
857                 new_secrets,
858                 interim_transcript_hash,
859                 confirmation_tag,
860                 provisional_state,
861             )
862             .await?;
863 
864             Ok(CommitMessageDescription {
865                 is_external: matches!(auth_content.content.sender, Sender::NewMemberCommit),
866                 authenticated_data: auth_content.content.authenticated_data,
867                 committer: *sender,
868                 state_update,
869             })
870         } else {
871             Err(MlsError::InvalidConfirmationTag)
872         }
873     }
874 
group_state(&self) -> &GroupState875     fn group_state(&self) -> &GroupState;
group_state_mut(&mut self) -> &mut GroupState876     fn group_state_mut(&mut self) -> &mut GroupState;
mls_rules(&self) -> Self::MlsRules877     fn mls_rules(&self) -> Self::MlsRules;
identity_provider(&self) -> Self::IdentityProvider878     fn identity_provider(&self) -> Self::IdentityProvider;
cipher_suite_provider(&self) -> &Self::CipherSuiteProvider879     fn cipher_suite_provider(&self) -> &Self::CipherSuiteProvider;
psk_storage(&self) -> Self::PreSharedKeyStorage880     fn psk_storage(&self) -> Self::PreSharedKeyStorage;
can_continue_processing(&self, provisional_state: &ProvisionalState) -> bool881     fn can_continue_processing(&self, provisional_state: &ProvisionalState) -> bool;
882 
883     #[cfg(feature = "private_message")]
min_epoch_available(&self) -> Option<u64>884     fn min_epoch_available(&self) -> Option<u64>;
885 
check_metadata(&self, message: &MlsMessage) -> Result<(), MlsError>886     fn check_metadata(&self, message: &MlsMessage) -> Result<(), MlsError> {
887         let context = &self.group_state().context;
888 
889         if message.version != context.protocol_version {
890             return Err(MlsError::ProtocolVersionMismatch);
891         }
892 
893         if let Some((group_id, epoch, content_type)) = match &message.payload {
894             MlsMessagePayload::Plain(plaintext) => Some((
895                 &plaintext.content.group_id,
896                 plaintext.content.epoch,
897                 plaintext.content.content_type(),
898             )),
899             #[cfg(feature = "private_message")]
900             MlsMessagePayload::Cipher(ciphertext) => Some((
901                 &ciphertext.group_id,
902                 ciphertext.epoch,
903                 ciphertext.content_type,
904             )),
905             _ => None,
906         } {
907             if group_id != &context.group_id {
908                 return Err(MlsError::GroupIdMismatch);
909             }
910 
911             match content_type {
912                 ContentType::Commit => {
913                     if context.epoch != epoch {
914                         Err(MlsError::InvalidEpoch)
915                     } else {
916                         Ok(())
917                     }
918                 }
919                 #[cfg(feature = "by_ref_proposal")]
920                 ContentType::Proposal => {
921                     if context.epoch != epoch {
922                         Err(MlsError::InvalidEpoch)
923                     } else {
924                         Ok(())
925                     }
926                 }
927                 #[cfg(feature = "private_message")]
928                 ContentType::Application => {
929                     if let Some(min) = self.min_epoch_available() {
930                         if epoch < min {
931                             Err(MlsError::InvalidEpoch)
932                         } else {
933                             Ok(())
934                         }
935                     } else {
936                         Ok(())
937                     }
938                 }
939             }?;
940 
941             // Proposal and commit messages must be sent in the current epoch
942             let check_epoch = content_type == ContentType::Commit;
943 
944             #[cfg(feature = "by_ref_proposal")]
945             let check_epoch = check_epoch || content_type == ContentType::Proposal;
946 
947             if check_epoch && epoch != context.epoch {
948                 return Err(MlsError::InvalidEpoch);
949             }
950 
951             // Unencrypted application messages are not allowed
952             #[cfg(feature = "private_message")]
953             if !matches!(&message.payload, MlsMessagePayload::Cipher(_))
954                 && content_type == ContentType::Application
955             {
956                 return Err(MlsError::UnencryptedApplicationMessage);
957             }
958         }
959 
960         Ok(())
961     }
962 
validate_welcome( &self, welcome: &Welcome, version: ProtocolVersion, ) -> Result<(), MlsError>963     fn validate_welcome(
964         &self,
965         welcome: &Welcome,
966         version: ProtocolVersion,
967     ) -> Result<(), MlsError> {
968         let state = self.group_state();
969 
970         (welcome.cipher_suite == state.context.cipher_suite
971             && version == state.context.protocol_version)
972             .then_some(())
973             .ok_or(MlsError::InvalidWelcomeMessage)
974     }
975 
validate_key_package( &self, key_package: &KeyPackage, version: ProtocolVersion, ) -> Result<(), MlsError>976     async fn validate_key_package(
977         &self,
978         key_package: &KeyPackage,
979         version: ProtocolVersion,
980     ) -> Result<(), MlsError> {
981         let cs = self.cipher_suite_provider();
982         let id = self.identity_provider();
983 
984         validate_key_package(key_package, version, cs, &id).await
985     }
986 
987     #[cfg(feature = "private_message")]
process_ciphertext( &mut self, cipher_text: &PrivateMessage, ) -> Result<EventOrContent<Self::OutputType>, MlsError>988     async fn process_ciphertext(
989         &mut self,
990         cipher_text: &PrivateMessage,
991     ) -> Result<EventOrContent<Self::OutputType>, MlsError>;
992 
verify_plaintext_authentication( &self, message: PublicMessage, ) -> Result<EventOrContent<Self::OutputType>, MlsError>993     async fn verify_plaintext_authentication(
994         &self,
995         message: PublicMessage,
996     ) -> Result<EventOrContent<Self::OutputType>, MlsError>;
997 
apply_update_path( &mut self, sender: LeafIndex, update_path: &ValidatedUpdatePath, provisional_state: &mut ProvisionalState, ) -> Result<Option<(TreeKemPrivate, PathSecret)>, MlsError>998     async fn apply_update_path(
999         &mut self,
1000         sender: LeafIndex,
1001         update_path: &ValidatedUpdatePath,
1002         provisional_state: &mut ProvisionalState,
1003     ) -> Result<Option<(TreeKemPrivate, PathSecret)>, MlsError> {
1004         provisional_state
1005             .public_tree
1006             .apply_update_path(
1007                 sender,
1008                 update_path,
1009                 &provisional_state.group_context.extensions,
1010                 self.identity_provider(),
1011                 self.cipher_suite_provider(),
1012             )
1013             .await
1014             .map(|_| None)
1015     }
1016 
update_key_schedule( &mut self, secrets: Option<(TreeKemPrivate, PathSecret)>, interim_transcript_hash: InterimTranscriptHash, confirmation_tag: &ConfirmationTag, provisional_public_state: ProvisionalState, ) -> Result<(), MlsError>1017     async fn update_key_schedule(
1018         &mut self,
1019         secrets: Option<(TreeKemPrivate, PathSecret)>,
1020         interim_transcript_hash: InterimTranscriptHash,
1021         confirmation_tag: &ConfirmationTag,
1022         provisional_public_state: ProvisionalState,
1023     ) -> Result<(), MlsError>;
1024 }
1025 
1026 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
validate_key_package<C: CipherSuiteProvider, I: IdentityProvider>( key_package: &KeyPackage, version: ProtocolVersion, cs: &C, id: &I, ) -> Result<(), MlsError>1027 pub(crate) async fn validate_key_package<C: CipherSuiteProvider, I: IdentityProvider>(
1028     key_package: &KeyPackage,
1029     version: ProtocolVersion,
1030     cs: &C,
1031     id: &I,
1032 ) -> Result<(), MlsError> {
1033     let validator = LeafNodeValidator::new(cs, id, None);
1034 
1035     #[cfg(feature = "std")]
1036     let context = Some(MlsTime::now());
1037 
1038     #[cfg(not(feature = "std"))]
1039     let context = None;
1040 
1041     let context = ValidationContext::Add(context);
1042 
1043     validator
1044         .check_if_valid(&key_package.leaf_node, context)
1045         .await?;
1046 
1047     validate_key_package_properties(key_package, version, cs).await?;
1048 
1049     Ok(())
1050 }
1051