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::{ExternalCommitError, WelcomeError},
10 },
11 messages::{
12 group_info::{GroupInfo, VerifiableGroupInfo},
13 Welcome,
14 },
15 schedule::psk::{store::ResumptionPskStore, PreSharedKeyId},
16 storage::OpenMlsProvider,
17 treesync::{
18 errors::{DerivePathError, PublicTreeError},
19 node::leaf_node::{Capabilities, LeafNodeParameters},
20 RatchetTreeIn,
21 },
22};
23
24#[cfg(doc)]
25use crate::key_packages::KeyPackage;
26
27impl MlsGroup {
28 pub fn builder() -> MlsGroupBuilder {
33 MlsGroupBuilder::new()
34 }
35
36 pub fn new<Provider: OpenMlsProvider>(
42 provider: &Provider,
43 signer: &impl Signer,
44 mls_group_create_config: &MlsGroupCreateConfig,
45 credential_with_key: CredentialWithKey,
46 ) -> Result<Self, NewGroupError<Provider::StorageError>> {
47 MlsGroupBuilder::new().build_internal(
48 provider,
49 signer,
50 credential_with_key,
51 Some(mls_group_create_config.clone()),
52 )
53 }
54
55 pub fn new_with_group_id<Provider: OpenMlsProvider>(
58 provider: &Provider,
59 signer: &impl Signer,
60 mls_group_create_config: &MlsGroupCreateConfig,
61 group_id: GroupId,
62 credential_with_key: CredentialWithKey,
63 ) -> Result<Self, NewGroupError<Provider::StorageError>> {
64 MlsGroupBuilder::new()
65 .with_group_id(group_id)
66 .build_internal(
67 provider,
68 signer,
69 credential_with_key,
70 Some(mls_group_create_config.clone()),
71 )
72 }
73
74 #[allow(clippy::too_many_arguments)]
89 #[deprecated(
90 since = "0.7.1",
91 note = "Use the `MlsGroup::external_commit_builder` instead."
92 )]
93 pub fn join_by_external_commit<Provider: OpenMlsProvider>(
94 provider: &Provider,
95 signer: &impl Signer,
96 ratchet_tree: Option<RatchetTreeIn>,
97 verifiable_group_info: VerifiableGroupInfo,
98 mls_group_config: &MlsGroupJoinConfig,
99 capabilities: Option<Capabilities>,
100 extensions: Option<Extensions>,
101 aad: &[u8],
102 credential_with_key: CredentialWithKey,
103 ) -> Result<(Self, MlsMessageOut, Option<GroupInfo>), ExternalCommitError<Provider::StorageError>>
104 {
105 let leaf_node_parameters = LeafNodeParameters::builder()
106 .with_capabilities(capabilities.unwrap_or_default())
107 .with_extensions(extensions.unwrap_or_default())
108 .build();
109
110 let mut external_commit_builder = ExternalCommitBuilder::new()
111 .with_aad(aad.to_vec())
112 .with_config(mls_group_config.clone());
113
114 if let Some(ratchet_tree) = ratchet_tree {
115 external_commit_builder = external_commit_builder.with_ratchet_tree(ratchet_tree)
116 }
117
118 let (mls_group, commit_message_bundle) = external_commit_builder
119 .build_group(provider, verifiable_group_info, credential_with_key)?
120 .leaf_node_parameters(leaf_node_parameters)
121 .load_psks(provider.storage())
122 .map_err(|e| {
123 log::error!("Error loading PSKs for external commit: {e:?}");
124 LibraryError::custom("Error loading PSKs for external commit")
125 })?
126 .build(provider.rand(), provider.crypto(), signer, |_| true)?
127 .finalize(provider)?;
128
129 let (commit, _, group_info) = commit_message_bundle.into_contents();
130
131 Ok((mls_group, commit, group_info))
132 }
133}
134
135impl ProcessedWelcome {
136 pub fn new_from_welcome<Provider: OpenMlsProvider>(
143 provider: &Provider,
144 mls_group_config: &MlsGroupJoinConfig,
145 welcome: Welcome,
146 ) -> Result<Self, WelcomeError<Provider::StorageError>> {
147 let (resumption_psk_store, key_package_bundle) =
148 keys_for_welcome(mls_group_config, &welcome, provider)?;
149
150 let ciphersuite = welcome.ciphersuite();
151 let Some(egs) = welcome.find_encrypted_group_secret(
152 key_package_bundle
153 .key_package()
154 .hash_ref(provider.crypto())?,
155 ) else {
156 return Err(WelcomeError::JoinerSecretNotFound);
157 };
158
159 if welcome.ciphersuite() != key_package_bundle.key_package().ciphersuite() {
162 let e = WelcomeError::CiphersuiteMismatch;
163 log::debug!("new_from_welcome {e:?}");
164 return Err(e);
165 }
166
167 let group_secrets = GroupSecrets::try_from_ciphertext(
168 key_package_bundle.init_private_key(),
169 egs.encrypted_group_secrets(),
170 welcome.encrypted_group_info(),
171 ciphersuite,
172 provider.crypto(),
173 )?;
174 let psk_secret = {
175 let psks = load_psks(
176 provider.storage(),
177 &resumption_psk_store,
178 &group_secrets.psks,
179 )?;
180
181 PskSecret::new(provider.crypto(), ciphersuite, psks)?
182 };
183 let key_schedule = KeySchedule::init(
184 ciphersuite,
185 provider.crypto(),
186 &group_secrets.joiner_secret,
187 psk_secret,
188 )?;
189 let (welcome_key, welcome_nonce) = key_schedule
190 .welcome(provider.crypto(), ciphersuite)
191 .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?
192 .derive_welcome_key_nonce(provider.crypto(), ciphersuite)
193 .map_err(LibraryError::unexpected_crypto_error)?;
194 let verifiable_group_info = VerifiableGroupInfo::try_from_ciphertext(
195 &welcome_key,
196 &welcome_nonce,
197 welcome.encrypted_group_info(),
198 &[],
199 provider.crypto(),
200 )?;
201 if let Some(required_capabilities) =
202 verifiable_group_info.extensions().required_capabilities()
203 {
204 key_package_bundle
207 .key_package()
208 .leaf_node()
209 .capabilities()
210 .supports_required_capabilities(required_capabilities)?;
211 }
212
213 if verifiable_group_info.ciphersuite() != key_package_bundle.key_package().ciphersuite() {
217 let e = WelcomeError::CiphersuiteMismatch;
218 log::debug!("new_from_welcome {e:?}");
219 return Err(e);
220 }
221
222 Ok(Self {
223 mls_group_config: mls_group_config.clone(),
224 ciphersuite,
225 group_secrets,
226 key_schedule,
227 verifiable_group_info,
228 resumption_psk_store,
229 key_package_bundle,
230 })
231 }
232
233 pub fn unverified_group_info(&self) -> &VerifiableGroupInfo {
237 &self.verifiable_group_info
238 }
239
240 pub fn psks(&self) -> &[PreSharedKeyId] {
244 &self.group_secrets.psks
245 }
246
247 pub fn into_staged_welcome<Provider: OpenMlsProvider>(
250 self,
251 provider: &Provider,
252 ratchet_tree: Option<RatchetTreeIn>,
253 ) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
254 self.into_staged_welcome_inner(provider, ratchet_tree, LeafNodeLifetimePolicy::Verify)
255 }
256
257 pub(crate) fn into_staged_welcome_inner<Provider: OpenMlsProvider>(
260 mut self,
261 provider: &Provider,
262 ratchet_tree: Option<RatchetTreeIn>,
263 validate_lifetimes: LeafNodeLifetimePolicy,
264 ) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
265 let ratchet_tree = match self.verifiable_group_info.extensions().ratchet_tree() {
272 Some(extension) => extension.ratchet_tree().clone(),
273 None => match ratchet_tree {
274 Some(ratchet_tree) => ratchet_tree,
275 None => return Err(WelcomeError::MissingRatchetTree),
276 },
277 };
278
279 let (public_group, _group_info_extensions) = PublicGroup::from_ratchet_tree(
282 provider.crypto(),
283 ratchet_tree,
284 self.verifiable_group_info.clone(),
285 ProposalStore::new(),
286 validate_lifetimes,
287 )?;
288
289 let own_leaf_index = public_group
291 .members()
292 .find_map(|m| {
293 if m.signature_key
294 == self
295 .key_package_bundle
296 .key_package()
297 .leaf_node()
298 .signature_key()
299 .as_slice()
300 {
301 Some(m.index)
302 } else {
303 None
304 }
305 })
306 .ok_or(WelcomeError::PublicTreeError(
307 PublicTreeError::MalformedTree,
308 ))?;
309
310 let (group_epoch_secrets, message_secrets) = {
311 let serialized_group_context = public_group
312 .group_context()
313 .tls_serialize_detached()
314 .map_err(LibraryError::missing_bound_check)?;
315
316 self.key_schedule
318 .add_context(provider.crypto(), &serialized_group_context)
319 .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
320
321 let epoch_secrets = self
322 .key_schedule
323 .epoch_secrets(provider.crypto(), self.ciphersuite)
324 .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
325
326 epoch_secrets.split_secrets(
327 serialized_group_context,
328 public_group.tree_size(),
329 own_leaf_index,
330 )
331 };
332
333 let confirmation_tag = message_secrets
334 .confirmation_key()
335 .tag(
336 provider.crypto(),
337 self.ciphersuite,
338 public_group.group_context().confirmed_transcript_hash(),
339 )
340 .map_err(LibraryError::unexpected_crypto_error)?;
341
342 if &confirmation_tag != public_group.confirmation_tag() {
345 log::error!("Confirmation tag mismatch");
346 log_crypto!(trace, " Got: {:x?}", confirmation_tag);
347 log_crypto!(trace, " Expected: {:x?}", public_group.confirmation_tag());
348 debug_assert!(false, "Confirmation tag mismatch");
349
350 if !crate::skip_validation::is_disabled::confirmation_tag() {
353 return Err(WelcomeError::ConfirmationTagMismatch);
354 }
355 }
356
357 let message_secrets_store = MessageSecretsStore::new_with_secret(0, message_secrets);
358
359 let resumption_psk = group_epoch_secrets.resumption_psk();
361 self.resumption_psk_store
362 .add(public_group.group_context().epoch(), resumption_psk.clone());
363
364 let welcome_sender_index = self.verifiable_group_info.signer();
365 let path_keypairs = if let Some(path_secret) = self.group_secrets.path_secret {
366 let (path_keypairs, _commit_secret) = public_group
367 .derive_path_secrets(
368 provider.crypto(),
369 self.ciphersuite,
370 path_secret,
371 welcome_sender_index,
372 own_leaf_index,
373 )
374 .map_err(|e| match e {
375 DerivePathError::LibraryError(e) => e.into(),
376 DerivePathError::PublicKeyMismatch => {
377 WelcomeError::PublicTreeError(PublicTreeError::PublicKeyMismatch)
378 }
379 })?;
380 Some(path_keypairs)
381 } else {
382 None
383 };
384
385 let staged_welcome = StagedWelcome {
386 mls_group_config: self.mls_group_config,
387 public_group,
388 group_epoch_secrets,
389 own_leaf_index,
390 message_secrets_store,
391 resumption_psk_store: self.resumption_psk_store,
392 verifiable_group_info: self.verifiable_group_info,
393 key_package_bundle: self.key_package_bundle,
394 path_keypairs,
395 };
396
397 Ok(staged_welcome)
398 }
399}
400
401impl StagedWelcome {
402 pub fn new_from_welcome<Provider: OpenMlsProvider>(
410 provider: &Provider,
411 mls_group_config: &MlsGroupJoinConfig,
412 welcome: Welcome,
413 ratchet_tree: Option<RatchetTreeIn>,
414 ) -> Result<Self, WelcomeError<Provider::StorageError>> {
415 let processed_welcome =
416 ProcessedWelcome::new_from_welcome(provider, mls_group_config, welcome)?;
417
418 processed_welcome.into_staged_welcome(provider, ratchet_tree)
419 }
420
421 pub fn build_from_welcome<'a, Provider: OpenMlsProvider>(
426 provider: &'a Provider,
427 mls_group_config: &MlsGroupJoinConfig,
428 welcome: Welcome,
429 ) -> Result<JoinBuilder<'a, Provider>, WelcomeError<Provider::StorageError>> {
431 let processed_welcome =
432 ProcessedWelcome::new_from_welcome(provider, mls_group_config, welcome)?;
433
434 Ok(JoinBuilder::new(provider, processed_welcome))
436 }
437
438 pub fn welcome_sender_index(&self) -> LeafNodeIndex {
442 self.verifiable_group_info.signer()
443 }
444
445 pub fn welcome_sender(&self) -> Result<&LeafNode, LibraryError> {
449 let sender_index = self.welcome_sender_index();
450 self.public_group
451 .leaf(sender_index)
452 .ok_or(LibraryError::custom(
453 "no leaf with given welcome sender index exists",
454 ))
455 }
456
457 pub fn group_context(&self) -> &GroupContext {
459 self.public_group.group_context()
460 }
461
462 pub fn members(&self) -> impl Iterator<Item = Member> + '_ {
464 self.public_group.members()
465 }
466
467 pub fn into_group<Provider: OpenMlsProvider>(
469 self,
470 provider: &Provider,
471 ) -> Result<MlsGroup, WelcomeError<Provider::StorageError>> {
472 let group_keypairs = if let Some(path_keypairs) = self.path_keypairs {
475 let mut keypairs = vec![self.key_package_bundle.encryption_key_pair()];
476 keypairs.extend_from_slice(&path_keypairs);
477 keypairs
478 } else {
479 vec![self.key_package_bundle.encryption_key_pair()]
480 };
481
482 let mut mls_group = MlsGroup {
483 mls_group_config: self.mls_group_config,
484 own_leaf_nodes: vec![],
485 aad: vec![],
486 group_state: MlsGroupState::Operational,
487 public_group: self.public_group,
488 group_epoch_secrets: self.group_epoch_secrets,
489 own_leaf_index: self.own_leaf_index,
490 message_secrets_store: self.message_secrets_store,
491 resumption_psk_store: self.resumption_psk_store,
492 };
493
494 mls_group
495 .store_epoch_keypairs(provider.storage(), group_keypairs.as_slice())
496 .map_err(WelcomeError::StorageError)?;
497 mls_group.set_max_past_epochs(mls_group.mls_group_config.max_past_epochs);
498
499 mls_group
500 .store(provider.storage())
501 .map_err(WelcomeError::StorageError)?;
502
503 Ok(mls_group)
504 }
505}
506
507fn keys_for_welcome<Provider: OpenMlsProvider>(
508 mls_group_config: &MlsGroupJoinConfig,
509 welcome: &Welcome,
510 provider: &Provider,
511) -> Result<
512 (ResumptionPskStore, KeyPackageBundle),
513 WelcomeError<<Provider as OpenMlsProvider>::StorageError>,
514> {
515 let resumption_psk_store = ResumptionPskStore::new(mls_group_config.number_of_resumption_psks);
516 let key_package_bundle: KeyPackageBundle = welcome
517 .secrets()
518 .iter()
519 .find_map(|egs| {
520 let hash_ref = egs.new_member();
521
522 provider
523 .storage()
524 .key_package(&hash_ref)
525 .map_err(WelcomeError::StorageError)
526 .transpose()
527 })
528 .ok_or(WelcomeError::NoMatchingKeyPackage)??;
529 if !key_package_bundle.key_package().last_resort() {
530 provider
531 .storage()
532 .delete_key_package(&key_package_bundle.key_package.hash_ref(provider.crypto())?)
533 .map_err(WelcomeError::StorageError)?;
534 } else {
535 log::debug!("Key package has last resort extension, not deleting");
536 }
537 Ok((resumption_psk_store, key_package_bundle))
538}
539
540#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
543pub enum LeafNodeLifetimePolicy {
544 #[default]
548 Verify,
549
550 Skip,
552}
553
554pub struct JoinBuilder<'a, Provider: OpenMlsProvider> {
558 provider: &'a Provider,
559 processed_welcome: ProcessedWelcome,
560 ratchet_tree: Option<RatchetTreeIn>,
561 validate_lifetimes: LeafNodeLifetimePolicy,
562}
563
564impl<'a, Provider: OpenMlsProvider> JoinBuilder<'a, Provider> {
565 pub(crate) fn new(provider: &'a Provider, processed_welcome: ProcessedWelcome) -> Self {
567 Self {
568 provider,
569 processed_welcome,
570 ratchet_tree: None,
571 validate_lifetimes: LeafNodeLifetimePolicy::Verify,
572 }
573 }
574
575 pub fn with_ratchet_tree(mut self, ratchet_tree: RatchetTreeIn) -> Self {
577 self.ratchet_tree = Some(ratchet_tree);
578 self
579 }
580
581 pub fn skip_lifetime_validation(mut self) -> Self {
586 self.validate_lifetimes = LeafNodeLifetimePolicy::Skip;
587 self
588 }
589
590 pub fn processed_welcome(&self) -> &ProcessedWelcome {
594 &self.processed_welcome
595 }
596
597 pub fn build(self) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
599 self.processed_welcome.into_staged_welcome_inner(
600 self.provider,
601 self.ratchet_tree,
602 self.validate_lifetimes,
603 )
604 }
605}