1use super::leaf_node::{LeafNode, LeafNodeSigningContext, LeafNodeSource};
6use crate::client::MlsError;
7use crate::CipherSuiteProvider;
8use crate::{signer::Signable, time::MlsTime};
9use mls_rs_core::identity::MemberValidationContext;
10use mls_rs_core::{error::IntoAnyError, identity::IdentityProvider};
11
12use crate::extension::RequiredCapabilitiesExt;
13
14#[cfg(feature = "by_ref_proposal")]
15use crate::extension::ExternalSendersExt;
16
17pub enum ValidationContext<'a> {
18 Add(Option<MlsTime>),
19 Update((&'a [u8], u32, Option<MlsTime>)),
20 Commit((&'a [u8], u32, Option<MlsTime>)),
21}
22
23impl ValidationContext<'_> {
24 fn signing_context(&self) -> LeafNodeSigningContext<'_> {
25 match *self {
26 ValidationContext::Add(_) => Default::default(),
27 ValidationContext::Update((group_id, leaf_index, _)) => (group_id, leaf_index).into(),
28 ValidationContext::Commit((group_id, leaf_index, _)) => (group_id, leaf_index).into(),
29 }
30 }
31
32 fn generation_time(&self) -> Option<MlsTime> {
33 match *self {
34 ValidationContext::Add(t) => t,
35 ValidationContext::Update((_, _, t)) => t,
36 ValidationContext::Commit((_, _, t)) => t,
37 }
38 }
39}
40
41#[derive(Clone, Debug)]
42pub struct LeafNodeValidator<'a, C, CP>
43where
44 C: IdentityProvider,
45 CP: CipherSuiteProvider,
46{
47 cipher_suite_provider: &'a CP,
48 identity_provider: &'a C,
49 context: MemberValidationContext<'a>,
50}
51
52impl<'a, C: IdentityProvider, CP: CipherSuiteProvider> LeafNodeValidator<'a, C, CP> {
53 pub fn new(
54 cipher_suite_provider: &'a CP,
55 identity_provider: &'a C,
56 context: MemberValidationContext<'a>,
57 ) -> Self {
58 Self {
59 cipher_suite_provider,
60 identity_provider,
61 context,
62 }
63 }
64
65 fn check_context(
66 &self,
67 leaf_node: &LeafNode,
68 context: &ValidationContext,
69 ) -> Result<(), MlsError> {
70 match context {
72 ValidationContext::Add(time) => {
73 if let LeafNodeSource::KeyPackage(lifetime) = &leaf_node.leaf_node_source {
75 if let Some(current_time) = *time {
76 if !lifetime.within_lifetime(current_time) {
77 return Err(MlsError::InvalidLifetime {
78 not_before: lifetime.not_before,
79 not_after: lifetime.not_after,
80 timestamp: current_time,
81 });
82 }
83 }
84 } else {
85 return Err(MlsError::InvalidLeafNodeSource);
87 }
88 }
89 ValidationContext::Update(_) => {
90 if !matches!(leaf_node.leaf_node_source, LeafNodeSource::Update) {
92 return Err(MlsError::InvalidLeafNodeSource);
93 }
94 }
95 ValidationContext::Commit(_) => {
96 if !matches!(leaf_node.leaf_node_source, LeafNodeSource::Commit(_)) {
98 return Err(MlsError::InvalidLeafNodeSource);
99 }
100 }
101 }
102
103 Ok(())
104 }
105
106 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
107 pub async fn revalidate(
108 &self,
109 leaf_node: &LeafNode,
110 group_id: &[u8],
111 leaf_index: u32,
112 maybe_time: Option<MlsTime>,
113 ) -> Result<(), MlsError> {
114 let context = match leaf_node.leaf_node_source {
115 LeafNodeSource::KeyPackage(_) => ValidationContext::Add(maybe_time),
116 LeafNodeSource::Update => ValidationContext::Update((group_id, leaf_index, maybe_time)),
117 LeafNodeSource::Commit(_) => {
118 ValidationContext::Commit((group_id, leaf_index, maybe_time))
119 }
120 };
121
122 self.check_if_valid(leaf_node, context).await
123 }
124
125 pub fn validate_required_capabilities(&self, leaf_node: &LeafNode) -> Result<(), MlsError> {
126 let Some(required_capabilities) = self
127 .context
128 .new_extensions()
129 .and_then(|ext| ext.get_as::<RequiredCapabilitiesExt>().transpose())
130 .transpose()?
131 else {
132 return Ok(());
133 };
134
135 for extension in &required_capabilities.extensions {
136 if !leaf_node.capabilities.extensions.contains(extension) {
137 return Err(MlsError::RequiredExtensionNotFound(*extension));
138 }
139 }
140
141 for proposal in &required_capabilities.proposals {
142 if !leaf_node.capabilities.proposals.contains(proposal) {
143 return Err(MlsError::RequiredProposalNotFound(*proposal));
144 }
145 }
146
147 for credential in &required_capabilities.credentials {
148 if !leaf_node.capabilities.credentials.contains(credential) {
149 return Err(MlsError::RequiredCredentialNotFound(*credential));
150 }
151 }
152
153 Ok(())
154 }
155
156 #[cfg(feature = "by_ref_proposal")]
157 pub fn validate_external_senders_ext_credentials(
158 &self,
159 leaf_node: &LeafNode,
160 ) -> Result<(), MlsError> {
161 let Some(ext) = self
162 .context
163 .new_extensions()
164 .and_then(|ext| ext.get_as::<ExternalSendersExt>().transpose())
165 .transpose()?
166 else {
167 return Ok(());
168 };
169
170 ext.allowed_senders.iter().try_for_each(|sender| {
171 let cred_type = sender.credential.credential_type();
172 leaf_node
173 .capabilities
174 .credentials
175 .contains(&cred_type)
176 .then_some(())
177 .ok_or(MlsError::RequiredCredentialNotFound(cred_type))
178 })
179 }
180
181 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
182 pub(crate) async fn check_if_valid(
183 &self,
184 leaf_node: &LeafNode,
185 context: ValidationContext<'_>,
186 ) -> Result<(), MlsError> {
187 self.check_context(leaf_node, &context)?;
189
190 self.identity_provider
192 .validate_member(
193 &leaf_node.signing_identity,
194 context.generation_time(),
195 self.context,
196 )
197 .await
198 .map_err(|e| MlsError::IdentityProviderError(e.into_any_error()))?;
199
200 leaf_node
202 .verify(
203 self.cipher_suite_provider,
204 &leaf_node.signing_identity.signature_key,
205 &context.signing_context(),
206 )
207 .await?;
208
209 self.validate_required_capabilities(leaf_node)?;
211
212 for one_ext in &*leaf_node.extensions {
214 if !leaf_node
215 .capabilities
216 .extensions
217 .contains(&one_ext.extension_type)
218 {
219 return Err(MlsError::ExtensionNotInCapabilities(one_ext.extension_type));
220 }
221 }
222
223 if let Some(extensions) = self.context.new_extensions() {
225 extensions
226 .iter()
227 .map(|ext| ext.extension_type)
228 .find(|ext_type| {
229 !ext_type.is_default() && !leaf_node.capabilities.extensions.contains(ext_type)
230 })
231 .map(MlsError::UnsupportedGroupExtension)
232 .map_or(Ok(()), Err)?;
233 }
234
235 leaf_node.validate_no_default_values_listed()?;
236
237 #[cfg(feature = "by_ref_proposal")]
238 self.validate_external_senders_ext_credentials(leaf_node)?;
239
240 Ok(())
241 }
242}
243
244impl LeafNode {
245 pub fn validate_no_default_values_listed(&self) -> Result<(), MlsError> {
246 self.capabilities
249 .extensions
250 .iter()
251 .all(|ext| !ext.is_default())
252 .then_some(())
253 .ok_or(MlsError::DefaultValueListed)?;
254
255 self.capabilities
256 .proposals
257 .iter()
258 .all(|prop| !prop.is_default())
259 .then_some(())
260 .ok_or(MlsError::DefaultValueListed)?;
261
262 Ok(())
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use crate::client::test_utils::TEST_PROTOCOL_VERSION;
269 use crate::crypto::test_utils::try_test_cipher_suite_provider;
270 use crate::extension::MlsExtension;
271 use alloc::vec;
272 use assert_matches::assert_matches;
273 #[cfg(feature = "std")]
274 use core::time::Duration;
275 use mls_rs_core::crypto::CipherSuite;
276 use mls_rs_core::group::GroupContext;
277 use mls_rs_core::group::ProposalType;
278
279 use super::*;
280
281 use crate::client::test_utils::TEST_CIPHER_SUITE;
282 use crate::crypto::test_utils::test_cipher_suite_provider;
283 use crate::crypto::test_utils::TestCryptoProvider;
284 use crate::crypto::SignatureSecretKey;
285 use crate::extension::test_utils::TestExtension;
286 use crate::group::test_utils::random_bytes;
287 use crate::identity::basic::BasicCredential;
288 use crate::identity::basic::BasicIdentityProvider;
289 use crate::identity::test_utils::get_test_signing_identity;
290 use crate::tree_kem::leaf_node::test_utils::*;
291 use crate::tree_kem::leaf_node_validator::test_utils::FailureIdentityProvider;
292 use crate::tree_kem::Capabilities;
293 use crate::ExtensionList;
294
295 impl<'a, P: IdentityProvider, C: CipherSuiteProvider> LeafNodeValidator<'a, P, C> {
296 pub fn new_for_test(cipher_suite_provider: &'a C, identity_provider: &'a P) -> Self {
297 Self {
298 cipher_suite_provider,
299 identity_provider,
300 context: MemberValidationContext::None,
301 }
302 }
303 }
304
305 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
306 async fn get_test_add_node() -> (LeafNode, SignatureSecretKey) {
307 let (signing_identity, secret) = get_test_signing_identity(TEST_CIPHER_SUITE, b"foo").await;
308
309 let (leaf_node, _) =
310 get_test_node(TEST_CIPHER_SUITE, signing_identity, &secret, None, None).await;
311
312 (leaf_node, secret)
313 }
314
315 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
316 async fn test_basic_add_validation() {
317 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
318
319 let (leaf_node, _) = get_test_add_node().await;
320
321 let test_validator =
322 LeafNodeValidator::new_for_test(&cipher_suite_provider, &BasicIdentityProvider);
323
324 let res = test_validator
325 .check_if_valid(&leaf_node, ValidationContext::Add(None))
326 .await;
327
328 assert_matches!(res, Ok(_));
329 }
330
331 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
332 async fn test_failed_validation() {
333 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
334 let (leaf_node, _) = get_test_add_node().await;
335
336 let fail_test_validator =
337 LeafNodeValidator::new_for_test(&cipher_suite_provider, &FailureIdentityProvider);
338
339 let res = fail_test_validator
340 .check_if_valid(&leaf_node, ValidationContext::Add(None))
341 .await;
342
343 assert_matches!(res, Err(MlsError::IdentityProviderError(_)));
344 }
345
346 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
347 async fn test_basic_update_validation() {
348 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
349 let group_id = b"group_id";
350
351 let (mut leaf_node, secret) = get_test_add_node().await;
352
353 leaf_node
354 .update(
355 &cipher_suite_provider,
356 group_id,
357 0,
358 Some(default_properties()),
360 None,
361 &secret,
362 )
363 .await
364 .unwrap();
365
366 let test_validator =
367 LeafNodeValidator::new_for_test(&cipher_suite_provider, &BasicIdentityProvider);
368
369 let res = test_validator
370 .check_if_valid(&leaf_node, ValidationContext::Update((group_id, 0, None)))
371 .await;
372
373 assert_matches!(res, Ok(_));
374 }
375
376 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
377 async fn test_basic_commit_validation() {
378 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
379 let group_id = b"group_id";
380
381 let (mut leaf_node, secret) = get_test_add_node().await;
382
383 leaf_node.leaf_node_source = LeafNodeSource::Commit(hex!("f00d").into());
384
385 leaf_node
386 .commit(
387 &cipher_suite_provider,
388 group_id,
389 0,
390 Some(default_properties()),
391 None,
392 &secret,
393 )
394 .await
395 .unwrap();
396
397 let test_validator =
398 LeafNodeValidator::new_for_test(&cipher_suite_provider, &BasicIdentityProvider);
399
400 let res = test_validator
401 .check_if_valid(&leaf_node, ValidationContext::Commit((group_id, 0, None)))
402 .await;
403
404 assert_matches!(res, Ok(_));
405 }
406
407 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
408 async fn test_incorrect_context() {
409 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
410
411 let test_validator =
412 LeafNodeValidator::new_for_test(&cipher_suite_provider, &BasicIdentityProvider);
413
414 let (mut leaf_node, secret) = get_test_add_node().await;
415
416 let res = test_validator
417 .check_if_valid(&leaf_node, ValidationContext::Update((b"foo", 0, None)))
418 .await;
419
420 assert_matches!(res, Err(MlsError::InvalidLeafNodeSource));
421
422 let res = test_validator
423 .check_if_valid(&leaf_node, ValidationContext::Commit((b"foo", 0, None)))
424 .await;
425
426 assert_matches!(res, Err(MlsError::InvalidLeafNodeSource));
427
428 leaf_node
429 .update(
430 &cipher_suite_provider,
431 b"foo",
432 0,
433 Some(default_properties()),
434 None,
435 &secret,
436 )
437 .await
438 .unwrap();
439
440 let res = test_validator
441 .check_if_valid(&leaf_node, ValidationContext::Add(None))
442 .await;
443
444 assert_matches!(res, Err(MlsError::InvalidLeafNodeSource));
445
446 let res = test_validator
447 .check_if_valid(&leaf_node, ValidationContext::Commit((b"foo", 0, None)))
448 .await;
449
450 assert_matches!(res, Err(MlsError::InvalidLeafNodeSource));
451
452 leaf_node.leaf_node_source = LeafNodeSource::Commit(hex!("f00d").into());
453
454 leaf_node
455 .commit(
456 &cipher_suite_provider,
457 b"foo",
458 0,
459 Some(default_properties()),
460 None,
461 &secret,
462 )
463 .await
464 .unwrap();
465
466 let res = test_validator
467 .check_if_valid(&leaf_node, ValidationContext::Add(None))
468 .await;
469
470 assert_matches!(res, Err(MlsError::InvalidLeafNodeSource));
471
472 let res = test_validator
473 .check_if_valid(&leaf_node, ValidationContext::Update((b"foo", 0, None)))
474 .await;
475
476 assert_matches!(res, Err(MlsError::InvalidLeafNodeSource));
477 }
478
479 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
480 async fn test_bad_signature() {
481 for cipher_suite in TestCryptoProvider::all_supported_cipher_suites() {
482 let cipher_suite_provider = test_cipher_suite_provider(cipher_suite);
483
484 let (signing_identity, secret) = get_test_signing_identity(cipher_suite, b"foo").await;
485
486 let (mut leaf_node, _) =
487 get_test_node(cipher_suite, signing_identity, &secret, None, None).await;
488
489 leaf_node.signature = random_bytes(leaf_node.signature.len());
490
491 let test_validator =
492 LeafNodeValidator::new_for_test(&cipher_suite_provider, &BasicIdentityProvider);
493
494 let res = test_validator
495 .check_if_valid(&leaf_node, ValidationContext::Add(None))
496 .await;
497
498 assert_matches!(res, Err(MlsError::InvalidSignature));
499 }
500 }
501
502 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
503 async fn test_capabilities_mismatch() {
504 let (signing_identity, secret) = get_test_signing_identity(TEST_CIPHER_SUITE, b"foo").await;
505
506 let mut extensions = ExtensionList::new();
507
508 extensions.set_from(TestExtension::from(0)).unwrap();
509
510 let capabilities = Capabilities {
511 credentials: vec![BasicCredential::credential_type()],
512 ..Default::default()
513 };
514
515 let (leaf_node, _) = get_test_node(
516 TEST_CIPHER_SUITE,
517 signing_identity,
518 &secret,
519 Some(capabilities),
520 Some(extensions),
521 )
522 .await;
523
524 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
525
526 let test_validator =
527 LeafNodeValidator::new_for_test(&cipher_suite_provider, &BasicIdentityProvider);
528
529 let res = test_validator
530 .check_if_valid(&leaf_node, ValidationContext::Add(None))
531 .await;
532
533 assert_matches!(res,
534 Err(MlsError::ExtensionNotInCapabilities(ext)) if ext == 42.into());
535 }
536
537 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
538 async fn test_cipher_suite_mismatch() {
539 for another_cipher_suite in CipherSuite::all().filter(|cs| cs != &TEST_CIPHER_SUITE) {
540 if let Some(cs) = try_test_cipher_suite_provider(*another_cipher_suite) {
541 let (leaf_node, _) = get_test_add_node().await;
542
543 let test_validator = LeafNodeValidator::new_for_test(&cs, &BasicIdentityProvider);
544
545 let res = test_validator
546 .check_if_valid(&leaf_node, ValidationContext::Add(None))
547 .await;
548
549 assert_matches!(res, Err(MlsError::InvalidSignature));
550 }
551 }
552 }
553
554 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
555 async fn test_required_extension() {
556 let required_capabilities = RequiredCapabilitiesExt {
557 extensions: vec![43.into()],
558 ..Default::default()
559 };
560
561 let (leaf_node, _) = get_test_add_node().await;
562
563 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
564
565 let group_context_extensions =
566 core::iter::once(required_capabilities.into_extension().unwrap()).collect();
567
568 let context = MemberValidationContext::ForCommit {
569 current_context: &fake_group_context(),
570 new_extensions: &group_context_extensions,
571 };
572
573 let test_validator =
574 LeafNodeValidator::new(&cipher_suite_provider, &BasicIdentityProvider, context);
575
576 let res = test_validator
577 .check_if_valid(&leaf_node, ValidationContext::Add(None))
578 .await;
579
580 assert_matches!(
581 res,
582 Err(MlsError::RequiredExtensionNotFound(v)) if v == 43.into()
583 );
584 }
585
586 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
587 async fn test_required_proposal() {
588 let required_capabilities = RequiredCapabilitiesExt {
589 proposals: vec![42.into()],
590 ..Default::default()
591 };
592
593 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
594
595 let (leaf_node, _) = get_test_add_node().await;
596
597 let group_context_extensions =
598 core::iter::once(required_capabilities.into_extension().unwrap()).collect();
599
600 let context = MemberValidationContext::ForCommit {
601 current_context: &fake_group_context(),
602 new_extensions: &group_context_extensions,
603 };
604
605 let test_validator =
606 LeafNodeValidator::new(&cipher_suite_provider, &BasicIdentityProvider, context);
607
608 let res = test_validator
609 .check_if_valid(&leaf_node, ValidationContext::Add(None))
610 .await;
611
612 assert_matches!(
613 res,
614 Err(MlsError::RequiredProposalNotFound(p)) if p == ProposalType::new(42)
615 );
616 }
617
618 fn fake_group_context() -> GroupContext {
619 GroupContext {
620 protocol_version: TEST_PROTOCOL_VERSION,
621 cipher_suite: TEST_CIPHER_SUITE,
622 group_id: b"unused".into(),
623 epoch: 0,
624 tree_hash: b"unused".into(),
625 confirmed_transcript_hash: b"unused".to_vec().into(),
626 extensions: Default::default(),
627 }
628 }
629
630 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
631 async fn test_required_credential() {
632 let required_capabilities = RequiredCapabilitiesExt {
633 credentials: vec![0.into()],
634 ..Default::default()
635 };
636
637 let (leaf_node, _) = get_test_add_node().await;
638
639 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
640
641 let group_context_extensions =
642 core::iter::once(required_capabilities.into_extension().unwrap()).collect();
643
644 let context = MemberValidationContext::ForCommit {
645 current_context: &fake_group_context(),
646 new_extensions: &group_context_extensions,
647 };
648
649 let test_validator =
650 LeafNodeValidator::new(&cipher_suite_provider, &BasicIdentityProvider, context);
651
652 let res = test_validator
653 .check_if_valid(&leaf_node, ValidationContext::Add(None))
654 .await;
655
656 assert_matches!(res,
657 Err(MlsError::RequiredCredentialNotFound(ext)) if ext == 0.into()
658 );
659 }
660
661 #[cfg(feature = "std")]
662 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
663 async fn test_add_lifetime() {
664 let (leaf_node, _) = get_test_add_node().await;
665
666 let cipher_suite_provider = test_cipher_suite_provider(TEST_CIPHER_SUITE);
667
668 let test_validator =
669 LeafNodeValidator::new_for_test(&cipher_suite_provider, &BasicIdentityProvider);
670
671 let good_lifetime = MlsTime::now();
672
673 let over_one_year = good_lifetime.seconds_since_epoch() + (86400 * 366);
674
675 let bad_lifetime = MlsTime::from_duration_since_epoch(Duration::from_secs(over_one_year));
676
677 let res = test_validator
678 .check_if_valid(&leaf_node, ValidationContext::Add(Some(good_lifetime)))
679 .await;
680
681 assert_matches!(res, Ok(()));
682
683 let res = test_validator
684 .check_if_valid(&leaf_node, ValidationContext::Add(Some(bad_lifetime)))
685 .await;
686
687 assert_matches!(
688 res,
689 Err(MlsError::InvalidLifetime { timestamp, .. })
690 if timestamp == bad_lifetime
691 );
692 }
693}
694
695#[cfg(test)]
696pub(crate) mod test_utils {
697 #[cfg(feature = "std")]
698 use alloc::boxed::Box;
699 use alloc::vec;
700 use alloc::vec::Vec;
701 use mls_rs_codec::MlsEncode;
702 use mls_rs_core::{
703 error::IntoAnyError,
704 extension::ExtensionList,
705 identity::{BasicCredential, IdentityProvider, MemberValidationContext},
706 };
707
708 use crate::{identity::SigningIdentity, time::MlsTime};
709
710 #[derive(Clone, Debug, Default)]
711 pub struct FailureIdentityProvider;
712
713 #[cfg(feature = "by_ref_proposal")]
714 impl FailureIdentityProvider {
715 pub fn new() -> Self {
716 Self
717 }
718 }
719
720 #[derive(Debug)]
721 #[cfg_attr(feature = "std", derive(thiserror::Error))]
722 #[cfg_attr(feature = "std", error("test error"))]
723 pub struct TestFailureError;
724
725 impl IntoAnyError for TestFailureError {
726 #[cfg(feature = "std")]
727 fn into_dyn_error(self) -> Result<Box<dyn std::error::Error + Send + Sync>, Self> {
728 Ok(self.into())
729 }
730 }
731
732 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
733 #[cfg_attr(mls_build_async, maybe_async::must_be_async)]
734 impl IdentityProvider for FailureIdentityProvider {
735 type Error = TestFailureError;
736
737 async fn validate_member(
738 &self,
739 _signing_identity: &SigningIdentity,
740 _timestamp: Option<MlsTime>,
741 _context: MemberValidationContext<'_>,
742 ) -> Result<(), Self::Error> {
743 Err(TestFailureError)
744 }
745
746 async fn validate_external_sender(
747 &self,
748 _signing_identity: &SigningIdentity,
749 _timestamp: Option<MlsTime>,
750 _extensions: Option<&ExtensionList>,
751 ) -> Result<(), Self::Error> {
752 Err(TestFailureError)
753 }
754
755 #[cfg_attr(coverage_nightly, coverage(off))]
756 async fn identity(
757 &self,
758 signing_id: &SigningIdentity,
759 _extensions: &ExtensionList,
760 ) -> Result<Vec<u8>, Self::Error> {
761 Ok(signing_id.credential.mls_encode_to_vec().unwrap())
762 }
763
764 #[cfg_attr(coverage_nightly, coverage(off))]
765 async fn valid_successor(
766 &self,
767 _predecessor: &SigningIdentity,
768 _successor: &SigningIdentity,
769 _extensions: &ExtensionList,
770 ) -> Result<bool, Self::Error> {
771 Err(TestFailureError)
772 }
773
774 #[cfg_attr(coverage_nightly, coverage(off))]
775 fn supported_types(&self) -> Vec<crate::identity::CredentialType> {
776 vec![BasicCredential::credential_type()]
777 }
778 }
779}