Join a group from a Welcome message
To join a group from a Welcome
message, a new MlsGroup
can be instantiated from
the MlsMessageIn
message containing the Welcome
and an MlsGroupJoinConfig
(see Group configuration for more details). This is a
two-step process: a StagedWelcome
is constructed from the Welcome
and can then be turned into an MlsGroup
. If the group configuration does not
use the ratchet tree extension, the ratchet tree needs to be provided.
let staged_join = StagedWelcome::new_from_welcome(provider, &mls_group_config, welcome, None)
.expect("Error constructing staged join");
let mut bob_group = staged_join
.into_group(provider)
.expect("Error joining group from StagedWelcome");
The reason for this two-phase process is to allow the recipient of a Welcome
to inspect the message, e.g. to determine the identity of the sender.
Pay attention not to forward a Welcome message to a client before its associated commit has been accepted by the Delivery Service. Otherwise, you would end up with an invalid MLS group instance.
Examining a welcome message
When a client is invited to join a group, the application can allow the client to decide whether or not to join the group. In order to determine whether to join the group, the application can inspect information provided in the welcome message, such as who invited them, who else is in the group, what extensions are available, and more. If the application decides not to join the group, the welcome must be discarded to ensure that the local state is cleaned up.
After receiving a MlsMessageIn
from the delivery service, the first step is to extract the MlsMessageBodyIn
, and determine whether it is a welcome message.
let welcome = match welcome.extract() {
MlsMessageBodyIn::Welcome(welcome) => welcome,
_ => unimplemented!("Handle other message types"),
};
The next step is to process the Welcome
. This removes the consumed KeyPackage
from the StorageProvider
, unless it is a last resort KeyPackage
.
let join_config = MlsGroupJoinConfig::default();
// This deletes the keys used to decrypt the welcome, except if it is a last resort key
// package.
let processed_welcome = ProcessedWelcome::new_from_welcome(bob_provider, &join_config, welcome)
.expect("Error constructing processed welcome");
At this stage, there are some more pieces of information in the ProcessedWelcome
that could be useful to the application. For example, it can be useful to check which extensions are available. However, the pieces of information that are retrieved from the ProcessedWelcome
are unverified, and verified values are only available from the StagedWelcome
that is produced in the next step.
// unverified pre-shared keys (`&[PreSharedKeyId]`)
let _unverified_psks = processed_welcome.psks();
// unverified group info (`VerifiableGroupInfo`)
let unverified_group_info = processed_welcome.unverified_group_info();
// From the unverified group info, the ciphersuite, group_id, and other information
// can be retrieved.
let _ciphersuite = unverified_group_info.ciphersuite();
let _group_id = unverified_group_info.group_id();
let _epoch = unverified_group_info.epoch();
// Can also retrieve any available extensions
let extensions = unverified_group_info.extensions();
// Retrieving the ratchet tree extension
let ratchet_tree_extension = extensions
.ratchet_tree()
.expect("No ratchet tree extension");
// The (unverified) ratchet tree itself can also be inspected
let _ratchet_tree = ratchet_tree_extension.ratchet_tree();
The next step is to stage the ProcessedWelcome
.
let staged_welcome: StagedWelcome = processed_welcome
.into_staged_welcome(bob_provider, None)
.expect("Error constructing staged welcome");
Then, more information about the welcome message's sender, such as the credential, signature public key, and encryption public key can also be individually inspected. The welcome message sender's credential can be validated at this stage.
let welcome_sender: &LeafNode = staged_welcome
.welcome_sender()
.expect("Welcome sender could not be retrieved");
// Inspect sender's credential...
let _credential = welcome_sender.credential();
// Inspect sender's signature public key...
let _signature_key = welcome_sender.signature_key();
Additionally, some information about the other group members is made available, e.g. credentials and signature public keys for credential validation.
// Inspect the group members
for member in staged_welcome.members() {
// leaf node index
let _leaf_node_index = member.index;
// credential
let _credential = member.credential;
// encryption public key
let _encryption_key = member.encryption_key;
// signature public key
let _signature_key = member.signature_key;
}
Lastly, the GroupContext
contains several other useful pieces of information, including the protocol version, the extensions enabled on the group, and the required extension, proposal, and credential types.
// Inspect group context...
let group_context = staged_welcome.group_context();
// inspect protocol version...
let _protocol_version = group_context.protocol_version();
// Inspect ciphersuite...
let _ciphersuite = group_context.ciphersuite();
// Inspect extensions...
let extensions: &Extensions = group_context.extensions();
// Can check which extensions are enabled
let _has_ratchet_extension = extensions.ratchet_tree().is_some();
// Inspect required capabilities...
if let Some(capabilities) = group_context.required_capabilities() {
// Inspect required extension types...
let _extension_types: &[ExtensionType] = capabilities.extension_types();
// Inspect required proposal types...
let _proposal_types: &[ProposalType] = capabilities.proposal_types();
// Inspect required credential types...
let _credential_types: &[CredentialType] = capabilities.credential_types();
}
// Additional information from the `GroupContext`
let _group_id = group_context.group_id();
let _epoch = group_context.epoch();
let _tree_hash = group_context.tree_hash();
let _confirmed_transcript_hash = group_context.confirmed_transcript_hash();