1use core::fmt::Debug;
2use std::mem;
3
4#[cfg(feature = "extensions-draft-08")]
5use openmls_traits::crypto::OpenMlsCrypto;
6use openmls_traits::storage::StorageProvider as _;
7use serde::{Deserialize, Serialize};
8use tls_codec::Serialize as _;
9
10use super::proposal_store::{
11 QueuedAddProposal, QueuedPskProposal, QueuedRemoveProposal, QueuedUpdateProposal,
12};
13
14#[cfg(feature = "extensions-draft-08")]
15use super::proposal_store::QueuedAppEphemeralProposal;
16
17use super::{
18 super::errors::*, load_psks, Credential, Extension, GroupContext, GroupEpochSecrets, GroupId,
19 JoinerSecret, KeySchedule, LeafNode, LibraryError, MessageSecrets, MlsGroup, OpenMlsProvider,
20 Proposal, ProposalQueue, PskSecret, QueuedProposal, Sender,
21};
22#[cfg(feature = "extensions-draft-08")]
23use crate::schedule::application_export_tree::ApplicationExportTree;
24
25use crate::{
26 ciphersuite::{hash_ref::ProposalRef, Secret},
27 framing::mls_auth_content::AuthenticatedContent,
28 group::public_group::{
29 diff::{apply_proposals::ApplyProposalsValues, StagedPublicGroupDiff},
30 staged_commit::PublicStagedCommitState,
31 },
32 schedule::{CommitSecret, EpochAuthenticator, EpochSecretsResult, InitSecret, PreSharedKeyId},
33 treesync::node::encryption_keys::EncryptionKeyPair,
34};
35
36impl MlsGroup {
37 fn derive_epoch_secrets(
38 &self,
39 provider: &impl OpenMlsProvider,
40 apply_proposals_values: ApplyProposalsValues,
41 epoch_secrets: &GroupEpochSecrets,
42 commit_secret: CommitSecret,
43 serialized_provisional_group_context: &[u8],
44 ) -> Result<EpochSecretsResult, StageCommitError> {
45 let joiner_secret = if let Some(ref external_init_proposal) =
48 apply_proposals_values.external_init_proposal_option
49 {
50 let external_priv = epoch_secrets
52 .external_secret()
53 .derive_external_keypair(provider.crypto(), self.ciphersuite())
54 .map_err(LibraryError::unexpected_crypto_error)?
55 .private;
56 let init_secret = InitSecret::from_kem_output(
57 provider.crypto(),
58 self.ciphersuite(),
59 self.version(),
60 &external_priv,
61 external_init_proposal.kem_output(),
62 )?;
63 JoinerSecret::new(
64 provider.crypto(),
65 self.ciphersuite(),
66 commit_secret,
67 &init_secret,
68 serialized_provisional_group_context,
69 )
70 .map_err(LibraryError::unexpected_crypto_error)?
71 } else {
72 JoinerSecret::new(
73 provider.crypto(),
74 self.ciphersuite(),
75 commit_secret,
76 epoch_secrets.init_secret(),
77 serialized_provisional_group_context,
78 )
79 .map_err(LibraryError::unexpected_crypto_error)?
80 };
81
82 let psk_secret = {
85 let psks: Vec<(&PreSharedKeyId, Secret)> = load_psks(
86 provider.storage(),
87 &self.resumption_psk_store,
88 &apply_proposals_values.presharedkeys,
89 )?;
90
91 PskSecret::new(provider.crypto(), self.ciphersuite(), psks)?
92 };
93
94 let mut key_schedule = KeySchedule::init(
96 self.ciphersuite(),
97 provider.crypto(),
98 &joiner_secret,
99 psk_secret,
100 )?;
101
102 key_schedule
103 .add_context(provider.crypto(), serialized_provisional_group_context)
104 .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
105 Ok(key_schedule
106 .epoch_secrets(provider.crypto(), self.ciphersuite())
107 .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?)
108 }
109
110 pub(crate) fn stage_commit(
149 &self,
150 mls_content: &AuthenticatedContent,
151 old_epoch_keypairs: Vec<EncryptionKeyPair>,
152 leaf_node_keypairs: Vec<EncryptionKeyPair>,
153 provider: &impl OpenMlsProvider,
154 ) -> Result<StagedCommit, StageCommitError> {
155 if let Sender::Member(member) = mls_content.sender() {
157 if member == &self.own_leaf_index() {
158 return Err(StageCommitError::OwnCommit);
159 }
160 }
161
162 let ciphersuite = self.ciphersuite();
163
164 let (commit, proposal_queue, sender_index) = self
165 .public_group
166 .validate_commit(mls_content, provider.crypto())?;
167
168 let mut diff = self.public_group.empty_diff();
171
172 let apply_proposals_values =
173 diff.apply_proposals(&proposal_queue, self.own_leaf_index())?;
174
175 let (commit_secret, new_keypairs, new_leaf_keypair_option, update_path_leaf_node) =
177 if let Some(path) = commit.path.clone() {
178 diff.apply_received_update_path(
181 provider.crypto(),
182 ciphersuite,
183 sender_index,
184 &path,
185 )?;
186
187 diff.update_group_context(
189 provider.crypto(),
190 apply_proposals_values.extensions.clone(),
191 )?;
192
193 if apply_proposals_values.self_removed {
195 let staged_diff = diff.into_staged_diff(provider.crypto(), ciphersuite)?;
197 let staged_state = PublicStagedCommitState::new(
198 staged_diff,
199 commit.path.as_ref().map(|path| path.leaf_node().clone()),
200 );
201 let staged_commit = StagedCommit::new(
202 proposal_queue,
203 StagedCommitState::PublicState(Box::new(staged_state)),
204 );
205 return Ok(staged_commit);
206 }
207
208 let decryption_keypairs: Vec<&EncryptionKeyPair> = old_epoch_keypairs
209 .iter()
210 .chain(leaf_node_keypairs.iter())
211 .collect();
212
213 let (new_keypairs, commit_secret) = diff.decrypt_path(
216 provider.crypto(),
217 &decryption_keypairs,
218 self.own_leaf_index(),
219 sender_index,
220 path.nodes(),
221 &apply_proposals_values.exclusion_list(),
222 )?;
223
224 let new_leaf_keypair_option = if let Some(leaf) = diff.leaf(self.own_leaf_index()) {
230 leaf_node_keypairs.into_iter().find_map(|keypair| {
231 if leaf.encryption_key() == keypair.public_key() {
232 Some(keypair)
233 } else {
234 None
235 }
236 })
237 } else {
238 debug_assert!(false);
240 None
241 };
242
243 let update_path_leaf_node = Some(path.leaf_node().clone());
247 debug_assert_eq!(diff.leaf(sender_index), path.leaf_node().into());
248
249 (
250 commit_secret,
251 new_keypairs,
252 new_leaf_keypair_option,
253 update_path_leaf_node,
254 )
255 } else {
256 if apply_proposals_values.path_required {
257 return Err(StageCommitError::RequiredPathNotFound);
259 }
260
261 diff.update_group_context(
263 provider.crypto(),
264 apply_proposals_values.extensions.clone(),
265 )?;
266
267 (CommitSecret::zero_secret(ciphersuite), vec![], None, None)
268 };
269
270 diff.update_confirmed_transcript_hash(provider.crypto(), mls_content)?;
272
273 let received_confirmation_tag = mls_content
274 .confirmation_tag()
275 .ok_or(StageCommitError::ConfirmationTagMissing)?;
276
277 let serialized_provisional_group_context = diff
278 .group_context()
279 .tls_serialize_detached()
280 .map_err(LibraryError::missing_bound_check)?;
281
282 let EpochSecretsResult {
283 epoch_secrets,
284 #[cfg(feature = "extensions-draft-08")]
285 application_exporter,
286 } = self.derive_epoch_secrets(
287 provider,
288 apply_proposals_values,
289 self.group_epoch_secrets(),
290 commit_secret,
291 &serialized_provisional_group_context,
292 )?;
293 let (provisional_group_secrets, provisional_message_secrets) = epoch_secrets.split_secrets(
294 serialized_provisional_group_context,
295 diff.tree_size(),
296 self.own_leaf_index(),
297 );
298
299 let own_confirmation_tag = provisional_message_secrets
302 .confirmation_key()
303 .tag(
304 provider.crypto(),
305 self.ciphersuite(),
306 diff.group_context().confirmed_transcript_hash(),
307 )
308 .map_err(LibraryError::unexpected_crypto_error)?;
309 if &own_confirmation_tag != received_confirmation_tag {
310 log::error!("Confirmation tag mismatch");
311 log_crypto!(trace, " Got: {:x?}", received_confirmation_tag);
312 log_crypto!(trace, " Expected: {:x?}", own_confirmation_tag);
313 if !crate::skip_validation::is_disabled::confirmation_tag() {
320 return Err(StageCommitError::ConfirmationTagMismatch);
321 }
322 }
323
324 diff.update_interim_transcript_hash(ciphersuite, provider.crypto(), own_confirmation_tag)?;
325
326 let staged_diff = diff.into_staged_diff(provider.crypto(), ciphersuite)?;
327 #[cfg(feature = "extensions-draft-08")]
328 let application_export_tree = ApplicationExportTree::new(application_exporter);
329 let staged_commit_state =
330 StagedCommitState::GroupMember(Box::new(MemberStagedCommitState::new(
331 provisional_group_secrets,
332 provisional_message_secrets,
333 staged_diff,
334 new_keypairs,
335 new_leaf_keypair_option,
336 update_path_leaf_node,
337 #[cfg(feature = "extensions-draft-08")]
338 application_export_tree,
339 )));
340 let staged_commit = StagedCommit::new(proposal_queue, staged_commit_state);
341
342 Ok(staged_commit)
343 }
344
345 pub(crate) fn merge_commit<Provider: OpenMlsProvider>(
351 &mut self,
352 provider: &Provider,
353 staged_commit: StagedCommit,
354 ) -> Result<(), MergeCommitError<Provider::StorageError>> {
355 let old_epoch_keypairs = self
358 .read_epoch_keypairs(provider.storage())
359 .map_err(MergeCommitError::StorageError)?;
360 match staged_commit.state {
361 StagedCommitState::PublicState(staged_state) => {
362 self.public_group
363 .merge_diff(staged_state.into_staged_diff());
364 self.store(provider.storage())
365 .map_err(MergeCommitError::StorageError)?;
366 Ok(())
367 }
368 StagedCommitState::GroupMember(state) => {
369 let past_epoch = self.context().epoch();
371 let leaves = self.public_group().members().collect();
373 self.group_epoch_secrets = state.group_epoch_secrets;
376
377 let mut message_secrets = state.message_secrets;
379 mem::swap(
380 &mut message_secrets,
381 self.message_secrets_store.message_secrets_mut(),
382 );
383 self.message_secrets_store
384 .add(past_epoch, message_secrets, leaves);
385
386 #[cfg(feature = "extensions-draft-08")]
388 {
389 if let Some(application_export_tree) = state.application_export_tree {
393 use openmls_traits::storage::StorageProvider as _;
396 provider
397 .storage()
398 .write_application_export_tree(
399 self.group_id(),
400 &application_export_tree,
401 )
402 .map_err(MergeCommitError::StorageError)?;
403
404 self.application_export_tree = Some(application_export_tree);
405 }
406 }
407
408 self.public_group.merge_diff(state.staged_diff);
409
410 let leaf_keypair = if let Some(keypair) = &state.new_leaf_keypair_option {
411 vec![keypair.clone()]
412 } else {
413 vec![]
414 };
415
416 let new_owned_encryption_keys = self
418 .public_group()
419 .owned_encryption_keys(self.own_leaf_index());
420 let epoch_keypairs: Vec<EncryptionKeyPair> = old_epoch_keypairs
422 .into_iter()
423 .chain(state.new_keypairs)
424 .chain(leaf_keypair)
425 .filter(|keypair| new_owned_encryption_keys.contains(keypair.public_key()))
426 .collect();
427
428 debug_assert_eq!(new_owned_encryption_keys.len(), epoch_keypairs.len());
430 if new_owned_encryption_keys.len() != epoch_keypairs.len() {
431 return Err(LibraryError::custom(
432 "We should have all the private key material we need.",
433 )
434 .into());
435 }
436
437 let storage = provider.storage();
439 let group_id = self.group_id();
440
441 self.public_group
442 .store(storage)
443 .map_err(MergeCommitError::StorageError)?;
444 storage
445 .write_group_epoch_secrets(group_id, &self.group_epoch_secrets)
446 .map_err(MergeCommitError::StorageError)?;
447 storage
448 .write_message_secrets(group_id, &self.message_secrets_store)
449 .map_err(MergeCommitError::StorageError)?;
450
451 self.store_epoch_keypairs(storage, epoch_keypairs.as_slice())
453 .map_err(MergeCommitError::StorageError)?;
454
455 self.delete_previous_epoch_keypairs(storage)
457 .map_err(MergeCommitError::StorageError)?;
458 if let Some(keypair) = state.new_leaf_keypair_option {
459 keypair
460 .delete(storage)
461 .map_err(MergeCommitError::StorageError)?;
462 }
463
464 storage
466 .clear_proposal_queue::<GroupId, ProposalRef>(group_id)
467 .map_err(MergeCommitError::StorageError)?;
468 self.proposal_store_mut().empty();
469
470 Ok(())
471 }
472 }
473 }
474}
475
476#[derive(Debug, Serialize, Deserialize)]
478#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
479pub(crate) enum StagedCommitState {
480 PublicState(Box<PublicStagedCommitState>),
482 GroupMember(Box<MemberStagedCommitState>),
484}
485
486#[derive(Debug, Serialize, Deserialize)]
488#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
489pub struct StagedCommit {
490 pub staged_proposal_queue: ProposalQueue,
492 state: StagedCommitState,
494}
495
496impl StagedCommit {
497 pub(crate) fn new(staged_proposal_queue: ProposalQueue, state: StagedCommitState) -> Self {
500 StagedCommit {
501 staged_proposal_queue,
502 state,
503 }
504 }
505
506 pub fn add_proposals(&self) -> impl Iterator<Item = QueuedAddProposal<'_>> {
508 self.staged_proposal_queue.add_proposals()
509 }
510
511 pub fn remove_proposals(&self) -> impl Iterator<Item = QueuedRemoveProposal<'_>> {
513 self.staged_proposal_queue.remove_proposals()
514 }
515
516 pub fn update_proposals(&self) -> impl Iterator<Item = QueuedUpdateProposal<'_>> {
518 self.staged_proposal_queue.update_proposals()
519 }
520
521 pub fn psk_proposals(&self) -> impl Iterator<Item = QueuedPskProposal<'_>> {
523 self.staged_proposal_queue.psk_proposals()
524 }
525
526 #[cfg(feature = "extensions-draft-08")]
527 pub fn queued_app_ephemeral_proposals(
530 &self,
531 ) -> impl Iterator<Item = QueuedAppEphemeralProposal<'_>> {
532 self.staged_proposal_queue.app_ephemeral_proposals()
533 }
534
535 pub fn queued_proposals(&self) -> impl Iterator<Item = &QueuedProposal> {
537 self.staged_proposal_queue.queued_proposals()
538 }
539
540 pub fn update_path_leaf_node(&self) -> Option<&LeafNode> {
542 match self.state {
543 StagedCommitState::PublicState(ref public_state) => {
544 public_state.update_path_leaf_node()
545 }
546 StagedCommitState::GroupMember(ref group_member_state) => {
547 group_member_state.update_path_leaf_node.as_ref()
548 }
549 }
550 }
551
552 pub fn credentials_to_verify(&self) -> impl Iterator<Item = &Credential> {
554 let update_path_leaf_node_cred = if let Some(node) = self.update_path_leaf_node() {
555 vec![node.credential()]
556 } else {
557 vec![]
558 };
559
560 update_path_leaf_node_cred
561 .into_iter()
562 .chain(
563 self.queued_proposals()
564 .flat_map(|proposal: &QueuedProposal| match proposal.proposal() {
565 Proposal::Update(update_proposal) => {
566 vec![update_proposal.leaf_node().credential()].into_iter()
567 }
568 Proposal::Add(add_proposal) => {
569 vec![add_proposal.key_package().leaf_node().credential()].into_iter()
570 }
571 Proposal::GroupContextExtensions(gce_proposal) => gce_proposal
572 .extensions()
573 .iter()
574 .flat_map(|extension| {
575 match extension {
576 Extension::ExternalSenders(external_senders) => {
577 external_senders
578 .iter()
579 .map(|external_sender| external_sender.credential())
580 .collect()
581 }
582 _ => vec![],
583 }
584 .into_iter()
585 })
586 .collect::<Vec<_>>()
593 .into_iter(),
594 _ => vec![].into_iter(),
595 }),
596 )
597 }
598
599 pub fn self_removed(&self) -> bool {
602 matches!(self.state, StagedCommitState::PublicState(_))
603 }
604
605 pub fn group_context(&self) -> &GroupContext {
607 match self.state {
608 StagedCommitState::PublicState(ref ps) => ps.staged_diff().group_context(),
609 StagedCommitState::GroupMember(ref gm) => gm.group_context(),
610 }
611 }
612 pub(crate) fn into_state(self) -> StagedCommitState {
614 self.state
615 }
616
617 pub fn epoch_authenticator(&self) -> Option<&EpochAuthenticator> {
621 if let StagedCommitState::GroupMember(ref gm) = self.state {
622 Some(gm.group_epoch_secrets.epoch_authenticator())
623 } else {
624 None
625 }
626 }
627
628 #[cfg(feature = "extensions-draft-08")]
629 pub(crate) fn safe_export_secret(
630 &mut self,
631 crypto: &impl OpenMlsCrypto,
632 component_id: u16,
633 ) -> Result<Vec<u8>, StagedSafeExportSecretError> {
634 let ciphersuite = self.group_context().ciphersuite();
635 let StagedCommitState::GroupMember(ref mut staged_commit) = self.state else {
636 return Err(StagedSafeExportSecretError::NotGroupMember);
637 };
638 let Some(application_export_tree) = staged_commit.application_export_tree.as_mut() else {
639 return Err(StagedSafeExportSecretError::Unsupported);
640 };
641 let secret =
642 application_export_tree.safe_export_secret(crypto, ciphersuite, component_id)?;
643 Ok(secret.as_slice().to_vec())
644 }
645}
646
647#[derive(Debug, Serialize, Deserialize)]
649#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
650pub(crate) struct MemberStagedCommitState {
651 group_epoch_secrets: GroupEpochSecrets,
652 message_secrets: MessageSecrets,
653 staged_diff: StagedPublicGroupDiff,
654 new_keypairs: Vec<EncryptionKeyPair>,
655 new_leaf_keypair_option: Option<EncryptionKeyPair>,
656 update_path_leaf_node: Option<LeafNode>,
657 #[cfg(feature = "extensions-draft-08")]
658 #[serde(default)]
659 application_export_tree: Option<ApplicationExportTree>,
662}
663
664impl MemberStagedCommitState {
665 pub(crate) fn new(
666 group_epoch_secrets: GroupEpochSecrets,
667 message_secrets: MessageSecrets,
668 staged_diff: StagedPublicGroupDiff,
669 new_keypairs: Vec<EncryptionKeyPair>,
670 new_leaf_keypair_option: Option<EncryptionKeyPair>,
671 update_path_leaf_node: Option<LeafNode>,
672 #[cfg(feature = "extensions-draft-08")] application_export_tree: ApplicationExportTree,
673 ) -> Self {
674 Self {
675 group_epoch_secrets,
676 message_secrets,
677 staged_diff,
678 new_keypairs,
679 new_leaf_keypair_option,
680 update_path_leaf_node,
681 #[cfg(feature = "extensions-draft-08")]
682 application_export_tree: Some(application_export_tree),
683 }
684 }
685
686 pub(crate) fn group_context(&self) -> &GroupContext {
688 self.staged_diff.group_context()
689 }
690}