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 error::LibraryError,
13 extensions::Extensions,
14 framing::{mls_auth_content::AuthenticatedContent, MlsMessageOut},
15 group::{errors::CreateAddProposalError, GroupContext, GroupId, ValidationError},
16 key_packages::KeyPackage,
17 messages::{group_info::GroupInfo, proposals::ProposalOrRefType},
18 schedule::PreSharedKeyId,
19 storage::{OpenMlsProvider, StorageProvider},
20 treesync::{LeafNode, LeafNodeParameters},
21 versions::ProtocolVersion,
22};
23
24#[cfg(feature = "extensions-draft-08")]
25use crate::{
26 component::ComponentId,
27 messages::proposals::{AppDataUpdateOperation, AppDataUpdateProposal},
28};
29
30#[derive(Debug, PartialEq, Clone)]
32pub enum Propose {
33 Add(KeyPackage),
35
36 Update(LeafNodeParameters),
38
39 Remove(u32),
41
42 RemoveCredential(Credential),
44
45 PreSharedKey(PreSharedKeyId),
47
48 ReInit {
50 group_id: GroupId,
51 version: ProtocolVersion,
52 ciphersuite: Ciphersuite,
53 extensions: Extensions<GroupContext>,
54 },
55
56 ExternalInit(Vec<u8>),
58
59 GroupContextExtensions(Extensions<GroupContext>),
61
62 #[cfg(feature = "extensions-draft-08")]
63 UpdateAppDataComponent {
65 component_id: ComponentId,
67 update: Vec<u8>,
69 },
70 #[cfg(feature = "extensions-draft-08")]
71 RemoveAppDataComponent {
73 component_id: ComponentId,
75 },
76
77 Custom(CustomProposal),
79}
80
81macro_rules! impl_propose_fun {
82 ($name:ident, $value_ty:ty, $group_fun:ident, $ref_or_value:expr) => {
83 pub fn $name<Provider: OpenMlsProvider>(
88 &mut self,
89 provider: &Provider,
90 signer: &impl Signer,
91 value: $value_ty,
92 ) -> Result<(MlsMessageOut, ProposalRef), ProposalError<Provider::StorageError>> {
93 self.is_operational()?;
94
95 let aad = self.outgoing_authenticated_data()?;
96 let framing_parameters = FramingParameters::new(&aad, self.outgoing_wire_format());
97 let proposal = self.$group_fun(framing_parameters, value, signer)?;
98
99 let queued_proposal = QueuedProposal::from_authenticated_content(
100 self.ciphersuite(),
101 provider.crypto(),
102 proposal.clone(),
103 $ref_or_value,
104 )?;
105 let proposal_ref = queued_proposal.proposal_reference();
106
107 log::trace!("Storing proposal in queue {:?}", queued_proposal);
108 provider
109 .storage()
110 .queue_proposal(self.group_id(), &proposal_ref, &queued_proposal)
111 .map_err(ProposalError::StorageError)?;
112 self.proposal_store_mut().add(queued_proposal);
113
114 let mls_message = self.content_to_mls_message(proposal, provider)?;
115
116 self.reset_aad();
117 Ok((mls_message, proposal_ref))
118 }
119 };
120}
121
122impl MlsGroup {
123 impl_propose_fun!(
124 propose_add_member_by_value,
125 KeyPackage,
126 create_add_proposal,
127 ProposalOrRefType::Proposal
128 );
129
130 impl_propose_fun!(
131 propose_remove_member_by_value,
132 LeafNodeIndex,
133 create_remove_proposal,
134 ProposalOrRefType::Proposal
135 );
136
137 impl_propose_fun!(
138 propose_external_psk,
139 PreSharedKeyId,
140 create_presharedkey_proposal,
141 ProposalOrRefType::Reference
142 );
143
144 impl_propose_fun!(
145 propose_external_psk_by_value,
146 PreSharedKeyId,
147 create_presharedkey_proposal,
148 ProposalOrRefType::Proposal
149 );
150
151 impl_propose_fun!(
152 propose_custom_proposal_by_value,
153 CustomProposal,
154 create_custom_proposal,
155 ProposalOrRefType::Proposal
156 );
157
158 impl_propose_fun!(
159 propose_custom_proposal_by_reference,
160 CustomProposal,
161 create_custom_proposal,
162 ProposalOrRefType::Reference
163 );
164
165 pub fn propose<Provider: OpenMlsProvider>(
167 &mut self,
168 provider: &Provider,
169 signer: &impl Signer,
170 propose: Propose,
171 ref_or_value: ProposalOrRefType,
172 ) -> Result<(MlsMessageOut, ProposalRef), ProposalError<Provider::StorageError>> {
173 match propose {
174 Propose::Add(key_package) => match ref_or_value {
175 ProposalOrRefType::Proposal => {
176 self.propose_add_member_by_value(provider, signer, key_package)
177 }
178 ProposalOrRefType::Reference => self
179 .propose_add_member(provider, signer, &key_package)
180 .map_err(|e| e.into()),
181 },
182
183 Propose::Update(leaf_node_parameters) => match ref_or_value {
184 ProposalOrRefType::Proposal => self
185 .propose_self_update(provider, signer, leaf_node_parameters)
186 .map_err(|e| e.into()),
187 ProposalOrRefType::Reference => self
188 .propose_self_update(provider, signer, leaf_node_parameters)
189 .map_err(|e| e.into()),
190 },
191
192 Propose::Remove(leaf_index) => match ref_or_value {
193 ProposalOrRefType::Proposal => self.propose_remove_member_by_value(
194 provider,
195 signer,
196 LeafNodeIndex::new(leaf_index),
197 ),
198 ProposalOrRefType::Reference => self
199 .propose_remove_member(provider, signer, LeafNodeIndex::new(leaf_index))
200 .map_err(|e| e.into()),
201 },
202
203 Propose::RemoveCredential(credential) => match ref_or_value {
204 ProposalOrRefType::Proposal => {
205 self.propose_remove_member_by_credential_by_value(provider, signer, &credential)
206 }
207 ProposalOrRefType::Reference => self
208 .propose_remove_member_by_credential(provider, signer, &credential)
209 .map_err(|e| e.into()),
210 },
211 Propose::PreSharedKey(psk_id) => match psk_id.psk() {
212 crate::schedule::Psk::External(_) => match ref_or_value {
213 ProposalOrRefType::Proposal => {
214 self.propose_external_psk_by_value(provider, signer, psk_id)
215 }
216 ProposalOrRefType::Reference => {
217 self.propose_external_psk(provider, signer, psk_id)
218 }
219 },
220 crate::schedule::Psk::Resumption(_) => Err(ProposalError::LibraryError(
221 LibraryError::custom("Invalid PSk argument"),
222 )),
223 },
224 Propose::ReInit {
225 group_id: _,
226 version: _,
227 ciphersuite: _,
228 extensions: _,
229 } => Err(ProposalError::LibraryError(LibraryError::custom(
230 "Unsupported proposal type ReInit",
231 ))),
232 Propose::ExternalInit(_) => Err(ProposalError::LibraryError(LibraryError::custom(
233 "Unsupported proposal type ExternalInit",
234 ))),
235 Propose::GroupContextExtensions(_) => Err(ProposalError::LibraryError(
236 LibraryError::custom("Unsupported proposal type GroupContextExtensions"),
237 )),
238 #[cfg(feature = "extensions-draft-08")]
240 Propose::UpdateAppDataComponent {
241 component_id,
242 update,
243 } => self.propose_app_data_update(
244 provider,
245 signer,
246 component_id,
247 AppDataUpdateOperation::Update(update.into()),
248 ),
249 #[cfg(feature = "extensions-draft-08")]
250 Propose::RemoveAppDataComponent { component_id } => self.propose_app_data_update(
251 provider,
252 signer,
253 component_id,
254 AppDataUpdateOperation::Remove,
255 ),
256
257 Propose::Custom(custom_proposal) => match ref_or_value {
259 ProposalOrRefType::Proposal => {
260 self.propose_custom_proposal_by_value(provider, signer, custom_proposal)
261 }
262 ProposalOrRefType::Reference => {
263 self.propose_custom_proposal_by_reference(provider, signer, custom_proposal)
264 }
265 },
266 }
267 }
268
269 pub fn propose_add_member<Provider: OpenMlsProvider>(
273 &mut self,
274 provider: &Provider,
275 signer: &impl Signer,
276 key_package: &KeyPackage,
277 ) -> Result<(MlsMessageOut, ProposalRef), ProposeAddMemberError<Provider::StorageError>> {
278 self.is_operational()?;
279
280 let aad = self.outgoing_authenticated_data()?;
281 let framing_parameters = FramingParameters::new(&aad, self.outgoing_wire_format());
282 let add_proposal = self
283 .create_add_proposal(framing_parameters, key_package.clone(), signer)
284 .map_err(|e| match e {
285 CreateAddProposalError::LibraryError(e) => e.into(),
286 CreateAddProposalError::LeafNodeValidation(error) => {
287 ProposeAddMemberError::LeafNodeValidation(error)
288 }
289 })?;
290
291 let proposal = QueuedProposal::from_authenticated_content_by_ref(
292 self.ciphersuite(),
293 provider.crypto(),
294 add_proposal.clone(),
295 )?;
296 let proposal_ref = proposal.proposal_reference();
297 provider
298 .storage()
299 .queue_proposal(self.group_id(), &proposal_ref, &proposal)
300 .map_err(ProposeAddMemberError::StorageError)?;
301 self.proposal_store_mut().add(proposal);
302
303 let mls_message = self.content_to_mls_message(add_proposal, provider)?;
304
305 self.reset_aad();
306 Ok((mls_message, proposal_ref))
307 }
308
309 pub fn propose_remove_member<Provider: OpenMlsProvider>(
314 &mut self,
315 provider: &Provider,
316 signer: &impl Signer,
317 member: LeafNodeIndex,
318 ) -> Result<(MlsMessageOut, ProposalRef), ProposeRemoveMemberError<Provider::StorageError>>
319 {
320 self.is_operational()?;
321
322 let aad = self.outgoing_authenticated_data()?;
323 let framing_parameters = FramingParameters::new(&aad, self.outgoing_wire_format());
324 let remove_proposal = self
325 .create_remove_proposal(framing_parameters, member, signer)
326 .map_err(|_| ProposeRemoveMemberError::UnknownMember)?;
327
328 let proposal = QueuedProposal::from_authenticated_content_by_ref(
329 self.ciphersuite(),
330 provider.crypto(),
331 remove_proposal.clone(),
332 )?;
333 let proposal_ref = proposal.proposal_reference();
334 provider
335 .storage()
336 .queue_proposal(self.group_id(), &proposal_ref, &proposal)
337 .map_err(ProposeRemoveMemberError::StorageError)?;
338 self.proposal_store_mut().add(proposal);
339
340 let mls_message = self.content_to_mls_message(remove_proposal, provider)?;
341
342 self.reset_aad();
343 Ok((mls_message, proposal_ref))
344 }
345
346 pub fn propose_remove_member_by_credential<Provider: OpenMlsProvider>(
351 &mut self,
352 provider: &Provider,
353 signer: &impl Signer,
354 member: &Credential,
355 ) -> Result<(MlsMessageOut, ProposalRef), ProposeRemoveMemberError<Provider::StorageError>>
356 {
357 let member_index = self
359 .public_group()
360 .members()
361 .find(|m| &m.credential == member)
362 .map(|m| m.index);
363
364 if let Some(member_index) = member_index {
365 self.propose_remove_member(provider, signer, member_index)
366 } else {
367 Err(ProposeRemoveMemberError::UnknownMember)
368 }
369 }
370
371 pub fn propose_remove_member_by_credential_by_value<Provider: OpenMlsProvider>(
376 &mut self,
377 provider: &Provider,
378 signer: &impl Signer,
379 member: &Credential,
380 ) -> Result<(MlsMessageOut, ProposalRef), ProposalError<Provider::StorageError>> {
381 let member_index = self
383 .public_group()
384 .members()
385 .find(|m| &m.credential == member)
386 .map(|m| m.index);
387
388 if let Some(member_index) = member_index {
389 self.propose_remove_member_by_value(provider, signer, member_index)
390 } else {
391 Err(ProposalError::ProposeRemoveMemberError(
392 ProposeRemoveMemberError::UnknownMember,
393 ))
394 }
395 }
396
397 pub fn propose_group_context_extensions<Provider: OpenMlsProvider>(
402 &mut self,
403 provider: &Provider,
404 extensions: Extensions<GroupContext>,
405 signer: &impl Signer,
406 ) -> Result<(MlsMessageOut, ProposalRef), ProposalError<Provider::StorageError>> {
407 self.is_operational()?;
408
409 let aad = self.outgoing_authenticated_data()?;
410 let framing_parameters = FramingParameters::new(&aad, self.outgoing_wire_format());
411 let proposal = self.create_group_context_ext_proposal::<Provider>(
412 framing_parameters,
413 extensions,
414 signer,
415 )?;
416
417 let queued_proposal = QueuedProposal::from_authenticated_content_by_ref(
418 self.ciphersuite(),
419 provider.crypto(),
420 proposal.clone(),
421 )?;
422
423 let proposal_ref = queued_proposal.proposal_reference();
424 provider
425 .storage()
426 .queue_proposal(self.group_id(), &proposal_ref, &queued_proposal)
427 .map_err(ProposalError::StorageError)?;
428 self.proposal_store_mut().add(queued_proposal);
429
430 let mls_message = self.content_to_mls_message(proposal, provider)?;
431
432 self.reset_aad();
433 Ok((mls_message, proposal_ref))
434 }
435
436 #[allow(clippy::type_complexity)]
444 pub fn update_group_context_extensions<Provider: OpenMlsProvider>(
445 &mut self,
446 provider: &Provider,
447 extensions: Extensions<GroupContext>,
448 signer: &impl Signer,
449 ) -> Result<
450 (MlsMessageOut, Option<MlsMessageOut>, Option<GroupInfo>),
451 CreateGroupContextExtProposalError<Provider::StorageError>,
452 > {
453 self.is_operational()?;
454
455 let bundle = self
457 .commit_builder()
458 .propose_group_context_extensions(extensions)?
459 .load_psks(provider.storage())?
460 .build(provider.rand(), provider.crypto(), signer, |_| true)?
461 .stage_commit(provider)?;
462
463 let (commit, welcome, group_info) = bundle.into_contents();
465 let welcome = welcome.map(|welcome| MlsMessageOut::from_welcome(welcome, self.version()));
466
467 Ok((commit, welcome, group_info))
468 }
469
470 #[cfg(feature = "extensions-draft-08")]
472 pub fn propose_app_data_update<Provider: OpenMlsProvider>(
473 &mut self,
474 provider: &Provider,
475 signer: &impl Signer,
476 component_id: ComponentId,
477 operation: AppDataUpdateOperation,
478 ) -> Result<(MlsMessageOut, ProposalRef), ProposalError<Provider::StorageError>> {
479 self.is_operational()?;
480
481 let aad = self.outgoing_authenticated_data()?;
482 let framing_parameters = FramingParameters::new(&aad, self.outgoing_wire_format());
483 let proposal = self.create_app_data_update_proposal(
484 framing_parameters,
485 component_id,
486 operation,
487 signer,
488 )?;
489
490 let queued_proposal = QueuedProposal::from_authenticated_content(
491 self.ciphersuite(),
492 provider.crypto(),
493 proposal.clone(),
494 ProposalOrRefType::Proposal,
495 )?;
496 let proposal_ref = queued_proposal.proposal_reference();
497
498 log::trace!("Storing proposal in queue {:?}", queued_proposal);
499 provider
500 .storage()
501 .queue_proposal(self.group_id(), &proposal_ref, &queued_proposal)
502 .map_err(ProposalError::StorageError)?;
503 self.proposal_store_mut().add(queued_proposal);
504
505 let mls_message = self.content_to_mls_message(proposal, provider)?;
506
507 self.reset_aad();
508 Ok((mls_message, proposal_ref))
509 }
510
511 pub fn remove_pending_proposal<Storage: StorageProvider>(
513 &mut self,
514 storage: &Storage,
515 proposal_ref: &ProposalRef,
516 ) -> Result<(), RemoveProposalError<Storage::Error>> {
517 storage
518 .remove_proposal(self.group_id(), proposal_ref)
519 .map_err(RemoveProposalError::Storage)?;
520 self.proposal_store_mut()
521 .remove(proposal_ref)
522 .ok_or(RemoveProposalError::ProposalNotFound)
523 }
524
525 pub(crate) fn create_add_proposal(
532 &self,
533 framing_parameters: FramingParameters,
534 joiner_key_package: KeyPackage,
535 signer: &impl Signer,
536 ) -> Result<AuthenticatedContent, CreateAddProposalError> {
537 if let Some(required_capabilities) = self.required_capabilities() {
538 joiner_key_package
539 .leaf_node()
540 .capabilities()
541 .supports_required_capabilities(required_capabilities)?;
542 }
543 let add_proposal = AddProposal {
544 key_package: joiner_key_package,
545 };
546 let proposal = Proposal::add(add_proposal);
547 AuthenticatedContent::member_proposal(
548 framing_parameters,
549 self.own_leaf_index(),
550 proposal,
551 self.context(),
552 signer,
553 )
554 .map_err(|e| e.into())
555 }
556
557 pub(crate) fn create_update_proposal(
562 &self,
563 framing_parameters: FramingParameters,
564 leaf_node: LeafNode,
567 signer: &impl Signer,
568 ) -> Result<AuthenticatedContent, LibraryError> {
569 let update_proposal = UpdateProposal { leaf_node };
570 let proposal = Proposal::update(update_proposal);
571 AuthenticatedContent::member_proposal(
572 framing_parameters,
573 self.own_leaf_index(),
574 proposal,
575 self.context(),
576 signer,
577 )
578 }
579
580 pub(crate) fn create_remove_proposal(
585 &self,
586 framing_parameters: FramingParameters,
587 removed: LeafNodeIndex,
588 signer: &impl Signer,
589 ) -> Result<AuthenticatedContent, ValidationError> {
590 if self.public_group().leaf(removed).is_none() {
591 return Err(ValidationError::UnknownMember);
592 }
593 let remove_proposal = RemoveProposal { removed };
594 let proposal = Proposal::remove(remove_proposal);
595 AuthenticatedContent::member_proposal(
596 framing_parameters,
597 self.own_leaf_index(),
598 proposal,
599 self.context(),
600 signer,
601 )
602 .map_err(ValidationError::LibraryError)
603 }
604
605 pub(crate) fn create_self_remove_proposal(
608 &self,
609 aad: &[u8],
610 signer: &impl Signer,
611 ) -> Result<AuthenticatedContent, LibraryError> {
612 let proposal = Proposal::SelfRemove;
613 let framing_parameters = FramingParameters::new(aad, WireFormat::PublicMessage);
614 AuthenticatedContent::member_proposal(
615 framing_parameters,
616 self.own_leaf_index(),
617 proposal,
618 self.context(),
619 signer,
620 )
621 }
622
623 pub(crate) fn create_presharedkey_proposal(
629 &self,
630 framing_parameters: FramingParameters,
631 psk: PreSharedKeyId,
632 signer: &impl Signer,
633 ) -> Result<AuthenticatedContent, LibraryError> {
634 let presharedkey_proposal = PreSharedKeyProposal::new(psk);
635 let proposal = Proposal::psk(presharedkey_proposal);
636 AuthenticatedContent::member_proposal(
637 framing_parameters,
638 self.own_leaf_index(),
639 proposal,
640 self.context(),
641 signer,
642 )
643 }
644
645 #[cfg(feature = "extensions-draft-08")]
646 pub(crate) fn create_app_data_update_proposal(
647 &self,
648 framing_parameters: FramingParameters,
649 component_id: ComponentId,
650 operation: AppDataUpdateOperation,
651 signer: &impl Signer,
652 ) -> Result<AuthenticatedContent, LibraryError> {
653 let proposal = Proposal::AppDataUpdate(Box::new(AppDataUpdateProposal::new(
654 component_id,
655 operation,
656 )));
657 AuthenticatedContent::member_proposal(
658 framing_parameters,
659 self.own_leaf_index(),
660 proposal,
661 self.context(),
662 signer,
663 )
664 }
665
666 pub(crate) fn create_custom_proposal(
667 &self,
668 framing_parameters: FramingParameters,
669 custom_proposal: CustomProposal,
670 signer: &impl Signer,
671 ) -> Result<AuthenticatedContent, LibraryError> {
672 let proposal = Proposal::custom(custom_proposal);
673 AuthenticatedContent::member_proposal(
674 framing_parameters,
675 self.own_leaf_index(),
676 proposal,
677 self.context(),
678 signer,
679 )
680 }
681}