Skip to main content

mls_rs/tree_kem/
leaf_node_validator.rs

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
5use 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        // Context specific checks
71        match context {
72            ValidationContext::Add(time) => {
73                // If the context is add, and we specified a time to check for lifetime, verify it
74                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                    // If the leaf_node_source is anything other than Add it is invalid
86                    return Err(MlsError::InvalidLeafNodeSource);
87                }
88            }
89            ValidationContext::Update(_) => {
90                // If the leaf_node_source is anything other than Update it is invalid
91                if !matches!(leaf_node.leaf_node_source, LeafNodeSource::Update) {
92                    return Err(MlsError::InvalidLeafNodeSource);
93                }
94            }
95            ValidationContext::Commit(_) => {
96                // If the leaf_node_source is anything other than Commit it is invalid
97                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        // Check that we are validating within the proper context
188        self.check_context(leaf_node, &context)?;
189
190        // Verify the credential
191        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        // Verify that the credential signed the leaf node
201        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        // If required capabilities are specified, verify the leaf node meets the requirements
210        self.validate_required_capabilities(leaf_node)?;
211
212        // If there are extensions, make sure they are referenced in the capabilities field
213        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        // Verify that group extensions are supported by the leaf
224        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        // The following proposal and extension types are considered "default" and
247        // MUST NOT be listed
248        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                // TODO remove identity from input
359                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}