Revision control

Copy as Markdown

Other Tools

// 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::group::{proposal_filter::ProposalBundle, Roster};
#[cfg(feature = "private_message")]
use crate::{
group::{padding::PaddingMode, Sender},
WireFormat,
};
use alloc::boxed::Box;
use core::convert::Infallible;
use mls_rs_core::{
error::IntoAnyError, extension::ExtensionList, group::Member, identity::SigningIdentity,
};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum CommitDirection {
Send,
Receive,
}
/// The source of the commit: either a current member or a new member joining
/// via external commit.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CommitSource {
ExistingMember(Member),
NewMember(SigningIdentity),
}
/// Options controlling commit generation
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct CommitOptions {
pub path_required: bool,
pub ratchet_tree_extension: bool,
pub single_welcome_message: bool,
pub allow_external_commit: bool,
}
impl Default for CommitOptions {
fn default() -> Self {
CommitOptions {
path_required: false,
ratchet_tree_extension: true,
single_welcome_message: true,
allow_external_commit: false,
}
}
}
impl CommitOptions {
pub fn new() -> Self {
Self::default()
}
pub fn with_path_required(self, path_required: bool) -> Self {
Self {
path_required,
..self
}
}
pub fn with_ratchet_tree_extension(self, ratchet_tree_extension: bool) -> Self {
Self {
ratchet_tree_extension,
..self
}
}
pub fn with_single_welcome_message(self, single_welcome_message: bool) -> Self {
Self {
single_welcome_message,
..self
}
}
pub fn with_allow_external_commit(self, allow_external_commit: bool) -> Self {
Self {
allow_external_commit,
..self
}
}
}
/// Options controlling encryption of control and application messages
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct EncryptionOptions {
#[cfg(feature = "private_message")]
pub encrypt_control_messages: bool,
#[cfg(feature = "private_message")]
pub padding_mode: PaddingMode,
}
#[cfg(feature = "private_message")]
impl EncryptionOptions {
pub fn new(encrypt_control_messages: bool, padding_mode: PaddingMode) -> Self {
Self {
encrypt_control_messages,
padding_mode,
}
}
pub(crate) fn control_wire_format(&self, sender: Sender) -> WireFormat {
match sender {
Sender::Member(_) if self.encrypt_control_messages => WireFormat::PrivateMessage,
_ => WireFormat::PublicMessage,
}
}
}
/// A set of user controlled rules that customize the behavior of MLS.
#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
#[cfg_attr(mls_build_async, maybe_async::must_be_async)]
pub trait MlsRules: Send + Sync {
type Error: IntoAnyError;
/// This is called when preparing or receiving a commit to pre-process the set of committed
/// proposals.
///
/// Both proposals received during the current epoch and at the time of commit
/// will be presented for validation and filtering. Filter and validate will
/// present a raw list of proposals. Standard MLS rules are applied internally
/// on the result of these rules.
///
/// Each member of a group MUST apply the same proposal rules in order to
/// maintain a working group.
///
/// Typically, any invalid proposal should result in an error. The exception are invalid
/// by-reference proposals processed when _preparing_ a commit, which should be filtered
/// out instead. This is to avoid the deadlock situation when no commit can be generated
/// after receiving an invalid set of proposal messages.
///
/// `ProposalBundle` can be arbitrarily modified. For example, a Remove proposal that
/// removes a moderator can result in adding a GroupContextExtensions proposal that updates
/// the moderator list in the group context. The resulting `ProposalBundle` is validated
/// by the library.
async fn filter_proposals(
&self,
direction: CommitDirection,
source: CommitSource,
current_roster: &Roster,
extension_list: &ExtensionList,
proposals: ProposalBundle,
) -> Result<ProposalBundle, Self::Error>;
/// This is called when preparing a commit to determine various options: whether to enforce an update
/// path in case it is not mandated by MLS, whether to include the ratchet tree in the welcome
/// message (if the commit adds members) and whether to generate a single welcome message, or one
/// welcome message for each added member.
///
/// The `new_roster` and `new_extension_list` describe the group state after the commit.
fn commit_options(
&self,
new_roster: &Roster,
new_extension_list: &ExtensionList,
proposals: &ProposalBundle,
) -> Result<CommitOptions, Self::Error>;
/// This is called when sending any packet. For proposals and commits, this determines whether to
/// encrypt them. For any encrypted packet, this determines the padding mode used.
///
/// Note that for commits, the `current_roster` and `current_extension_list` describe the group state
/// before the commit, unlike in [commit_options](MlsRules::commit_options).
fn encryption_options(
&self,
current_roster: &Roster,
current_extension_list: &ExtensionList,
) -> Result<EncryptionOptions, Self::Error>;
}
macro_rules! delegate_mls_rules {
($implementer:ty) => {
#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
#[cfg_attr(mls_build_async, maybe_async::must_be_async)]
impl<T: MlsRules + ?Sized> MlsRules for $implementer {
type Error = T::Error;
#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
async fn filter_proposals(
&self,
direction: CommitDirection,
source: CommitSource,
current_roster: &Roster,
extension_list: &ExtensionList,
proposals: ProposalBundle,
) -> Result<ProposalBundle, Self::Error> {
(**self)
.filter_proposals(direction, source, current_roster, extension_list, proposals)
.await
}
fn commit_options(
&self,
roster: &Roster,
extension_list: &ExtensionList,
proposals: &ProposalBundle,
) -> Result<CommitOptions, Self::Error> {
(**self).commit_options(roster, extension_list, proposals)
}
fn encryption_options(
&self,
roster: &Roster,
extension_list: &ExtensionList,
) -> Result<EncryptionOptions, Self::Error> {
(**self).encryption_options(roster, extension_list)
}
}
};
}
delegate_mls_rules!(Box<T>);
delegate_mls_rules!(&T);
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
/// Default MLS rules with pass-through proposal filter and customizable options.
pub struct DefaultMlsRules {
pub commit_options: CommitOptions,
pub encryption_options: EncryptionOptions,
}
impl DefaultMlsRules {
/// Create new MLS rules with default settings: do not enforce path and do
/// put the ratchet tree in the extension.
pub fn new() -> Self {
Default::default()
}
/// Set commit options.
pub fn with_commit_options(self, commit_options: CommitOptions) -> Self {
Self {
commit_options,
encryption_options: self.encryption_options,
}
}
/// Set encryption options.
pub fn with_encryption_options(self, encryption_options: EncryptionOptions) -> Self {
Self {
commit_options: self.commit_options,
encryption_options,
}
}
}
#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
#[cfg_attr(mls_build_async, maybe_async::must_be_async)]
impl MlsRules for DefaultMlsRules {
type Error = Infallible;
async fn filter_proposals(
&self,
_direction: CommitDirection,
_source: CommitSource,
_current_roster: &Roster,
_extension_list: &ExtensionList,
proposals: ProposalBundle,
) -> Result<ProposalBundle, Self::Error> {
Ok(proposals)
}
fn commit_options(
&self,
_: &Roster,
_: &ExtensionList,
_: &ProposalBundle,
) -> Result<CommitOptions, Self::Error> {
Ok(self.commit_options)
}
fn encryption_options(
&self,
_: &Roster,
_: &ExtensionList,
) -> Result<EncryptionOptions, Self::Error> {
Ok(self.encryption_options)
}
}