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


Quelle  lib.rs   Sprache: unbekannt

 
// Copyright (c) 2024 Mozilla Corporation and contributors.
// SPDX-License-Identifier: (Apache-2.0 OR MIT)

mod state;

use mls_rs::error::{AnyError, IntoAnyError};
use mls_rs::group::proposal::{CustomProposal, ProposalType};
use mls_rs::group::{Capabilities, ExportedTree, ReceivedMessage};
use mls_rs::identity::SigningIdentity;
use mls_rs::mls_rs_codec::{MlsDecode, MlsEncode};
use mls_rs::{CipherSuiteProvider, CryptoProvider, Extension, ExtensionList};

use serde::de::{self, MapAccess, Visitor};
use serde::ser::SerializeStruct;
use serde::{Deserialize, Deserializer, Serialize, Serializer};

pub use state::{PlatformState, TemporaryState};
use std::fmt;

pub type DefaultCryptoProvider = mls_rs_crypto_nss::NssCryptoProvider;
pub type DefaultIdentityProvider = mls_rs::identity::basic::BasicIdentityProvider;

// Re-export the mls_rs types
pub use mls_rs::CipherSuite;
pub use mls_rs::MlsMessage;
pub use mls_rs::ProtocolVersion;

// Define new types
pub type GroupState = Vec<u8>;

pub type MlsGroupId = Vec<u8>;
pub type MlsGroupIdArg<'a> = &'a [u8];

pub type MlsGroupEpoch = u64;

pub type MlsCredential = Vec<u8>;
pub type MlsCredentialArg<'a> = &'a [u8];

pub type Identity = Vec<u8>;
pub type IdentityArg<'a> = &'a [u8];

#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum MessageOrAck {
    Ack(MlsGroupId),
    MlsMessage(MlsMessage),
}

///
/// Errors
///
#[derive(Debug, thiserror::Error)]
pub enum PlatformError {
    #[error("CoreError")]
    CoreError,
    #[error(transparent)]
    LibraryError(#[from] mls_rs::error::MlsError),
    #[error("InternalError")]
    InternalError,
    #[error("IdentityError")]
    IdentityError(AnyError),
    #[error("CryptoError")]
    CryptoError(AnyError),
    #[error("UnsupportedCiphersuite")]
    UnsupportedCiphersuite,
    #[error("UnsupportedGroupConfig")]
    UnsupportedGroupConfig,
    #[error("UnsupportedMessage")]
    UnsupportedMessage,
    #[error("UndefinedIdentity")]
    UndefinedIdentity,
    #[error("StorageError")]
    StorageError(AnyError),
    #[error("UnavailableSecret")]
    UnavailableSecret,
    #[error("MutexError")]
    MutexError,
    #[error("JsonConversionError")]
    JsonConversionError,
    #[error(transparent)]
    CodecError(#[from] mls_rs::mls_rs_codec::Error),
    #[error(transparent)]
    BincodeError(#[from] bincode::Error),
    #[error(transparent)]
    IOError(#[from] std::io::Error),
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct GroupIdEpoch {
    pub group_id: MlsGroupId,
    pub group_epoch: MlsGroupEpoch,
}

///
/// Generate or Retrieve a PlatformState.
///
pub fn state_access(name: &str, key: &[u8; 32]) -> Result<PlatformState, PlatformError> {
    PlatformState::new(name, key)
}

///
/// Delete a PlatformState.
///
pub fn state_delete(name: &str) -> Result<(), PlatformError> {
    PlatformState::delete(name)
}

///
/// Delete a specific group in the PlatformState.
///
pub fn state_delete_group(
    state: &PlatformState,
    gid: MlsGroupIdArg,
    myself: IdentityArg,
) -> Result<GroupIdEpoch, PlatformError> {
    state.delete_group(gid, myself)?;

    // Return the group id and 0xFF..FF epoch to signal the group is closed
    Ok(GroupIdEpoch {
        group_id: gid.to_vec(),
        group_epoch: 0xFFFFFFFFFFFFFFFF,
    })
}

///
/// Configurations
///

// Possibly temporary, allows to add an option to the config without changing every
// call to client() function
#[derive(Clone, Debug, Default)]
pub struct ClientConfig {
    pub key_package_extensions: Option<ExtensionList>,
    pub leaf_node_extensions: Option<ExtensionList>,
    pub leaf_node_capabilities: Option<Capabilities>,
    pub key_package_lifetime_s: Option<u64>,
    pub allow_external_commits: bool,
}

// Assuming GroupConfig is a struct
#[derive(Debug, Clone)]
pub struct GroupConfig {
    pub ciphersuite: CipherSuite,
    pub version: ProtocolVersion,
    pub options: ExtensionList,
}

impl Default for GroupConfig {
    fn default() -> Self {
        GroupConfig {
            // Set default ciphersuite.
            ciphersuite: CipherSuite::CURVE25519_AES128,
            // Set default protocol version.
            version: ProtocolVersion::MLS_10,
            // Set default options.
            options: ExtensionList::new(),
        }
    }
}

///
/// Generate a credential.
///
pub fn mls_generate_credential_basic(content: &[u8]) -> Result<MlsCredential, PlatformError> {
    let credential =
        mls_rs::identity::basic::BasicCredential::new(content.to_vec()).into_credential();
    let credential_bytes = credential.mls_encode_to_vec()?;
    Ok(credential_bytes)
}

///
/// Generate a Signature Keypair
///
pub fn mls_generate_signature_keypair(
    state: &PlatformState,
    cs: CipherSuite,
    // _randomness: Option<Vec<u8>>,
) -> Result<Vec<u8>, PlatformError> {
    let crypto_provider = DefaultCryptoProvider::default();
    let cipher_suite = crypto_provider
        .cipher_suite_provider(cs)
        .ok_or(PlatformError::UnsupportedCiphersuite)?;

    // Generate a signature key pair.
    let (signature_key, signature_pubkey) = cipher_suite
        .signature_key_generate()
        .map_err(|_| PlatformError::UnsupportedCiphersuite)?;

    let cipher_suite_provider = crypto_provider
        .cipher_suite_provider(cs)
        .ok_or(PlatformError::UnsupportedCiphersuite)?;

    let identifier = cipher_suite_provider
        .hash(&signature_pubkey)
        .map_err(|e| PlatformError::CryptoError(e.into_any_error()))?;

    // Store the signature key pair.
    state.insert_sigkey(&signature_key, &signature_pubkey, cs, &identifier)?;

    Ok(identifier)
}

///
/// Generate a KeyPackage.
///
pub fn mls_generate_key_package(
    state: &PlatformState,
    myself: IdentityArg,
    credential: MlsCredentialArg,
    config: &ClientConfig,
    // _randomness: Option<Vec<u8>>,
) -> Result<MlsMessage, PlatformError> {
    // Decode the Credential
    let mut credential_slice: &[u8] = credential;
    let decoded_cred = mls_rs::identity::Credential::mls_decode(&mut credential_slice)?;

    // Create a client for that state
    let client = state.client(myself, Some(decoded_cred), ProtocolVersion::MLS_10, config)?;

    // Generate a KeyPackage from that client_default
    let key_package = client.generate_key_package_message()?;

    // Result
    Ok(key_package)
}

///
/// Get group members.
///

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct ClientIdentifiers {
    pub identity: Identity,
    pub credential: MlsCredential,
    // TODO: identities: Vec<(Identity, Credential, ExtensionList, Capabilities)>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct GroupMembers {
    pub group_id: MlsGroupId,
    pub group_epoch: u64,
    pub group_members: Vec<ClientIdentifiers>,
}

// Note: The identity is needed because it is allowed to have multiple
//       identities in a group.
pub fn mls_group_members(
    state: &PlatformState,
    gid: MlsGroupIdArg,
    myself: IdentityArg,
) -> Result<GroupMembers, PlatformError> {
    let crypto_provider = DefaultCryptoProvider::default();

    let group = state.client_default(myself)?.load_group(gid)?;
    let epoch = group.current_epoch();

    let cipher_suite_provider = crypto_provider
        .cipher_suite_provider(group.cipher_suite())
        .ok_or(PlatformError::UnsupportedCiphersuite)?;

    // Return Vec<(Identity, Credential)>
    let members = group
        .roster()
        .member_identities_iter()
        .map(|identity| {
            Ok(ClientIdentifiers {
                identity: cipher_suite_provider
                    .hash(&identity.signature_key)
                    .map_err(|e| PlatformError::CryptoError(e.into_any_error()))?,
                credential: identity.credential.mls_encode_to_vec()?,
            })
        })
        .collect::<Result<Vec<_>, PlatformError>>()?;

    let members = GroupMembers {
        group_id: gid.to_vec(),
        group_epoch: epoch,
        group_members: members,
    };

    Ok(members)
}

///
/// Group management: Create a Group
///

// Note: We internally set the protocol version to avoid issues with compat
pub fn mls_group_create(
    pstate: &mut PlatformState,
    myself: IdentityArg,
    credential: MlsCredentialArg,
    gid: Option<MlsGroupIdArg>,
    group_context_extensions: Option<ExtensionList>,
    config: &ClientConfig,
) -> Result<GroupIdEpoch, PlatformError> {
    // Build the client
    let mut credential_slice: &[u8] = credential;
    let decoded_cred = mls_rs::identity::Credential::mls_decode(&mut credential_slice)?;

    let client = pstate.client(myself, Some(decoded_cred), ProtocolVersion::MLS_10, config)?;

    // Generate a GroupId if none is provided
    let mut group = match gid {
        Some(gid) => client.create_group_with_id(
            gid.to_vec(),
            group_context_extensions.unwrap_or_default().clone(),
        )?,
        None => client.create_group(group_context_extensions.unwrap_or_default().clone())?,
    };

    // The state needs to be returned or stored somewhere
    group.write_to_storage()?;
    let gid = group.group_id().to_vec();
    let epoch = group.current_epoch();

    // Return
    Ok(GroupIdEpoch {
        group_id: gid,
        group_epoch: epoch,
    })
}

///
/// Group management: Adding a user.
///

#[derive(Clone, Debug, PartialEq)]
pub struct MlsCommitOutput {
    pub commit: MlsMessage,
    pub welcome: Vec<MlsMessage>,
    pub group_info: Option<MlsMessage>,
    pub ratchet_tree: Option<Vec<u8>>,
    // pub unused_proposals: Vec<crate::mls_rules::ProposalInfo<Proposal>>, from mls_rs
    pub identity: Option<Identity>,
}

impl Serialize for MlsCommitOutput {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut state = serializer.serialize_struct("MlsCommitOutput", 4)?;

        // Handle serialization for `commit`
        let commit_bytes = self
            .commit
            .mls_encode_to_vec()
            .map_err(serde::ser::Error::custom)?;
        state.serialize_field("commit", &commit_bytes)?;

        // Handle serialization for `welcome`. Collect into a Result to handle potential errors.
        let welcome_bytes: Result<Vec<_>, _> = self
            .welcome
            .iter()
            .map(|msg| msg.mls_encode_to_vec().map_err(serde::ser::Error::custom))
            .collect();
        // Unwrap the Result here, after all potential errors have been handled.
        state.serialize_field("welcome", &welcome_bytes?)?;

        // Handle serialization for `group_info`
        let group_info_bytes = match self.group_info.as_ref().map(|gi| gi.mls_encode_to_vec()) {
            Some(Ok(bytes)) => Some(bytes),
            Some(Err(e)) => return Err(serde::ser::Error::custom(e)),
            None => None,
        };
        state.serialize_field("group_info", &group_info_bytes)?;

        // Directly serialize `ratchet_tree` as it is already an Option<Vec<u8>>
        state.serialize_field("ratchet_tree", &self.ratchet_tree)?;

        state.end()
    }
}

impl<'de> Deserialize<'de> for MlsCommitOutput {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct MlsCommitOutputVisitor;

        impl<'de> Visitor<'de> for MlsCommitOutputVisitor {
            type Value = MlsCommitOutput;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("struct MlsCommitOutput")
            }

            fn visit_map<V>(self, mut map: V) -> Result<MlsCommitOutput, V::Error>
            where
                V: MapAccess<'de>,
            {
                let mut commit = None;
                let mut welcome = None;
                let mut group_info = None;
                let mut ratchet_tree = None;

                while let Some(key) = map.next_key::<String>()? {
                    match key.as_str() {
                        "commit" => {
                            let value: Vec<u8> = map.next_value()?;
                            commit = Some(
                                MlsMessage::mls_decode(&mut &value[..])
                                    .map_err(de::Error::custom)?,
                            );
                        }
                        "welcome" => {
                            let values: Vec<Vec<u8>> = map.next_value()?;
                            welcome = Some(
                                values
                                    .into_iter()
                                    .map(|v| {
                                        MlsMessage::mls_decode(&mut &v[..])
                                            .map_err(de::Error::custom)
                                    })
                                    .collect::<Result<_, _>>()?,
                            );
                        }
                        "group_info" => {
                            if let Some(value) = map.next_value::<Option<Vec<u8>>>()? {
                                group_info = Some(
                                    MlsMessage::mls_decode(&mut &value[..])
                                        .map_err(de::Error::custom)?,
                                );
                            }
                        }
                        "ratchet_tree" => {
                            ratchet_tree = map.next_value()?;
                        }
                        _ => { /* Ignore unknown fields */ }
                    }
                }

                Ok(MlsCommitOutput {
                    commit: commit.ok_or_else(|| de::Error::missing_field("commit"))?,
                    welcome: welcome.ok_or_else(|| de::Error::missing_field("welcome"))?,
                    group_info,
                    ratchet_tree,
                    identity: None,
                })
            }
        }

        const FIELDS: &[&str] = &["commit", "welcome", "group_info", "ratchet_tree"];
        deserializer.deserialize_struct("MlsCommitOutput", FIELDS, MlsCommitOutputVisitor)
    }
}

pub fn mls_group_add(
    pstate: &mut PlatformState,
    gid: MlsGroupIdArg,
    myself: IdentityArg,
    new_members: Vec<MlsMessage>,
) -> Result<MlsCommitOutput, PlatformError> {
    // Get the group from the state
    let client = pstate.client_default(myself)?;
    let mut group = client.load_group(gid)?;

    let commit_output = new_members
        .into_iter()
        .try_fold(group.commit_builder(), |commit_builder, user| {
            commit_builder.add_member(user)
        })?
        .build()?;

    // We use the default mode which returns only one welcome message
    let welcomes = commit_output.welcome_messages; //.remove(0);

    let commit_output = MlsCommitOutput {
        commit: commit_output.commit_message.clone(),
        welcome: welcomes,
        group_info: commit_output.external_commit_group_info,
        ratchet_tree: None, // TODO: Handle this !
        identity: None,
    };

    // Write the group to the storage
    group.write_to_storage()?;

    Ok(commit_output)
}

pub fn mls_group_propose_add(
    pstate: &mut PlatformState,
    gid: MlsGroupIdArg,
    myself: IdentityArg,
    new_member: MlsMessage,
) -> Result<MlsMessage, PlatformError> {
    let client = pstate.client_default(myself)?;
    let mut group = client.load_group(gid)?;

    let proposal = group.propose_add(new_member, vec![])?;
    group.write_to_storage()?;

    Ok(proposal.clone())
}

// Variant 1: Vec<MlsMessage>
// pub fn mls_group_propose_add(
//     pstate: &mut PlatformState,
//     gid: &MlsGroupId,
//     myself: &Identity,
//     new_members: Vec<MlsMessage>,
// ) -> Result<MlsMessage, PlatformError> {
//     let client = pstate.client_default(myself)?;
//     let mut group = client.load_group(gid)?;

//     let proposals: Result<Vec<_>, _> = new_members
//         .into_iter()
//         .map(|member| group.propose_add(member, vec![]))
//         .collect();
//     let proposals = proposals?;

//     let proposal = proposals.first().unwrap();
//     group.write_to_storage()?;

//     Ok(proposal.clone())
// }

///
/// Group management: Removing a user.
///
pub fn mls_group_remove(
    pstate: &PlatformState,
    gid: MlsGroupIdArg,
    myself: IdentityArg,
    removed: IdentityArg, // TODO: Make this Vec<Identities>?
) -> Result<MlsCommitOutput, PlatformError> {
    let mut group = pstate.client_default(myself)?.load_group(gid)?;

    let crypto_provider = DefaultCryptoProvider::default();

    let cipher_suite_provider = crypto_provider
        .cipher_suite_provider(group.cipher_suite())
        .ok_or(PlatformError::UnsupportedCiphersuite)?;

    let removed = group
        .roster()
        .members_iter()
        .find_map(|m| {
            let h = cipher_suite_provider
                .hash(&m.signing_identity.signature_key)
                .ok()?;
            (h == *removed).then_some(m.index)
        })
        .ok_or(PlatformError::UndefinedIdentity)?;
    // Handle separate error message for inability to remove yourself

    let commit = group.commit_builder().remove_member(removed)?.build()?;

    // Write the group to the storage
    group.write_to_storage()?;

    let commit_output = MlsCommitOutput {
        commit: commit.commit_message,
        welcome: commit.welcome_messages,
        group_info: commit.external_commit_group_info,
        ratchet_tree: commit
            .ratchet_tree
            .map(|tree| tree.to_bytes())
            .transpose()?,
        identity: None,
    };

    Ok(commit_output)
}

pub fn mls_group_propose_remove(
    pstate: &PlatformState,
    gid: MlsGroupIdArg,
    myself: IdentityArg,
    removed: IdentityArg, // TODO: Make this Vec<Identities>?
) -> Result<MlsMessage, PlatformError> {
    let mut group = pstate.client_default(myself)?.load_group(gid)?;

    let crypto_provider = DefaultCryptoProvider::default();

    let cipher_suite_provider = crypto_provider
        .cipher_suite_provider(group.cipher_suite())
        .ok_or(PlatformError::UnsupportedCiphersuite)?;

    let removed = group
        .roster()
        .members_iter()
        .find_map(|m| {
            let h = cipher_suite_provider
                .hash(&m.signing_identity.signature_key)
                .ok()?;
            (h == *removed).then_some(m.index)
        })
        .ok_or(PlatformError::UndefinedIdentity)?;

    let proposal = group.propose_remove(removed, vec![])?;

    // Remember the proposal
    group.write_to_storage()?;

    Ok(proposal)
}

///
/// Key updates
///

/// TODO: Possibly add a random nonce as an optional parameter.
pub fn mls_group_update(
    pstate: &mut PlatformState,
    gid: MlsGroupIdArg,
    myself: IdentityArg,
    signature_key: Option<&[u8]>,
    credential: Option<MlsCredentialArg>,
    group_context_extensions: Option<ExtensionList>,
    config: &ClientConfig,
) -> Result<MlsCommitOutput, PlatformError> {
    let crypto_provider = DefaultCryptoProvider::default();

    // Propose + Commit
    let decoded_cred = credential
        .as_ref()
        .map(|credential| {
            let mut credential_slice: &[u8] = credential;
            mls_rs::identity::Credential::mls_decode(&mut credential_slice)
        })
        .transpose()?;

    let client = pstate.client(myself, decoded_cred, ProtocolVersion::MLS_10, config)?;
    let mut group = client.load_group(gid)?;

    let cipher_suite_provider = crypto_provider
        .cipher_suite_provider(group.cipher_suite())
        .ok_or(PlatformError::UnsupportedCiphersuite)?;

    let mut commit_builder = group.commit_builder();

    if let Some(group_context_extensions) = group_context_extensions {
        commit_builder = commit_builder.set_group_context_ext(group_context_extensions)?;
    }

    let identity = if let Some((key, cred)) = signature_key.zip(credential) {
        let signature_secret_key = key.to_vec().into();
        let signature_public_key = cipher_suite_provider
            .signature_key_derive_public(&signature_secret_key)
            .map_err(|e| PlatformError::CryptoError(e.into_any_error()))?;

        let mut credential_slice: &[u8] = cred;
        let decoded_cred = mls_rs::identity::Credential::mls_decode(&mut credential_slice)?;
        let signing_identity = SigningIdentity::new(decoded_cred, signature_public_key);

        // Return the identity
        cipher_suite_provider
            .hash(&signing_identity.signature_key)
            .map_err(|e| PlatformError::CryptoError(e.into_any_error()))?
    } else {
        myself.to_vec().into()
    };

    let commit = commit_builder.build()?;

    group.write_to_storage()?;

    let commit_output = MlsCommitOutput {
        commit: commit.commit_message,
        welcome: commit.welcome_messages,
        group_info: commit.external_commit_group_info,
        ratchet_tree: commit
            .ratchet_tree
            .map(|tree| tree.to_bytes())
            .transpose()?,
        identity: Some(identity),
    };
    // Generate the signature keypair
    // Return the signing Identity
    // Hash the signingIdentity to get the Identifier

    Ok(commit_output)
}

///
/// Process Welcome message.
///

pub fn mls_group_join(
    pstate: &PlatformState,
    myself: IdentityArg,
    welcome: &MlsMessage,
    ratchet_tree: Option<ExportedTree<'static>>,
) -> Result<GroupIdEpoch, PlatformError> {
    let client = pstate.client_default(myself)?;
    let (mut group, _info) = client.join_group(ratchet_tree, welcome)?;
    let gid = group.group_id().to_vec();
    let epoch = group.current_epoch();

    // Store the state
    group.write_to_storage()?;

    // Return the group identifier
    Ok(GroupIdEpoch {
        group_id: gid,
        group_epoch: epoch,
    })
}

///
/// Close a group by removing all members.
///

// TODO: Define a custom proposal instead.
pub fn mls_group_close(
    pstate: &PlatformState,
    gid: MlsGroupIdArg,
    myself: IdentityArg,
) -> Result<MlsCommitOutput, PlatformError> {
    // Remove everyone from the group.
    let mut group = pstate.client_default(myself)?.load_group(gid)?;
    let self_index = group.current_member_index();

    let all_but_me = group
        .roster()
        .members_iter()
        .filter_map(|m| (m.index != self_index).then_some(m.index))
        .collect::<Vec<_>>();

    let commit_output = all_but_me
        .into_iter()
        .try_fold(group.commit_builder(), |builder, index| {
            builder.remove_member(index)
        })?
        .build()?;

    let commit_output = MlsCommitOutput {
        commit: commit_output.commit_message.clone(),
        welcome: vec![],
        group_info: commit_output.external_commit_group_info,
        ratchet_tree: None, // TODO: Handle this !
        identity: None,
    };
    // TODO we should delete state when we receive an ACK. but it's not super clear how to
    // determine on receive that this was a "close" commit. Would be easier if we had a custom
    // proposal

    // Write the group to the storage
    group.write_to_storage()?;

    Ok(commit_output)
}

///
/// Receive a message
///

#[derive(Clone, Debug, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum Received {
    None,
    ApplicationMessage(Vec<u8>),
    GroupIdEpoch(GroupIdEpoch),
    CommitOutput(MlsCommitOutput),
}

pub fn mls_receive(
    pstate: &PlatformState,
    myself: IdentityArg,
    message_or_ack: &MessageOrAck,
) -> Result<(Vec<u8>, Received), PlatformError> {
    // Extract the gid from the Message
    let gid = match &message_or_ack {
        MessageOrAck::Ack(gid) => gid,
        MessageOrAck::MlsMessage(message) => match message.group_id() {
            Some(gid) => gid,
            None => return Err(PlatformError::UnsupportedMessage),
        },
    };

    let mut group = pstate.client_default(myself)?.load_group(gid)?;

    let received_message = match &message_or_ack {
        MessageOrAck::Ack(_) => group.apply_pending_commit().map(ReceivedMessage::Commit),
        MessageOrAck::MlsMessage(message) => group.process_incoming_message(message.clone()),
    };

    //
    let result = match received_message? {
        ReceivedMessage::ApplicationMessage(app_data_description) => Ok((
            gid.to_vec(),
            Received::ApplicationMessage(app_data_description.data().to_vec()),
        )),
        ReceivedMessage::Proposal(_proposal) => {
            // TODO: We inconditionally return the commit for the received proposal
            let commit = group.commit(vec![])?;

            group.write_to_storage()?;

            let commit_output = MlsCommitOutput {
                commit: commit.commit_message,
                welcome: commit.welcome_messages,
                group_info: commit.external_commit_group_info,
                ratchet_tree: commit
                    .ratchet_tree
                    .map(|tree| tree.to_bytes())
                    .transpose()?,
                identity: None,
            };
            Ok((gid.to_vec(), Received::CommitOutput(commit_output)))
        }
        ReceivedMessage::Commit(commit) => {
            // Check if the group is active or not after applying the commit
            if !commit.state_update.is_active() {
                // Delete the group from the state of the client
                pstate.delete_group(gid, myself)?;

                // Return the group id and 0xFF..FF epoch to signal the group is closed
                let group_epoch = GroupIdEpoch {
                    group_id: group.group_id().to_vec(),
                    group_epoch: 0xFFFFFFFFFFFFFFFF,
                };

                Ok((gid.to_vec(), Received::GroupIdEpoch(group_epoch)))
            } else {
                // TODO: Receiving a group_close commit means the sender receiving
                // is left alone in the group. We should be able delete group automatically.
                // As of now, the user calling group_close has to delete group manually.

                // If this is a normal commit, return the affected group and new epoch
                let group_epoch = GroupIdEpoch {
                    group_id: group.group_id().to_vec(),
                    group_epoch: group.current_epoch(),
                };

                Ok((gid.to_vec(), Received::GroupIdEpoch(group_epoch)))
            }
        }
        // TODO: We could make this more user friendly by allowing to
        // pass a Welcome message. KeyPackages should be rejected.
        _ => Err(PlatformError::UnsupportedMessage),
    }?;

    // Write the state to storage
    group.write_to_storage()?;

    Ok(result)
}

pub fn mls_has_pending_commit(
    pstate: &PlatformState,
    gid: MlsGroupIdArg,
    myself: IdentityArg,
) -> Result<bool, PlatformError> {
    let group = pstate.client_default(myself)?.load_group(gid)?;
    let result = group.has_pending_commit();
    Ok(result)
}

pub fn mls_clear_pending_commit(
    pstate: &PlatformState,
    gid: MlsGroupIdArg,
    myself: IdentityArg,
) -> Result<bool, PlatformError> {
    let mut group = pstate.client_default(myself)?.load_group(gid)?;
    group.clear_pending_commit();
    group.write_to_storage()?;
    Ok(true)
}

pub fn mls_apply_pending_commit(
    pstate: &PlatformState,
    gid: MlsGroupIdArg,
    myself: IdentityArg,
) -> Result<Received, PlatformError> {
    let mut group = pstate.client_default(myself)?.load_group(gid)?;

    let received_message = group.apply_pending_commit().map(ReceivedMessage::Commit);

    // Check if the group is active or not after applying the commit
    let result = match received_message? {
        ReceivedMessage::Commit(commit) => {
            // Check if the group is active or not after applying the commit
            if !commit.state_update.is_active() {
                // Delete the group from the state of the client
                pstate.delete_group(gid, myself)?;

                // Return the group id and 0xFF..FF epoch to signal the group is closed
                let group_epoch = GroupIdEpoch {
                    group_id: group.group_id().to_vec(),
                    group_epoch: 0xFFFFFFFFFFFFFFFF,
                };

                Ok(Received::GroupIdEpoch(group_epoch))
            } else {
                // TODO: Receiving a group_close commit means the sender receiving
                // is left alone in the group. We should be able delete group automatically.
                // As of now, the user calling group_close has to delete group manually.

                // If this is a normal commit, return the affected group and new epoch
                let group_epoch = GroupIdEpoch {
                    group_id: group.group_id().to_vec(),
                    group_epoch: group.current_epoch(),
                };

                Ok(Received::GroupIdEpoch(group_epoch))
            }
        }
        _ => Err(PlatformError::UnsupportedMessage),
    }?;

    // Write the state to storage
    group.write_to_storage()?;

    Ok(result)
}

//
// Encrypt a message.
//

pub fn mls_send(
    pstate: &PlatformState,
    gid: MlsGroupIdArg,
    myself: IdentityArg,
    message: &[u8],
) -> Result<MlsMessage, PlatformError> {
    let mut group = pstate.client_default(myself)?.load_group(gid)?;

    let out = group.encrypt_application_message(message, vec![])?;
    group.write_to_storage()?;

    Ok(out)
}

///
/// Propose + Commit a GroupContextExtension
///
pub fn mls_send_group_context_extension(
    pstate: &PlatformState,
    gid: MlsGroupIdArg,
    myself: IdentityArg,
    new_gce: Vec<Extension>,
) -> Result<mls_rs::MlsMessage, PlatformError> {
    let mut group = pstate.client_default(myself)?.load_group(&gid)?;

    let commit = group
        .commit_builder()
        .set_group_context_ext(new_gce.into())?
        .build()?;

    Ok(commit.commit_message)
}

///
/// Create and send a custom proposal.
///
pub fn mls_send_custom_proposal(
    pstate: &PlatformState,
    gid: MlsGroupIdArg,
    myself: IdentityArg,
    proposal_type: ProposalType,
    data: Vec<u8>,
) -> Result<mls_rs::MlsMessage, PlatformError> {
    let mut group = pstate.client_default(myself)?.load_group(&gid)?;
    let custom_proposal = CustomProposal::new(proposal_type, data);
    let proposal = group.propose_custom(custom_proposal, vec![])?;

    Ok(proposal)
}

///
/// Export a group secret.
///

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct ExporterOutput {
    pub group_id: MlsGroupId,
    pub group_epoch: MlsGroupEpoch,
    pub label: Vec<u8>,
    pub context: Vec<u8>,
    pub exporter: Vec<u8>,
}

pub fn mls_derive_exporter(
    pstate: &PlatformState,
    gid: MlsGroupIdArg,
    myself: IdentityArg,
    label: &[u8],
    context: &[u8],
    len: u64,
) -> Result<ExporterOutput, PlatformError> {
    let group = pstate.client_default(myself)?.load_group(gid)?;
    let secret = group
        .export_secret(label, context, len.try_into().unwrap())?
        .to_vec();

    // Construct the output object
    let epoch_and_exporter = ExporterOutput {
        group_id: gid.to_vec(),
        group_epoch: group.current_epoch(),
        label: label.to_vec(),
        context: label.to_vec(),
        exporter: secret,
    };

    Ok(epoch_and_exporter)
}

///
/// Join a group using the external commit mechanism
///

#[derive(Clone, Debug, PartialEq)]
pub struct MlsExternalCommitOutput {
    pub gid: MlsGroupId,
    pub external_commit: MlsMessage,
}

impl Serialize for MlsExternalCommitOutput {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut state = serializer.serialize_struct("MlsExternalCommitOutput", 2)?;
        state.serialize_field("gid", &self.gid)?;

        // Handle serialization for `commit`
        let external_commit_bytes = self
            .external_commit
            .mls_encode_to_vec()
            .map_err(serde::ser::Error::custom)?;

        state.serialize_field("external_commit", &external_commit_bytes)?;

        state.end()
    }
}

impl<'de> Deserialize<'de> for MlsExternalCommitOutput {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct MlsExternalCommitOutputVisitor;

        impl<'de> Visitor<'de> for MlsExternalCommitOutputVisitor {
            type Value = MlsExternalCommitOutput;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("struct MlsExternalCommitOutput")
            }

            fn visit_map<V>(self, mut map: V) -> Result<MlsExternalCommitOutput, V::Error>
            where
                V: MapAccess<'de>,
            {
                let mut gid = None;
                let mut external_commit = None;

                while let Some(key) = map.next_key::<String>()? {
                    match key.as_str() {
                        "external_commit" => {
                            let value: Vec<u8> = map.next_value()?;
                            external_commit = Some(
                                MlsMessage::mls_decode(&mut &value[..])
                                    .map_err(de::Error::custom)?,
                            );
                        }
                        "gid" => gid = Some(map.next_value()?),
                        _ => { /* Ignore unknown fields */ }
                    }
                }

                Ok(MlsExternalCommitOutput {
                    gid: gid.ok_or_else(|| de::Error::missing_field("gid"))?,
                    external_commit: external_commit
                        .ok_or_else(|| de::Error::missing_field("external_commit"))?,
                })
            }
        }

        const FIELDS: &[&str] = &["gid", "external_commit"];
        deserializer.deserialize_struct(
            "MlsExternalCommitOutput",
            FIELDS,
            MlsExternalCommitOutputVisitor,
        )
    }
}

pub fn mls_group_external_commit(
    pstate: &PlatformState,
    myself: IdentityArg,
    credential: MlsCredentialArg,
    group_info: &MlsMessage,
    ratchet_tree: Option<ExportedTree<'static>>,
) -> Result<MlsExternalCommitOutput, PlatformError> {
    // Clone the credential to avoid mutating the original
    let mut credential_slice: &[u8] = credential;

    // Decode the credential
    let decoded_cred = mls_rs::identity::Credential::mls_decode(&mut credential_slice)?;

    let client = pstate.client(
        myself,
        Some(decoded_cred),
        ProtocolVersion::MLS_10,
        &ClientConfig::default(),
    )?;

    let mut commit_builder = client.external_commit_builder()?;

    if let Some(ratchet_tree) = ratchet_tree {
        commit_builder = commit_builder.with_tree_data(ratchet_tree);
    }

    let (mut group, external_commit) = commit_builder.build(group_info.clone())?;
    let gid = group.group_id().to_vec();

    // Store the state
    group.write_to_storage()?;

    // Encode the output
    let gid_and_message = MlsExternalCommitOutput {
        gid,
        external_commit,
    };

    Ok(gid_and_message)
}

///
/// Utility functions
///

pub fn mls_get_group_id(message_or_ack: &MessageOrAck) -> Result<Vec<u8>, PlatformError> {
    // Extract the gid from the Message
    let gid = match &message_or_ack {
        MessageOrAck::Ack(gid) => gid,
        MessageOrAck::MlsMessage(message) => match message.group_id() {
            Some(gid) => gid,
            None => return Err(PlatformError::UnsupportedMessage),
        },
    };

    Ok(gid.to_vec())
}

use serde_json::{Error, Value};

// This function takes a JSON string and converts byte arrays into hex strings.
fn convert_bytes_fields_to_hex(input_str: &str) -> Result<String, Error> {
    // Parse the JSON string into a serde_json::Value
    let mut value: Value = serde_json::from_str(input_str)?;

    // Recursive function to process each element
    fn process_element(element: &mut Value) {
        match element {
            Value::Array(ref mut vec) => {
                if vec
                    .iter()
                    .all(|x| matches!(x, Value::Number(n) if n.is_u64()))
                {
                    // Convert all elements to a Vec<u8> if they are numbers
                    let bytes: Vec<u8> = vec
                        .iter()
                        .filter_map(|x| x.as_u64().map(|n| n as u8))
                        .collect();
                    // Check if the conversion makes sense (the length matches)
                    if bytes.len() == vec.len() {
                        *element = Value::String(hex::encode(bytes));
                    } else {
                        vec.iter_mut().for_each(process_element);
                    }
                } else {
                    vec.iter_mut().for_each(process_element);
                }
            }
            Value::Object(ref mut map) => {
                map.values_mut().for_each(process_element);
            }
            _ => {}
        }
    }
    // Process the element and return the new Json string
    process_element(&mut value);
    serde_json::to_string(&value)
}

// This function accepts bytes, converts them to a string, and then processes the string.
pub fn utils_json_bytes_to_string_custom(input_bytes: &[u8]) -> Result<String, PlatformError> {
    // Convert input bytes to a string
    let input_str =
        std::str::from_utf8(input_bytes).map_err(|_| PlatformError::JsonConversionError)?;

    // Call the original function with the decoded string
    convert_bytes_fields_to_hex(input_str).map_err(|_| PlatformError::JsonConversionError)
}

[ Dauer der Verarbeitung: 0.38 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