Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  client.rs   Sprache: unbekannt

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

use crate::cipher_suite::CipherSuite;
use crate::client_builder::{recreate_config, BaseConfig, ClientBuilder, MakeConfig};
use crate::client_config::ClientConfig;
use crate::group::framing::MlsMessage;

#[cfg(feature = "by_ref_proposal")]
use crate::group::{
    framing::{Content, MlsMessagePayload, PublicMessage, Sender, WireFormat},
    message_signature::AuthenticatedContent,
    proposal::{AddProposal, Proposal},
};
use crate::group::{snapshot::Snapshot, ExportedTree, Group, NewMemberInfo};
use crate::identity::SigningIdentity;
use crate::key_package::{KeyPackageGeneration, KeyPackageGenerator};
use crate::protocol_version::ProtocolVersion;
use crate::tree_kem::node::NodeIndex;
use alloc::vec::Vec;
use mls_rs_codec::MlsDecode;
use mls_rs_core::crypto::{CryptoProvider, SignatureSecretKey};
use mls_rs_core::error::{AnyError, IntoAnyError};
use mls_rs_core::extension::{ExtensionError, ExtensionList, ExtensionType};
use mls_rs_core::group::{GroupStateStorage, ProposalType};
use mls_rs_core::identity::CredentialType;
use mls_rs_core::key_package::KeyPackageStorage;

use crate::group::external_commit::ExternalCommitBuilder;

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

#[derive(Debug)]
#[cfg_attr(feature = "std", derive(thiserror::Error))]
#[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::enum_to_error_code)]
#[non_exhaustive]
pub enum MlsError {
    #[cfg_attr(feature = "std", error(transparent))]
    IdentityProviderError(AnyError),
    #[cfg_attr(feature = "std", error(transparent))]
    CryptoProviderError(AnyError),
    #[cfg_attr(feature = "std", error(transparent))]
    KeyPackageRepoError(AnyError),
    #[cfg_attr(feature = "std", error(transparent))]
    GroupStorageError(AnyError),
    #[cfg_attr(feature = "std", error(transparent))]
    PskStoreError(AnyError),
    #[cfg_attr(feature = "std", error(transparent))]
    MlsRulesError(AnyError),
    #[cfg_attr(feature = "std", error(transparent))]
    SerializationError(AnyError),
    #[cfg_attr(feature = "std", error(transparent))]
    ExtensionError(AnyError),
    #[cfg_attr(feature = "std", error("Cipher suite does not match"))]
    CipherSuiteMismatch,
    #[cfg_attr(feature = "std", error("Invalid commit, missing required path"))]
    CommitMissingPath,
    #[cfg_attr(feature = "std", error("plaintext message for incorrect epoch"))]
    InvalidEpoch,
    #[cfg_attr(feature = "std", error("invalid signature found"))]
    InvalidSignature,
    #[cfg_attr(feature = "std", error("invalid confirmation tag"))]
    InvalidConfirmationTag,
    #[cfg_attr(feature = "std", error("invalid membership tag"))]
    InvalidMembershipTag,
    #[cfg_attr(feature = "std", error("corrupt private key, missing required values"))]
    InvalidTreeKemPrivateKey,
    #[cfg_attr(feature = "std", error("key package not found, unable to process"))]
    WelcomeKeyPackageNotFound,
    #[cfg_attr(feature = "std", error("leaf not found in tree for index {0}"))]
    LeafNotFound(u32),
    #[cfg_attr(feature = "std", error("message from self can't be processed"))]
    CantProcessMessageFromSelf,
    #[cfg_attr(
        feature = "std",
        error("pending proposals found, commit required before application messages can be sent")
    )]
    CommitRequired,
    #[cfg_attr(
        feature = "std",
        error("ratchet tree not provided or discovered in GroupInfo")
    )]
    RatchetTreeNotFound,
    #[cfg_attr(feature = "std", error("External sender cannot commit"))]
    ExternalSenderCannotCommit,
    #[cfg_attr(feature = "std", error("Unsupported protocol version {0:?}"))]
    UnsupportedProtocolVersion(ProtocolVersion),
    #[cfg_attr(feature = "std", error("Protocol version mismatch"))]
    ProtocolVersionMismatch,
    #[cfg_attr(feature = "std", error("Unsupported cipher suite {0:?}"))]
    UnsupportedCipherSuite(CipherSuite),
    #[cfg_attr(feature = "std", error("Signing key of external sender is unknown"))]
    UnknownSigningIdentityForExternalSender,
    #[cfg_attr(
        feature = "std",
        error("External proposals are disabled for this group")
    )]
    ExternalProposalsDisabled,
    #[cfg_attr(
        feature = "std",
        error("Signing identity is not allowed to externally propose")
    )]
    InvalidExternalSigningIdentity,
    #[cfg_attr(feature = "std", error("Missing ExternalPub extension"))]
    MissingExternalPubExtension,
    #[cfg_attr(feature = "std", error("Epoch not found"))]
    EpochNotFound,
    #[cfg_attr(feature = "std", error("Unencrypted application message"))]
    UnencryptedApplicationMessage,
    #[cfg_attr(
        feature = "std",
        error("NewMemberCommit sender type can only be used to send Commit content")
    )]
    ExpectedCommitForNewMemberCommit,
    #[cfg_attr(
        feature = "std",
        error("NewMemberProposal sender type can only be used to send add proposals")
    )]
    ExpectedAddProposalForNewMemberProposal,
    #[cfg_attr(
        feature = "std",
        error("External commit missing ExternalInit proposal")
    )]
    ExternalCommitMissingExternalInit,
    #[cfg_attr(
        feature = "std",
        error(
            "A ReIinit has been applied. The next action must be creating or receiving a welcome."
        )
    )]
    GroupUsedAfterReInit,
    #[cfg_attr(feature = "std", error("Pending ReIinit not found."))]
    PendingReInitNotFound,
    #[cfg_attr(
        feature = "std",
        error("The extensions in the welcome message and in the reinit do not match.")
    )]
    ReInitExtensionsMismatch,
    #[cfg_attr(feature = "std", error("signer not found for given identity"))]
    SignerNotFound,
    #[cfg_attr(feature = "std", error("commit already pending"))]
    ExistingPendingCommit,
    #[cfg_attr(feature = "std", error("pending commit not found"))]
    PendingCommitNotFound,
    #[cfg_attr(feature = "std", error("unexpected message type for action"))]
    UnexpectedMessageType,
    #[cfg_attr(
        feature = "std",
        error("membership tag on MlsPlaintext for non-member sender")
    )]
    MembershipTagForNonMember,
    #[cfg_attr(feature = "std", error("No member found for given identity id."))]
    MemberNotFound,
    #[cfg_attr(feature = "std", error("group not found"))]
    GroupNotFound,
    #[cfg_attr(feature = "std", error("unexpected PSK ID"))]
    UnexpectedPskId,
    #[cfg_attr(feature = "std", error("invalid sender for content type"))]
    InvalidSender,
    #[cfg_attr(feature = "std", error("GroupID mismatch"))]
    GroupIdMismatch,
    #[cfg_attr(feature = "std", error("storage retention can not be zero"))]
    NonZeroRetentionRequired,
    #[cfg_attr(feature = "std", error("Too many PSK IDs to compute PSK secret"))]
    TooManyPskIds,
    #[cfg_attr(feature = "std", error("Missing required Psk"))]
    MissingRequiredPsk,
    #[cfg_attr(feature = "std", error("Old group state not found"))]
    OldGroupStateNotFound,
    #[cfg_attr(feature = "std", error("leaf secret already consumed"))]
    InvalidLeafConsumption,
    #[cfg_attr(feature = "std", error("key not available, invalid generation {0}"))]
    KeyMissing(u32),
    #[cfg_attr(
        feature = "std",
        error("requested generation {0} is too far ahead of current generation")
    )]
    InvalidFutureGeneration(u32),
    #[cfg_attr(feature = "std", error("leaf node has no children"))]
    LeafNodeNoChildren,
    #[cfg_attr(feature = "std", error("root node has no parent"))]
    LeafNodeNoParent,
    #[cfg_attr(feature = "std", error("index out of range"))]
    InvalidTreeIndex,
    #[cfg_attr(feature = "std", error("time overflow"))]
    TimeOverflow,
    #[cfg_attr(feature = "std", error("invalid leaf_node_source"))]
    InvalidLeafNodeSource,
    #[cfg_attr(feature = "std", error("key package has expired or is not valid yet"))]
    InvalidLifetime,
    #[cfg_attr(feature = "std", error("required extension not found"))]
    RequiredExtensionNotFound(ExtensionType),
    #[cfg_attr(feature = "std", error("required proposal not found"))]
    RequiredProposalNotFound(ProposalType),
    #[cfg_attr(feature = "std", error("required credential not found"))]
    RequiredCredentialNotFound(CredentialType),
    #[cfg_attr(feature = "std", error("capabilities must describe extensions used"))]
    ExtensionNotInCapabilities(ExtensionType),
    #[cfg_attr(feature = "std", error("expected non-blank node"))]
    ExpectedNode,
    #[cfg_attr(feature = "std", error("node index is out of bounds {0}"))]
    InvalidNodeIndex(NodeIndex),
    #[cfg_attr(feature = "std", error("unexpected empty node found"))]
    UnexpectedEmptyNode,
    #[cfg_attr(
        feature = "std",
        error("duplicate signature key, hpke key or identity found at index {0}")
    )]
    DuplicateLeafData(u32),
    #[cfg_attr(
        feature = "std",
        error("In-use credential type not supported by new leaf at index")
    )]
    InUseCredentialTypeUnsupportedByNewLeaf,
    #[cfg_attr(
        feature = "std",
        error("Not all members support the credential type used by new leaf")
    )]
    CredentialTypeOfNewLeafIsUnsupported,
    #[cfg_attr(
        feature = "std",
        error("the length of the update path is different than the length of the direct path")
    )]
    WrongPathLen,
    #[cfg_attr(
        feature = "std",
        error("same HPKE leaf key before and after applying the update path for leaf {0}")
    )]
    SameHpkeKey(u32),
    #[cfg_attr(feature = "std", error("init key is not valid for cipher suite"))]
    InvalidInitKey,
    #[cfg_attr(
        feature = "std",
        error("init key can not be equal to leaf node public key")
    )]
    InitLeafKeyEquality,
    #[cfg_attr(feature = "std", error("different identity in update for leaf {0}"))]
    DifferentIdentityInUpdate(u32),
    #[cfg_attr(feature = "std", error("update path pub key mismatch"))]
    PubKeyMismatch,
    #[cfg_attr(feature = "std", error("tree hash mismatch"))]
    TreeHashMismatch,
    #[cfg_attr(feature = "std", error("bad update: no suitable secret key"))]
    UpdateErrorNoSecretKey,
    #[cfg_attr(feature = "std", error("invalid lca, not found on direct path"))]
    LcaNotFoundInDirectPath,
    #[cfg_attr(feature = "std", error("update path parent hash mismatch"))]
    ParentHashMismatch,
    #[cfg_attr(feature = "std", error("unexpected pattern of unmerged leaves"))]
    UnmergedLeavesMismatch,
    #[cfg_attr(feature = "std", error("empty tree"))]
    UnexpectedEmptyTree,
    #[cfg_attr(feature = "std", error("trailing blanks"))]
    UnexpectedTrailingBlanks,
    // Proposal Rules errors
    #[cfg_attr(
        feature = "std",
        error("Commiter must not include any update proposals generated by the commiter")
    )]
    InvalidCommitSelfUpdate,
    #[cfg_attr(feature = "std", error("A PreSharedKey proposal must have a PSK of type External or type Resumption and usage Application"))]
    InvalidTypeOrUsageInPreSharedKeyProposal,
    #[cfg_attr(feature = "std", error("psk nonce length does not match cipher suite"))]
    InvalidPskNonceLength,
    #[cfg_attr(
        feature = "std",
        error("ReInit proposal protocol version is less than the version of the original group")
    )]
    InvalidProtocolVersionInReInit,
    #[cfg_attr(feature = "std", error("More than one proposal applying to leaf: {0}"))]
    MoreThanOneProposalForLeaf(u32),
    #[cfg_attr(
        feature = "std",
        error("More than one GroupContextExtensions proposal")
    )]
    MoreThanOneGroupContextExtensionsProposal,
    #[cfg_attr(feature = "std", error("Invalid proposal type for sender"))]
    InvalidProposalTypeForSender,
    #[cfg_attr(
        feature = "std",
        error("External commit must have exactly one ExternalInit proposal")
    )]
    ExternalCommitMustHaveExactlyOneExternalInit,
    #[cfg_attr(feature = "std", error("External commit must have a new leaf"))]
    ExternalCommitMustHaveNewLeaf,
    #[cfg_attr(
        feature = "std",
        error("External commit contains removal of other identity")
    )]
    ExternalCommitRemovesOtherIdentity,
    #[cfg_attr(
        feature = "std",
        error("External commit contains more than one Remove proposal")
    )]
    ExternalCommitWithMoreThanOneRemove,
    #[cfg_attr(feature = "std", error("Duplicate PSK IDs"))]
    DuplicatePskIds,
    #[cfg_attr(
        feature = "std",
        error("Invalid proposal type {0:?} in external commit")
    )]
    InvalidProposalTypeInExternalCommit(ProposalType),
    #[cfg_attr(feature = "std", error("Committer can not remove themselves"))]
    CommitterSelfRemoval,
    #[cfg_attr(
        feature = "std",
        error("Only members can commit proposals by reference")
    )]
    OnlyMembersCanCommitProposalsByRef,
    #[cfg_attr(feature = "std", error("Other proposal with ReInit"))]
    OtherProposalWithReInit,
    #[cfg_attr(feature = "std", error("Unsupported group extension {0:?}"))]
    UnsupportedGroupExtension(ExtensionType),
    #[cfg_attr(feature = "std", error("Unsupported custom proposal type {0:?}"))]
    UnsupportedCustomProposal(ProposalType),
    #[cfg_attr(feature = "std", error("by-ref proposal not found"))]
    ProposalNotFound,
    #[cfg_attr(
        feature = "std",
        error("Removing non-existing member (or removing a member twice)")
    )]
    RemovingNonExistingMember,
    #[cfg_attr(feature = "std", error("Updated identity not a valid successor"))]
    InvalidSuccessor,
    #[cfg_attr(
        feature = "std",
        error("Updating non-existing member (or updating a member twice)")
    )]
    UpdatingNonExistingMember,
    #[cfg_attr(feature = "std", error("Failed generating next path secret"))]
    FailedGeneratingPathSecret,
    #[cfg_attr(feature = "std", error("Invalid group info"))]
    InvalidGroupInfo,
    #[cfg_attr(feature = "std", error("Invalid welcome message"))]
    InvalidWelcomeMessage,
}

impl IntoAnyError for MlsError {
    #[cfg(feature = "std")]
    fn into_dyn_error(self) -> Result<Box<dyn std::error::Error + Send + Sync>, Self> {
        Ok(self.into())
    }
}

impl From<mls_rs_codec::Error> for MlsError {
    #[inline]
    fn from(e: mls_rs_codec::Error) -> Self {
        MlsError::SerializationError(e.into_any_error())
    }
}

impl From<ExtensionError> for MlsError {
    #[inline]
    fn from(e: ExtensionError) -> Self {
        MlsError::ExtensionError(e.into_any_error())
    }
}

/// MLS client used to create key packages and manage groups.
///
/// [`Client::builder`] can be used to instantiate it.
///
/// Clients are able to support multiple protocol versions, ciphersuites
/// and underlying identities used to join groups and generate key packages.
/// Applications may decide to create one or many clients depending on their
/// specific needs.
#[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::ffi_type(opaque))]
#[derive(Clone, Debug)]
pub struct Client<C> {
    pub(crate) config: C,
    pub(crate) signing_identity: Option<(SigningIdentity, CipherSuite)>,
    pub(crate) signer: Option<SignatureSecretKey>,
    pub(crate) version: ProtocolVersion,
}

impl Client<()> {
    /// Returns a [`ClientBuilder`]
    /// used to configure client preferences and providers.
    pub fn builder() -> ClientBuilder<BaseConfig> {
        ClientBuilder::new()
    }
}

#[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::safer_ffi_gen)]
impl<C> Client<C>
where
    C: ClientConfig + Clone,
{
    pub(crate) fn new(
        config: C,
        signer: Option<SignatureSecretKey>,
        signing_identity: Option<(SigningIdentity, CipherSuite)>,
        version: ProtocolVersion,
    ) -> Self {
        Client {
            config,
            signer,
            signing_identity,
            version,
        }
    }

    #[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::safer_ffi_gen_ignore)]
    pub fn to_builder(&self) -> ClientBuilder<MakeConfig<C>> {
        ClientBuilder::from_config(recreate_config(
            self.config.clone(),
            self.signer.clone(),
            self.signing_identity.clone(),
            self.version,
        ))
    }

    /// Creates a new key package message that can be used to to add this
    /// client to a [Group](crate::group::Group). Each call to this function
    /// will produce a unique value that is signed by `signing_identity`.
    ///
    /// The secret keys for the resulting key package message will be stored in
    /// the [KeyPackageStorage](crate::KeyPackageStorage)
    /// that was used to configure the client and will
    /// automatically be erased when this key package is used to
    /// [join a group](Client::join_group).
    ///
    /// # Warning
    ///
    /// A key package message may only be used once.
    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
    pub async fn generate_key_package_message(&self) -> Result<MlsMessage, MlsError> {
        Ok(self.generate_key_package().await?.key_package_message())
    }

    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
    async fn generate_key_package(&self) -> Result<KeyPackageGeneration, MlsError> {
        let (signing_identity, cipher_suite) = self.signing_identity()?;

        let cipher_suite_provider = self
            .config
            .crypto_provider()
            .cipher_suite_provider(cipher_suite)
            .ok_or(MlsError::UnsupportedCipherSuite(cipher_suite))?;

        let key_package_generator = KeyPackageGenerator {
            protocol_version: self.version,
            cipher_suite_provider: &cipher_suite_provider,
            signing_key: self.signer()?,
            signing_identity,
            identity_provider: &self.config.identity_provider(),
        };

        let key_pkg_gen = key_package_generator
            .generate(
                self.config.lifetime(),
                self.config.capabilities(),
                self.config.key_package_extensions(),
                self.config.leaf_node_extensions(),
            )
            .await?;

        let (id, key_package_data) = key_pkg_gen.to_storage()?;

        self.config
            .key_package_repo()
            .insert(id, key_package_data)
            .await
            .map_err(|e| MlsError::KeyPackageRepoError(e.into_any_error()))?;

        Ok(key_pkg_gen)
    }

    /// Create a group with a specific group_id.
    ///
    /// This function behaves the same way as
    /// [create_group](Client::create_group) except that it
    /// specifies a specific unique group identifier to be used.
    ///
    /// # Warning
    ///
    /// It is recommended to use [create_group](Client::create_group)
    /// instead of this function because it guarantees that group_id values
    /// are globally unique.
    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
    pub async fn create_group_with_id(
        &self,
        group_id: Vec<u8>,
        group_context_extensions: ExtensionList,
    ) -> Result<Group<C>, MlsError> {
        let (signing_identity, cipher_suite) = self.signing_identity()?;

        Group::new(
            self.config.clone(),
            Some(group_id),
            cipher_suite,
            self.version,
            signing_identity.clone(),
            group_context_extensions,
            self.signer()?.clone(),
        )
        .await
    }

    /// Create a MLS group.
    ///
    /// The `cipher_suite` provided must be supported by the
    /// [CipherSuiteProvider](crate::CipherSuiteProvider)
    /// that was used to build the client.
    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
    pub async fn create_group(
        &self,
        group_context_extensions: ExtensionList,
    ) -> Result<Group<C>, MlsError> {
        let (signing_identity, cipher_suite) = self.signing_identity()?;

        Group::new(
            self.config.clone(),
            None,
            cipher_suite,
            self.version,
            signing_identity.clone(),
            group_context_extensions,
            self.signer()?.clone(),
        )
        .await
    }

    /// Join a MLS group via a welcome message created by a
    /// [Commit](crate::group::CommitOutput).
    ///
    /// `tree_data` is required to be provided out of band if the client that
    /// created `welcome_message` did not use the `ratchet_tree_extension`
    /// according to [`MlsRules::commit_options`](`crate::MlsRules::commit_options`).
    /// at the time the welcome message was created. `tree_data` can
    /// be exported from a group using the
    /// [export tree function](crate::group::Group::export_tree).
    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
    pub async fn join_group(
        &self,
        tree_data: Option<ExportedTree<'_>>,
        welcome_message: &MlsMessage,
    ) -> Result<(Group<C>, NewMemberInfo), MlsError> {
        Group::join(
            welcome_message,
            tree_data,
            self.config.clone(),
            self.signer()?.clone(),
        )
        .await
    }

    /// 0-RTT add to an existing [group](crate::group::Group)
    ///
    /// External commits allow for immediate entry into a
    /// [group](crate::group::Group), even if all of the group members
    /// are currently offline and unable to process messages. Sending an
    /// external commit is only allowed for groups that have provided
    /// a public `group_info_message` containing an
    /// [ExternalPubExt](crate::extension::ExternalPubExt), which can be
    /// generated by an existing group member using the
    /// [group_info_message](crate::group::Group::group_info_message)
    /// function.
    ///
    /// `tree_data` may be provided following the same rules as [Client::join_group]
    ///
    /// If PSKs are provided in `external_psks`, the
    /// [PreSharedKeyStorage](crate::PreSharedKeyStorage)
    /// used to configure the client will be searched to resolve their values.
    ///
    /// `to_remove` may be used to remove an existing member provided that the
    /// identity of the existing group member at that [index](crate::group::Member::index)
    /// is a [valid successor](crate::IdentityProvider::valid_successor)
    /// of `signing_identity` as defined by the
    /// [IdentityProvider](crate::IdentityProvider) that this client
    /// was configured with.
    ///
    /// # Warning
    ///
    /// Only one external commit can be performed against a given group info.
    /// There may also be security trade-offs to this approach.
    ///
    // TODO: Add a comment about forward secrecy and a pointer to the future
    // book chapter on this topic
    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
    pub async fn commit_external(
        &self,
        group_info_msg: MlsMessage,
    ) -> Result<(Group<C>, MlsMessage), MlsError> {
        ExternalCommitBuilder::new(
            self.signer()?.clone(),
            self.signing_identity()?.0.clone(),
            self.config.clone(),
        )
        .build(group_info_msg)
        .await
    }

    pub fn external_commit_builder(&self) -> Result<ExternalCommitBuilder<C>, MlsError> {
        Ok(ExternalCommitBuilder::new(
            self.signer()?.clone(),
            self.signing_identity()?.0.clone(),
            self.config.clone(),
        ))
    }

    /// Load an existing group state into this client using the
    /// [GroupStateStorage](crate::GroupStateStorage) that
    /// this client was configured to use.
    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
    #[inline(never)]
    pub async fn load_group(&self, group_id: &[u8]) -> Result<Group<C>, MlsError> {
        let snapshot = self
            .config
            .group_state_storage()
            .state(group_id)
            .await
            .map_err(|e| MlsError::GroupStorageError(e.into_any_error()))?
            .ok_or(MlsError::GroupNotFound)?;

        let snapshot = Snapshot::mls_decode(&mut &*snapshot)?;

        Group::from_snapshot(self.config.clone(), snapshot).await
    }

    /// Request to join an existing [group](crate::group::Group).
    ///
    /// An existing group member will need to perform a
    /// [commit](crate::Group::commit) to complete the add and the resulting
    /// welcome message can be used by [join_group](Client::join_group).
    #[cfg(feature = "by_ref_proposal")]
    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
    pub async fn external_add_proposal(
        &self,
        group_info: &MlsMessage,
        tree_data: Option<crate::group::ExportedTree<'_>>,
        authenticated_data: Vec<u8>,
    ) -> Result<MlsMessage, MlsError> {
        let protocol_version = group_info.version;

        if !self.config.version_supported(protocol_version) && protocol_version == self.version {
            return Err(MlsError::UnsupportedProtocolVersion(protocol_version));
        }

        let group_info = group_info
            .as_group_info()
            .ok_or(MlsError::UnexpectedMessageType)?;

        let cipher_suite = group_info.group_context.cipher_suite;

        let cipher_suite_provider = self
            .config
            .crypto_provider()
            .cipher_suite_provider(cipher_suite)
            .ok_or(MlsError::UnsupportedCipherSuite(cipher_suite))?;

        crate::group::validate_group_info_joiner(
            protocol_version,
            group_info,
            tree_data,
            &self.config.identity_provider(),
            &cipher_suite_provider,
        )
        .await?;

        let key_package = self.generate_key_package().await?.key_package;

        (key_package.cipher_suite == cipher_suite)
            .then_some(())
            .ok_or(MlsError::UnsupportedCipherSuite(cipher_suite))?;

        let message = AuthenticatedContent::new_signed(
            &cipher_suite_provider,
            &group_info.group_context,
            Sender::NewMemberProposal,
            Content::Proposal(Box::new(Proposal::Add(Box::new(AddProposal {
                key_package,
            })))),
            self.signer()?,
            WireFormat::PublicMessage,
            authenticated_data,
        )
        .await?;

        let plaintext = PublicMessage {
            content: message.content,
            auth: message.auth,
            membership_tag: None,
        };

        Ok(MlsMessage {
            version: protocol_version,
            payload: MlsMessagePayload::Plain(plaintext),
        })
    }

    fn signer(&self) -> Result<&SignatureSecretKey, MlsError> {
        self.signer.as_ref().ok_or(MlsError::SignerNotFound)
    }

    #[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::safer_ffi_gen_ignore)]
    pub fn signing_identity(&self) -> Result<(&SigningIdentity, CipherSuite), MlsError> {
        self.signing_identity
            .as_ref()
            .map(|(id, cs)| (id, *cs))
            .ok_or(MlsError::SignerNotFound)
    }

    /// Returns key package extensions used by this client
    pub fn key_package_extensions(&self) -> ExtensionList {
        self.config.key_package_extensions()
    }

    /// The [KeyPackageStorage] that this client was configured to use.
    #[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::safer_ffi_gen_ignore)]
    pub fn key_package_store(&self) -> <C as ClientConfig>::KeyPackageRepository {
        self.config.key_package_repo()
    }

    /// The [PreSharedKeyStorage](crate::PreSharedKeyStorage) that
    /// this client was configured to use.
    #[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::safer_ffi_gen_ignore)]
    pub fn secret_store(&self) -> <C as ClientConfig>::PskStore {
        self.config.secret_store()
    }

    /// The [GroupStateStorage] that this client was configured to use.
    #[cfg_attr(all(feature = "ffi", not(test)), safer_ffi_gen::safer_ffi_gen_ignore)]
    pub fn group_state_storage(&self) -> <C as ClientConfig>::GroupStateStorage {
        self.config.group_state_storage()
    }
}

#[cfg(test)]
pub(crate) mod test_utils {
    use super::*;
    use crate::identity::test_utils::get_test_signing_identity;

    pub use crate::client_builder::test_utils::{TestClientBuilder, TestClientConfig};

    pub const TEST_PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion::MLS_10;
    pub const TEST_CIPHER_SUITE: CipherSuite = CipherSuite::P256_AES128;
    pub const TEST_CUSTOM_PROPOSAL_TYPE: ProposalType = ProposalType::new(65001);

    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
    pub async fn test_client_with_key_pkg(
        protocol_version: ProtocolVersion,
        cipher_suite: CipherSuite,
        identity: &str,
    ) -> (Client<TestClientConfig>, MlsMessage) {
        test_client_with_key_pkg_custom(protocol_version, cipher_suite, identity, |_| {}).await
    }

    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
    pub async fn test_client_with_key_pkg_custom<F>(
        protocol_version: ProtocolVersion,
        cipher_suite: CipherSuite,
        identity: &str,
        mut config: F,
    ) -> (Client<TestClientConfig>, MlsMessage)
    where
        F: FnMut(&mut TestClientConfig),
    {
        let (identity, secret_key) =
            get_test_signing_identity(cipher_suite, identity.as_bytes()).await;

        let mut client = TestClientBuilder::new_for_test()
            .used_protocol_version(protocol_version)
            .signing_identity(identity.clone(), secret_key, cipher_suite)
            .build();

        config(&mut client.config);

        let key_package = client.generate_key_package_message().await.unwrap();

        (client, key_package)
    }
}

#[cfg(test)]
mod tests {
    use super::test_utils::*;

    use super::*;
    use crate::{
        crypto::test_utils::TestCryptoProvider,
        identity::test_utils::{get_test_basic_credential, get_test_signing_identity},
        tree_kem::leaf_node::LeafNodeSource,
    };
    use assert_matches::assert_matches;

    use crate::{
        group::{
            message_processor::ProposalMessageDescription,
            proposal::Proposal,
            test_utils::{test_group, test_group_custom_config},
            ReceivedMessage,
        },
        psk::{ExternalPskId, PreSharedKey},
    };

    use alloc::vec;

    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn test_keygen() {
        // This is meant to test the inputs to the internal key package generator
        // See KeyPackageGenerator tests for key generation specific tests
        for (protocol_version, cipher_suite) in ProtocolVersion::all().flat_map(|p| {
            TestCryptoProvider::all_supported_cipher_suites()
                .into_iter()
                .map(move |cs| (p, cs))
        }) {
            let (identity, secret_key) = get_test_signing_identity(cipher_suite, b"foo").await;

            let client = TestClientBuilder::new_for_test()
                .signing_identity(identity.clone(), secret_key, cipher_suite)
                .build();

            // TODO: Tests around extensions
            let key_package = client.generate_key_package_message().await.unwrap();

            assert_eq!(key_package.version, protocol_version);

            let key_package = key_package.into_key_package().unwrap();

            assert_eq!(key_package.cipher_suite, cipher_suite);

            assert_eq!(
                &key_package.leaf_node.signing_identity.credential,
                &get_test_basic_credential(b"foo".to_vec())
            );

            assert_eq!(key_package.leaf_node.signing_identity, identity);

            let capabilities = key_package.leaf_node.ungreased_capabilities();
            assert_eq!(capabilities, client.config.capabilities());

            let client_lifetime = client.config.lifetime();
            assert_matches!(key_package.leaf_node.leaf_node_source, LeafNodeSource::KeyPackage(lifetime) if (lifetime.not_after - lifetime.not_before) == (client_lifetime.not_after - client_lifetime.not_before));
        }
    }

    #[cfg(feature = "by_ref_proposal")]
    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn new_member_add_proposal_adds_to_group() {
        let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;

        let (bob_identity, secret_key) = get_test_signing_identity(TEST_CIPHER_SUITE, b"bob").await;

        let bob = TestClientBuilder::new_for_test()
            .signing_identity(bob_identity.clone(), secret_key, TEST_CIPHER_SUITE)
            .build();

        let proposal = bob
            .external_add_proposal(
                &alice_group.group.group_info_message(true).await.unwrap(),
                None,
                vec![],
            )
            .await
            .unwrap();

        let message = alice_group
            .group
            .process_incoming_message(proposal)
            .await
            .unwrap();

        assert_matches!(
            message,
            ReceivedMessage::Proposal(ProposalMessageDescription {
                proposal: Proposal::Add(p), ..}
            ) if p.key_package.leaf_node.signing_identity == bob_identity
        );

        alice_group.group.commit(vec![]).await.unwrap();
        alice_group.group.apply_pending_commit().await.unwrap();

        // Check that the new member is in the group
        assert!(alice_group
            .group
            .roster()
            .members_iter()
            .any(|member| member.signing_identity == bob_identity))
    }

    #[cfg(feature = "psk")]
    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
    async fn join_via_external_commit(do_remove: bool, with_psk: bool) -> Result<(), MlsError> {
        // An external commit cannot be the first commit in a group as it requires
        // interim_transcript_hash to be computed from the confirmed_transcript_hash and
        // confirmation_tag, which is not the case for the initial interim_transcript_hash.

        let psk = PreSharedKey::from(b"psk".to_vec());
        let psk_id = ExternalPskId::new(b"psk id".to_vec());

        let mut alice_group =
            test_group_custom_config(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, |c| {
                c.psk(psk_id.clone(), psk.clone())
            })
            .await;

        let (mut bob_group, _) = alice_group
            .join_with_custom_config("bob", false, |c| {
                c.0.psk_store.insert(psk_id.clone(), psk.clone());
            })
            .await
            .unwrap();

        let group_info_msg = alice_group
            .group
            .group_info_message_allowing_ext_commit(true)
            .await
            .unwrap();

        let new_client_id = if do_remove { "bob" } else { "charlie" };

        let (new_client_identity, secret_key) =
            get_test_signing_identity(TEST_CIPHER_SUITE, new_client_id.as_bytes()).await;

        let new_client = TestClientBuilder::new_for_test()
            .psk(psk_id.clone(), psk)
            .signing_identity(new_client_identity.clone(), secret_key, TEST_CIPHER_SUITE)
            .build();

        let mut builder = new_client.external_commit_builder().unwrap();

        if do_remove {
            builder = builder.with_removal(1);
        }

        if with_psk {
            builder = builder.with_external_psk(psk_id);
        }

        let (new_group, external_commit) = builder.build(group_info_msg).await?;

        let num_members = if do_remove { 2 } else { 3 };

        assert_eq!(new_group.roster().members_iter().count(), num_members);

        let _ = alice_group
            .group
            .process_incoming_message(external_commit.clone())
            .await
            .unwrap();

        let bob_current_epoch = bob_group.group.current_epoch();

        let message = bob_group
            .group
            .process_incoming_message(external_commit)
            .await
            .unwrap();

        assert!(alice_group.group.roster().members_iter().count() == num_members);

        if !do_remove {
            assert!(bob_group.group.roster().members_iter().count() == num_members);
        } else {
            // Bob was removed so his epoch must stay the same
            assert_eq!(bob_group.group.current_epoch(), bob_current_epoch);

            #[cfg(feature = "state_update")]
            assert_matches!(message, ReceivedMessage::Commit(desc) if !desc.state_update.active);

            #[cfg(not(feature = "state_update"))]
            assert_matches!(message, ReceivedMessage::Commit(_));
        }

        // Comparing epoch authenticators is sufficient to check that members are in sync.
        assert_eq!(
            alice_group.group.epoch_authenticator().unwrap(),
            new_group.epoch_authenticator().unwrap()
        );

        Ok(())
    }

    #[cfg(feature = "psk")]
    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn test_external_commit() {
        // New member can join
        join_via_external_commit(false, false).await.unwrap();
        // New member can remove an old copy of themselves
        join_via_external_commit(true, false).await.unwrap();
        // New member can inject a PSK
        join_via_external_commit(false, true).await.unwrap();
        // All works together
        join_via_external_commit(true, true).await.unwrap();
    }

    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn creating_an_external_commit_requires_a_group_info_message() {
        let (alice_identity, secret_key) =
            get_test_signing_identity(TEST_CIPHER_SUITE, b"alice").await;

        let alice = TestClientBuilder::new_for_test()
            .signing_identity(alice_identity.clone(), secret_key, TEST_CIPHER_SUITE)
            .build();

        let msg = alice.generate_key_package_message().await.unwrap();
        let res = alice.commit_external(msg).await.map(|_| ());

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

    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
    async fn external_commit_with_invalid_group_info_fails() {
        let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
        let mut bob_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;

        bob_group.group.commit(vec![]).await.unwrap();
        bob_group.group.apply_pending_commit().await.unwrap();

        let group_info_msg = bob_group
            .group
            .group_info_message_allowing_ext_commit(true)
            .await
            .unwrap();

        let (carol_identity, secret_key) =
            get_test_signing_identity(TEST_CIPHER_SUITE, b"carol").await;

        let carol = TestClientBuilder::new_for_test()
            .signing_identity(carol_identity, secret_key, TEST_CIPHER_SUITE)
            .build();

        let (_, external_commit) = carol
            .external_commit_builder()
            .unwrap()
            .build(group_info_msg)
            .await
            .unwrap();

        // If Carol tries to join Alice's group using the group info from Bob's group, that fails.
        let res = alice_group
            .group
            .process_incoming_message(external_commit)
            .await;
        assert_matches!(res, Err(_));
    }

    #[test]
    fn builder_can_be_obtained_from_client_to_edit_properties_for_new_client() {
        let alice = TestClientBuilder::new_for_test()
            .extension_type(33.into())
            .build();
        let bob = alice.to_builder().extension_type(34.into()).build();
        assert_eq!(bob.config.supported_extensions(), [33, 34].map(Into::into));
    }
}

[ Dauer der Verarbeitung: 0.31 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge