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, 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 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
522 pub async fn create_group_with_id(
523 &self,
524 group_id: Vec<u8>,
525 group_context_extensions: ExtensionList,
526 leaf_node_extensions: ExtensionList,
527 timestamp: Option<MlsTime>,
528 ) -> Result<Group<C>, MlsError> {
529 let (signing_identity, cipher_suite) = self.signing_identity()?;
530
531 Group::new(
532 self.config.clone(),
533 Some(group_id),
534 cipher_suite,
535 self.version,
536 signing_identity.clone(),
537 group_context_extensions,
538 leaf_node_extensions,
539 self.signer()?.clone(),
540 timestamp,
541 )
542 .await
543 }
544
545 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
551 pub async fn create_group(
552 &self,
553 group_context_extensions: ExtensionList,
554 leaf_node_extensions: ExtensionList,
555 timestamp: Option<MlsTime>,
556 ) -> Result<Group<C>, MlsError> {
557 let (signing_identity, cipher_suite) = self.signing_identity()?;
558
559 Group::new(
560 self.config.clone(),
561 None,
562 cipher_suite,
563 self.version,
564 signing_identity.clone(),
565 group_context_extensions,
566 leaf_node_extensions,
567 self.signer()?.clone(),
568 timestamp,
569 )
570 .await
571 }
572
573 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
583 pub async fn join_group(
584 &self,
585 tree_data: Option<ExportedTree<'_>>,
586 welcome_message: &MlsMessage,
587 maybe_time: Option<MlsTime>,
588 ) -> Result<(Group<C>, NewMemberInfo), MlsError> {
589 Group::join(
590 welcome_message,
591 tree_data,
592 self.config.clone(),
593 self.signer()?.clone(),
594 maybe_time,
595 )
596 .await
597 }
598
599 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
602 pub async fn examine_welcome_message(
603 &self,
604 welcome_message: &MlsMessage,
605 ) -> Result<GroupInfo, MlsError> {
606 Group::decrypt_group_info(welcome_message, &self.config).await
607 }
608
609 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
613 pub async fn validate_group_info(
614 &self,
615 group_info_message: &MlsMessage,
616 signer: &SigningIdentity,
617 ) -> Result<(), MlsError> {
618 let MlsMessagePayload::GroupInfo(group_info) = &group_info_message.payload else {
619 return Err(MlsError::UnexpectedMessageType);
620 };
621
622 let cs = cipher_suite_provider(
623 self.config.crypto_provider(),
624 group_info.group_context.cipher_suite,
625 )?;
626
627 let id = self.config.identity_provider();
628
629 validate_group_info_joiner(group_info_message.version, group_info, signer, &id, &cs)
630 .await?;
631
632 let context = MemberValidationContext::ForNewGroup {
633 current_context: &group_info.group_context,
634 };
635
636 id.validate_member(signer, None, context)
637 .await
638 .map_err(|e| MlsError::IdentityProviderError(e.into_any_error()))?;
639
640 Ok(())
641 }
642
643 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
676 pub async fn commit_external(
677 &self,
678 group_info_msg: MlsMessage,
679 ) -> Result<(Group<C>, MlsMessage), MlsError> {
680 ExternalCommitBuilder::new(
681 self.signer()?.clone(),
682 self.signing_identity()?.0.clone(),
683 self.config.clone(),
684 )
685 .build(group_info_msg)
686 .await
687 }
688
689 pub fn external_commit_builder(&self) -> Result<ExternalCommitBuilder<C>, MlsError> {
690 Ok(ExternalCommitBuilder::new(
691 self.signer()?.clone(),
692 self.signing_identity()?.0.clone(),
693 self.config.clone(),
694 ))
695 }
696
697 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
701 #[inline(never)]
702 pub async fn load_group(&self, group_id: &[u8]) -> Result<Group<C>, MlsError> {
703 let snapshot = self
704 .config
705 .group_state_storage()
706 .state(group_id)
707 .await
708 .map_err(|e| MlsError::GroupStorageError(e.into_any_error()))?
709 .ok_or(MlsError::GroupNotFound)?;
710
711 let snapshot = Snapshot::mls_decode(&mut &**snapshot)?;
712
713 Group::from_snapshot(self.config.clone(), snapshot).await
714 }
715
716 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
721 #[inline(never)]
722 pub async fn load_group_with_ratchet_tree(
723 &self,
724 group_id: &[u8],
725 tree_data: ExportedTree<'_>,
726 ) -> Result<Group<C>, MlsError> {
727 let snapshot = self
728 .config
729 .group_state_storage()
730 .state(group_id)
731 .await
732 .map_err(|e| MlsError::GroupStorageError(e.into_any_error()))?
733 .ok_or(MlsError::GroupNotFound)?;
734
735 let mut snapshot = Snapshot::mls_decode(&mut &**snapshot)?;
736 snapshot.state.public_tree.nodes = tree_data.0.into_owned();
737
738 Group::from_snapshot(self.config.clone(), snapshot).await
739 }
740
741 #[cfg(feature = "by_ref_proposal")]
747 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
748 pub async fn external_add_proposal(
749 &self,
750 group_info: &MlsMessage,
751 tree_data: Option<crate::group::ExportedTree<'_>>,
752 authenticated_data: Vec<u8>,
753 key_package_extensions: ExtensionList,
754 leaf_node_extensions: ExtensionList,
755 timestamp: Option<MlsTime>,
756 ) -> Result<MlsMessage, MlsError> {
757 let protocol_version = group_info.version;
758
759 let protocol_version_ok =
760 self.config.version_supported(protocol_version) && protocol_version == self.version;
761
762 if !protocol_version_ok {
763 return Err(MlsError::UnsupportedProtocolVersion(protocol_version));
764 }
765
766 let group_info = group_info
767 .as_group_info()
768 .ok_or(MlsError::UnexpectedMessageType)?;
769
770 let cipher_suite = group_info.group_context.cipher_suite;
771
772 let cipher_suite_provider = self
773 .config
774 .crypto_provider()
775 .cipher_suite_provider(cipher_suite)
776 .ok_or(MlsError::UnsupportedCipherSuite(cipher_suite))?;
777
778 crate::group::validate_tree_and_info_joiner(
779 protocol_version,
780 group_info,
781 tree_data,
782 &self.config.identity_provider(),
783 &cipher_suite_provider,
784 timestamp,
785 )
786 .await?;
787
788 let key_package = self
789 .generate_key_package(key_package_extensions, leaf_node_extensions, timestamp)
790 .await?
791 .key_package;
792
793 (key_package.cipher_suite == cipher_suite)
794 .then_some(())
795 .ok_or(MlsError::UnsupportedCipherSuite(cipher_suite))?;
796
797 let message = AuthenticatedContent::new_signed(
798 &cipher_suite_provider,
799 &group_info.group_context,
800 Sender::NewMemberProposal,
801 Content::Proposal(Box::new(Proposal::Add(Box::new(AddProposal {
802 key_package,
803 })))),
804 self.signer()?,
805 WireFormat::PublicMessage,
806 authenticated_data,
807 )
808 .await?;
809
810 let plaintext = PublicMessage {
811 content: message.content,
812 auth: message.auth,
813 membership_tag: None,
814 };
815
816 Ok(MlsMessage {
817 version: protocol_version,
818 payload: MlsMessagePayload::Plain(plaintext),
819 })
820 }
821
822 fn signer(&self) -> Result<&SignatureSecretKey, MlsError> {
823 self.signer.as_ref().ok_or(MlsError::SignerNotFound)
824 }
825
826 pub fn signing_identity(&self) -> Result<(&SigningIdentity, CipherSuite), MlsError> {
827 self.signing_identity
828 .as_ref()
829 .map(|(id, cs)| (id, *cs))
830 .ok_or(MlsError::SignerNotFound)
831 }
832
833 pub fn key_package_store(&self) -> <C as ClientConfig>::KeyPackageRepository {
835 self.config.key_package_repo()
836 }
837
838 pub fn secret_store(&self) -> <C as ClientConfig>::PskStore {
841 self.config.secret_store()
842 }
843
844 pub fn group_state_storage(&self) -> <C as ClientConfig>::GroupStateStorage {
846 self.config.group_state_storage()
847 }
848
849 pub fn identity_provider(&self) -> <C as ClientConfig>::IdentityProvider {
851 self.config.identity_provider()
852 }
853}
854
855#[cfg(test)]
856pub(crate) mod test_utils {
857 use super::*;
858 use crate::identity::test_utils::get_test_signing_identity;
859
860 pub use crate::client_builder::test_utils::{TestClientBuilder, TestClientConfig};
861
862 pub const TEST_PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion::MLS_10;
863 pub const TEST_CIPHER_SUITE: CipherSuite = CipherSuite::P256_AES128;
864 pub const TEST_CUSTOM_PROPOSAL_TYPE: ProposalType = ProposalType::new(65001);
865
866 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
867 pub async fn test_client_with_key_pkg(
868 protocol_version: ProtocolVersion,
869 cipher_suite: CipherSuite,
870 identity: &str,
871 ) -> (Client<TestClientConfig>, MlsMessage) {
872 test_client_with_key_pkg_custom(
873 protocol_version,
874 cipher_suite,
875 identity,
876 Default::default(),
877 Default::default(),
878 |_| {},
879 )
880 .await
881 }
882
883 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
884 pub async fn test_client_with_key_pkg_custom<F>(
885 protocol_version: ProtocolVersion,
886 cipher_suite: CipherSuite,
887 identity: &str,
888 key_package_extensions: ExtensionList,
889 leaf_node_extensions: ExtensionList,
890 mut config: F,
891 ) -> (Client<TestClientConfig>, MlsMessage)
892 where
893 F: FnMut(&mut TestClientConfig),
894 {
895 let (identity, secret_key) =
896 get_test_signing_identity(cipher_suite, identity.as_bytes()).await;
897
898 let mut client = TestClientBuilder::new_for_test()
899 .used_protocol_version(protocol_version)
900 .signing_identity(identity.clone(), secret_key, cipher_suite)
901 .build();
902
903 config(&mut client.config);
904
905 let key_package = client
906 .generate_key_package_message(key_package_extensions, leaf_node_extensions, None)
907 .await
908 .unwrap();
909
910 (client, key_package)
911 }
912}
913
914#[cfg(test)]
915mod tests {
916 use super::test_utils::*;
917
918 use super::*;
919 use crate::{
920 crypto::test_utils::TestCryptoProvider,
921 identity::test_utils::{get_test_basic_credential, get_test_signing_identity},
922 tree_kem::leaf_node::LeafNodeSource,
923 };
924 use assert_matches::assert_matches;
925
926 #[cfg(feature = "by_ref_proposal")]
927 use crate::group::message_processor::ProposalMessageDescription;
928 #[cfg(feature = "by_ref_proposal")]
929 use crate::group::proposal::Proposal;
930 use crate::group::test_utils::test_group;
931 #[cfg(feature = "psk")]
932 use crate::group::test_utils::test_group_custom_config;
933 #[cfg(feature = "by_ref_proposal")]
934 use crate::group::ReceivedMessage;
935 #[cfg(feature = "psk")]
936 use crate::psk::{ExternalPskId, PreSharedKey};
937 use alloc::vec;
938
939 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
940 async fn test_keygen() {
941 for (protocol_version, cipher_suite) in ProtocolVersion::all().flat_map(|p| {
944 TestCryptoProvider::all_supported_cipher_suites()
945 .into_iter()
946 .map(move |cs| (p, cs))
947 }) {
948 let (identity, secret_key) = get_test_signing_identity(cipher_suite, b"foo").await;
949
950 let client = TestClientBuilder::new_for_test()
951 .signing_identity(identity.clone(), secret_key, cipher_suite)
952 .build();
953
954 let key_package = client
956 .generate_key_package_message(Default::default(), Default::default(), None)
957 .await
958 .unwrap();
959
960 assert_eq!(key_package.version, protocol_version);
961
962 let key_package = key_package.into_key_package().unwrap();
963
964 assert_eq!(key_package.cipher_suite, cipher_suite);
965
966 assert_eq!(
967 &key_package.leaf_node.signing_identity.credential,
968 &get_test_basic_credential(b"foo".to_vec())
969 );
970
971 assert_eq!(key_package.leaf_node.signing_identity, identity);
972
973 let capabilities = key_package.leaf_node.ungreased_capabilities();
974 assert_eq!(capabilities, client.config.capabilities());
975
976 let client_lifetime = client.config.lifetime(None);
977 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));
978 }
979 }
980
981 #[cfg(feature = "by_ref_proposal")]
982 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
983 async fn new_member_add_proposal_adds_to_group() {
984 let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
985
986 let (bob_identity, secret_key) = get_test_signing_identity(TEST_CIPHER_SUITE, b"bob").await;
987
988 let bob = TestClientBuilder::new_for_test()
989 .signing_identity(bob_identity.clone(), secret_key, TEST_CIPHER_SUITE)
990 .build();
991
992 let proposal = bob
993 .external_add_proposal(
994 &alice_group.group_info_message(true).await.unwrap(),
995 None,
996 vec![],
997 Default::default(),
998 Default::default(),
999 None,
1000 )
1001 .await
1002 .unwrap();
1003
1004 let message = alice_group
1005 .process_incoming_message(proposal)
1006 .await
1007 .unwrap();
1008
1009 assert_matches!(
1010 message,
1011 ReceivedMessage::Proposal(ProposalMessageDescription {
1012 proposal: Proposal::Add(p), ..}
1013 ) if p.key_package.leaf_node.signing_identity == bob_identity
1014 );
1015
1016 alice_group.commit(vec![]).await.unwrap();
1017 alice_group.apply_pending_commit().await.unwrap();
1018
1019 assert!(alice_group
1021 .roster()
1022 .members_iter()
1023 .any(|member| member.signing_identity == bob_identity))
1024 }
1025
1026 #[cfg(feature = "psk")]
1027 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
1028 async fn join_via_external_commit(do_remove: bool, with_psk: bool) -> Result<(), MlsError> {
1029 use crate::group::{message_processor::CommitEffect, CommitMessageDescription};
1034
1035 let psk = PreSharedKey::from(b"psk".to_vec());
1036 let psk_id = ExternalPskId::new(b"psk id".to_vec());
1037
1038 let mut alice_group =
1039 test_group_custom_config(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, |c| {
1040 c.psk(psk_id.clone(), psk.clone())
1041 })
1042 .await;
1043
1044 let (mut bob_group, _) = alice_group
1045 .join_with_custom_config("bob", false, |c| {
1046 c.0.psk_store.insert(psk_id.clone(), psk.clone());
1047 })
1048 .await
1049 .unwrap();
1050
1051 let group_info_msg = alice_group
1052 .group_info_message_allowing_ext_commit(true)
1053 .await
1054 .unwrap();
1055
1056 let new_client_id = if do_remove { "bob" } else { "charlie" };
1057
1058 let (new_client_identity, secret_key) =
1059 get_test_signing_identity(TEST_CIPHER_SUITE, new_client_id.as_bytes()).await;
1060
1061 let new_client = TestClientBuilder::new_for_test()
1062 .psk(psk_id.clone(), psk)
1063 .signing_identity(new_client_identity.clone(), secret_key, TEST_CIPHER_SUITE)
1064 .build();
1065
1066 let mut builder = new_client.external_commit_builder().unwrap();
1067
1068 if do_remove {
1069 builder = builder.with_removal(1);
1070 }
1071
1072 if with_psk {
1073 builder = builder.with_external_psk(psk_id);
1074 }
1075
1076 let (new_group, external_commit) = builder.build(group_info_msg).await?;
1077
1078 let num_members = if do_remove { 2 } else { 3 };
1079
1080 assert_eq!(new_group.roster().members_iter().count(), num_members);
1081
1082 let _ = alice_group
1083 .process_incoming_message(external_commit.clone())
1084 .await
1085 .unwrap();
1086
1087 let bob_current_epoch = bob_group.current_epoch();
1088
1089 let message = bob_group
1090 .process_incoming_message(external_commit)
1091 .await
1092 .unwrap();
1093
1094 assert!(alice_group.roster().members_iter().count() == num_members);
1095
1096 if !do_remove {
1097 assert!(bob_group.roster().members_iter().count() == num_members);
1098 } else {
1099 assert_eq!(bob_group.current_epoch(), bob_current_epoch);
1101
1102 assert_matches!(
1103 message,
1104 ReceivedMessage::Commit(CommitMessageDescription {
1105 effect: CommitEffect::Removed {
1106 new_epoch: _,
1107 remover: _
1108 },
1109 ..
1110 })
1111 );
1112 }
1113
1114 assert_eq!(
1116 alice_group.epoch_authenticator().unwrap(),
1117 new_group.epoch_authenticator().unwrap()
1118 );
1119
1120 Ok(())
1121 }
1122
1123 #[cfg(feature = "psk")]
1124 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
1125 async fn test_external_commit() {
1126 join_via_external_commit(false, false).await.unwrap();
1128 join_via_external_commit(true, false).await.unwrap();
1130 join_via_external_commit(false, true).await.unwrap();
1132 join_via_external_commit(true, true).await.unwrap();
1134 }
1135
1136 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
1137 async fn creating_an_external_commit_requires_a_group_info_message() {
1138 let (alice_identity, secret_key) =
1139 get_test_signing_identity(TEST_CIPHER_SUITE, b"alice").await;
1140
1141 let alice = TestClientBuilder::new_for_test()
1142 .signing_identity(alice_identity.clone(), secret_key, TEST_CIPHER_SUITE)
1143 .build();
1144
1145 let msg = alice
1146 .generate_key_package_message(Default::default(), Default::default(), None)
1147 .await
1148 .unwrap();
1149 let res = alice.commit_external(msg).await.map(|_| ());
1150
1151 assert_matches!(res, Err(MlsError::UnexpectedMessageType));
1152 }
1153
1154 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
1155 async fn external_commit_with_invalid_group_info_fails() {
1156 let mut alice_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
1157 let mut bob_group = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE).await;
1158
1159 bob_group.commit(vec![]).await.unwrap();
1160 bob_group.apply_pending_commit().await.unwrap();
1161
1162 let group_info_msg = bob_group
1163 .group_info_message_allowing_ext_commit(true)
1164 .await
1165 .unwrap();
1166
1167 let (carol_identity, secret_key) =
1168 get_test_signing_identity(TEST_CIPHER_SUITE, b"carol").await;
1169
1170 let carol = TestClientBuilder::new_for_test()
1171 .signing_identity(carol_identity, secret_key, TEST_CIPHER_SUITE)
1172 .build();
1173
1174 let (_, external_commit) = carol
1175 .external_commit_builder()
1176 .unwrap()
1177 .build(group_info_msg)
1178 .await
1179 .unwrap();
1180
1181 let res = alice_group.process_incoming_message(external_commit).await;
1183 assert_matches!(res, Err(_));
1184 }
1185
1186 #[test]
1187 fn builder_can_be_obtained_from_client_to_edit_properties_for_new_client() {
1188 let alice = TestClientBuilder::new_for_test()
1189 .extension_type(33.into())
1190 .build();
1191 let bob = alice.to_builder(None).extension_type(34.into()).build();
1192 assert_eq!(bob.config.supported_extensions(), [33, 34].map(Into::into));
1193 }
1194
1195 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
1196 async fn examine_welcome_message() {
1197 let mut alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE)
1198 .await
1199 .group;
1200
1201 let (bob, kp) =
1202 test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob").await;
1203
1204 let commit = alice
1205 .commit_builder()
1206 .add_member(kp)
1207 .unwrap()
1208 .build()
1209 .await
1210 .unwrap();
1211
1212 alice.apply_pending_commit().await.unwrap();
1213
1214 let mut group_info = bob
1215 .examine_welcome_message(&commit.welcome_messages[0])
1216 .await
1217 .unwrap();
1218
1219 group_info.signature = vec![];
1221 group_info.ungrease();
1222
1223 let mut expected_group_info = alice
1224 .group_info_message(commit.ratchet_tree.is_none())
1225 .await
1226 .unwrap()
1227 .into_group_info()
1228 .unwrap();
1229
1230 expected_group_info.signature = vec![];
1231 expected_group_info.ungrease();
1232
1233 assert_eq!(expected_group_info, group_info);
1234 }
1235
1236 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
1237 async fn validate_group_info() {
1238 let alice = test_group(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE)
1239 .await
1240 .group;
1241
1242 let bob = test_client_with_key_pkg(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "bob")
1243 .await
1244 .0;
1245
1246 let group_info = alice.group_info_message(false).await.unwrap();
1247 let alice_signer = alice.current_member_signing_identity().unwrap().clone();
1248
1249 bob.validate_group_info(&group_info, &alice_signer)
1250 .await
1251 .unwrap();
1252
1253 let other_signer = get_test_signing_identity(TEST_CIPHER_SUITE, b"alice")
1254 .await
1255 .0;
1256
1257 let res = bob.validate_group_info(&group_info, &other_signer).await;
1258 assert_matches!(res, Err(MlsError::InvalidSignature));
1259 }
1260
1261 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
1262
1263 async fn cannot_list_default_extensions_in_capabilities() {
1264 let res = TestClientBuilder::new_for_test()
1265 .with_random_signing_identity("client", TEST_CIPHER_SUITE)
1266 .await
1267 .extension_type(ExtensionType::APPLICATION_ID)
1268 .build()
1269 .generate_key_package(Default::default(), Default::default(), Default::default())
1270 .await;
1271
1272 assert_matches!(res, Err(MlsError::DefaultValueListed));
1273 }
1274
1275 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
1276
1277 async fn cannot_list_default_proposals_in_capabilities() {
1278 let res = TestClientBuilder::new_for_test()
1279 .with_random_signing_identity("client", TEST_CIPHER_SUITE)
1280 .await
1281 .custom_proposal_type(ProposalType::ADD)
1282 .build()
1283 .generate_key_package(Default::default(), Default::default(), Default::default())
1284 .await;
1285
1286 assert_matches!(res, Err(MlsError::DefaultValueListed));
1287 }
1288}