1use errors::NewGroupError;
2use openmls_traits::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::{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 debug_assert!(false, "Confirmation tag mismatch");
412
413 if !crate::skip_validation::is_disabled::confirmation_tag() {
416 return Err(WelcomeError::ConfirmationTagMismatch);
417 }
418 }
419
420 let message_secrets_store = MessageSecretsStore::new_with_secret(0, message_secrets);
421
422 let resumption_psk = group_epoch_secrets.resumption_psk();
424 self.resumption_psk_store
425 .add(public_group.group_context().epoch(), resumption_psk.clone());
426
427 let welcome_sender_index = self.verifiable_group_info.signer();
428 let path_keypairs = if let Some(path_secret) = self.group_secrets.path_secret {
429 let (path_keypairs, _commit_secret) = public_group
430 .derive_path_secrets(
431 provider.crypto(),
432 self.ciphersuite,
433 path_secret,
434 welcome_sender_index,
435 own_leaf_index,
436 )
437 .map_err(|e| match e {
438 DerivePathError::LibraryError(e) => e.into(),
439 DerivePathError::PublicKeyMismatch => {
440 WelcomeError::PublicTreeError(PublicTreeError::PublicKeyMismatch)
441 }
442 })?;
443 Some(path_keypairs)
444 } else {
445 None
446 };
447
448 let staged_welcome = StagedWelcome {
449 mls_group_config: self.mls_group_config,
450 public_group,
451 group_epoch_secrets,
452 own_leaf_index,
453 message_secrets_store,
454 #[cfg(feature = "extensions-draft-08")]
455 application_export_secret,
456 resumption_psk_store: self.resumption_psk_store,
457 verifiable_group_info: self.verifiable_group_info,
458 key_package_bundle: self.key_package_bundle,
459 path_keypairs,
460 };
461
462 Ok(staged_welcome)
463 }
464}
465
466impl StagedWelcome {
467 pub fn new_from_welcome<Provider: OpenMlsProvider>(
475 provider: &Provider,
476 mls_group_config: &MlsGroupJoinConfig,
477 welcome: Welcome,
478 ratchet_tree: Option<RatchetTreeIn>,
479 ) -> Result<Self, WelcomeError<Provider::StorageError>> {
480 let processed_welcome =
481 ProcessedWelcome::new_from_welcome(provider, mls_group_config, welcome)?;
482
483 processed_welcome.into_staged_welcome(provider, ratchet_tree)
484 }
485
486 pub fn build_from_welcome<'a, Provider: OpenMlsProvider>(
491 provider: &'a Provider,
492 mls_group_config: &MlsGroupJoinConfig,
493 welcome: Welcome,
494 ) -> Result<JoinBuilder<'a, Provider>, WelcomeError<Provider::StorageError>> {
496 let processed_welcome =
497 ProcessedWelcome::new_from_welcome(provider, mls_group_config, welcome)?;
498
499 Ok(JoinBuilder::new(provider, processed_welcome))
501 }
502
503 pub fn welcome_sender_index(&self) -> LeafNodeIndex {
507 self.verifiable_group_info.signer()
508 }
509
510 pub fn welcome_sender(&self) -> Result<&LeafNode, LibraryError> {
514 let sender_index = self.welcome_sender_index();
515 self.public_group
516 .leaf(sender_index)
517 .ok_or(LibraryError::custom(
518 "no leaf with given welcome sender index exists",
519 ))
520 }
521
522 pub fn group_context(&self) -> &GroupContext {
524 self.public_group.group_context()
525 }
526
527 pub fn members(&self) -> impl Iterator<Item = Member> + '_ {
529 self.public_group.members()
530 }
531
532 #[cfg(feature = "extensions-draft-08")]
534 pub fn application_export_secret(&self) -> &ApplicationExportSecret {
535 &self.application_export_secret
536 }
537
538 pub fn into_group<Provider: OpenMlsProvider>(
540 self,
541 provider: &Provider,
542 ) -> Result<MlsGroup, WelcomeError<Provider::StorageError>> {
543 let group_keypairs = if let Some(path_keypairs) = self.path_keypairs {
546 let mut keypairs = vec![self.key_package_bundle.encryption_key_pair()];
547 keypairs.extend_from_slice(&path_keypairs);
548 keypairs
549 } else {
550 vec![self.key_package_bundle.encryption_key_pair()]
551 };
552
553 #[cfg(feature = "extensions-draft-08")]
554 let application_export_tree = ApplicationExportTree::new(self.application_export_secret);
555
556 let mut mls_group = MlsGroup {
557 mls_group_config: self.mls_group_config,
558 own_leaf_nodes: vec![],
559 aad: vec![],
560 group_state: MlsGroupState::Operational,
561 public_group: self.public_group,
562 group_epoch_secrets: self.group_epoch_secrets,
563 own_leaf_index: self.own_leaf_index,
564 message_secrets_store: self.message_secrets_store,
565 resumption_psk_store: self.resumption_psk_store,
566 #[cfg(feature = "extensions-draft-08")]
567 application_export_tree: Some(application_export_tree),
568 };
569
570 mls_group
571 .store_epoch_keypairs(provider.storage(), group_keypairs.as_slice())
572 .map_err(WelcomeError::StorageError)?;
573 mls_group.set_max_past_epochs(mls_group.mls_group_config.max_past_epochs);
574
575 mls_group
576 .store(provider.storage())
577 .map_err(WelcomeError::StorageError)?;
578
579 Ok(mls_group)
580 }
581}
582
583fn keys_for_welcome<Provider: OpenMlsProvider>(
584 mls_group_config: &MlsGroupJoinConfig,
585 welcome: &Welcome,
586 provider: &Provider,
587) -> Result<
588 (ResumptionPskStore, KeyPackageBundle),
589 WelcomeError<<Provider as OpenMlsProvider>::StorageError>,
590> {
591 let resumption_psk_store = ResumptionPskStore::new(mls_group_config.number_of_resumption_psks);
592 let key_package_bundle: KeyPackageBundle = welcome
593 .secrets()
594 .iter()
595 .find_map(|egs| {
596 let hash_ref = egs.new_member();
597
598 provider
599 .storage()
600 .key_package(&hash_ref)
601 .map_err(WelcomeError::StorageError)
602 .transpose()
603 })
604 .ok_or(WelcomeError::NoMatchingKeyPackage)??;
605 if !key_package_bundle.key_package().last_resort() {
606 provider
607 .storage()
608 .delete_key_package(&key_package_bundle.key_package.hash_ref(provider.crypto())?)
609 .map_err(WelcomeError::StorageError)?;
610 } else {
611 log::debug!("Key package has last resort extension, not deleting");
612 }
613 Ok((resumption_psk_store, key_package_bundle))
614}
615
616#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
619pub enum LeafNodeLifetimePolicy {
620 #[default]
624 Verify,
625
626 Skip,
628}
629
630pub struct JoinBuilder<'a, Provider: OpenMlsProvider> {
634 provider: &'a Provider,
635 processed_welcome: ProcessedWelcome,
636 ratchet_tree: Option<RatchetTreeIn>,
637 validate_lifetimes: LeafNodeLifetimePolicy,
638 replace_old_group: bool,
639}
640
641impl<'a, Provider: OpenMlsProvider> JoinBuilder<'a, Provider> {
642 pub fn new(provider: &'a Provider, processed_welcome: ProcessedWelcome) -> Self {
644 Self {
645 provider,
646 processed_welcome,
647 ratchet_tree: None,
648 replace_old_group: false,
649 validate_lifetimes: LeafNodeLifetimePolicy::Verify,
650 }
651 }
652
653 pub fn with_ratchet_tree(mut self, ratchet_tree: RatchetTreeIn) -> Self {
655 self.ratchet_tree = Some(ratchet_tree);
656 self
657 }
658
659 pub fn replace_old_group(mut self) -> Self {
661 self.replace_old_group = true;
662 self
663 }
664
665 pub fn skip_lifetime_validation(mut self) -> Self {
670 self.validate_lifetimes = LeafNodeLifetimePolicy::Skip;
671 self
672 }
673
674 pub fn processed_welcome(&self) -> &ProcessedWelcome {
678 &self.processed_welcome
679 }
680
681 pub fn build(self) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
683 self.processed_welcome.into_staged_welcome_inner(
684 self.provider,
685 self.ratchet_tree,
686 self.validate_lifetimes,
687 self.replace_old_group,
688 )
689 }
690}