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