1use crate::cipher_suite::CipherSuite;
6use crate::client_builder::{recreate_config, BaseConfig, ClientBuilder, MakeConfig};
7use crate::client_config::ClientConfig;
8use crate::group::framing::MlsMessage;
9
10use crate::group::{cipher_suite_provider, validate_group_info_joiner, GroupBuilder, GroupInfo};
11use crate::group::{
12 framing::MlsMessagePayload, snapshot::Snapshot, ExportedTree, Group, NewMemberInfo,
13};
14#[cfg(feature = "by_ref_proposal")]
15use crate::group::{
16 framing::{Content, PublicMessage, Sender, WireFormat},
17 message_signature::AuthenticatedContent,
18 proposal::{AddProposal, Proposal},
19};
20use crate::identity::SigningIdentity;
21use crate::key_package::{KeyPackageGeneration, KeyPackageGenerator};
22use crate::protocol_version::ProtocolVersion;
23use crate::time::MlsTime;
24use crate::tree_kem::node::NodeIndex;
25use alloc::vec::Vec;
26use mls_rs_codec::MlsDecode;
27use mls_rs_core::crypto::{CryptoProvider, SignatureSecretKey};
28use mls_rs_core::error::{AnyError, IntoAnyError};
29use mls_rs_core::extension::{ExtensionError, ExtensionList, ExtensionType};
30use mls_rs_core::group::{GroupStateStorage, ProposalType};
31use mls_rs_core::identity::{CredentialType, IdentityProvider, MemberValidationContext};
32use mls_rs_core::key_package::KeyPackageStorage;
33
34use crate::group::external_commit::ExternalCommitBuilder;
35
36#[cfg(feature = "by_ref_proposal")]
37use alloc::boxed::Box;
38
39#[derive(Debug)]
40#[cfg_attr(feature = "std", derive(thiserror::Error))]
41#[non_exhaustive]
42pub enum MlsError {
43 #[cfg_attr(feature = "std", error(transparent))]
44 IdentityProviderError(AnyError),
45 #[cfg_attr(feature = "std", error(transparent))]
46 CryptoProviderError(AnyError),
47 #[cfg_attr(feature = "std", error(transparent))]
48 KeyPackageRepoError(AnyError),
49 #[cfg_attr(feature = "std", error(transparent))]
50 GroupStorageError(AnyError),
51 #[cfg_attr(feature = "std", error(transparent))]
52 PskStoreError(AnyError),
53 #[cfg_attr(feature = "std", error(transparent))]
54 MlsRulesError(AnyError),
55 #[cfg_attr(feature = "std", error(transparent))]
56 SerializationError(AnyError),
57 #[cfg_attr(feature = "std", error(transparent))]
58 ExtensionError(AnyError),
59 #[cfg_attr(feature = "std", error("Cipher suite does not match"))]
60 CipherSuiteMismatch,
61 #[cfg_attr(feature = "std", error("Initial epoch must be 1"))]
62 InitialEpochNotOne,
63 #[cfg_attr(feature = "std", error("Invalid commit, missing required path"))]
64 CommitMissingPath,
65 #[cfg_attr(feature = "std", error("plaintext message for incorrect epoch"))]
66 InvalidEpoch,
67 #[cfg_attr(feature = "std", error("invalid signature found"))]
68 InvalidSignature,
69 #[cfg_attr(feature = "std", error("invalid confirmation tag"))]
70 InvalidConfirmationTag,
71 #[cfg_attr(feature = "std", error("invalid membership tag"))]
72 InvalidMembershipTag,
73 #[cfg_attr(feature = "std", error("corrupt private key, missing required values"))]
74 InvalidTreeKemPrivateKey,
75 #[cfg_attr(feature = "std", error("key package not found, unable to process"))]
76 WelcomeKeyPackageNotFound,
77 #[cfg_attr(feature = "std", error("leaf not found in tree for index {0}"))]
78 LeafNotFound(u32),
79 #[cfg_attr(feature = "std", error("message from self can't be processed"))]
80 CantProcessMessageFromSelf,
81 #[cfg_attr(
82 feature = "std",
83 error("pending proposals found, commit required before application messages can be sent")
84 )]
85 CommitRequired,
86 #[cfg_attr(
87 feature = "std",
88 error("ratchet tree not provided or discovered in GroupInfo")
89 )]
90 RatchetTreeNotFound,
91 #[cfg_attr(feature = "std", error("External sender cannot commit"))]
92 ExternalSenderCannotCommit,
93 #[cfg_attr(feature = "std", error("Unsupported protocol version {0:?}"))]
94 UnsupportedProtocolVersion(ProtocolVersion),
95 #[cfg_attr(feature = "std", error("Protocol version mismatch"))]
96 ProtocolVersionMismatch,
97 #[cfg_attr(feature = "std", error("Unsupported cipher suite {0:?}"))]
98 UnsupportedCipherSuite(CipherSuite),
99 #[cfg_attr(feature = "std", error("Signing key of external sender is unknown"))]
100 UnknownSigningIdentityForExternalSender,
101 #[cfg_attr(
102 feature = "std",
103 error("External proposals are disabled for this group")
104 )]
105 ExternalProposalsDisabled,
106 #[cfg_attr(
107 feature = "std",
108 error("Signing identity is not allowed to externally propose")
109 )]
110 InvalidExternalSigningIdentity,
111 #[cfg_attr(feature = "std", error("Missing ExternalPub extension"))]
112 MissingExternalPubExtension,
113 #[cfg_attr(feature = "std", error("Epoch not found"))]
114 EpochNotFound,
115 #[cfg_attr(feature = "std", error("Unencrypted application message"))]
116 UnencryptedApplicationMessage,
117 #[cfg_attr(
118 feature = "std",
119 error("NewMemberCommit sender type can only be used to send Commit content")
120 )]
121 ExpectedCommitForNewMemberCommit,
122 #[cfg_attr(
123 feature = "std",
124 error("NewMemberProposal sender type can only be used to send add proposals")
125 )]
126 ExpectedAddProposalForNewMemberProposal,
127 #[cfg_attr(
128 feature = "std",
129 error("External commit missing ExternalInit proposal")
130 )]
131 ExternalCommitMissingExternalInit,
132 #[cfg_attr(
133 feature = "std",
134 error(
135 "A ReIinit has been applied. The next action must be creating or receiving a welcome."
136 )
137 )]
138 GroupUsedAfterReInit,
139 #[cfg_attr(feature = "std", error("Pending ReIinit not found."))]
140 PendingReInitNotFound,
141 #[cfg_attr(
142 feature = "std",
143 error("The extensions in the welcome message and in the reinit do not match.")
144 )]
145 ReInitExtensionsMismatch,
146 #[cfg_attr(feature = "std", error("signer not found for given identity"))]
147 SignerNotFound,
148 #[cfg_attr(feature = "std", error("commit already pending"))]
149 ExistingPendingCommit,
150 #[cfg_attr(feature = "std", error("pending commit not found"))]
151 PendingCommitNotFound,
152 #[cfg_attr(feature = "std", error("unexpected message type for action"))]
153 UnexpectedMessageType,
154 #[cfg_attr(
155 feature = "std",
156 error("membership tag on MlsPlaintext for non-member sender")
157 )]
158 MembershipTagForNonMember,
159 #[cfg_attr(feature = "std", error("No member found for given identity id."))]
160 MemberNotFound,
161 #[cfg_attr(feature = "std", error("group not found"))]
162 GroupNotFound,
163 #[cfg_attr(feature = "std", error("unexpected PSK ID"))]
164 UnexpectedPskId,
165 #[cfg_attr(feature = "std", error("invalid sender for content type"))]
166 InvalidSender,
167 #[cfg_attr(feature = "std", error("GroupID mismatch"))]
168 GroupIdMismatch,
169 #[cfg_attr(feature = "std", error("storage retention can not be zero"))]
170 NonZeroRetentionRequired,
171 #[cfg_attr(feature = "std", error("Too many PSK IDs to compute PSK secret"))]
172 TooManyPskIds,
173 #[cfg_attr(feature = "std", error("Missing required Psk"))]
174 MissingRequiredPsk,
175 #[cfg_attr(feature = "std", error("Old group state not found"))]
176 OldGroupStateNotFound,
177 #[cfg_attr(feature = "std", error("leaf secret already consumed"))]
178 InvalidLeafConsumption,
179 #[cfg_attr(feature = "std", error("key not available, invalid generation {0}"))]
180 KeyMissing(u32),
181 #[cfg_attr(
182 feature = "std",
183 error("requested generation {0} is too far ahead of current generation")
184 )]
185 InvalidFutureGeneration(u32),
186 #[cfg_attr(feature = "std", error("leaf node has no children"))]
187 LeafNodeNoChildren,
188 #[cfg_attr(feature = "std", error("root node has no parent"))]
189 LeafNodeNoParent,
190 #[cfg_attr(feature = "std", error("index out of range"))]
191 InvalidTreeIndex,
192 #[cfg_attr(feature = "std", error("time overflow"))]
193 TimeOverflow,
194 #[cfg_attr(feature = "std", error("invalid leaf_node_source"))]
195 InvalidLeafNodeSource,
196 #[cfg_attr(
197 feature = "std",
198 error("current time ({}) is not within key package lifetime ({} to {})",
199 timestamp.seconds_since_epoch(),
200 not_before.seconds_since_epoch(),
201 not_after.seconds_since_epoch(),
202 )
203 )]
204 InvalidLifetime {
205 not_before: MlsTime,
206 not_after: MlsTime,
207 timestamp: MlsTime,
208 },
209 #[cfg_attr(feature = "std", error("required extension not found"))]
210 RequiredExtensionNotFound(ExtensionType),
211 #[cfg_attr(feature = "std", error("required proposal not found"))]
212 RequiredProposalNotFound(ProposalType),
213 #[cfg_attr(feature = "std", error("required credential not found"))]
214 RequiredCredentialNotFound(CredentialType),
215 #[cfg_attr(feature = "std", error("capabilities must describe extensions used"))]
216 ExtensionNotInCapabilities(ExtensionType),
217 #[cfg_attr(feature = "std", error("expected non-blank node"))]
218 ExpectedNode,
219 #[cfg_attr(feature = "std", error("node index is out of bounds {0}"))]
220 InvalidNodeIndex(NodeIndex),
221 #[cfg_attr(feature = "std", error("unexpected empty node found"))]
222 UnexpectedEmptyNode,
223 #[cfg_attr(
224 feature = "std",
225 error("duplicate signature key, hpke key or identity found at index {0}")
226 )]
227 DuplicateLeafData(u32),
228 #[cfg_attr(
229 feature = "std",
230 error("In-use credential type not supported by new leaf at index")
231 )]
232 InUseCredentialTypeUnsupportedByNewLeaf,
233 #[cfg_attr(
234 feature = "std",
235 error("Not all members support the credential type used by new leaf")
236 )]
237 CredentialTypeOfNewLeafIsUnsupported,
238 #[cfg_attr(
239 feature = "std",
240 error("the length of the update path is different than the length of the direct path")
241 )]
242 WrongPathLen,
243 #[cfg_attr(
244 feature = "std",
245 error("same HPKE leaf key before and after applying the update path for leaf {0}")
246 )]
247 SameHpkeKey(u32),
248 #[cfg_attr(feature = "std", error("init key is not valid for cipher suite"))]
249 InvalidInitKey,
250 #[cfg_attr(
251 feature = "std",
252 error("init key can not be equal to leaf node public key")
253 )]
254 InitLeafKeyEquality,
255 #[cfg_attr(feature = "std", error("different identity in update for leaf {0}"))]
256 DifferentIdentityInUpdate(u32),
257 #[cfg_attr(feature = "std", error("update path pub key mismatch"))]
258 PubKeyMismatch,
259 #[cfg_attr(feature = "std", error("tree hash mismatch"))]
260 TreeHashMismatch,
261 #[cfg_attr(feature = "std", error("bad update: no suitable secret key"))]
262 UpdateErrorNoSecretKey,
263 #[cfg_attr(feature = "std", error("invalid lca, not found on direct path"))]
264 LcaNotFoundInDirectPath,
265 #[cfg_attr(feature = "std", error("update path parent hash mismatch"))]
266 ParentHashMismatch,
267 #[cfg_attr(feature = "std", error("unexpected pattern of unmerged leaves"))]
268 UnmergedLeavesMismatch,
269 #[cfg_attr(feature = "std", error("empty tree"))]
270 UnexpectedEmptyTree,
271 #[cfg_attr(feature = "std", error("trailing blanks"))]
272 UnexpectedTrailingBlanks,
273 #[cfg_attr(
275 feature = "std",
276 error("Commiter must not include any update proposals generated by the commiter")
277 )]
278 InvalidCommitSelfUpdate,
279 #[cfg_attr(feature = "std", error("A PreSharedKey proposal must have a PSK of type External or type Resumption and usage Application"))]
280 InvalidTypeOrUsageInPreSharedKeyProposal,
281 #[cfg_attr(feature = "std", error("psk nonce length does not match cipher suite"))]
282 InvalidPskNonceLength,
283 #[cfg_attr(
284 feature = "std",
285 error("ReInit proposal protocol version is less than the version of the original group")
286 )]
287 InvalidProtocolVersionInReInit,
288 #[cfg_attr(feature = "std", error("More than one proposal applying to leaf: {0}"))]
289 MoreThanOneProposalForLeaf(u32),
290 #[cfg_attr(
291 feature = "std",
292 error("More than one GroupContextExtensions proposal")
293 )]
294 MoreThanOneGroupContextExtensionsProposal,
295 #[cfg_attr(feature = "std", error("Invalid proposal type for sender"))]
296 InvalidProposalTypeForSender,
297 #[cfg_attr(
298 feature = "std",
299 error("External commit must have exactly one ExternalInit proposal")
300 )]
301 ExternalCommitMustHaveExactlyOneExternalInit,
302 #[cfg_attr(feature = "std", error("External commit must have a new leaf"))]
303 ExternalCommitMustHaveNewLeaf,
304 #[cfg_attr(
305 feature = "std",
306 error("External commit contains removal of other identity")
307 )]
308 ExternalCommitRemovesOtherIdentity,
309 #[cfg_attr(
310 feature = "std",
311 error("External commit contains more than one Remove proposal")
312 )]
313 ExternalCommitWithMoreThanOneRemove,
314 #[cfg_attr(feature = "std", error("Duplicate PSK IDs"))]
315 DuplicatePskIds,
316 #[cfg_attr(
317 feature = "std",
318 error("Invalid proposal type {0:?} in external commit")
319 )]
320 InvalidProposalTypeInExternalCommit(ProposalType),
321 #[cfg_attr(feature = "std", error("Committer can not remove themselves"))]
322 CommitterSelfRemoval,
323 #[cfg_attr(
324 feature = "std",
325 error("Only members can commit proposals by reference")
326 )]
327 OnlyMembersCanCommitProposalsByRef,
328 #[cfg_attr(feature = "std", error("Other proposal with ReInit"))]
329 OtherProposalWithReInit,
330 #[cfg_attr(feature = "std", error("Unsupported group extension {0:?}"))]
331 UnsupportedGroupExtension(ExtensionType),
332 #[cfg_attr(feature = "std", error("Unsupported custom proposal type {0:?}"))]
333 UnsupportedCustomProposal(ProposalType),
334 #[cfg_attr(feature = "std", error("by-ref proposal not found"))]
335 ProposalNotFound,
336 #[cfg_attr(
337 feature = "std",
338 error("Removing non-existing member (or removing a member twice)")
339 )]
340 RemovingNonExistingMember,
341 #[cfg_attr(feature = "std", error("Updated identity not a valid successor"))]
342 InvalidSuccessor,
343 #[cfg_attr(
344 feature = "std",
345 error("Updating non-existing member (or updating a member twice)")
346 )]
347 UpdatingNonExistingMember,
348 #[cfg_attr(feature = "std", error("Failed generating next path secret"))]
349 FailedGeneratingPathSecret,
350 #[cfg_attr(feature = "std", error("Invalid group info"))]
351 InvalidGroupInfo,
352 #[cfg_attr(feature = "std", error("Invalid welcome message"))]
353 InvalidWelcomeMessage,
354 #[cfg_attr(feature = "std", error("Exporter deleted"))]
355 ExporterDeleted,
356 #[cfg_attr(feature = "std", error("Self-remove already proposed"))]
357 SelfRemoveAlreadyProposed,
358 #[cfg_attr(feature = "std", error("Default value listed"))]
359 DefaultValueListed,
360 #[cfg_attr(feature = "std", error("not a subgroup"))]
361 NotASubgroup,
362}
363
364impl IntoAnyError for MlsError {
365 #[cfg(feature = "std")]
366 fn into_dyn_error(self) -> Result<Box<dyn std::error::Error + Send + Sync>, Self> {
367 Ok(self.into())
368 }
369}
370
371impl From<mls_rs_codec::Error> for MlsError {
372 #[inline]
373 fn from(e: mls_rs_codec::Error) -> Self {
374 MlsError::SerializationError(e.into_any_error())
375 }
376}
377
378impl From<ExtensionError> for MlsError {
379 #[inline]
380 fn from(e: ExtensionError) -> Self {
381 MlsError::ExtensionError(e.into_any_error())
382 }
383}
384
385#[derive(Clone, Debug)]
394pub struct Client<C> {
395 pub(crate) config: C,
396 pub(crate) signing_identity: Option<(SigningIdentity, CipherSuite)>,
397 pub(crate) signer: Option<SignatureSecretKey>,
398 pub(crate) version: ProtocolVersion,
399}
400
401impl Client<()> {
402 pub fn builder() -> ClientBuilder<BaseConfig> {
405 ClientBuilder::new()
406 }
407}
408
409impl<C> Client<C>
410where
411 C: ClientConfig + Clone,
412{
413 pub(crate) fn new(
414 config: C,
415 signer: Option<SignatureSecretKey>,
416 signing_identity: Option<(SigningIdentity, CipherSuite)>,
417 version: ProtocolVersion,
418 ) -> Self {
419 Client {
420 config,
421 signer,
422 signing_identity,
423 version,
424 }
425 }
426
427 pub fn to_builder(&self, timestamp: Option<MlsTime>) -> ClientBuilder<MakeConfig<C>> {
428 ClientBuilder::from_config(recreate_config(
429 self.config.clone(),
430 self.signer.clone(),
431 self.signing_identity.clone(),
432 self.version,
433 timestamp,
434 ))
435 }
436
437 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
451 pub async fn generate_key_package_message(
452 &self,
453 key_package_extensions: ExtensionList,
454 leaf_node_extensions: ExtensionList,
455 timestamp: Option<MlsTime>,
456 ) -> Result<MlsMessage, MlsError> {
457 Ok(self
458 .generate_key_package(key_package_extensions, leaf_node_extensions, timestamp)
459 .await?
460 .key_package_message())
461 }
462
463 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
464 async fn generate_key_package(
465 &self,
466 key_package_extensions: ExtensionList,
467 leaf_node_extensions: ExtensionList,
468 timestamp: Option<MlsTime>,
469 ) -> Result<KeyPackageGeneration, MlsError> {
470 let (signing_identity, cipher_suite) = self.signing_identity()?;
471
472 let cipher_suite_provider = self
473 .config
474 .crypto_provider()
475 .cipher_suite_provider(cipher_suite)
476 .ok_or(MlsError::UnsupportedCipherSuite(cipher_suite))?;
477
478 let key_package_generator = KeyPackageGenerator {
479 protocol_version: self.version,
480 cipher_suite_provider: &cipher_suite_provider,
481 signing_key: self.signer()?,
482 signing_identity,
483 };
484
485 let key_pkg_gen = key_package_generator
486 .generate(
487 self.config.lifetime(timestamp),
488 self.config.capabilities(),
489 key_package_extensions,
490 leaf_node_extensions,
491 )
492 .await?;
493
494 key_pkg_gen
495 .key_package
496 .leaf_node
497 .validate_no_default_values_listed()?;
498
499 let (id, key_package_data) = key_pkg_gen.to_storage()?;
500
501 self.config
502 .key_package_repo()
503 .insert(id, key_package_data)
504 .await
505 .map_err(|e| MlsError::KeyPackageRepoError(e.into_any_error()))?;
506
507 Ok(key_pkg_gen)
508 }
509
510 pub fn group_builder(&self) -> Result<GroupBuilder<C>, MlsError> {
514 let (signing_identity, cipher_suite) = self.signing_identity()?;
515
516 Ok(GroupBuilder::new(
517 self.config.clone(),
518 cipher_suite,
519 signing_identity.clone(),
520 self.signer()?.clone(),
521 ))
522 }
523
524 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
535 pub async fn create_group_with_id(
536 &self,
537 group_id: Vec<u8>,
538 group_context_extensions: ExtensionList,
539 leaf_node_extensions: ExtensionList,
540 timestamp: Option<MlsTime>,
541 ) -> Result<Group<C>, MlsError> {
542 let mut builder = self
543 .group_builder()?
544 .with_group_id(group_id)
545 .with_group_context_extensions(group_context_extensions)
546 .with_leaf_node_extensions(leaf_node_extensions);
547
548 if let Some(time) = timestamp {
549 builder = builder.with_now_time(time)
550 }
551
552 builder.build().await
553 }
554
555 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
562 pub async fn create_group(
563 &self,
564 group_context_extensions: ExtensionList,
565 leaf_node_extensions: ExtensionList,
566 timestamp: Option<MlsTime>,
567 ) -> Result<Group<C>, MlsError> {
568 let mut builder = self
569 .group_builder()?
570 .with_group_context_extensions(group_context_extensions)
571 .with_leaf_node_extensions(leaf_node_extensions);
572
573 if let Some(time) = timestamp {
574 builder = builder.with_now_time(time)
575 }
576
577 builder.build().await
578 }
579
580 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
590 pub async fn join_group(
591 &self,
592 tree_data: Option<ExportedTree<'_>>,
593 welcome_message: &MlsMessage,
594 maybe_time: Option<MlsTime>,
595 ) -> Result<(Group<C>, NewMemberInfo), MlsError> {
596 Group::join(
597 welcome_message,
598 tree_data,
599 self.config.clone(),
600 self.signer()?.clone(),
601 maybe_time,
602 )
603 .await
604 }
605
606 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
609 pub async fn examine_welcome_message(
610 &self,
611 welcome_message: &MlsMessage,
612 ) -> Result<GroupInfo, MlsError> {
613 Group::decrypt_group_info(welcome_message, &self.config).await
614 }
615
616 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
620 pub async fn validate_group_info(
621 &self,
622 group_info_message: &MlsMessage,
623 signer: &SigningIdentity,
624 ) -> Result<(), MlsError> {
625 let MlsMessagePayload::GroupInfo(group_info) = &group_info_message.payload else {
626 return Err(MlsError::UnexpectedMessageType);
627 };
628
629 let cs = cipher_suite_provider(
630 self.config.crypto_provider(),
631 group_info.group_context.cipher_suite,
632 )?;
633
634 let id = self.config.identity_provider();
635
636 validate_group_info_joiner(group_info_message.version, group_info, signer, &id, &cs)
637 .await?;
638
639 let context = MemberValidationContext::ForNewGroup {
640 current_context: &group_info.group_context,
641 };
642
643 id.validate_member(signer, None, context)
644 .await
645 .map_err(|e| MlsError::IdentityProviderError(e.into_any_error()))?;
646
647 Ok(())
648 }
649
650 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
683 pub async fn commit_external(
684 &self,
685 group_info_msg: MlsMessage,
686 ) -> Result<(Group<C>, MlsMessage), MlsError> {
687 ExternalCommitBuilder::new(
688 self.signer()?.clone(),
689 self.signing_identity()?.0.clone(),
690 self.config.clone(),
691 )
692 .build(group_info_msg)
693 .await
694 }
695
696 pub fn external_commit_builder(&self) -> Result<ExternalCommitBuilder<C>, MlsError> {
697 Ok(ExternalCommitBuilder::new(
698 self.signer()?.clone(),
699 self.signing_identity()?.0.clone(),
700 self.config.clone(),
701 ))
702 }
703
704 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
708 #[inline(never)]
709 pub async fn load_group(&self, group_id: &[u8]) -> Result<Group<C>, MlsError> {
710 let snapshot = self
711 .config
712 .group_state_storage()
713 .state(group_id)
714 .await
715 .map_err(|e| MlsError::GroupStorageError(e.into_any_error()))?
716 .ok_or(MlsError::GroupNotFound)?;
717
718 let snapshot = Snapshot::mls_decode(&mut &**snapshot)?;
719
720 Group::from_snapshot(self.config.clone(), snapshot).await
721 }
722
723 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
728 #[inline(never)]
729 pub async fn load_group_with_ratchet_tree(
730 &self,
731 group_id: &[u8],
732 tree_data: ExportedTree<'_>,
733 ) -> Result<Group<C>, MlsError> {
734 let snapshot = self
735 .config
736 .group_state_storage()
737 .state(group_id)
738 .await
739 .map_err(|e| MlsError::GroupStorageError(e.into_any_error()))?
740 .ok_or(MlsError::GroupNotFound)?;
741
742 let mut snapshot = Snapshot::mls_decode(&mut &**snapshot)?;
743 snapshot.state.public_tree.nodes = tree_data.0.into_owned();
744
745 Group::from_snapshot(self.config.clone(), snapshot).await
746 }
747
748 #[cfg(feature = "by_ref_proposal")]
754 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
755 pub async fn external_add_proposal(
756 &self,
757 group_info: &MlsMessage,
758 tree_data: Option<crate::group::ExportedTree<'_>>,
759 authenticated_data: Vec<u8>,
760 key_package_extensions: ExtensionList,
761 leaf_node_extensions: ExtensionList,
762 timestamp: Option<MlsTime>,
763 ) -> Result<MlsMessage, MlsError> {
764 let protocol_version = group_info.version;
765
766 let protocol_version_ok =
767 self.config.version_supported(protocol_version) && protocol_version == self.version;
768
769 if !protocol_version_ok {
770 return Err(MlsError::UnsupportedProtocolVersion(protocol_version));
771 }
772
773 let group_info = group_info
774 .as_group_info()
775 .ok_or(MlsError::UnexpectedMessageType)?;
776
777 let cipher_suite = group_info.group_context.cipher_suite;
778
779 let cipher_suite_provider = self
780 .config
781 .crypto_provider()
782 .cipher_suite_provider(cipher_suite)
783 .ok_or(MlsError::UnsupportedCipherSuite(cipher_suite))?;
784
785 crate::group::validate_tree_and_info_joiner(
786 protocol_version,
787 group_info,
788 tree_data,
789 &self.config.identity_provider(),
790 &cipher_suite_provider,
791 timestamp,
792 )
793 .await?;
794
795 let key_package = self
796 .generate_key_package(key_package_extensions, leaf_node_extensions, timestamp)
797 .await?
798 .key_package;
799
800 (key_package.cipher_suite == cipher_suite)
801 .then_some(())
802 .ok_or(MlsError::UnsupportedCipherSuite(cipher_suite))?;
803
804 let message = AuthenticatedContent::new_signed(
805 &cipher_suite_provider,
806 &group_info.group_context,
807 Sender::NewMemberProposal,
808 Content::Proposal(Box::new(Proposal::Add(Box::new(AddProposal {
809 key_package,
810 })))),
811 self.signer()?,
812 WireFormat::PublicMessage,
813 authenticated_data,
814 )
815 .await?;
816
817 let plaintext = PublicMessage {
818 content: message.content,
819 auth: message.auth,
820 membership_tag: None,
821 };
822
823 Ok(MlsMessage {
824 version: protocol_version,
825 payload: MlsMessagePayload::Plain(plaintext),
826 })
827 }
828
829 fn signer(&self) -> Result<&SignatureSecretKey, MlsError> {
830 self.signer.as_ref().ok_or(MlsError::SignerNotFound)
831 }
832
833 pub fn signing_identity(&self) -> Result<(&SigningIdentity, CipherSuite), MlsError> {
834 self.signing_identity
835 .as_ref()
836 .map(|(id, cs)| (id, *cs))
837 .ok_or(MlsError::SignerNotFound)
838 }
839
840 pub fn key_package_store(&self) -> <C as ClientConfig>::KeyPackageRepository {
842 self.config.key_package_repo()
843 }
844
845 pub fn secret_store(&self) -> <C as ClientConfig>::PskStore {
848 self.config.secret_store()
849 }
850
851 pub fn group_state_storage(&self) -> <C as ClientConfig>::GroupStateStorage {
853 self.config.group_state_storage()
854 }
855
856 pub fn identity_provider(&self) -> <C as ClientConfig>::IdentityProvider {
858 self.config.identity_provider()
859 }
860}
861
862#[cfg(test)]
863pub(crate) mod test_utils {
864 use super::*;
865 use crate::identity::test_utils::get_test_signing_identity;
866
867 pub use crate::client_builder::test_utils::{TestClientBuilder, TestClientConfig};
868
869 pub const TEST_PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion::MLS_10;
870 pub const TEST_CIPHER_SUITE: CipherSuite = CipherSuite::P256_AES128;
871 pub const TEST_CUSTOM_PROPOSAL_TYPE: ProposalType = ProposalType::new(65001);
872
873 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
874 pub async fn test_client_with_key_pkg(
875 protocol_version: ProtocolVersion,
876 cipher_suite: CipherSuite,
877 identity: &str,
878 ) -> (Client<TestClientConfig>, MlsMessage) {
879 test_client_with_key_pkg_custom(
880 protocol_version,
881 cipher_suite,
882 identity,
883 Default::default(),
884 Default::default(),
885 |_| {},
886 )
887 .await
888 }
889
890 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
891 pub async fn test_client_with_key_pkg_custom<F>(
892 protocol_version: ProtocolVersion,
893 cipher_suite: CipherSuite,
894 identity: &str,
895 key_package_extensions: ExtensionList,
896 leaf_node_extensions: ExtensionList,
897 mut config: F,
898 ) -> (Client<TestClientConfig>, MlsMessage)
899 where
900 F: FnMut(&mut TestClientConfig),
901 {
902 let (identity, secret_key) =
903 get_test_signing_identity(cipher_suite, identity.as_bytes()).await;
904
905 let mut client = TestClientBuilder::new_for_test()
906 .used_protocol_version(protocol_version)
907 .signing_identity(identity.clone(), secret_key, cipher_suite)
908 .build();
909
910 config(&mut client.config);
911
912 let key_package = client
913 .generate_key_package_message(key_package_extensions, leaf_node_extensions, None)
914 .await
915 .unwrap();
916
917 (client, key_package)
918 }
919}
920
921#[cfg(test)]
922mod tests {
923 use super::test_utils::*;
924
925 use super::*;
926 use crate::{
927 crypto::test_utils::TestCryptoProvider,
928 identity::test_utils::{get_test_basic_credential, get_test_signing_identity},
929 tree_kem::leaf_node::LeafNodeSource,
930 };
931 use assert_matches::assert_matches;
932
933 #[cfg(feature = "by_ref_proposal")]
934 use crate::group::message_processor::ProposalMessageDescription;
935 #[cfg(feature = "by_ref_proposal")]
936 use crate::group::proposal::Proposal;
937 use crate::group::test_utils::test_group;
938 #[cfg(feature = "psk")]
939 use crate::group::test_utils::test_group_custom_config;
940 #[cfg(feature = "by_ref_proposal")]
941 use crate::group::ReceivedMessage;
942 #[cfg(feature = "psk")]
943 use crate::psk::{ExternalPskId, PreSharedKey};
944 use alloc::vec;
945
946 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
947 async fn test_keygen() {
948 for (protocol_version, cipher_suite) in ProtocolVersion::all().flat_map(|p| {
951 TestCryptoProvider::all_supported_cipher_suites()
952 .into_iter()
953 .map(move |cs| (p, cs))
954 }) {
955 let (identity, secret_key) = get_test_signing_identity(cipher_suite, b"foo").await;
956
957 let client = TestClientBuilder::new_for_test()
958 .signing_identity(identity.clone(), secret_key, cipher_suite)
959 .build();
960
961 let key_package = client
963 .generate_key_package_message(Default::default(), Default::default(), None)
964 .await
965 .unwrap();
966
967 assert_eq!(key_package.version, protocol_version);
968
969 let key_package = key_package.into_key_package().unwrap();
970
971 assert_eq!(key_package.cipher_suite, cipher_suite);
972
973 assert_eq!(
974 &key_package.leaf_node.signing_identity.credential,
975 &get_test_basic_credential(b"foo".to_vec())
976 );
977
978 assert_eq!(key_package.leaf_node.signing_identity, identity);
979
980 let capabilities = key_package.leaf_node.ungreased_capabilities();
981 assert_eq!(capabilities, client.config.capabilities());
982
983 let client_lifetime = client.config.lifetime(None);
984 assert_matches!(key_package.leaf_node.leaf_node_source, LeafNodeSource::KeyPackage(lifetime) if (lifetime.not_after - lifetime.not_before) == (client_lifetime.not_after - client_lifetime.not_before));
985 }
986 }
987
988 #[cfg(feature = "by_ref_proposal")]
989 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
990 async fn new_member_add_proposal_adds_to_group() {
991 let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
992
993 let (bob_identity, secret_key) = get_test_signing_identity(TEST_CIPHER_SUITE, b"bob").await;
994
995 let bob = TestClientBuilder::new_for_test()
996 .signing_identity(bob_identity.clone(), secret_key, TEST_CIPHER_SUITE)
997 .build();
998
999 let proposal = bob
1000 .external_add_proposal(
1001 &alice_group.group_info_message(true).await.unwrap(),
1002 None,
1003 vec![],
1004 Default::default(),
1005 Default::default(),
1006 None,
1007 )
1008 .await
1009 .unwrap();
1010
1011 let message = alice_group
1012 .process_incoming_message(proposal)
1013 .await
1014 .unwrap();
1015
1016 assert_matches!(
1017 message,
1018 ReceivedMessage::Proposal(ProposalMessageDescription {
1019 proposal: Proposal::Add(p), ..}
1020 ) if p.key_package.leaf_node.signing_identity == bob_identity
1021 );
1022
1023 alice_group.commit(vec![]).await.unwrap();
1024 alice_group.apply_pending_commit().await.unwrap();
1025
1026 assert!(alice_group
1028 .roster()
1029 .members_iter()
1030 .any(|member| member.signing_identity == bob_identity))
1031 }
1032
1033 #[cfg(feature = "psk")]
1034 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1035 async fn join_via_external_commit(do_remove: bool, with_psk: bool) -> Result<(), MlsError> {
1036 use crate::group::{message_processor::CommitEffect, CommitMessageDescription};
1041
1042 let psk = PreSharedKey::from(b"psk".to_vec());
1043 let psk_id = ExternalPskId::new(b"psk id".to_vec());
1044
1045 let mut alice_group =
1046 test_group_custom_config(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, |c| {
1047 c.psk(psk_id.clone(), psk.clone())
1048 })
1049 .await;
1050
1051 let (mut bob_group, _) = alice_group
1052 .join_with_custom_config("bob", false, |c| {
1053 c.0.psk_store.insert(psk_id.clone(), psk.clone());
1054 })
1055 .await
1056 .unwrap();
1057
1058 let group_info_msg = alice_group
1059 .group_info_message_allowing_ext_commit(true)
1060 .await
1061 .unwrap();
1062
1063 let new_client_id = if do_remove { "bob" } else { "charlie" };
1064
1065 let (new_client_identity, secret_key) =
1066 get_test_signing_identity(TEST_CIPHER_SUITE, new_client_id.as_bytes()).await;
1067
1068 let new_client = TestClientBuilder::new_for_test()
1069 .psk(psk_id.clone(), psk)
1070 .signing_identity(new_client_identity.clone(), secret_key, TEST_CIPHER_SUITE)
1071 .build();
1072
1073 let mut builder = new_client.external_commit_builder().unwrap();
1074
1075 if do_remove {
1076 builder = builder.with_removal(1);
1077 }
1078
1079 if with_psk {
1080 builder = builder.with_external_psk(psk_id);
1081 }
1082
1083 let (new_group, external_commit) = builder.build(group_info_msg).await?;
1084
1085 let num_members = if do_remove { 2 } else { 3 };
1086
1087 assert_eq!(new_group.roster().members_iter().count(), num_members);
1088
1089 let _ = alice_group
1090 .process_incoming_message(external_commit.clone())
1091 .await
1092 .unwrap();
1093
1094 let bob_current_epoch = bob_group.current_epoch();
1095
1096 let message = bob_group
1097 .process_incoming_message(external_commit)
1098 .await
1099 .unwrap();
1100
1101 assert!(alice_group.roster().members_iter().count() == num_members);
1102
1103 if !do_remove {
1104 assert!(bob_group.roster().members_iter().count() == num_members);
1105 } else {
1106 assert_eq!(bob_group.current_epoch(), bob_current_epoch);
1108
1109 assert_matches!(
1110 message,
1111 ReceivedMessage::Commit(CommitMessageDescription {
1112 effect: CommitEffect::Removed {
1113 new_epoch: _,
1114 remover: _
1115 },
1116 ..
1117 })
1118 );
1119 }
1120
1121 assert_eq!(
1123 alice_group.epoch_authenticator().unwrap(),
1124 new_group.epoch_authenticator().unwrap()
1125 );
1126
1127 Ok(())
1128 }
1129
1130 #[cfg(feature = "psk")]
1131 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
1132 async fn test_external_commit() {
1133 join_via_external_commit(false, false).await.unwrap();
1135 join_via_external_commit(true, false).await.unwrap();
1137 join_via_external_commit(false, true).await.unwrap();
1139 join_via_external_commit(true, true).await.unwrap();
1141 }
1142
1143 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
1144 async fn external_commit_path_leaf_node_is_readable_from_the_message() {
1145 let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
1146
1147 alice_group.commit(vec![]).await.unwrap();
1148 alice_group.apply_pending_commit().await.unwrap();
1149
1150 let group_info_msg = alice_group
1151 .group_info_message_allowing_ext_commit(true)
1152 .await
1153 .unwrap();
1154
1155 assert!(group_info_msg.commit_path_leaf_node().is_none());
1156
1157 let (bob_identity, secret_key) = get_test_signing_identity(TEST_CIPHER_SUITE, b"bob").await;
1158
1159 let bob = TestClientBuilder::new_for_test()
1160 .signing_identity(bob_identity.clone(), secret_key, TEST_CIPHER_SUITE)
1161 .build();
1162
1163 let (_, external_commit) = bob.commit_external(group_info_msg).await.unwrap();
1164
1165 let leaf_node = external_commit.commit_path_leaf_node().unwrap();
1166
1167 assert_eq!(leaf_node.signing_identity, bob_identity);
1168 }
1169
1170 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
1171 async fn creating_an_external_commit_requires_a_group_info_message() {
1172 let (alice_identity, secret_key) =
1173 get_test_signing_identity(TEST_CIPHER_SUITE, b"alice").await;
1174
1175 let alice = TestClientBuilder::new_for_test()
1176 .signing_identity(alice_identity.clone(), secret_key, TEST_CIPHER_SUITE)
1177 .build();
1178
1179 let msg = alice
1180 .generate_key_package_message(Default::default(), Default::default(), None)
1181 .await
1182 .unwrap();
1183 let res = alice.commit_external(msg).await.map(|_| ());
1184
1185 assert_matches!(res, Err(MlsError::UnexpectedMessageType));
1186 }
1187
1188 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
1189 async fn external_commit_with_invalid_group_info_fails() {
1190 let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
1191 let mut bob_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
1192
1193 bob_group.commit(vec![]).await.unwrap();
1194 bob_group.apply_pending_commit().await.unwrap();
1195
1196 let group_info_msg = bob_group
1197 .group_info_message_allowing_ext_commit(true)
1198 .await
1199 .unwrap();
1200
1201 let (carol_identity, secret_key) =
1202 get_test_signing_identity(TEST_CIPHER_SUITE, b"carol").await;
1203
1204 let carol = TestClientBuilder::new_for_test()
1205 .signing_identity(carol_identity, secret_key, TEST_CIPHER_SUITE)
1206 .build();
1207
1208 let (_, external_commit) = carol
1209 .external_commit_builder()
1210 .unwrap()
1211 .build(group_info_msg)
1212 .await
1213 .unwrap();
1214
1215 let res = alice_group.process_incoming_message(external_commit).await;
1217 assert_matches!(res, Err(_));
1218 }
1219
1220 #[test]
1221 fn builder_can_be_obtained_from_client_to_edit_properties_for_new_client() {
1222 let alice = TestClientBuilder::new_for_test()
1223 .extension_type(33.into())
1224 .build();
1225 let bob = alice.to_builder(None).extension_type(34.into()).build();
1226 assert_eq!(bob.config.supported_extensions(), [33, 34].map(Into::into));
1227 }
1228
1229 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
1230 async fn examine_welcome_message() {
1231 let mut alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE)
1232 .await
1233 .group;
1234
1235 let (bob, kp) =
1236 test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob").await;
1237
1238 let commit = alice
1239 .commit_builder()
1240 .add_member(kp)
1241 .unwrap()
1242 .build()
1243 .await
1244 .unwrap();
1245
1246 alice.apply_pending_commit().await.unwrap();
1247
1248 let mut group_info = bob
1249 .examine_welcome_message(&commit.welcome_messages[0])
1250 .await
1251 .unwrap();
1252
1253 group_info.signature = vec![];
1255 group_info.ungrease();
1256
1257 let mut expected_group_info = alice
1258 .group_info_message(commit.ratchet_tree.is_none())
1259 .await
1260 .unwrap()
1261 .into_group_info()
1262 .unwrap();
1263
1264 expected_group_info.signature = vec![];
1265 expected_group_info.ungrease();
1266
1267 assert_eq!(expected_group_info, group_info);
1268 }
1269
1270 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
1271 async fn validate_group_info() {
1272 let alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE)
1273 .await
1274 .group;
1275
1276 let bob = test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob")
1277 .await
1278 .0;
1279
1280 let group_info = alice.group_info_message(false).await.unwrap();
1281 let alice_signer = alice.current_member_signing_identity().unwrap().clone();
1282
1283 bob.validate_group_info(&group_info, &alice_signer)
1284 .await
1285 .unwrap();
1286
1287 let other_signer = get_test_signing_identity(TEST_CIPHER_SUITE, b"alice")
1288 .await
1289 .0;
1290
1291 let res = bob.validate_group_info(&group_info, &other_signer).await;
1292 assert_matches!(res, Err(MlsError::InvalidSignature));
1293 }
1294
1295 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
1296
1297 async fn cannot_list_default_extensions_in_capabilities() {
1298 let res = TestClientBuilder::new_for_test()
1299 .with_random_signing_identity("client", TEST_CIPHER_SUITE)
1300 .await
1301 .extension_type(ExtensionType::APPLICATION_ID)
1302 .build()
1303 .generate_key_package(Default::default(), Default::default(), Default::default())
1304 .await;
1305
1306 assert_matches!(res, Err(MlsError::DefaultValueListed));
1307 }
1308
1309 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
1310
1311 async fn cannot_list_default_proposals_in_capabilities() {
1312 let res = TestClientBuilder::new_for_test()
1313 .with_random_signing_identity("client", TEST_CIPHER_SUITE)
1314 .await
1315 .custom_proposal_type(ProposalType::ADD)
1316 .build()
1317 .generate_key_package(Default::default(), Default::default(), Default::default())
1318 .await;
1319
1320 assert_matches!(res, Err(MlsError::DefaultValueListed));
1321 }
1322}