1 // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 // Copyright by contributors to this project. 3 // SPDX-License-Identifier: (Apache-2.0 OR MIT) 4 5 use crate::group::{proposal_filter::ProposalBundle, Roster}; 6 7 #[cfg(feature = "private_message")] 8 use crate::{ 9 group::{padding::PaddingMode, Sender}, 10 WireFormat, 11 }; 12 13 use alloc::boxed::Box; 14 use core::convert::Infallible; 15 use mls_rs_core::{ 16 error::IntoAnyError, extension::ExtensionList, group::Member, identity::SigningIdentity, 17 }; 18 19 #[derive(Copy, Clone, Debug, PartialEq, Eq)] 20 pub enum CommitDirection { 21 Send, 22 Receive, 23 } 24 25 /// The source of the commit: either a current member or a new member joining 26 /// via external commit. 27 #[derive(Clone, Debug, PartialEq, Eq)] 28 pub enum CommitSource { 29 ExistingMember(Member), 30 NewMember(SigningIdentity), 31 } 32 33 /// Options controlling commit generation 34 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 35 #[non_exhaustive] 36 pub struct CommitOptions { 37 pub path_required: bool, 38 pub ratchet_tree_extension: bool, 39 pub single_welcome_message: bool, 40 pub allow_external_commit: bool, 41 } 42 43 impl Default for CommitOptions { default() -> Self44 fn default() -> Self { 45 CommitOptions { 46 path_required: false, 47 ratchet_tree_extension: true, 48 single_welcome_message: true, 49 allow_external_commit: false, 50 } 51 } 52 } 53 54 impl CommitOptions { new() -> Self55 pub fn new() -> Self { 56 Self::default() 57 } 58 with_path_required(self, path_required: bool) -> Self59 pub fn with_path_required(self, path_required: bool) -> Self { 60 Self { 61 path_required, 62 ..self 63 } 64 } 65 with_ratchet_tree_extension(self, ratchet_tree_extension: bool) -> Self66 pub fn with_ratchet_tree_extension(self, ratchet_tree_extension: bool) -> Self { 67 Self { 68 ratchet_tree_extension, 69 ..self 70 } 71 } 72 with_single_welcome_message(self, single_welcome_message: bool) -> Self73 pub fn with_single_welcome_message(self, single_welcome_message: bool) -> Self { 74 Self { 75 single_welcome_message, 76 ..self 77 } 78 } 79 with_allow_external_commit(self, allow_external_commit: bool) -> Self80 pub fn with_allow_external_commit(self, allow_external_commit: bool) -> Self { 81 Self { 82 allow_external_commit, 83 ..self 84 } 85 } 86 } 87 88 /// Options controlling encryption of control and application messages 89 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] 90 #[non_exhaustive] 91 pub struct EncryptionOptions { 92 #[cfg(feature = "private_message")] 93 pub encrypt_control_messages: bool, 94 #[cfg(feature = "private_message")] 95 pub padding_mode: PaddingMode, 96 } 97 98 #[cfg(feature = "private_message")] 99 impl EncryptionOptions { new(encrypt_control_messages: bool, padding_mode: PaddingMode) -> Self100 pub fn new(encrypt_control_messages: bool, padding_mode: PaddingMode) -> Self { 101 Self { 102 encrypt_control_messages, 103 padding_mode, 104 } 105 } 106 control_wire_format(&self, sender: Sender) -> WireFormat107 pub(crate) fn control_wire_format(&self, sender: Sender) -> WireFormat { 108 match sender { 109 Sender::Member(_) if self.encrypt_control_messages => WireFormat::PrivateMessage, 110 _ => WireFormat::PublicMessage, 111 } 112 } 113 } 114 115 /// A set of user controlled rules that customize the behavior of MLS. 116 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] 117 #[cfg_attr(mls_build_async, maybe_async::must_be_async)] 118 pub trait MlsRules: Send + Sync { 119 type Error: IntoAnyError; 120 121 /// This is called when preparing or receiving a commit to pre-process the set of committed 122 /// proposals. 123 /// 124 /// Both proposals received during the current epoch and at the time of commit 125 /// will be presented for validation and filtering. Filter and validate will 126 /// present a raw list of proposals. Standard MLS rules are applied internally 127 /// on the result of these rules. 128 /// 129 /// Each member of a group MUST apply the same proposal rules in order to 130 /// maintain a working group. 131 /// 132 /// Typically, any invalid proposal should result in an error. The exception are invalid 133 /// by-reference proposals processed when _preparing_ a commit, which should be filtered 134 /// out instead. This is to avoid the deadlock situation when no commit can be generated 135 /// after receiving an invalid set of proposal messages. 136 /// 137 /// `ProposalBundle` can be arbitrarily modified. For example, a Remove proposal that 138 /// removes a moderator can result in adding a GroupContextExtensions proposal that updates 139 /// the moderator list in the group context. The resulting `ProposalBundle` is validated 140 /// by the library. filter_proposals( &self, direction: CommitDirection, source: CommitSource, current_roster: &Roster, extension_list: &ExtensionList, proposals: ProposalBundle, ) -> Result<ProposalBundle, Self::Error>141 async fn filter_proposals( 142 &self, 143 direction: CommitDirection, 144 source: CommitSource, 145 current_roster: &Roster, 146 extension_list: &ExtensionList, 147 proposals: ProposalBundle, 148 ) -> Result<ProposalBundle, Self::Error>; 149 150 /// This is called when preparing a commit to determine various options: whether to enforce an update 151 /// path in case it is not mandated by MLS, whether to include the ratchet tree in the welcome 152 /// message (if the commit adds members) and whether to generate a single welcome message, or one 153 /// welcome message for each added member. 154 /// 155 /// The `new_roster` and `new_extension_list` describe the group state after the commit. commit_options( &self, new_roster: &Roster, new_extension_list: &ExtensionList, proposals: &ProposalBundle, ) -> Result<CommitOptions, Self::Error>156 fn commit_options( 157 &self, 158 new_roster: &Roster, 159 new_extension_list: &ExtensionList, 160 proposals: &ProposalBundle, 161 ) -> Result<CommitOptions, Self::Error>; 162 163 /// This is called when sending any packet. For proposals and commits, this determines whether to 164 /// encrypt them. For any encrypted packet, this determines the padding mode used. 165 /// 166 /// Note that for commits, the `current_roster` and `current_extension_list` describe the group state 167 /// before the commit, unlike in [commit_options](MlsRules::commit_options). encryption_options( &self, current_roster: &Roster, current_extension_list: &ExtensionList, ) -> Result<EncryptionOptions, Self::Error>168 fn encryption_options( 169 &self, 170 current_roster: &Roster, 171 current_extension_list: &ExtensionList, 172 ) -> Result<EncryptionOptions, Self::Error>; 173 } 174 175 macro_rules! delegate_mls_rules { 176 ($implementer:ty) => { 177 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] 178 #[cfg_attr(mls_build_async, maybe_async::must_be_async)] 179 impl<T: MlsRules + ?Sized> MlsRules for $implementer { 180 type Error = T::Error; 181 182 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] 183 async fn filter_proposals( 184 &self, 185 direction: CommitDirection, 186 source: CommitSource, 187 current_roster: &Roster, 188 extension_list: &ExtensionList, 189 proposals: ProposalBundle, 190 ) -> Result<ProposalBundle, Self::Error> { 191 (**self) 192 .filter_proposals(direction, source, current_roster, extension_list, proposals) 193 .await 194 } 195 196 fn commit_options( 197 &self, 198 roster: &Roster, 199 extension_list: &ExtensionList, 200 proposals: &ProposalBundle, 201 ) -> Result<CommitOptions, Self::Error> { 202 (**self).commit_options(roster, extension_list, proposals) 203 } 204 205 fn encryption_options( 206 &self, 207 roster: &Roster, 208 extension_list: &ExtensionList, 209 ) -> Result<EncryptionOptions, Self::Error> { 210 (**self).encryption_options(roster, extension_list) 211 } 212 } 213 }; 214 } 215 216 delegate_mls_rules!(Box<T>); 217 delegate_mls_rules!(&T); 218 219 #[derive(Clone, Debug, Default)] 220 #[non_exhaustive] 221 /// Default MLS rules with pass-through proposal filter and customizable options. 222 pub struct DefaultMlsRules { 223 pub commit_options: CommitOptions, 224 pub encryption_options: EncryptionOptions, 225 } 226 227 impl DefaultMlsRules { 228 /// Create new MLS rules with default settings: do not enforce path and do 229 /// put the ratchet tree in the extension. new() -> Self230 pub fn new() -> Self { 231 Default::default() 232 } 233 234 /// Set commit options. with_commit_options(self, commit_options: CommitOptions) -> Self235 pub fn with_commit_options(self, commit_options: CommitOptions) -> Self { 236 Self { 237 commit_options, 238 encryption_options: self.encryption_options, 239 } 240 } 241 242 /// Set encryption options. with_encryption_options(self, encryption_options: EncryptionOptions) -> Self243 pub fn with_encryption_options(self, encryption_options: EncryptionOptions) -> Self { 244 Self { 245 commit_options: self.commit_options, 246 encryption_options, 247 } 248 } 249 } 250 251 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] 252 #[cfg_attr(mls_build_async, maybe_async::must_be_async)] 253 impl MlsRules for DefaultMlsRules { 254 type Error = Infallible; 255 filter_proposals( &self, _direction: CommitDirection, _source: CommitSource, _current_roster: &Roster, _extension_list: &ExtensionList, proposals: ProposalBundle, ) -> Result<ProposalBundle, Self::Error>256 async fn filter_proposals( 257 &self, 258 _direction: CommitDirection, 259 _source: CommitSource, 260 _current_roster: &Roster, 261 _extension_list: &ExtensionList, 262 proposals: ProposalBundle, 263 ) -> Result<ProposalBundle, Self::Error> { 264 Ok(proposals) 265 } 266 commit_options( &self, _: &Roster, _: &ExtensionList, _: &ProposalBundle, ) -> Result<CommitOptions, Self::Error>267 fn commit_options( 268 &self, 269 _: &Roster, 270 _: &ExtensionList, 271 _: &ProposalBundle, 272 ) -> Result<CommitOptions, Self::Error> { 273 Ok(self.commit_options) 274 } 275 encryption_options( &self, _: &Roster, _: &ExtensionList, ) -> Result<EncryptionOptions, Self::Error>276 fn encryption_options( 277 &self, 278 _: &Roster, 279 _: &ExtensionList, 280 ) -> Result<EncryptionOptions, Self::Error> { 281 Ok(self.encryption_options) 282 } 283 } 284