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 #[cfg(feature = "by_ref_proposal")]
6 use alloc::{vec, vec::Vec};
7 
8 use crate::{
9     client::MlsError,
10     crypto::SignaturePublicKey,
11     group::{GroupContext, PublicMessage, Sender},
12     signer::Signable,
13     tree_kem::{node::LeafIndex, TreeKemPublic},
14     CipherSuiteProvider,
15 };
16 
17 #[cfg(feature = "by_ref_proposal")]
18 use crate::{extension::ExternalSendersExt, identity::SigningIdentity};
19 
20 use super::{
21     key_schedule::KeySchedule,
22     message_signature::{AuthenticatedContent, MessageSigningContext},
23     state::GroupState,
24 };
25 
26 #[cfg(feature = "by_ref_proposal")]
27 use super::proposal::Proposal;
28 
29 #[derive(Debug)]
30 pub(crate) enum SignaturePublicKeysContainer<'a> {
31     RatchetTree(&'a TreeKemPublic),
32     #[cfg(feature = "private_message")]
33     List(&'a [Option<SignaturePublicKey>]),
34 }
35 
36 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
verify_plaintext_authentication<P: CipherSuiteProvider>( cipher_suite_provider: &P, plaintext: PublicMessage, key_schedule: Option<&KeySchedule>, self_index: Option<LeafIndex>, state: &GroupState, ) -> Result<AuthenticatedContent, MlsError>37 pub(crate) async fn verify_plaintext_authentication<P: CipherSuiteProvider>(
38     cipher_suite_provider: &P,
39     plaintext: PublicMessage,
40     key_schedule: Option<&KeySchedule>,
41     self_index: Option<LeafIndex>,
42     state: &GroupState,
43 ) -> Result<AuthenticatedContent, MlsError> {
44     let tag = plaintext.membership_tag.clone();
45     let auth_content = AuthenticatedContent::from(plaintext);
46     let context = &state.context;
47 
48     #[cfg(feature = "by_ref_proposal")]
49     let external_signers = external_signers(context);
50 
51     let current_tree = &state.public_tree;
52 
53     // Verify the membership tag if needed
54     match &auth_content.content.sender {
55         Sender::Member(index) => {
56             if let Some(key_schedule) = key_schedule {
57                 let expected_tag = &key_schedule
58                     .get_membership_tag(&auth_content, context, cipher_suite_provider)
59                     .await?;
60 
61                 let plaintext_tag = tag.as_ref().ok_or(MlsError::InvalidMembershipTag)?;
62 
63                 if expected_tag != plaintext_tag {
64                     return Err(MlsError::InvalidMembershipTag);
65                 }
66             }
67 
68             if self_index == Some(LeafIndex(*index)) {
69                 return Err(MlsError::CantProcessMessageFromSelf);
70             }
71         }
72         _ => {
73             tag.is_none()
74                 .then_some(())
75                 .ok_or(MlsError::MembershipTagForNonMember)?;
76         }
77     }
78 
79     // Verify that the signature on the MLSAuthenticatedContent verifies using the public key
80     // from the credential stored at the leaf in the tree indicated by the sender field.
81     verify_auth_content_signature(
82         cipher_suite_provider,
83         SignaturePublicKeysContainer::RatchetTree(current_tree),
84         context,
85         &auth_content,
86         #[cfg(feature = "by_ref_proposal")]
87         &external_signers,
88     )
89     .await?;
90 
91     Ok(auth_content)
92 }
93 
94 #[cfg(feature = "by_ref_proposal")]
external_signers(context: &GroupContext) -> Vec<SigningIdentity>95 fn external_signers(context: &GroupContext) -> Vec<SigningIdentity> {
96     context
97         .extensions
98         .get_as::<ExternalSendersExt>()
99         .unwrap_or(None)
100         .map_or(vec![], |extern_senders_ext| {
101             extern_senders_ext.allowed_senders
102         })
103 }
104 
105 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
verify_auth_content_signature<P: CipherSuiteProvider>( cipher_suite_provider: &P, signature_keys_container: SignaturePublicKeysContainer<'_>, context: &GroupContext, auth_content: &AuthenticatedContent, #[cfg(feature = "by_ref_proposal")] external_signers: &[SigningIdentity], ) -> Result<(), MlsError>106 pub(crate) async fn verify_auth_content_signature<P: CipherSuiteProvider>(
107     cipher_suite_provider: &P,
108     signature_keys_container: SignaturePublicKeysContainer<'_>,
109     context: &GroupContext,
110     auth_content: &AuthenticatedContent,
111     #[cfg(feature = "by_ref_proposal")] external_signers: &[SigningIdentity],
112 ) -> Result<(), MlsError> {
113     let sender_public_key = signing_identity_for_sender(
114         signature_keys_container,
115         &auth_content.content.sender,
116         &auth_content.content.content,
117         #[cfg(feature = "by_ref_proposal")]
118         external_signers,
119     )?;
120 
121     let context = MessageSigningContext {
122         group_context: Some(context),
123         protocol_version: context.protocol_version,
124     };
125 
126     auth_content
127         .verify(cipher_suite_provider, &sender_public_key, &context)
128         .await?;
129 
130     Ok(())
131 }
132 
signing_identity_for_sender( signature_keys_container: SignaturePublicKeysContainer, sender: &Sender, content: &super::framing::Content, #[cfg(feature = "by_ref_proposal")] external_signers: &[SigningIdentity], ) -> Result<SignaturePublicKey, MlsError>133 fn signing_identity_for_sender(
134     signature_keys_container: SignaturePublicKeysContainer,
135     sender: &Sender,
136     content: &super::framing::Content,
137     #[cfg(feature = "by_ref_proposal")] external_signers: &[SigningIdentity],
138 ) -> Result<SignaturePublicKey, MlsError> {
139     match sender {
140         Sender::Member(leaf_index) => {
141             signing_identity_for_member(signature_keys_container, LeafIndex(*leaf_index))
142         }
143         #[cfg(feature = "by_ref_proposal")]
144         Sender::External(external_key_index) => {
145             signing_identity_for_external(*external_key_index, external_signers)
146         }
147         Sender::NewMemberCommit => signing_identity_for_new_member_commit(content),
148         #[cfg(feature = "by_ref_proposal")]
149         Sender::NewMemberProposal => signing_identity_for_new_member_proposal(content),
150     }
151 }
152 
signing_identity_for_member( signature_keys_container: SignaturePublicKeysContainer, leaf_index: LeafIndex, ) -> Result<SignaturePublicKey, MlsError>153 fn signing_identity_for_member(
154     signature_keys_container: SignaturePublicKeysContainer,
155     leaf_index: LeafIndex,
156 ) -> Result<SignaturePublicKey, MlsError> {
157     match signature_keys_container {
158         SignaturePublicKeysContainer::RatchetTree(tree) => Ok(tree
159             .get_leaf_node(leaf_index)?
160             .signing_identity
161             .signature_key
162             .clone()), // TODO: We can probably get rid of this clone
163         #[cfg(feature = "private_message")]
164         SignaturePublicKeysContainer::List(list) => list
165             .get(leaf_index.0 as usize)
166             .cloned()
167             .flatten()
168             .ok_or(MlsError::LeafNotFound(*leaf_index)),
169     }
170 }
171 
172 #[cfg(feature = "by_ref_proposal")]
signing_identity_for_external( index: u32, external_signers: &[SigningIdentity], ) -> Result<SignaturePublicKey, MlsError>173 fn signing_identity_for_external(
174     index: u32,
175     external_signers: &[SigningIdentity],
176 ) -> Result<SignaturePublicKey, MlsError> {
177     external_signers
178         .get(index as usize)
179         .map(|spk| spk.signature_key.clone())
180         .ok_or(MlsError::UnknownSigningIdentityForExternalSender)
181 }
182 
signing_identity_for_new_member_commit( content: &super::framing::Content, ) -> Result<SignaturePublicKey, MlsError>183 fn signing_identity_for_new_member_commit(
184     content: &super::framing::Content,
185 ) -> Result<SignaturePublicKey, MlsError> {
186     match content {
187         super::framing::Content::Commit(commit) => {
188             if let Some(path) = &commit.path {
189                 Ok(path.leaf_node.signing_identity.signature_key.clone())
190             } else {
191                 Err(MlsError::CommitMissingPath)
192             }
193         }
194         #[cfg(any(feature = "private_message", feature = "by_ref_proposal"))]
195         _ => Err(MlsError::ExpectedCommitForNewMemberCommit),
196     }
197 }
198 
199 #[cfg(feature = "by_ref_proposal")]
signing_identity_for_new_member_proposal( content: &super::framing::Content, ) -> Result<SignaturePublicKey, MlsError>200 fn signing_identity_for_new_member_proposal(
201     content: &super::framing::Content,
202 ) -> Result<SignaturePublicKey, MlsError> {
203     match content {
204         super::framing::Content::Proposal(proposal) => {
205             if let Proposal::Add(p) = proposal.as_ref() {
206                 Ok(p.key_package
207                     .leaf_node
208                     .signing_identity
209                     .signature_key
210                     .clone())
211             } else {
212                 Err(MlsError::ExpectedAddProposalForNewMemberProposal)
213             }
214         }
215         _ => Err(MlsError::ExpectedAddProposalForNewMemberProposal),
216     }
217 }
218 
219 #[cfg(test)]
220 mod tests {
221     use crate::{
222         client::{
223             test_utils::{test_client_with_key_pkg, TEST_CIPHER_SUITE, TEST_PROTOCOL_VERSION},
224             MlsError,
225         },
226         client_builder::test_utils::TestClientConfig,
227         crypto::test_utils::test_cipher_suite_provider,
228         group::{
229             membership_tag::MembershipTag,
230             message_signature::{AuthenticatedContent, MessageSignature},
231             test_utils::{test_group_custom, TestGroup},
232             Group, PublicMessage,
233         },
234         tree_kem::node::LeafIndex,
235     };
236     use alloc::vec;
237     use assert_matches::assert_matches;
238 
239     #[cfg(feature = "by_ref_proposal")]
240     use crate::{extension::ExternalSendersExt, ExtensionList};
241 
242     #[cfg(feature = "by_ref_proposal")]
243     use crate::{
244         crypto::SignatureSecretKey,
245         group::{
246             message_signature::MessageSigningContext,
247             proposal::{AddProposal, Proposal, RemoveProposal},
248             Content,
249         },
250         key_package::KeyPackageGeneration,
251         signer::Signable,
252         WireFormat,
253     };
254 
255     #[cfg(feature = "by_ref_proposal")]
256     use alloc::boxed::Box;
257 
258     use crate::group::{
259         test_utils::{test_group, test_member},
260         Sender,
261     };
262 
263     #[cfg(feature = "by_ref_proposal")]
264     use crate::identity::test_utils::get_test_signing_identity;
265 
266     use super::{verify_auth_content_signature, verify_plaintext_authentication};
267 
268     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
make_signed_plaintext(group: &mut Group<TestClientConfig>) -> PublicMessage269     async fn make_signed_plaintext(group: &mut Group<TestClientConfig>) -> PublicMessage {
270         group
271             .commit(vec![])
272             .await
273             .unwrap()
274             .commit_message
275             .into_plaintext()
276             .unwrap()
277     }
278 
279     struct TestEnv {
280         alice: TestGroup,
281         bob: TestGroup,
282     }
283 
284     impl TestEnv {
285         #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
new() -> Self286         async fn new() -> Self {
287             let mut alice = test_group_custom(
288                 TEST_PROTOCOL_VERSION,
289                 TEST_CIPHER_SUITE,
290                 Default::default(),
291                 None,
292                 None,
293             )
294             .await;
295 
296             let (bob_client, bob_key_pkg) =
297                 test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob").await;
298 
299             let commit_output = alice
300                 .group
301                 .commit_builder()
302                 .add_member(bob_key_pkg)
303                 .unwrap()
304                 .build()
305                 .await
306                 .unwrap();
307 
308             alice.group.apply_pending_commit().await.unwrap();
309 
310             let (bob, _) = Group::join(
311                 &commit_output.welcome_messages[0],
312                 None,
313                 bob_client.config,
314                 bob_client.signer.unwrap(),
315             )
316             .await
317             .unwrap();
318 
319             TestEnv {
320                 alice,
321                 bob: TestGroup { group: bob },
322             }
323         }
324     }
325 
326     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
valid_plaintext_is_verified()327     async fn valid_plaintext_is_verified() {
328         let mut env = TestEnv::new().await;
329 
330         let message = make_signed_plaintext(&mut env.alice.group).await;
331 
332         verify_plaintext_authentication(
333             &env.bob.group.cipher_suite_provider,
334             message,
335             Some(&env.bob.group.key_schedule),
336             None,
337             &env.bob.group.state,
338         )
339         .await
340         .unwrap();
341     }
342 
343     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
valid_auth_content_is_verified()344     async fn valid_auth_content_is_verified() {
345         let mut env = TestEnv::new().await;
346 
347         let message = AuthenticatedContent::from(make_signed_plaintext(&mut env.alice.group).await);
348 
349         verify_auth_content_signature(
350             &env.bob.group.cipher_suite_provider,
351             super::SignaturePublicKeysContainer::RatchetTree(&env.bob.group.state.public_tree),
352             env.bob.group.context(),
353             &message,
354             #[cfg(feature = "by_ref_proposal")]
355             &[],
356         )
357         .await
358         .unwrap();
359     }
360 
361     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
invalid_plaintext_is_not_verified()362     async fn invalid_plaintext_is_not_verified() {
363         let mut env = TestEnv::new().await;
364         let mut message = make_signed_plaintext(&mut env.alice.group).await;
365         message.auth.signature = MessageSignature::from(b"test".to_vec());
366 
367         message.membership_tag = env
368             .alice
369             .group
370             .key_schedule
371             .get_membership_tag(
372                 &AuthenticatedContent::from(message.clone()),
373                 env.alice.group.context(),
374                 &test_cipher_suite_provider(env.alice.group.cipher_suite()),
375             )
376             .await
377             .unwrap()
378             .into();
379 
380         let res = verify_plaintext_authentication(
381             &env.bob.group.cipher_suite_provider,
382             message,
383             Some(&env.bob.group.key_schedule),
384             None,
385             &env.bob.group.state,
386         )
387         .await;
388 
389         assert_matches!(res, Err(MlsError::InvalidSignature));
390     }
391 
392     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
plaintext_from_member_requires_membership_tag()393     async fn plaintext_from_member_requires_membership_tag() {
394         let mut env = TestEnv::new().await;
395         let mut message = make_signed_plaintext(&mut env.alice.group).await;
396         message.membership_tag = None;
397 
398         let res = verify_plaintext_authentication(
399             &env.bob.group.cipher_suite_provider,
400             message,
401             Some(&env.bob.group.key_schedule),
402             None,
403             &env.bob.group.state,
404         )
405         .await;
406 
407         assert_matches!(res, Err(MlsError::InvalidMembershipTag));
408     }
409 
410     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
plaintext_fails_with_invalid_membership_tag()411     async fn plaintext_fails_with_invalid_membership_tag() {
412         let mut env = TestEnv::new().await;
413         let mut message = make_signed_plaintext(&mut env.alice.group).await;
414         message.membership_tag = Some(MembershipTag::from(b"test".to_vec()));
415 
416         let res = verify_plaintext_authentication(
417             &env.bob.group.cipher_suite_provider,
418             message,
419             Some(&env.bob.group.key_schedule),
420             None,
421             &env.bob.group.state,
422         )
423         .await;
424 
425         assert_matches!(res, Err(MlsError::InvalidMembershipTag));
426     }
427 
428     #[cfg(feature = "by_ref_proposal")]
429     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
test_new_member_proposal<F>( key_pkg_gen: KeyPackageGeneration, signer: &SignatureSecretKey, test_group: &TestGroup, mut edit: F, ) -> PublicMessage where F: FnMut(&mut AuthenticatedContent),430     async fn test_new_member_proposal<F>(
431         key_pkg_gen: KeyPackageGeneration,
432         signer: &SignatureSecretKey,
433         test_group: &TestGroup,
434         mut edit: F,
435     ) -> PublicMessage
436     where
437         F: FnMut(&mut AuthenticatedContent),
438     {
439         let mut content = AuthenticatedContent::new_signed(
440             &test_group.group.cipher_suite_provider,
441             test_group.group.context(),
442             Sender::NewMemberProposal,
443             Content::Proposal(Box::new(Proposal::Add(Box::new(AddProposal {
444                 key_package: key_pkg_gen.key_package,
445             })))),
446             signer,
447             WireFormat::PublicMessage,
448             vec![],
449         )
450         .await
451         .unwrap();
452 
453         edit(&mut content);
454 
455         let signing_context = MessageSigningContext {
456             group_context: Some(test_group.group.context()),
457             protocol_version: test_group.group.protocol_version(),
458         };
459 
460         content
461             .sign(
462                 &test_group.group.cipher_suite_provider,
463                 signer,
464                 &signing_context,
465             )
466             .await
467             .unwrap();
468 
469         PublicMessage {
470             content: content.content,
471             auth: content.auth,
472             membership_tag: None,
473         }
474     }
475 
476     #[cfg(feature = "by_ref_proposal")]
477     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
valid_proposal_from_new_member_is_verified()478     async fn valid_proposal_from_new_member_is_verified() {
479         let test_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
480         let (key_pkg_gen, signer) =
481             test_member(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, b"bob").await;
482         let message = test_new_member_proposal(key_pkg_gen, &signer, &test_group, |_| {}).await;
483 
484         verify_plaintext_authentication(
485             &test_group.group.cipher_suite_provider,
486             message,
487             Some(&test_group.group.key_schedule),
488             None,
489             &test_group.group.state,
490         )
491         .await
492         .unwrap();
493     }
494 
495     #[cfg(feature = "by_ref_proposal")]
496     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
proposal_from_new_member_must_not_have_membership_tag()497     async fn proposal_from_new_member_must_not_have_membership_tag() {
498         let test_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
499         let (key_pkg_gen, signer) =
500             test_member(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, b"bob").await;
501 
502         let mut message = test_new_member_proposal(key_pkg_gen, &signer, &test_group, |_| {}).await;
503         message.membership_tag = Some(MembershipTag::from(vec![]));
504 
505         let res = verify_plaintext_authentication(
506             &test_group.group.cipher_suite_provider,
507             message,
508             Some(&test_group.group.key_schedule),
509             None,
510             &test_group.group.state,
511         )
512         .await;
513 
514         assert_matches!(res, Err(MlsError::MembershipTagForNonMember));
515     }
516 
517     #[cfg(feature = "by_ref_proposal")]
518     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
new_member_proposal_sender_must_be_add_proposal()519     async fn new_member_proposal_sender_must_be_add_proposal() {
520         let test_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
521         let (key_pkg_gen, signer) =
522             test_member(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, b"bob").await;
523 
524         let message = test_new_member_proposal(key_pkg_gen, &signer, &test_group, |msg| {
525             msg.content.content = Content::Proposal(Box::new(Proposal::Remove(RemoveProposal {
526                 to_remove: LeafIndex(0),
527             })))
528         })
529         .await;
530 
531         let res: Result<AuthenticatedContent, MlsError> = verify_plaintext_authentication(
532             &test_group.group.cipher_suite_provider,
533             message,
534             Some(&test_group.group.key_schedule),
535             None,
536             &test_group.group.state,
537         )
538         .await;
539 
540         assert_matches!(res, Err(MlsError::ExpectedAddProposalForNewMemberProposal));
541     }
542 
543     #[cfg(feature = "by_ref_proposal")]
544     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
new_member_commit_must_be_external_commit()545     async fn new_member_commit_must_be_external_commit() {
546         let test_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
547         let (key_pkg_gen, signer) =
548             test_member(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, b"bob").await;
549 
550         let message = test_new_member_proposal(key_pkg_gen, &signer, &test_group, |msg| {
551             msg.content.sender = Sender::NewMemberCommit;
552         })
553         .await;
554 
555         let res = verify_plaintext_authentication(
556             &test_group.group.cipher_suite_provider,
557             message,
558             Some(&test_group.group.key_schedule),
559             None,
560             &test_group.group.state,
561         )
562         .await;
563 
564         assert_matches!(res, Err(MlsError::ExpectedCommitForNewMemberCommit));
565     }
566 
567     #[cfg(feature = "by_ref_proposal")]
568     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
valid_proposal_from_external_is_verified()569     async fn valid_proposal_from_external_is_verified() {
570         let (bob_key_pkg_gen, _) =
571             test_member(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, b"bob").await;
572 
573         let (ted_signing, ted_secret) = get_test_signing_identity(TEST_CIPHER_SUITE, b"ted").await;
574 
575         let mut test_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
576         let mut extensions = ExtensionList::default();
577 
578         extensions
579             .set_from(ExternalSendersExt {
580                 allowed_senders: vec![ted_signing],
581             })
582             .unwrap();
583 
584         test_group
585             .group
586             .commit_builder()
587             .set_group_context_ext(extensions)
588             .unwrap()
589             .build()
590             .await
591             .unwrap();
592 
593         test_group.group.apply_pending_commit().await.unwrap();
594 
595         let message = test_new_member_proposal(bob_key_pkg_gen, &ted_secret, &test_group, |msg| {
596             msg.content.sender = Sender::External(0)
597         })
598         .await;
599 
600         verify_plaintext_authentication(
601             &test_group.group.cipher_suite_provider,
602             message,
603             Some(&test_group.group.key_schedule),
604             None,
605             &test_group.group.state,
606         )
607         .await
608         .unwrap();
609     }
610 
611     #[cfg(feature = "by_ref_proposal")]
612     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
external_proposal_must_be_from_valid_sender()613     async fn external_proposal_must_be_from_valid_sender() {
614         let (bob_key_pkg_gen, _) =
615             test_member(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, b"bob").await;
616         let (_, ted_secret) = get_test_signing_identity(TEST_CIPHER_SUITE, b"ted").await;
617         let test_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
618 
619         let message = test_new_member_proposal(bob_key_pkg_gen, &ted_secret, &test_group, |msg| {
620             msg.content.sender = Sender::External(0)
621         })
622         .await;
623 
624         let res = verify_plaintext_authentication(
625             &test_group.group.cipher_suite_provider,
626             message,
627             Some(&test_group.group.key_schedule),
628             None,
629             &test_group.group.state,
630         )
631         .await;
632 
633         assert_matches!(res, Err(MlsError::UnknownSigningIdentityForExternalSender));
634     }
635 
636     #[cfg(feature = "by_ref_proposal")]
637     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
proposal_from_external_sender_must_not_have_membership_tag()638     async fn proposal_from_external_sender_must_not_have_membership_tag() {
639         let (bob_key_pkg_gen, _) =
640             test_member(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, b"bob").await;
641 
642         let (_, ted_secret) = get_test_signing_identity(TEST_CIPHER_SUITE, b"ted").await;
643 
644         let test_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
645 
646         let mut message =
647             test_new_member_proposal(bob_key_pkg_gen, &ted_secret, &test_group, |_| {}).await;
648 
649         message.membership_tag = Some(MembershipTag::from(vec![]));
650 
651         let res = verify_plaintext_authentication(
652             &test_group.group.cipher_suite_provider,
653             message,
654             Some(&test_group.group.key_schedule),
655             None,
656             &test_group.group.state,
657         )
658         .await;
659 
660         assert_matches!(res, Err(MlsError::MembershipTagForNonMember));
661     }
662 
663     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
plaintext_from_self_fails_verification()664     async fn plaintext_from_self_fails_verification() {
665         let mut env = TestEnv::new().await;
666 
667         let message = make_signed_plaintext(&mut env.alice.group).await;
668 
669         let res = verify_plaintext_authentication(
670             &env.alice.group.cipher_suite_provider,
671             message,
672             Some(&env.alice.group.key_schedule),
673             Some(LeafIndex::new(env.alice.group.current_member_index())),
674             &env.alice.group.state,
675         )
676         .await;
677 
678         assert_matches!(res, Err(MlsError::CantProcessMessageFromSelf))
679     }
680 }
681