1use openmls_traits::{signatures::Signer, storage::StorageProvider as _, types::Ciphersuite};
2
3use super::{
4 errors::{ProposalError, ProposeAddMemberError, ProposeRemoveMemberError, RemoveProposalError},
5 AddProposal, CreateGroupContextExtProposalError, CustomProposal, FramingParameters, MlsGroup,
6 PreSharedKeyProposal, Proposal, QueuedProposal, RemoveProposal, UpdateProposal, WireFormat,
7};
8use crate::{
9 binary_tree::LeafNodeIndex,
10 ciphersuite::hash_ref::ProposalRef,
11 credentials::Credential,
12 extensions::Extensions,
13 framing::{mls_auth_content::AuthenticatedContent, MlsMessageOut},
14 group::{errors::CreateAddProposalError, GroupId, ValidationError},
15 key_packages::KeyPackage,
16 messages::{group_info::GroupInfo, proposals::ProposalOrRefType},
17 prelude::LibraryError,
18 schedule::PreSharedKeyId,
19 storage::{OpenMlsProvider, StorageProvider},
20 treesync::{LeafNode, LeafNodeParameters},
21 versions::ProtocolVersion,
22};
23
24#[derive(Debug, PartialEq, Clone)]
26pub enum Propose {
27 Add(KeyPackage),
29
30 Update(LeafNodeParameters),
32
33 Remove(u32),
35
36 RemoveCredential(Credential),
38
39 PreSharedKey(PreSharedKeyId),
41
42 ReInit {
44 group_id: GroupId,
45 version: ProtocolVersion,
46 ciphersuite: Ciphersuite,
47 extensions: Extensions,
48 },
49
50 ExternalInit(Vec<u8>),
52
53 GroupContextExtensions(Extensions),
55
56 Custom(CustomProposal),
58}
59
60macro_rules! impl_propose_fun {
61 ($name:ident, $value_ty:ty, $group_fun:ident, $ref_or_value:expr) => {
62 pub fn $name<Provider: OpenMlsProvider>(
67 &mut self,
68 provider: &Provider,
69 signer: &impl Signer,
70 value: $value_ty,
71 ) -> Result<(MlsMessageOut, ProposalRef), ProposalError<Provider::StorageError>> {
72 self.is_operational()?;
73
74 let proposal = self.$group_fun(self.framing_parameters(), value, signer)?;
75
76 let queued_proposal = QueuedProposal::from_authenticated_content(
77 self.ciphersuite(),
78 provider.crypto(),
79 proposal.clone(),
80 $ref_or_value,
81 )?;
82 let proposal_ref = queued_proposal.proposal_reference();
83
84 log::trace!("Storing proposal in queue {:?}", queued_proposal);
85 provider
86 .storage()
87 .queue_proposal(self.group_id(), &proposal_ref, &queued_proposal)
88 .map_err(ProposalError::StorageError)?;
89 self.proposal_store_mut().add(queued_proposal);
90
91 let mls_message = self.content_to_mls_message(proposal, provider)?;
92
93 self.reset_aad();
94 Ok((mls_message, proposal_ref))
95 }
96 };
97}
98
99impl MlsGroup {
100 impl_propose_fun!(
101 propose_add_member_by_value,
102 KeyPackage,
103 create_add_proposal,
104 ProposalOrRefType::Proposal
105 );
106
107 impl_propose_fun!(
108 propose_remove_member_by_value,
109 LeafNodeIndex,
110 create_remove_proposal,
111 ProposalOrRefType::Proposal
112 );
113
114 impl_propose_fun!(
115 propose_external_psk,
116 PreSharedKeyId,
117 create_presharedkey_proposal,
118 ProposalOrRefType::Reference
119 );
120
121 impl_propose_fun!(
122 propose_external_psk_by_value,
123 PreSharedKeyId,
124 create_presharedkey_proposal,
125 ProposalOrRefType::Proposal
126 );
127
128 impl_propose_fun!(
129 propose_custom_proposal_by_value,
130 CustomProposal,
131 create_custom_proposal,
132 ProposalOrRefType::Proposal
133 );
134
135 impl_propose_fun!(
136 propose_custom_proposal_by_reference,
137 CustomProposal,
138 create_custom_proposal,
139 ProposalOrRefType::Reference
140 );
141
142 pub fn propose<Provider: OpenMlsProvider>(
144 &mut self,
145 provider: &Provider,
146 signer: &impl Signer,
147 propose: Propose,
148 ref_or_value: ProposalOrRefType,
149 ) -> Result<(MlsMessageOut, ProposalRef), ProposalError<Provider::StorageError>> {
150 match propose {
151 Propose::Add(key_package) => match ref_or_value {
152 ProposalOrRefType::Proposal => {
153 self.propose_add_member_by_value(provider, signer, key_package)
154 }
155 ProposalOrRefType::Reference => self
156 .propose_add_member(provider, signer, &key_package)
157 .map_err(|e| e.into()),
158 },
159
160 Propose::Update(leaf_node_parameters) => match ref_or_value {
161 ProposalOrRefType::Proposal => self
162 .propose_self_update(provider, signer, leaf_node_parameters)
163 .map_err(|e| e.into()),
164 ProposalOrRefType::Reference => self
165 .propose_self_update(provider, signer, leaf_node_parameters)
166 .map_err(|e| e.into()),
167 },
168
169 Propose::Remove(leaf_index) => match ref_or_value {
170 ProposalOrRefType::Proposal => self.propose_remove_member_by_value(
171 provider,
172 signer,
173 LeafNodeIndex::new(leaf_index),
174 ),
175 ProposalOrRefType::Reference => self
176 .propose_remove_member(provider, signer, LeafNodeIndex::new(leaf_index))
177 .map_err(|e| e.into()),
178 },
179
180 Propose::RemoveCredential(credential) => match ref_or_value {
181 ProposalOrRefType::Proposal => {
182 self.propose_remove_member_by_credential_by_value(provider, signer, &credential)
183 }
184 ProposalOrRefType::Reference => self
185 .propose_remove_member_by_credential(provider, signer, &credential)
186 .map_err(|e| e.into()),
187 },
188 Propose::PreSharedKey(psk_id) => match psk_id.psk() {
189 crate::schedule::Psk::External(_) => match ref_or_value {
190 ProposalOrRefType::Proposal => {
191 self.propose_external_psk_by_value(provider, signer, psk_id)
192 }
193 ProposalOrRefType::Reference => {
194 self.propose_external_psk(provider, signer, psk_id)
195 }
196 },
197 crate::schedule::Psk::Resumption(_) => Err(ProposalError::LibraryError(
198 LibraryError::custom("Invalid PSk argument"),
199 )),
200 },
201 Propose::ReInit {
202 group_id: _,
203 version: _,
204 ciphersuite: _,
205 extensions: _,
206 } => Err(ProposalError::LibraryError(LibraryError::custom(
207 "Unsupported proposal type ReInit",
208 ))),
209 Propose::ExternalInit(_) => Err(ProposalError::LibraryError(LibraryError::custom(
210 "Unsupported proposal type ExternalInit",
211 ))),
212 Propose::GroupContextExtensions(_) => Err(ProposalError::LibraryError(
213 LibraryError::custom("Unsupported proposal type GroupContextExtensions"),
214 )),
215 Propose::Custom(custom_proposal) => match ref_or_value {
216 ProposalOrRefType::Proposal => {
217 self.propose_custom_proposal_by_value(provider, signer, custom_proposal)
218 }
219 ProposalOrRefType::Reference => {
220 self.propose_custom_proposal_by_reference(provider, signer, custom_proposal)
221 }
222 },
223 }
224 }
225
226 pub fn propose_add_member<Provider: OpenMlsProvider>(
230 &mut self,
231 provider: &Provider,
232 signer: &impl Signer,
233 key_package: &KeyPackage,
234 ) -> Result<(MlsMessageOut, ProposalRef), ProposeAddMemberError<Provider::StorageError>> {
235 self.is_operational()?;
236
237 let add_proposal = self
238 .create_add_proposal(self.framing_parameters(), key_package.clone(), signer)
239 .map_err(|e| match e {
240 CreateAddProposalError::LibraryError(e) => e.into(),
241 CreateAddProposalError::LeafNodeValidation(error) => {
242 ProposeAddMemberError::LeafNodeValidation(error)
243 }
244 })?;
245
246 let proposal = QueuedProposal::from_authenticated_content_by_ref(
247 self.ciphersuite(),
248 provider.crypto(),
249 add_proposal.clone(),
250 )?;
251 let proposal_ref = proposal.proposal_reference();
252 provider
253 .storage()
254 .queue_proposal(self.group_id(), &proposal_ref, &proposal)
255 .map_err(ProposeAddMemberError::StorageError)?;
256 self.proposal_store_mut().add(proposal);
257
258 let mls_message = self.content_to_mls_message(add_proposal, provider)?;
259
260 self.reset_aad();
261 Ok((mls_message, proposal_ref))
262 }
263
264 pub fn propose_remove_member<Provider: OpenMlsProvider>(
269 &mut self,
270 provider: &Provider,
271 signer: &impl Signer,
272 member: LeafNodeIndex,
273 ) -> Result<(MlsMessageOut, ProposalRef), ProposeRemoveMemberError<Provider::StorageError>>
274 {
275 self.is_operational()?;
276
277 let remove_proposal = self
278 .create_remove_proposal(self.framing_parameters(), member, signer)
279 .map_err(|_| ProposeRemoveMemberError::UnknownMember)?;
280
281 let proposal = QueuedProposal::from_authenticated_content_by_ref(
282 self.ciphersuite(),
283 provider.crypto(),
284 remove_proposal.clone(),
285 )?;
286 let proposal_ref = proposal.proposal_reference();
287 provider
288 .storage()
289 .queue_proposal(self.group_id(), &proposal_ref, &proposal)
290 .map_err(ProposeRemoveMemberError::StorageError)?;
291 self.proposal_store_mut().add(proposal);
292
293 let mls_message = self.content_to_mls_message(remove_proposal, provider)?;
294
295 self.reset_aad();
296 Ok((mls_message, proposal_ref))
297 }
298
299 pub fn propose_remove_member_by_credential<Provider: OpenMlsProvider>(
304 &mut self,
305 provider: &Provider,
306 signer: &impl Signer,
307 member: &Credential,
308 ) -> Result<(MlsMessageOut, ProposalRef), ProposeRemoveMemberError<Provider::StorageError>>
309 {
310 let member_index = self
312 .public_group()
313 .members()
314 .find(|m| &m.credential == member)
315 .map(|m| m.index);
316
317 if let Some(member_index) = member_index {
318 self.propose_remove_member(provider, signer, member_index)
319 } else {
320 Err(ProposeRemoveMemberError::UnknownMember)
321 }
322 }
323
324 pub fn propose_remove_member_by_credential_by_value<Provider: OpenMlsProvider>(
329 &mut self,
330 provider: &Provider,
331 signer: &impl Signer,
332 member: &Credential,
333 ) -> Result<(MlsMessageOut, ProposalRef), ProposalError<Provider::StorageError>> {
334 let member_index = self
336 .public_group()
337 .members()
338 .find(|m| &m.credential == member)
339 .map(|m| m.index);
340
341 if let Some(member_index) = member_index {
342 self.propose_remove_member_by_value(provider, signer, member_index)
343 } else {
344 Err(ProposalError::ProposeRemoveMemberError(
345 ProposeRemoveMemberError::UnknownMember,
346 ))
347 }
348 }
349
350 pub fn propose_group_context_extensions<Provider: OpenMlsProvider>(
355 &mut self,
356 provider: &Provider,
357 extensions: Extensions,
358 signer: &impl Signer,
359 ) -> Result<(MlsMessageOut, ProposalRef), ProposalError<Provider::StorageError>> {
360 self.is_operational()?;
361
362 let proposal = self.create_group_context_ext_proposal::<Provider>(
363 self.framing_parameters(),
364 extensions,
365 signer,
366 )?;
367
368 let queued_proposal = QueuedProposal::from_authenticated_content_by_ref(
369 self.ciphersuite(),
370 provider.crypto(),
371 proposal.clone(),
372 )?;
373
374 let proposal_ref = queued_proposal.proposal_reference();
375 provider
376 .storage()
377 .queue_proposal(self.group_id(), &proposal_ref, &queued_proposal)
378 .map_err(ProposalError::StorageError)?;
379 self.proposal_store_mut().add(queued_proposal);
380
381 let mls_message = self.content_to_mls_message(proposal, provider)?;
382
383 self.reset_aad();
384 Ok((mls_message, proposal_ref))
385 }
386
387 #[allow(clippy::type_complexity)]
395 pub fn update_group_context_extensions<Provider: OpenMlsProvider>(
396 &mut self,
397 provider: &Provider,
398 extensions: Extensions,
399 signer: &impl Signer,
400 ) -> Result<
401 (MlsMessageOut, Option<MlsMessageOut>, Option<GroupInfo>),
402 CreateGroupContextExtProposalError<Provider::StorageError>,
403 > {
404 self.is_operational()?;
405
406 let bundle = self
408 .commit_builder()
409 .propose_group_context_extensions(extensions)
410 .load_psks(provider.storage())?
411 .build(provider.rand(), provider.crypto(), signer, |_| true)?
412 .stage_commit(provider)?;
413
414 let (commit, welcome, group_info) = bundle.into_contents();
416 let welcome = welcome.map(|welcome| MlsMessageOut::from_welcome(welcome, self.version()));
417
418 Ok((commit, welcome, group_info))
419 }
420
421 pub fn remove_pending_proposal<Storage: StorageProvider>(
423 &mut self,
424 storage: &Storage,
425 proposal_ref: &ProposalRef,
426 ) -> Result<(), RemoveProposalError<Storage::Error>> {
427 storage
428 .remove_proposal(self.group_id(), proposal_ref)
429 .map_err(RemoveProposalError::Storage)?;
430 self.proposal_store_mut()
431 .remove(proposal_ref)
432 .ok_or(RemoveProposalError::ProposalNotFound)
433 }
434
435 pub(crate) fn create_add_proposal(
442 &self,
443 framing_parameters: FramingParameters,
444 joiner_key_package: KeyPackage,
445 signer: &impl Signer,
446 ) -> Result<AuthenticatedContent, CreateAddProposalError> {
447 if let Some(required_capabilities) = self.required_capabilities() {
448 joiner_key_package
449 .leaf_node()
450 .capabilities()
451 .supports_required_capabilities(required_capabilities)?;
452 }
453 let add_proposal = AddProposal {
454 key_package: joiner_key_package,
455 };
456 let proposal = Proposal::Add(add_proposal);
457 AuthenticatedContent::member_proposal(
458 framing_parameters,
459 self.own_leaf_index(),
460 proposal,
461 self.context(),
462 signer,
463 )
464 .map_err(|e| e.into())
465 }
466
467 pub(crate) fn create_update_proposal(
472 &self,
473 framing_parameters: FramingParameters,
474 leaf_node: LeafNode,
477 signer: &impl Signer,
478 ) -> Result<AuthenticatedContent, LibraryError> {
479 let update_proposal = UpdateProposal { leaf_node };
480 let proposal = Proposal::Update(update_proposal);
481 AuthenticatedContent::member_proposal(
482 framing_parameters,
483 self.own_leaf_index(),
484 proposal,
485 self.context(),
486 signer,
487 )
488 }
489
490 pub(crate) fn create_remove_proposal(
495 &self,
496 framing_parameters: FramingParameters,
497 removed: LeafNodeIndex,
498 signer: &impl Signer,
499 ) -> Result<AuthenticatedContent, ValidationError> {
500 if self.public_group().leaf(removed).is_none() {
501 return Err(ValidationError::UnknownMember);
502 }
503 let remove_proposal = RemoveProposal { removed };
504 let proposal = Proposal::Remove(remove_proposal);
505 AuthenticatedContent::member_proposal(
506 framing_parameters,
507 self.own_leaf_index(),
508 proposal,
509 self.context(),
510 signer,
511 )
512 .map_err(ValidationError::LibraryError)
513 }
514
515 pub(crate) fn create_self_remove_proposal(
518 &self,
519 aad: &[u8],
520 signer: &impl Signer,
521 ) -> Result<AuthenticatedContent, LibraryError> {
522 let proposal = Proposal::SelfRemove;
523 let framing_parameters = FramingParameters::new(aad, WireFormat::PublicMessage);
524 AuthenticatedContent::member_proposal(
525 framing_parameters,
526 self.own_leaf_index(),
527 proposal,
528 self.context(),
529 signer,
530 )
531 }
532
533 pub(crate) fn create_presharedkey_proposal(
539 &self,
540 framing_parameters: FramingParameters,
541 psk: PreSharedKeyId,
542 signer: &impl Signer,
543 ) -> Result<AuthenticatedContent, LibraryError> {
544 let presharedkey_proposal = PreSharedKeyProposal::new(psk);
545 let proposal = Proposal::PreSharedKey(presharedkey_proposal);
546 AuthenticatedContent::member_proposal(
547 framing_parameters,
548 self.own_leaf_index(),
549 proposal,
550 self.context(),
551 signer,
552 )
553 }
554
555 pub(crate) fn create_custom_proposal(
556 &self,
557 framing_parameters: FramingParameters,
558 custom_proposal: CustomProposal,
559 signer: &impl Signer,
560 ) -> Result<AuthenticatedContent, LibraryError> {
561 let proposal = Proposal::Custom(custom_proposal);
562 AuthenticatedContent::member_proposal(
563 framing_parameters,
564 self.own_leaf_index(),
565 proposal,
566 self.context(),
567 signer,
568 )
569 }
570}