1use errors::NewGroupError;
2use openmls_traits::storage::StorageProvider as StorageProviderTrait;
3
4use super::{builder::MlsGroupBuilder, *};
5use crate::{
6 credentials::CredentialWithKey,
7 group::{
8 commit_builder::external_commits::ExternalCommitBuilder,
9 errors::{CreateCommitError, ExternalCommitError, WelcomeError},
10 },
11 messages::{
12 group_info::{GroupInfo, VerifiableGroupInfo},
13 Welcome,
14 },
15 schedule::{
16 psk::{store::ResumptionPskStore, PreSharedKeyId},
17 EpochSecretsResult,
18 },
19 storage::OpenMlsProvider,
20 treesync::{
21 errors::{DerivePathError, PublicTreeError},
22 node::leaf_node::{Capabilities, LeafNodeParameters},
23 RatchetTreeIn,
24 },
25};
26
27#[cfg(doc)]
28use crate::key_packages::KeyPackage;
29
30impl MlsGroup {
31 pub fn builder() -> MlsGroupBuilder {
36 MlsGroupBuilder::new()
37 }
38
39 pub fn new<Provider: OpenMlsProvider>(
45 provider: &Provider,
46 signer: &impl Signer,
47 mls_group_create_config: &MlsGroupCreateConfig,
48 credential_with_key: CredentialWithKey,
49 ) -> Result<Self, NewGroupError<Provider::StorageError>> {
50 MlsGroupBuilder::new().build_internal(
51 provider,
52 signer,
53 credential_with_key,
54 Some(mls_group_create_config.clone()),
55 )
56 }
57
58 pub fn new_with_group_id<Provider: OpenMlsProvider>(
61 provider: &Provider,
62 signer: &impl Signer,
63 mls_group_create_config: &MlsGroupCreateConfig,
64 group_id: GroupId,
65 credential_with_key: CredentialWithKey,
66 ) -> Result<Self, NewGroupError<Provider::StorageError>> {
67 MlsGroupBuilder::new()
68 .with_group_id(group_id)
69 .build_internal(
70 provider,
71 signer,
72 credential_with_key,
73 Some(mls_group_create_config.clone()),
74 )
75 }
76
77 #[allow(clippy::too_many_arguments)]
92 #[deprecated(
93 since = "0.7.1",
94 note = "Use the `MlsGroup::external_commit_builder` instead."
95 )]
96 pub fn join_by_external_commit<Provider: OpenMlsProvider>(
97 provider: &Provider,
98 signer: &impl Signer,
99 ratchet_tree: Option<RatchetTreeIn>,
100 verifiable_group_info: VerifiableGroupInfo,
101 mls_group_config: &MlsGroupJoinConfig,
102 capabilities: Option<Capabilities>,
103 extensions: Option<Extensions>,
104 aad: &[u8],
105 credential_with_key: CredentialWithKey,
106 ) -> Result<(Self, MlsMessageOut, Option<GroupInfo>), ExternalCommitError<Provider::StorageError>>
107 {
108 let leaf_node_parameters = LeafNodeParameters::builder()
109 .with_capabilities(capabilities.unwrap_or_default())
110 .with_extensions(extensions.unwrap_or_default())
111 .map_err(CreateCommitError::from)?
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 if let Some(required_capabilities) =
210 verifiable_group_info.extensions().required_capabilities()
211 {
212 key_package_bundle
215 .key_package()
216 .leaf_node()
217 .capabilities()
218 .supports_required_capabilities(required_capabilities)?;
219 }
220
221 if verifiable_group_info.ciphersuite() != key_package_bundle.key_package().ciphersuite() {
225 let e = WelcomeError::CiphersuiteMismatch;
226 log::debug!("new_from_welcome {e:?}");
227 return Err(e);
228 }
229
230 Ok(Self {
231 mls_group_config: mls_group_config.clone(),
232 ciphersuite,
233 group_secrets,
234 key_schedule,
235 verifiable_group_info,
236 resumption_psk_store,
237 key_package_bundle,
238 })
239 }
240
241 pub fn unverified_group_info(&self) -> &VerifiableGroupInfo {
245 &self.verifiable_group_info
246 }
247
248 pub fn psks(&self) -> &[PreSharedKeyId] {
252 &self.group_secrets.psks
253 }
254
255 pub fn into_staged_welcome<Provider: OpenMlsProvider>(
258 self,
259 provider: &Provider,
260 ratchet_tree: Option<RatchetTreeIn>,
261 ) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
262 self.into_staged_welcome_inner(provider, ratchet_tree, LeafNodeLifetimePolicy::Verify)
263 }
264
265 pub(crate) fn into_staged_welcome_inner<Provider: OpenMlsProvider>(
268 mut self,
269 provider: &Provider,
270 ratchet_tree: Option<RatchetTreeIn>,
271 validate_lifetimes: LeafNodeLifetimePolicy,
272 ) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
273 let ratchet_tree = match self.verifiable_group_info.extensions().ratchet_tree() {
280 Some(extension) => extension.ratchet_tree().clone(),
281 None => match ratchet_tree {
282 Some(ratchet_tree) => ratchet_tree,
283 None => return Err(WelcomeError::MissingRatchetTree),
284 },
285 };
286
287 let (public_group, _group_info_extensions) = PublicGroup::from_ratchet_tree(
290 provider.crypto(),
291 ratchet_tree,
292 self.verifiable_group_info.clone(),
293 ProposalStore::new(),
294 validate_lifetimes,
295 )?;
296
297 let own_leaf_index = public_group
299 .members()
300 .find_map(|m| {
301 if m.signature_key
302 == self
303 .key_package_bundle
304 .key_package()
305 .leaf_node()
306 .signature_key()
307 .as_slice()
308 {
309 Some(m.index)
310 } else {
311 None
312 }
313 })
314 .ok_or(WelcomeError::PublicTreeError(
315 PublicTreeError::MalformedTree,
316 ))?;
317
318 struct KeyScheduleResult {
319 group_epoch_secrets: GroupEpochSecrets,
320 message_secrets: MessageSecrets,
321 #[cfg(feature = "extensions-draft-08")]
322 application_exporter: ApplicationExportSecret,
323 }
324 let KeyScheduleResult {
325 group_epoch_secrets,
326 message_secrets,
327 #[cfg(feature = "extensions-draft-08")]
328 application_exporter: application_export_secret,
329 } = {
330 let serialized_group_context = public_group
331 .group_context()
332 .tls_serialize_detached()
333 .map_err(LibraryError::missing_bound_check)?;
334
335 self.key_schedule
337 .add_context(provider.crypto(), &serialized_group_context)
338 .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
339
340 let EpochSecretsResult {
341 epoch_secrets,
342 #[cfg(feature = "extensions-draft-08")]
343 application_exporter,
344 } = self
345 .key_schedule
346 .epoch_secrets(provider.crypto(), self.ciphersuite)
347 .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
348
349 let (group_epoch_secrets, message_secrets) = epoch_secrets.split_secrets(
350 serialized_group_context,
351 public_group.tree_size(),
352 own_leaf_index,
353 );
354
355 KeyScheduleResult {
356 group_epoch_secrets,
357 message_secrets,
358 #[cfg(feature = "extensions-draft-08")]
359 application_exporter,
360 }
361 };
362
363 let confirmation_tag = message_secrets
364 .confirmation_key()
365 .tag(
366 provider.crypto(),
367 self.ciphersuite,
368 public_group.group_context().confirmed_transcript_hash(),
369 )
370 .map_err(LibraryError::unexpected_crypto_error)?;
371
372 if &confirmation_tag != public_group.confirmation_tag() {
375 log::error!("Confirmation tag mismatch");
376 log_crypto!(trace, " Got: {:x?}", confirmation_tag);
377 log_crypto!(trace, " Expected: {:x?}", public_group.confirmation_tag());
378 debug_assert!(false, "Confirmation tag mismatch");
379
380 if !crate::skip_validation::is_disabled::confirmation_tag() {
383 return Err(WelcomeError::ConfirmationTagMismatch);
384 }
385 }
386
387 let message_secrets_store = MessageSecretsStore::new_with_secret(0, message_secrets);
388
389 let resumption_psk = group_epoch_secrets.resumption_psk();
391 self.resumption_psk_store
392 .add(public_group.group_context().epoch(), resumption_psk.clone());
393
394 let welcome_sender_index = self.verifiable_group_info.signer();
395 let path_keypairs = if let Some(path_secret) = self.group_secrets.path_secret {
396 let (path_keypairs, _commit_secret) = public_group
397 .derive_path_secrets(
398 provider.crypto(),
399 self.ciphersuite,
400 path_secret,
401 welcome_sender_index,
402 own_leaf_index,
403 )
404 .map_err(|e| match e {
405 DerivePathError::LibraryError(e) => e.into(),
406 DerivePathError::PublicKeyMismatch => {
407 WelcomeError::PublicTreeError(PublicTreeError::PublicKeyMismatch)
408 }
409 })?;
410 Some(path_keypairs)
411 } else {
412 None
413 };
414
415 let staged_welcome = StagedWelcome {
416 mls_group_config: self.mls_group_config,
417 public_group,
418 group_epoch_secrets,
419 own_leaf_index,
420 message_secrets_store,
421 #[cfg(feature = "extensions-draft-08")]
422 application_export_secret,
423 resumption_psk_store: self.resumption_psk_store,
424 verifiable_group_info: self.verifiable_group_info,
425 key_package_bundle: self.key_package_bundle,
426 path_keypairs,
427 };
428
429 Ok(staged_welcome)
430 }
431}
432
433impl StagedWelcome {
434 pub fn new_from_welcome<Provider: OpenMlsProvider>(
442 provider: &Provider,
443 mls_group_config: &MlsGroupJoinConfig,
444 welcome: Welcome,
445 ratchet_tree: Option<RatchetTreeIn>,
446 ) -> Result<Self, WelcomeError<Provider::StorageError>> {
447 let processed_welcome =
448 ProcessedWelcome::new_from_welcome(provider, mls_group_config, welcome)?;
449
450 processed_welcome.into_staged_welcome(provider, ratchet_tree)
451 }
452
453 pub fn build_from_welcome<'a, Provider: OpenMlsProvider>(
458 provider: &'a Provider,
459 mls_group_config: &MlsGroupJoinConfig,
460 welcome: Welcome,
461 ) -> Result<JoinBuilder<'a, Provider>, WelcomeError<Provider::StorageError>> {
463 let processed_welcome =
464 ProcessedWelcome::new_from_welcome(provider, mls_group_config, welcome)?;
465
466 Ok(JoinBuilder::new(provider, processed_welcome))
468 }
469
470 pub fn welcome_sender_index(&self) -> LeafNodeIndex {
474 self.verifiable_group_info.signer()
475 }
476
477 pub fn welcome_sender(&self) -> Result<&LeafNode, LibraryError> {
481 let sender_index = self.welcome_sender_index();
482 self.public_group
483 .leaf(sender_index)
484 .ok_or(LibraryError::custom(
485 "no leaf with given welcome sender index exists",
486 ))
487 }
488
489 pub fn group_context(&self) -> &GroupContext {
491 self.public_group.group_context()
492 }
493
494 pub fn members(&self) -> impl Iterator<Item = Member> + '_ {
496 self.public_group.members()
497 }
498
499 #[cfg(feature = "extensions-draft-08")]
501 pub fn application_export_secret(&self) -> &ApplicationExportSecret {
502 &self.application_export_secret
503 }
504
505 pub fn into_group<Provider: OpenMlsProvider>(
507 self,
508 provider: &Provider,
509 ) -> Result<MlsGroup, WelcomeError<Provider::StorageError>> {
510 let group_keypairs = if let Some(path_keypairs) = self.path_keypairs {
513 let mut keypairs = vec![self.key_package_bundle.encryption_key_pair()];
514 keypairs.extend_from_slice(&path_keypairs);
515 keypairs
516 } else {
517 vec![self.key_package_bundle.encryption_key_pair()]
518 };
519
520 #[cfg(feature = "extensions-draft-08")]
521 let application_export_tree = ApplicationExportTree::new(self.application_export_secret);
522
523 let mut mls_group = MlsGroup {
524 mls_group_config: self.mls_group_config,
525 own_leaf_nodes: vec![],
526 aad: vec![],
527 group_state: MlsGroupState::Operational,
528 public_group: self.public_group,
529 group_epoch_secrets: self.group_epoch_secrets,
530 own_leaf_index: self.own_leaf_index,
531 message_secrets_store: self.message_secrets_store,
532 resumption_psk_store: self.resumption_psk_store,
533 #[cfg(feature = "extensions-draft-08")]
534 application_export_tree: Some(application_export_tree),
535 };
536
537 mls_group
538 .store_epoch_keypairs(provider.storage(), group_keypairs.as_slice())
539 .map_err(WelcomeError::StorageError)?;
540 mls_group.set_max_past_epochs(mls_group.mls_group_config.max_past_epochs);
541
542 mls_group
543 .store(provider.storage())
544 .map_err(WelcomeError::StorageError)?;
545
546 Ok(mls_group)
547 }
548}
549
550fn keys_for_welcome<Provider: OpenMlsProvider>(
551 mls_group_config: &MlsGroupJoinConfig,
552 welcome: &Welcome,
553 provider: &Provider,
554) -> Result<
555 (ResumptionPskStore, KeyPackageBundle),
556 WelcomeError<<Provider as OpenMlsProvider>::StorageError>,
557> {
558 let resumption_psk_store = ResumptionPskStore::new(mls_group_config.number_of_resumption_psks);
559 let key_package_bundle: KeyPackageBundle = welcome
560 .secrets()
561 .iter()
562 .find_map(|egs| {
563 let hash_ref = egs.new_member();
564
565 provider
566 .storage()
567 .key_package(&hash_ref)
568 .map_err(WelcomeError::StorageError)
569 .transpose()
570 })
571 .ok_or(WelcomeError::NoMatchingKeyPackage)??;
572 if !key_package_bundle.key_package().last_resort() {
573 provider
574 .storage()
575 .delete_key_package(&key_package_bundle.key_package.hash_ref(provider.crypto())?)
576 .map_err(WelcomeError::StorageError)?;
577 } else {
578 log::debug!("Key package has last resort extension, not deleting");
579 }
580 Ok((resumption_psk_store, key_package_bundle))
581}
582
583#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
586pub enum LeafNodeLifetimePolicy {
587 #[default]
591 Verify,
592
593 Skip,
595}
596
597pub struct JoinBuilder<'a, Provider: OpenMlsProvider> {
601 provider: &'a Provider,
602 processed_welcome: ProcessedWelcome,
603 ratchet_tree: Option<RatchetTreeIn>,
604 validate_lifetimes: LeafNodeLifetimePolicy,
605}
606
607impl<'a, Provider: OpenMlsProvider> JoinBuilder<'a, Provider> {
608 pub(crate) fn new(provider: &'a Provider, processed_welcome: ProcessedWelcome) -> Self {
610 Self {
611 provider,
612 processed_welcome,
613 ratchet_tree: None,
614 validate_lifetimes: LeafNodeLifetimePolicy::Verify,
615 }
616 }
617
618 pub fn with_ratchet_tree(mut self, ratchet_tree: RatchetTreeIn) -> Self {
620 self.ratchet_tree = Some(ratchet_tree);
621 self
622 }
623
624 pub fn skip_lifetime_validation(mut self) -> Self {
629 self.validate_lifetimes = LeafNodeLifetimePolicy::Skip;
630 self
631 }
632
633 pub fn processed_welcome(&self) -> &ProcessedWelcome {
637 &self.processed_welcome
638 }
639
640 pub fn build(self) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
642 self.processed_welcome.into_staged_welcome_inner(
643 self.provider,
644 self.ratchet_tree,
645 self.validate_lifetimes,
646 )
647 }
648}