1use errors::NewGroupError;
2use openmls_traits::{crypto::OpenMlsCrypto, storage::StorageProvider as StorageProviderTrait};
3
4use super::{builder::MlsGroupBuilder, *};
5use crate::{
6 credentials::CredentialWithKey,
7 extensions::Extensions,
8 group::{
9 commit_builder::external_commits::ExternalCommitBuilder,
10 errors::{ExportSecretError, ExternalCommitError, WelcomeError},
11 },
12 messages::{
13 group_info::{GroupInfo, VerifiableGroupInfo},
14 Welcome,
15 },
16 schedule::{
17 psk::{store::ResumptionPskStore, PreSharedKeyId},
18 EpochSecretsResult,
19 },
20 storage::OpenMlsProvider,
21 treesync::{
22 errors::{DerivePathError, PublicTreeError},
23 node::leaf_node::{Capabilities, LeafNodeParameters},
24 RatchetTreeIn,
25 },
26};
27
28#[cfg(doc)]
29use crate::key_packages::KeyPackage;
30
31impl MlsGroup {
32 pub fn builder() -> MlsGroupBuilder {
37 MlsGroupBuilder::new()
38 }
39
40 pub fn new<Provider: OpenMlsProvider>(
46 provider: &Provider,
47 signer: &impl Signer,
48 mls_group_create_config: &MlsGroupCreateConfig,
49 credential_with_key: CredentialWithKey,
50 ) -> Result<Self, NewGroupError<Provider::StorageError>> {
51 MlsGroupBuilder::new().build_internal(
52 provider,
53 signer,
54 credential_with_key,
55 Some(mls_group_create_config.clone()),
56 )
57 }
58
59 pub fn new_with_group_id<Provider: OpenMlsProvider>(
62 provider: &Provider,
63 signer: &impl Signer,
64 mls_group_create_config: &MlsGroupCreateConfig,
65 group_id: GroupId,
66 credential_with_key: CredentialWithKey,
67 ) -> Result<Self, NewGroupError<Provider::StorageError>> {
68 MlsGroupBuilder::new()
69 .with_group_id(group_id)
70 .build_internal(
71 provider,
72 signer,
73 credential_with_key,
74 Some(mls_group_create_config.clone()),
75 )
76 }
77
78 #[allow(clippy::too_many_arguments)]
93 #[deprecated(
94 since = "0.7.1",
95 note = "Use the `MlsGroup::external_commit_builder` instead."
96 )]
97 pub fn join_by_external_commit<Provider: OpenMlsProvider>(
98 provider: &Provider,
99 signer: &impl Signer,
100 ratchet_tree: Option<RatchetTreeIn>,
101 verifiable_group_info: VerifiableGroupInfo,
102 mls_group_config: &MlsGroupJoinConfig,
103 capabilities: Option<Capabilities>,
104 extensions: Option<Extensions<LeafNode>>,
105 aad: &[u8],
106 credential_with_key: CredentialWithKey,
107 ) -> Result<(Self, MlsMessageOut, Option<GroupInfo>), ExternalCommitError<Provider::StorageError>>
108 {
109 let leaf_node_parameters = LeafNodeParameters::builder()
110 .with_capabilities(capabilities.unwrap_or_default())
111 .with_extensions(extensions.unwrap_or_default())
112 .build();
113
114 let mut external_commit_builder = ExternalCommitBuilder::new()
115 .with_aad(aad.to_vec())
116 .with_config(mls_group_config.clone());
117
118 if let Some(ratchet_tree) = ratchet_tree {
119 external_commit_builder = external_commit_builder.with_ratchet_tree(ratchet_tree)
120 }
121
122 let (mls_group, commit_message_bundle) = external_commit_builder
123 .build_group(provider, verifiable_group_info, credential_with_key)?
124 .leaf_node_parameters(leaf_node_parameters)
125 .load_psks(provider.storage())
126 .map_err(|e| {
127 log::error!("Error loading PSKs for external commit: {e:?}");
128 LibraryError::custom("Error loading PSKs for external commit")
129 })?
130 .build(provider.rand(), provider.crypto(), signer, |_| true)?
131 .finalize(provider)?;
132
133 let (commit, _, group_info) = commit_message_bundle.into_contents();
134
135 Ok((mls_group, commit, group_info))
136 }
137}
138
139impl ProcessedWelcome {
140 pub fn new_from_welcome<Provider: OpenMlsProvider>(
147 provider: &Provider,
148 mls_group_config: &MlsGroupJoinConfig,
149 welcome: Welcome,
150 ) -> Result<Self, WelcomeError<Provider::StorageError>> {
151 let (resumption_psk_store, key_package_bundle) =
152 keys_for_welcome(mls_group_config, &welcome, provider)?;
153
154 let ciphersuite = welcome.ciphersuite();
155 let Some(egs) = welcome.find_encrypted_group_secret(
156 key_package_bundle
157 .key_package()
158 .hash_ref(provider.crypto())?,
159 ) else {
160 return Err(WelcomeError::JoinerSecretNotFound);
161 };
162
163 if welcome.ciphersuite() != key_package_bundle.key_package().ciphersuite() {
166 let e = WelcomeError::CiphersuiteMismatch;
167 log::debug!("new_from_welcome {e:?}");
168 return Err(e);
169 }
170
171 let group_secrets = GroupSecrets::try_from_ciphertext(
172 key_package_bundle.init_private_key(),
173 egs.encrypted_group_secrets(),
174 welcome.encrypted_group_info(),
175 ciphersuite,
176 provider.crypto(),
177 )?;
178
179 PreSharedKeyId::validate_in_welcome(&group_secrets.psks, ciphersuite)?;
181
182 let psk_secret = {
183 let psks = load_psks(
184 provider.storage(),
185 &resumption_psk_store,
186 &group_secrets.psks,
187 )?;
188
189 PskSecret::new(provider.crypto(), ciphersuite, psks)?
190 };
191 let key_schedule = KeySchedule::init(
192 ciphersuite,
193 provider.crypto(),
194 &group_secrets.joiner_secret,
195 psk_secret,
196 )?;
197 let (welcome_key, welcome_nonce) = key_schedule
198 .welcome(provider.crypto(), ciphersuite)
199 .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?
200 .derive_welcome_key_nonce(provider.crypto(), ciphersuite)
201 .map_err(LibraryError::unexpected_crypto_error)?;
202 let verifiable_group_info = VerifiableGroupInfo::try_from_ciphertext(
203 &welcome_key,
204 &welcome_nonce,
205 welcome.encrypted_group_info(),
206 &[],
207 provider.crypto(),
208 )?;
209
210 if let Some(required_capabilities) =
211 verifiable_group_info.extensions().required_capabilities()
212 {
213 key_package_bundle
216 .key_package()
217 .leaf_node()
218 .capabilities()
219 .supports_required_capabilities(required_capabilities)?;
220 }
221
222 if verifiable_group_info.ciphersuite() != key_package_bundle.key_package().ciphersuite() {
226 let e = WelcomeError::CiphersuiteMismatch;
227 log::debug!("new_from_welcome {e:?}");
228 return Err(e);
229 }
230
231 Ok(Self {
232 mls_group_config: mls_group_config.clone(),
233 ciphersuite,
234 group_secrets,
235 key_schedule,
236 verifiable_group_info,
237 resumption_psk_store,
238 key_package_bundle,
239 })
240 }
241
242 pub fn unverified_group_info(&self) -> &VerifiableGroupInfo {
246 &self.verifiable_group_info
247 }
248
249 pub fn psks(&self) -> &[PreSharedKeyId] {
253 &self.group_secrets.psks
254 }
255
256 pub fn into_staged_welcome<Provider: OpenMlsProvider>(
259 self,
260 provider: &Provider,
261 ratchet_tree: Option<RatchetTreeIn>,
262 ) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
263 self.into_staged_welcome_inner(
264 provider,
265 ratchet_tree,
266 LeafNodeLifetimePolicy::Verify,
267 false,
268 )
269 }
270
271 pub(crate) fn into_staged_welcome_inner<Provider: OpenMlsProvider>(
274 mut self,
275 provider: &Provider,
276 ratchet_tree: Option<RatchetTreeIn>,
277 validate_lifetimes: LeafNodeLifetimePolicy,
278 replace_old_group: bool,
279 ) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
280 if !replace_old_group
282 && MlsGroup::load(provider.storage(), self.verifiable_group_info.group_id())
283 .map_err(WelcomeError::StorageError)?
284 .is_some()
285 {
286 return Err(WelcomeError::GroupAlreadyExists);
287 }
288
289 let ratchet_tree = match self.verifiable_group_info.extensions().ratchet_tree() {
296 Some(extension) => extension.ratchet_tree().clone(),
297 None => match ratchet_tree {
298 Some(ratchet_tree) => ratchet_tree,
299 None => return Err(WelcomeError::MissingRatchetTree),
300 },
301 };
302
303 let (public_group, _group_info_extensions) = PublicGroup::from_ratchet_tree(
306 provider.crypto(),
307 ratchet_tree,
308 self.verifiable_group_info.clone(),
309 ProposalStore::new(),
310 validate_lifetimes,
311 )?;
312
313 let added_leaf_supports_all_group_context_extensions = public_group
317 .group_context()
318 .extensions()
319 .iter()
320 .all(|extension| {
321 self.key_package_bundle
322 .key_package
323 .leaf_node()
324 .supports_extension(&extension.extension_type())
325 });
326 if !added_leaf_supports_all_group_context_extensions {
327 return Err(WelcomeError::UnsupportedExtensions);
328 }
329
330 let own_leaf_index = public_group
332 .members()
333 .find_map(|m| {
334 if m.signature_key
335 == self
336 .key_package_bundle
337 .key_package()
338 .leaf_node()
339 .signature_key()
340 .as_slice()
341 {
342 Some(m.index)
343 } else {
344 None
345 }
346 })
347 .ok_or(WelcomeError::PublicTreeError(
348 PublicTreeError::MalformedTree,
349 ))?;
350
351 struct KeyScheduleResult {
352 group_epoch_secrets: GroupEpochSecrets,
353 message_secrets: MessageSecrets,
354 #[cfg(feature = "extensions-draft-08")]
355 application_exporter: ApplicationExportSecret,
356 }
357 let KeyScheduleResult {
358 group_epoch_secrets,
359 message_secrets,
360 #[cfg(feature = "extensions-draft-08")]
361 application_exporter: application_export_secret,
362 } = {
363 let serialized_group_context = public_group
364 .group_context()
365 .tls_serialize_detached()
366 .map_err(LibraryError::missing_bound_check)?;
367
368 self.key_schedule
370 .add_context(provider.crypto(), &serialized_group_context)
371 .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
372
373 let EpochSecretsResult {
374 epoch_secrets,
375 #[cfg(feature = "extensions-draft-08")]
376 application_exporter,
377 } = self
378 .key_schedule
379 .epoch_secrets(provider.crypto(), self.ciphersuite)
380 .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
381
382 let (group_epoch_secrets, message_secrets) = epoch_secrets.split_secrets(
383 serialized_group_context,
384 public_group.tree_size(),
385 own_leaf_index,
386 );
387
388 KeyScheduleResult {
389 group_epoch_secrets,
390 message_secrets,
391 #[cfg(feature = "extensions-draft-08")]
392 application_exporter,
393 }
394 };
395
396 let confirmation_tag = message_secrets
397 .confirmation_key()
398 .tag(
399 provider.crypto(),
400 self.ciphersuite,
401 public_group.group_context().confirmed_transcript_hash(),
402 )
403 .map_err(LibraryError::unexpected_crypto_error)?;
404
405 if &confirmation_tag != public_group.confirmation_tag() {
408 log::error!("Confirmation tag mismatch");
409 log_crypto!(trace, " Got: {:x?}", confirmation_tag);
410 log_crypto!(trace, " Expected: {:x?}", public_group.confirmation_tag());
411
412 if !crate::skip_validation::is_disabled::confirmation_tag() {
415 return Err(WelcomeError::ConfirmationTagMismatch);
416 }
417 }
418
419 let message_secrets_store = MessageSecretsStore::new_with_secret(
420 &PastEpochDeletionPolicy::MaxEpochs(0),
421 message_secrets,
422 );
423
424 let resumption_psk = group_epoch_secrets.resumption_psk();
426 self.resumption_psk_store
427 .add(public_group.group_context().epoch(), resumption_psk.clone());
428
429 let welcome_sender_index = self.verifiable_group_info.signer();
430 let path_keypairs = if let Some(path_secret) = self.group_secrets.path_secret {
431 let (path_keypairs, _commit_secret) = public_group
432 .derive_path_secrets(
433 provider.crypto(),
434 self.ciphersuite,
435 path_secret,
436 welcome_sender_index,
437 own_leaf_index,
438 )
439 .map_err(|e| match e {
440 DerivePathError::LibraryError(e) => e.into(),
441 DerivePathError::PublicKeyMismatch => {
442 WelcomeError::PublicTreeError(PublicTreeError::PublicKeyMismatch)
443 }
444 })?;
445 Some(path_keypairs)
446 } else {
447 None
448 };
449
450 let staged_welcome = StagedWelcome {
451 mls_group_config: self.mls_group_config,
452 public_group,
453 group_epoch_secrets,
454 own_leaf_index,
455 message_secrets_store,
456 #[cfg(feature = "extensions-draft-08")]
457 application_export_secret,
458 resumption_psk_store: self.resumption_psk_store,
459 verifiable_group_info: self.verifiable_group_info,
460 key_package_bundle: self.key_package_bundle,
461 path_keypairs,
462 };
463
464 Ok(staged_welcome)
465 }
466}
467
468impl StagedWelcome {
469 pub fn new_from_welcome<Provider: OpenMlsProvider>(
477 provider: &Provider,
478 mls_group_config: &MlsGroupJoinConfig,
479 welcome: Welcome,
480 ratchet_tree: Option<RatchetTreeIn>,
481 ) -> Result<Self, WelcomeError<Provider::StorageError>> {
482 let processed_welcome =
483 ProcessedWelcome::new_from_welcome(provider, mls_group_config, welcome)?;
484
485 processed_welcome.into_staged_welcome(provider, ratchet_tree)
486 }
487
488 pub fn build_from_welcome<'a, Provider: OpenMlsProvider>(
493 provider: &'a Provider,
494 mls_group_config: &MlsGroupJoinConfig,
495 welcome: Welcome,
496 ) -> Result<JoinBuilder<'a, Provider>, WelcomeError<Provider::StorageError>> {
498 let processed_welcome =
499 ProcessedWelcome::new_from_welcome(provider, mls_group_config, welcome)?;
500
501 Ok(JoinBuilder::new(provider, processed_welcome))
503 }
504
505 pub fn welcome_sender_index(&self) -> LeafNodeIndex {
509 self.verifiable_group_info.signer()
510 }
511
512 pub fn welcome_sender(&self) -> Result<&LeafNode, LibraryError> {
516 let sender_index = self.welcome_sender_index();
517 self.public_group
518 .leaf(sender_index)
519 .ok_or(LibraryError::custom(
520 "no leaf with given welcome sender index exists",
521 ))
522 }
523
524 pub fn group_context(&self) -> &GroupContext {
526 self.public_group.group_context()
527 }
528
529 pub fn members(&self) -> impl Iterator<Item = Member> + '_ {
531 self.public_group.members()
532 }
533
534 #[cfg(feature = "extensions-draft-08")]
536 pub fn application_export_secret(&self) -> &ApplicationExportSecret {
537 &self.application_export_secret
538 }
539
540 pub fn into_group<Provider: OpenMlsProvider>(
542 self,
543 provider: &Provider,
544 ) -> Result<MlsGroup, WelcomeError<Provider::StorageError>> {
545 let group_keypairs = if let Some(path_keypairs) = self.path_keypairs {
548 let mut keypairs = vec![self.key_package_bundle.encryption_key_pair()];
549 keypairs.extend_from_slice(&path_keypairs);
550 keypairs
551 } else {
552 vec![self.key_package_bundle.encryption_key_pair()]
553 };
554
555 #[cfg(feature = "extensions-draft-08")]
556 let application_export_tree = ApplicationExportTree::new(self.application_export_secret);
557
558 let past_epoch_deletion_policy = self.mls_group_config.past_epoch_deletion_policy().clone();
559
560 let mut mls_group = MlsGroup {
561 mls_group_config: self.mls_group_config,
562 own_leaf_nodes: vec![],
563 aad: vec![],
564 group_state: MlsGroupState::Operational,
565 public_group: self.public_group,
566 group_epoch_secrets: self.group_epoch_secrets,
567 own_leaf_index: self.own_leaf_index,
568 message_secrets_store: self.message_secrets_store,
569 resumption_psk_store: self.resumption_psk_store,
570 #[cfg(feature = "extensions-draft-08")]
571 application_export_tree: Some(application_export_tree),
572 };
573
574 mls_group
575 .store_epoch_keypairs(provider.storage(), group_keypairs.as_slice())
576 .map_err(WelcomeError::StorageError)?;
577 mls_group.resize_message_secrets_store(&past_epoch_deletion_policy);
579
580 mls_group
581 .store(provider.storage())
582 .map_err(WelcomeError::StorageError)?;
583
584 Ok(mls_group)
585 }
586
587 pub fn export_secret<CryptoProvider: OpenMlsCrypto>(
592 &self,
593 crypto: &CryptoProvider,
594 label: &str,
595 context: &[u8],
596 key_length: usize,
597 ) -> Result<Vec<u8>, ExportSecretError> {
598 if key_length > u16::MAX as usize {
599 log::error!("Got a key that is larger than u16::MAX");
600 return Err(ExportSecretError::KeyLengthTooLong);
601 }
602
603 Ok(self
604 .group_epoch_secrets
605 .exporter_secret()
606 .derive_exported_secret(
607 self.group_context().ciphersuite(),
608 crypto,
609 label,
610 context,
611 key_length,
612 )
613 .map_err(LibraryError::unexpected_crypto_error)?)
614 }
615}
616
617fn keys_for_welcome<Provider: OpenMlsProvider>(
618 mls_group_config: &MlsGroupJoinConfig,
619 welcome: &Welcome,
620 provider: &Provider,
621) -> Result<
622 (ResumptionPskStore, KeyPackageBundle),
623 WelcomeError<<Provider as OpenMlsProvider>::StorageError>,
624> {
625 let resumption_psk_store = ResumptionPskStore::new(mls_group_config.number_of_resumption_psks);
626 let key_package_bundle: KeyPackageBundle = welcome
627 .secrets()
628 .iter()
629 .find_map(|egs| {
630 let hash_ref = egs.new_member();
631
632 provider
633 .storage()
634 .key_package(&hash_ref)
635 .map_err(WelcomeError::StorageError)
636 .transpose()
637 })
638 .ok_or(WelcomeError::NoMatchingKeyPackage)??;
639 if !key_package_bundle.key_package().last_resort() {
640 provider
641 .storage()
642 .delete_key_package(&key_package_bundle.key_package.hash_ref(provider.crypto())?)
643 .map_err(WelcomeError::StorageError)?;
644 } else {
645 log::debug!("Key package has last resort extension, not deleting");
646 }
647 Ok((resumption_psk_store, key_package_bundle))
648}
649
650#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
653pub enum LeafNodeLifetimePolicy {
654 #[default]
658 Verify,
659
660 Skip,
662}
663
664pub struct JoinBuilder<'a, Provider: OpenMlsProvider> {
668 provider: &'a Provider,
669 processed_welcome: ProcessedWelcome,
670 ratchet_tree: Option<RatchetTreeIn>,
671 validate_lifetimes: LeafNodeLifetimePolicy,
672 replace_old_group: bool,
673}
674
675impl<'a, Provider: OpenMlsProvider> JoinBuilder<'a, Provider> {
676 pub fn new(provider: &'a Provider, processed_welcome: ProcessedWelcome) -> Self {
678 Self {
679 provider,
680 processed_welcome,
681 ratchet_tree: None,
682 replace_old_group: false,
683 validate_lifetimes: LeafNodeLifetimePolicy::Verify,
684 }
685 }
686
687 pub fn with_ratchet_tree(mut self, ratchet_tree: RatchetTreeIn) -> Self {
689 self.ratchet_tree = Some(ratchet_tree);
690 self
691 }
692
693 pub fn replace_old_group(mut self) -> Self {
695 self.replace_old_group = true;
696 self
697 }
698
699 pub fn skip_lifetime_validation(mut self) -> Self {
704 self.validate_lifetimes = LeafNodeLifetimePolicy::Skip;
705 self
706 }
707
708 pub fn processed_welcome(&self) -> &ProcessedWelcome {
712 &self.processed_welcome
713 }
714
715 pub fn build(self) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
717 self.processed_welcome.into_staged_welcome_inner(
718 self.provider,
719 self.ratchet_tree,
720 self.validate_lifetimes,
721 self.replace_old_group,
722 )
723 }
724}