Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/rust/mls-rs/src/group/   (Firefox Browser Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 22 kB image not shown  

Quelle  message_verifier.rs

  Sprache: Rust
 

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// Copyright by contributors to this project.
// SPDX-License-Identifier: (Apache-2.0 OR MIT)

#[cfg(feature = "by_ref_proposal")]
use alloc::{vec, vec::Vec};

use crate::{
    client::MlsError,
    crypto::SignaturePublicKey,
    group::{GroupContext, PublicMessage, Sender},
    signer::Signable,
    tree_kem::{node::LeafIndex, TreeKemPublic},
    CipherSuiteProvider,
};

#[cfg(feature = "by_ref_proposal")]
use crate::{extension::ExternalSendersExt, identity::SigningIdentity};

use super::{
    key_schedule::KeySchedule,
    message_signature::{AuthenticatedContent, MessageSigningContext},
    state::GroupState,
};

#[cfg(feature = "by_ref_proposal")]
use super::proposal::Proposal;

#[derive(Debug)]
pub(crateenum SignaturePublicKeysContainer<'a> {
    RatchetTree(&'a TreeKemPublic),
    #[cfg(feature = "private_message")]
    List(&'a [Option<SignaturePublicKey>]),
}

#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
pub(crateasync fn verify_plaintext_authentication<P: CipherSuiteProvider>(
    cipher_suite_provider: &P,
    plaintext: PublicMessage,
    key_schedule: Option<&KeySchedule>,
    self_index: Option<LeafIndex>,
    state: &GroupState,
) -> Result<AuthenticatedContent, MlsError> {
    let tag = plaintext.membership_tag.clone();
    let auth_content = AuthenticatedContent::from(plaintext);
    let context = &state.context;

    #[cfg(feature = "by_ref_proposal")]
    let external_signers = external_signers(context);

    let current_tree = &state.public_tree;

    // Verify the membership tag if needed
    match &auth_content.content.sender {
        Sender::Member(index) => {
            if let Some(key_schedule) = key_schedule {
                let expected_tag = &key_schedule
                    .get_membership_tag(&auth_content, context, cipher_suite_provider)
                    .await?;

                let plaintext_tag = tag.as_ref().ok_or(MlsError::InvalidMembershipTag)?;

                if expected_tag != plaintext_tag {
                    return Err(MlsError::InvalidMembershipTag);
                }
            }

            if self_index == Some(LeafIndex(*index)) {
                return Err(MlsError::CantProcessMessageFromSelf);
            }
        }
        _ => {
            tag.is_none()
                .then_some(())
                .ok_or(MlsError::MembershipTagForNonMember)?;
        }
    }

    // Verify that the signature on the MLSAuthenticatedContent verifies using the public key
    // from the credential stored at the leaf in the tree indicated by the sender field.
    verify_auth_content_signature(
        cipher_suite_provider,
        SignaturePublicKeysContainer::RatchetTree(current_tree),
        context,
        &auth_content,
        #[cfg(feature = "by_ref_proposal")]
        &external_signers,
    )
    .await?;

    Ok(auth_content)
}

#[cfg(feature = "by_ref_proposal")]
fn external_signers(context: &GroupContext) -> Vec<SigningIdentity> {
    context
        .extensions
        .get_as::<ExternalSendersExt>()
        .unwrap_or(None)
        .map_or(vec![], |extern_senders_ext| {
            extern_senders_ext.allowed_senders
        })
}

#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
pub(crateasync fn 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> {
    let sender_public_key = signing_identity_for_sender(
        signature_keys_container,
        &auth_content.content.sender,
        &auth_content.content.content,
        #[cfg(feature = "by_ref_proposal")]
        external_signers,
    )?;

    let context = MessageSigningContext {
        group_context: Some(context),
        protocol_version: context.protocol_version,
    };

    auth_content
        .verify(cipher_suite_provider, &sender_public_key, &context)
        .await?;

    Ok(())
}

fn 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> {
    match sender {
        Sender::Member(leaf_index) => {
            signing_identity_for_member(signature_keys_container, LeafIndex(*leaf_index))
        }
        #[cfg(feature = "by_ref_proposal")]
        Sender::External(external_key_index) => {
            signing_identity_for_external(*external_key_index, external_signers)
        }
        Sender::NewMemberCommit => signing_identity_for_new_member_commit(content),
        #[cfg(feature = "by_ref_proposal")]
        Sender::NewMemberProposal => signing_identity_for_new_member_proposal(content),
    }
}

fn signing_identity_for_member(
    signature_keys_container: SignaturePublicKeysContainer,
    leaf_index: LeafIndex,
) -> Result<SignaturePublicKey, MlsError> {
    match signature_keys_container {
        SignaturePublicKeysContainer::RatchetTree(tree) => Ok(tree
            .get_leaf_node(leaf_index)?
            .signing_identity
            .signature_key
            .clone()), // TODO: We can probably get rid of this clone
        #[cfg(feature = "private_message")]
        SignaturePublicKeysContainer::List(list) => list
            .get(leaf_index.0 as usize)
            .cloned()
            .flatten()
            .ok_or(MlsError::LeafNotFound(*leaf_index)),
    }
}

#[cfg(feature = "by_ref_proposal")]
fn signing_identity_for_external(
    index: u32,
    external_signers: &[SigningIdentity],
) -> Result<SignaturePublicKey, MlsError> {
    external_signers
        .get(index as usize)
        .map(|spk| spk.signature_key.clone())
        .ok_or(MlsError::UnknownSigningIdentityForExternalSender)
}

fn signing_identity_for_new_member_commit(
    content: &super::framing::Content,
) -> Result<SignaturePublicKey, MlsError> {
    match content {
        super::framing::Content::Commit(commit) => {
            if let Some(path) = &commit.path {
                Ok(path.leaf_node.signing_identity.signature_key.clone())
            } else {
                Err(MlsError::CommitMissingPath)
            }
        }
        #[cfg(any(feature = "private_message", feature = "by_ref_proposal"))]
        _ => Err(MlsError::ExpectedCommitForNewMemberCommit),
    }
}

#[cfg(feature = "by_ref_proposal")]
fn signing_identity_for_new_member_proposal(
    content: &super::framing::Content,
) -> Result<SignaturePublicKey, MlsError> {
    match content {
        super::framing::Content::Proposal(proposal) => {
            if let Proposal::Add(p) = proposal.as_ref() {
                Ok(p.key_package
                    .leaf_node
                    .signing_identity
                    .signature_key
                    .clone())
            } else {
                Err(MlsError::ExpectedAddProposalForNewMemberProposal)
            }
        }
        _ => Err(MlsError::ExpectedAddProposalForNewMemberProposal),
    }
}

#[cfg(test)]
mod tests {
    use crate::{
        client::{
            test_utils::{test_client_with_key_pkg, TEST_CIPHER_SUITE, TEST_PROTOCOL_VERSION},
            MlsError,
        },
        client_builder::test_utils::TestClientConfig,
        crypto::test_utils::test_cipher_suite_provider,
        group::{
            membership_tag::MembershipTag,
            message_signature::{AuthenticatedContent, MessageSignature},
            test_utils::{test_group_custom, TestGroup},
            Group, PublicMessage,
        },
        tree_kem::node::LeafIndex,
    };
    use alloc::vec;
    use assert_matches::assert_matches;

    #[cfg(feature = "by_ref_proposal")]
    use crate::{extension::ExternalSendersExt, ExtensionList};

    #[cfg(feature = "by_ref_proposal")]
    use crate::{
        crypto::SignatureSecretKey,
        group::{
            message_signature::MessageSigningContext,
            proposal::{AddProposal, Proposal, RemoveProposal},
            Content,
        },
        key_package::KeyPackageGeneration,
        signer::Signable,
        WireFormat,
    };

    #[cfg(feature = "by_ref_proposal")]
    use alloc::boxed::Box;

    use crate::group::{
        test_utils::{test_group, test_member},
        Sender,
    };

    #[cfg(feature = "by_ref_proposal")]
    use crate::identity::test_utils::get_test_signing_identity;

    use super::{verify_auth_content_signature, verify_plaintext_authentication};

    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
    async fn make_signed_plaintext(group: &mut Group<TestClientConfig>) -> PublicMessage {
        group
            .commit(vec![])
            .await
            .unwrap()
            .commit_message
            .into_plaintext()
            .unwrap()
    }

    struct TestEnv {
        alice: TestGroup,
        bob: TestGroup,
    }

    impl TestEnv {
        #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
        async fn new() -> Self {
            let mut alice = test_group_custom(
                TEST_PROTOCOL_VERSION,
                TEST_CIPHER_SUITE,
                Default::default(),
                None,
                None,
            )
            .await;

            let (bob_client, bob_key_pkg) =
                test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob").await;

            let commit_output = alice
                .group
                .commit_builder()
                .add_member(bob_key_pkg)
                .unwrap()
                .build()
                .await
                .unwrap();

            alice.group.apply_pending_commit().await.unwrap();

            let (bob, _) = Group::join(
                &commit_output.welcome_messages[0],
                None,
                bob_client.config,
                bob_client.signer.unwrap(),
            )
            .await
            .unwrap();

            TestEnv {
                alice,
                bob: TestGroup { group: bob },
            }
        }
    }

    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn valid_plaintext_is_verified() {
        let mut env = TestEnv::new().await;

        let message = make_signed_plaintext(&mut env.alice.group).await;

        verify_plaintext_authentication(
            &env.bob.group.cipher_suite_provider,
            message,
            Some(&env.bob.group.key_schedule),
            None,
            &env.bob.group.state,
        )
        .await
        .unwrap();
    }

    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn valid_auth_content_is_verified() {
        let mut env = TestEnv::new().await;

        let message = AuthenticatedContent::from(make_signed_plaintext(&mut env.alice.group).await);

        verify_auth_content_signature(
            &env.bob.group.cipher_suite_provider,
            super::SignaturePublicKeysContainer::RatchetTree(&env.bob.group.state.public_tree),
            env.bob.group.context(),
            &message,
            #[cfg(feature = "by_ref_proposal")]
            &[],
        )
        .await
        .unwrap();
    }

    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn invalid_plaintext_is_not_verified() {
        let mut env = TestEnv::new().await;
        let mut message = make_signed_plaintext(&mut env.alice.group).await;
        message.auth.signature = MessageSignature::from(b"test".to_vec());

        message.membership_tag = env
            .alice
            .group
            .key_schedule
            .get_membership_tag(
                &AuthenticatedContent::from(message.clone()),
                env.alice.group.context(),
                &test_cipher_suite_provider(env.alice.group.cipher_suite()),
            )
            .await
            .unwrap()
            .into();

        let res = verify_plaintext_authentication(
            &env.bob.group.cipher_suite_provider,
            message,
            Some(&env.bob.group.key_schedule),
            None,
            &env.bob.group.state,
        )
        .await;

        assert_matches!(res, Err(MlsError::InvalidSignature));
    }

    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn plaintext_from_member_requires_membership_tag() {
        let mut env = TestEnv::new().await;
        let mut message = make_signed_plaintext(&mut env.alice.group).await;
        message.membership_tag = None;

        let res = verify_plaintext_authentication(
            &env.bob.group.cipher_suite_provider,
            message,
            Some(&env.bob.group.key_schedule),
            None,
            &env.bob.group.state,
        )
        .await;

        assert_matches!(res, Err(MlsError::InvalidMembershipTag));
    }

    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn plaintext_fails_with_invalid_membership_tag() {
        let mut env = TestEnv::new().await;
        let mut message = make_signed_plaintext(&mut env.alice.group).await;
        message.membership_tag = Some(MembershipTag::from(b"test".to_vec()));

        let res = verify_plaintext_authentication(
            &env.bob.group.cipher_suite_provider,
            message,
            Some(&env.bob.group.key_schedule),
            None,
            &env.bob.group.state,
        )
        .await;

        assert_matches!(res, Err(MlsError::InvalidMembershipTag));
    }

    #[cfg(feature = "by_ref_proposal")]
    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
    async fn test_new_member_proposal<F>(
        key_pkg_gen: KeyPackageGeneration,
        signer: &SignatureSecretKey,
        test_group: &TestGroup,
        mut edit: F,
    ) -> PublicMessage
    where
        F: FnMut(&mut AuthenticatedContent),
    {
        let mut content = AuthenticatedContent::new_signed(
            &test_group.group.cipher_suite_provider,
            test_group.group.context(),
            Sender::NewMemberProposal,
            Content::Proposal(Box::new(Proposal::Add(Box::new(AddProposal {
                key_package: key_pkg_gen.key_package,
            })))),
            signer,
            WireFormat::PublicMessage,
            vec![],
        )
        .await
        .unwrap();

        edit(&mut content);

        let signing_context = MessageSigningContext {
            group_context: Some(test_group.group.context()),
            protocol_version: test_group.group.protocol_version(),
        };

        content
            .sign(
                &test_group.group.cipher_suite_provider,
                signer,
                &signing_context,
            )
            .await
            .unwrap();

        PublicMessage {
            content: content.content,
            auth: content.auth,
            membership_tag: None,
        }
    }

    #[cfg(feature = "by_ref_proposal")]
    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn valid_proposal_from_new_member_is_verified() {
        let test_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
        let (key_pkg_gen, signer) =
            test_member(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, b"bob").await;
        let message = test_new_member_proposal(key_pkg_gen, &signer, &test_group, |_| {}).await;

        verify_plaintext_authentication(
            &test_group.group.cipher_suite_provider,
            message,
            Some(&test_group.group.key_schedule),
            None,
            &test_group.group.state,
        )
        .await
        .unwrap();
    }

    #[cfg(feature = "by_ref_proposal")]
    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn proposal_from_new_member_must_not_have_membership_tag() {
        let test_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
        let (key_pkg_gen, signer) =
            test_member(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, b"bob").await;

        let mut message = test_new_member_proposal(key_pkg_gen, &signer, &test_group, |_| {}).await;
        message.membership_tag = Some(MembershipTag::from(vec![]));

        let res = verify_plaintext_authentication(
            &test_group.group.cipher_suite_provider,
            message,
            Some(&test_group.group.key_schedule),
            None,
            &test_group.group.state,
        )
        .await;

        assert_matches!(res, Err(MlsError::MembershipTagForNonMember));
    }

    #[cfg(feature = "by_ref_proposal")]
    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn new_member_proposal_sender_must_be_add_proposal() {
        let test_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
        let (key_pkg_gen, signer) =
            test_member(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, b"bob").await;

        let message = test_new_member_proposal(key_pkg_gen, &signer, &test_group, |msg| {
            msg.content.content = Content::Proposal(Box::new(Proposal::Remove(RemoveProposal {
                to_remove: LeafIndex(0),
            })))
        })
        .await;

        let res: Result<AuthenticatedContent, MlsError> = verify_plaintext_authentication(
            &test_group.group.cipher_suite_provider,
            message,
            Some(&test_group.group.key_schedule),
            None,
            &test_group.group.state,
        )
        .await;

        assert_matches!(res, Err(MlsError::ExpectedAddProposalForNewMemberProposal));
    }

    #[cfg(feature = "by_ref_proposal")]
    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn new_member_commit_must_be_external_commit() {
        let test_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
        let (key_pkg_gen, signer) =
            test_member(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, b"bob").await;

        let message = test_new_member_proposal(key_pkg_gen, &signer, &test_group, |msg| {
            msg.content.sender = Sender::NewMemberCommit;
        })
        .await;

        let res = verify_plaintext_authentication(
            &test_group.group.cipher_suite_provider,
            message,
            Some(&test_group.group.key_schedule),
            None,
            &test_group.group.state,
        )
        .await;

        assert_matches!(res, Err(MlsError::ExpectedCommitForNewMemberCommit));
    }

    #[cfg(feature = "by_ref_proposal")]
    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn valid_proposal_from_external_is_verified() {
        let (bob_key_pkg_gen, _) =
            test_member(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, b"bob").await;

        let (ted_signing, ted_secret) = get_test_signing_identity(TEST_CIPHER_SUITE, b"ted").await;

        let mut test_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
        let mut extensions = ExtensionList::default();

        extensions
            .set_from(ExternalSendersExt {
                allowed_senders: vec![ted_signing],
            })
            .unwrap();

        test_group
            .group
            .commit_builder()
            .set_group_context_ext(extensions)
            .unwrap()
            .build()
            .await
            .unwrap();

        test_group.group.apply_pending_commit().await.unwrap();

        let message = test_new_member_proposal(bob_key_pkg_gen, &ted_secret, &test_group, |msg| {
            msg.content.sender = Sender::External(0)
        })
        .await;

        verify_plaintext_authentication(
            &test_group.group.cipher_suite_provider,
            message,
            Some(&test_group.group.key_schedule),
            None,
            &test_group.group.state,
        )
        .await
        .unwrap();
    }

    #[cfg(feature = "by_ref_proposal")]
    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn external_proposal_must_be_from_valid_sender() {
        let (bob_key_pkg_gen, _) =
            test_member(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, b"bob").await;
        let (_, ted_secret) = get_test_signing_identity(TEST_CIPHER_SUITE, b"ted").await;
        let test_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;

        let message = test_new_member_proposal(bob_key_pkg_gen, &ted_secret, &test_group, |msg| {
            msg.content.sender = Sender::External(0)
        })
        .await;

        let res = verify_plaintext_authentication(
            &test_group.group.cipher_suite_provider,
            message,
            Some(&test_group.group.key_schedule),
            None,
            &test_group.group.state,
        )
        .await;

        assert_matches!(res, Err(MlsError::UnknownSigningIdentityForExternalSender));
    }

    #[cfg(feature = "by_ref_proposal")]
    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn proposal_from_external_sender_must_not_have_membership_tag() {
        let (bob_key_pkg_gen, _) =
            test_member(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, b"bob").await;

        let (_, ted_secret) = get_test_signing_identity(TEST_CIPHER_SUITE, b"ted").await;

        let test_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;

        let mut message =
            test_new_member_proposal(bob_key_pkg_gen, &ted_secret, &test_group, |_| {}).await;

        message.membership_tag = Some(MembershipTag::from(vec![]));

        let res = verify_plaintext_authentication(
            &test_group.group.cipher_suite_provider,
            message,
            Some(&test_group.group.key_schedule),
            None,
            &test_group.group.state,
        )
        .await;

        assert_matches!(res, Err(MlsError::MembershipTagForNonMember));
    }

    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn plaintext_from_self_fails_verification() {
        let mut env = TestEnv::new().await;

        let message = make_signed_plaintext(&mut env.alice.group).await;

        let res = verify_plaintext_authentication(
            &env.alice.group.cipher_suite_provider,
            message,
            Some(&env.alice.group.key_schedule),
            Some(LeafIndex::new(env.alice.group.current_member_index())),
            &env.alice.group.state,
        )
        .await;

        assert_matches!(res, Err(MlsError::CantProcessMessageFromSelf))
    }
}

Messung V0.5 in Prozent
C=77 H=96 G=86

¤ Dauer der Verarbeitung: 0.12 Sekunden  (vorverarbeitet am  2026-06-23) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.