use openmls::{
prelude::{tls_codec::*, CustomProposal, *},
schedule::{ExternalPsk, PreSharedKeyId, Psk},
test_utils::*,
*,
};
use openmls_basic_credential::SignatureKeyPair;
use openmls_test::openmls_test;
use openmls_traits::{signatures::Signer, types::SignatureScheme};
use treesync::LeafNodeParameters;
#[test]
fn create_provider_rust_crypto() {
let provider: OpenMlsRustCrypto = OpenMlsRustCrypto::default();
let _provider = provider;
}
fn generate_credential(
identity: Vec<u8>,
signature_algorithm: SignatureScheme,
provider: &impl crate::storage::OpenMlsProvider,
) -> (CredentialWithKey, SignatureKeyPair) {
let credential = BasicCredential::new(identity);
let signature_keys = SignatureKeyPair::new(signature_algorithm).unwrap();
signature_keys.store(provider.storage()).unwrap();
(
CredentialWithKey {
credential: credential.into(),
signature_key: signature_keys.to_public_vec().into(),
},
signature_keys,
)
}
fn generate_key_package(
ciphersuite: Ciphersuite,
credential_with_key: CredentialWithKey,
extensions: Extensions<KeyPackage>,
provider: &impl crate::storage::OpenMlsProvider,
signer: &impl Signer,
) -> KeyPackageBundle {
KeyPackage::builder()
.key_package_extensions(extensions)
.build(ciphersuite, provider, signer, credential_with_key)
.unwrap()
}
#[openmls_test]
fn book_operations() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let charlie_provider = &Provider::default();
let dave_provider = &Provider::default();
let ds_provider = &Provider::default();
let (alice_credential, alice_signature_keys) = generate_credential(
"Alice".into(),
ciphersuite.signature_algorithm(),
alice_provider,
);
let (bob_credential, bob_signature_keys) = generate_credential(
"Bob".into(),
ciphersuite.signature_algorithm(),
bob_provider,
);
let (charlie_credential, charlie_signature_keys) = generate_credential(
"Charlie".into(),
ciphersuite.signature_algorithm(),
charlie_provider,
);
let (dave_credential, dave_signature_keys) = generate_credential(
"Dave".into(),
ciphersuite.signature_algorithm(),
dave_provider,
);
let bob_key_package = generate_key_package(
ciphersuite,
bob_credential.clone(),
Extensions::default(),
bob_provider,
&bob_signature_keys,
);
let (ds_credential_with_key, ds_signature_keys) = generate_credential(
"delivery-service".into(),
ciphersuite.signature_algorithm(),
ds_provider,
);
let mls_group_create_config = MlsGroupCreateConfig::builder()
.padding_size(100)
.sender_ratchet_configuration(SenderRatchetConfiguration::new(
10, 2000, ))
.with_group_context_extensions(
Extensions::single(Extension::ExternalSenders(vec![ExternalSender::new(
ds_credential_with_key.signature_key.clone(),
ds_credential_with_key.credential.clone(),
)]))
.expect("failed to create single-element extensions list"),
)
.ciphersuite(ciphersuite)
.capabilities(Capabilities::new(
None, None, Some(&[ExtensionType::Unknown(0xff00)]),
None, Some(&[CredentialType::Basic]),
))
.with_leaf_node_extensions(
Extensions::single(Extension::Unknown(
0xff00,
UnknownExtension(vec![0, 1, 2, 3]),
))
.expect("failed to create single-element extensions list"),
)
.expect("failed to configure leaf extensions")
.use_ratchet_tree_extension(true)
.build();
let mut alice_group = MlsGroup::new(
alice_provider,
&alice_signature_keys,
&mls_group_create_config,
alice_credential.clone(),
)
.expect("An unexpected error occurred.");
{
let group_id = GroupId::from_slice(b"123e4567e89b");
let mut alice_group = MlsGroup::new_with_group_id(
alice_provider,
&alice_signature_keys,
&mls_group_create_config,
group_id,
alice_credential.clone(),
)
.expect("An unexpected error occurred.");
let _ignore_mut_warning = &mut alice_group;
let external_senders_list = vec![];
let extensions =
Extensions::from_vec(vec![Extension::ExternalSenders(external_senders_list)])
.expect("failed to create extensions list");
let mut alice_group = MlsGroup::builder()
.padding_size(100)
.sender_ratchet_configuration(SenderRatchetConfiguration::new(
10, 2000, ))
.with_group_context_extensions(extensions) .use_ratchet_tree_extension(true)
.build(
alice_provider,
&alice_signature_keys,
alice_credential.clone(),
)
.expect("An unexpected error occurred.");
let _ignore_mut_warning = &mut alice_group;
let mut alice_group = MlsGroup::builder()
.padding_size(100)
.sender_ratchet_configuration(SenderRatchetConfiguration::new(
10, 2000, ))
.ciphersuite(ciphersuite)
.use_ratchet_tree_extension(true)
.build(
alice_provider,
&alice_signature_keys,
alice_credential.clone(),
)
.expect("An unexpected error occurred.");
let _ignore_mut_warning = &mut alice_group;
}
let group_id = alice_group.group_id().clone();
let (mls_message_out, welcome, group_info) = alice_group
.add_members(
alice_provider,
&alice_signature_keys,
core::slice::from_ref(bob_key_package.key_package()),
)
.expect("Could not add members.");
let _mls_message_out = mls_message_out;
let _group_info = group_info;
if let Some(staged_commit) = alice_group.pending_commit() {
let add = staged_commit
.add_proposals()
.next()
.expect("Expected a proposal.");
assert_eq!(
add.add_proposal().key_package().leaf_node().credential(),
&bob_credential.credential
);
assert!(matches!(
add.sender(),
Sender::Member(member) if *member == alice_group.own_leaf_index()
));
} else {
unreachable!("Expected a StagedCommit.");
}
alice_group
.merge_pending_commit(alice_provider)
.expect("error merging pending commit");
assert_eq!(alice_group.members().count(), 2);
let members = alice_group.members().collect::<Vec<Member>>();
let id0 = members[0].credential.serialized_content();
let id1 = members[1].credential.serialized_content();
assert_eq!(id0, b"Alice");
assert_eq!(id1, b"Bob");
let mls_group_config = MlsGroupJoinConfig::builder()
.padding_size(100)
.sender_ratchet_configuration(SenderRatchetConfiguration::new(
10, 2000, ))
.use_ratchet_tree_extension(true)
.build();
let welcome: MlsMessageIn = welcome.into();
let welcome = welcome
.into_welcome()
.expect("expected the message to be a welcome message");
let staged_join =
StagedWelcome::new_from_welcome(bob_provider, &mls_group_config, welcome, None)
.expect("Error constructing staged join");
let mut bob_group = staged_join
.into_group(bob_provider)
.expect("Error joining group from StagedWelcome");
let verifiable_group_info = alice_group
.export_group_info(alice_provider.crypto(), &alice_signature_keys, true)
.expect("Cannot export group info")
.into_verifiable_group_info()
.expect("Could not get group info");
let (mut dave_group, _bundle) = MlsGroup::external_commit_builder()
.with_config(mls_group_config.clone())
.build_group(dave_provider, verifiable_group_info, dave_credential)
.unwrap()
.load_psks(dave_provider.storage())
.unwrap()
.build(
dave_provider.rand(),
dave_provider.crypto(),
&dave_signature_keys,
|_| true,
)
.unwrap()
.finalize(dave_provider)
.expect("Error joining from external commit");
dave_group
.merge_pending_commit(dave_provider)
.expect("Cannot merge commit");
assert!(alice_group.members().eq(bob_group.members()));
assert_eq!(
alice_group.epoch_authenticator().as_slice(),
bob_group.epoch_authenticator().as_slice()
);
let message_alice = b"Hi, I'm Alice!";
let mls_message_out = alice_group
.create_message(alice_provider, &alice_signature_keys, message_alice)
.expect("Error creating application message.");
let bytes = mls_message_out
.to_bytes()
.expect("Could not serialize message.");
let mls_message =
MlsMessageIn::tls_deserialize_exact(bytes).expect("Could not deserialize message.");
let protocol_message: ProtocolMessage = mls_message
.try_into_protocol_message()
.expect("Expected a PublicMessage or a PrivateMessage");
let processed_message = bob_group
.process_message(bob_provider, protocol_message)
.expect("Could not process message.");
if let ProcessedMessageContent::ApplicationMessage(application_message) =
processed_message.into_content()
{
assert_eq!(application_message.into_bytes(), b"Hi, I'm Alice!");
}
else {
unreachable!("Expected an ApplicationMessage.");
}
alice_group.set_aad(b"Additional Authenticated Data".to_vec());
assert_eq!(alice_group.aad(), b"Additional Authenticated Data");
let message_alice = b"Hi, I'm Alice!";
let mls_message_out = alice_group
.create_message(alice_provider, &alice_signature_keys, message_alice)
.expect("Error creating application message.");
let bytes = mls_message_out
.to_bytes()
.expect("Could not serialize message.");
let mls_message =
MlsMessageIn::tls_deserialize_exact(bytes).expect("Could not deserialize message.");
let protocol_message: ProtocolMessage = mls_message
.try_into_protocol_message()
.expect("Expected a PublicMessage or a PrivateMessage");
let processed_message = bob_group
.process_message(bob_provider, protocol_message)
.expect("Could not process message.");
assert_eq!(processed_message.aad(), b"Additional Authenticated Data");
let (mls_message_out, welcome_option, _group_info) = bob_group
.self_update(
bob_provider,
&bob_signature_keys,
LeafNodeParameters::default(),
)
.expect("Could not update own key package.")
.into_contents();
let alice_processed_message = alice_group
.process_message(
alice_provider,
mls_message_out
.into_protocol_message()
.expect("Unexpected message type"),
)
.expect("Could not process message.");
if let ProcessedMessageContent::StagedCommitMessage(staged_commit) =
alice_processed_message.into_content()
{
alice_group
.merge_staged_commit(alice_provider, *staged_commit)
.expect("Error merging staged commit.");
} else {
unreachable!("Expected a StagedCommit.");
}
bob_group
.merge_pending_commit(bob_provider)
.expect("error merging pending commit");
assert!(welcome_option.is_none());
assert_eq!(
alice_group
.export_secret(alice_provider.crypto(), "", &[], 32)
.unwrap(),
bob_group
.export_secret(bob_provider.crypto(), "", &[], 32)
.unwrap()
);
assert_eq!(
alice_group.export_ratchet_tree(),
bob_group.export_ratchet_tree()
);
let (mls_message_out, _proposal_ref) = alice_group
.propose_self_update(
alice_provider,
&alice_signature_keys,
LeafNodeParameters::default(),
)
.expect("Could not create update proposal.");
let bob_processed_message = bob_group
.process_message(
bob_provider,
mls_message_out
.into_protocol_message()
.expect("Unexpected message type"),
)
.expect("Could not process message.");
if let ProcessedMessageContent::ProposalMessage(staged_proposal) =
bob_processed_message.into_content()
{
if let Proposal::Update(ref update_proposal) = staged_proposal.proposal() {
assert_eq!(
update_proposal.leaf_node().credential(),
&alice_credential.credential
);
alice_group
.store_pending_proposal(alice_provider.storage(), *staged_proposal.clone())
.unwrap();
} else {
unreachable!("Expected a Proposal.");
}
assert!(matches!(
staged_proposal.sender(),
Sender::Member(member) if *member == alice_group.own_leaf_index()
));
bob_group
.store_pending_proposal(bob_provider.storage(), *staged_proposal)
.unwrap();
} else {
unreachable!("Expected a QueuedProposal.");
}
let (mls_message_out, welcome_option, _group_info) = alice_group
.commit_to_pending_proposals(alice_provider, &alice_signature_keys)
.expect("Could not commit to pending proposals.");
let _welcome_option = welcome_option;
let bob_processed_message = bob_group
.process_message(
bob_provider,
mls_message_out
.into_protocol_message()
.expect("Unexpected message type"),
)
.expect("Could not process message.");
alice_group
.merge_pending_commit(alice_provider)
.expect("error merging pending commit");
if let ProcessedMessageContent::StagedCommitMessage(staged_commit) =
bob_processed_message.into_content()
{
let authenticator_bob = staged_commit
.epoch_authenticator()
.expect("Couldn't get authenticator.");
let authenticator_alice = alice_group.epoch_authenticator();
assert_eq!(authenticator_bob.as_slice(), authenticator_alice.as_slice());
bob_group
.merge_staged_commit(bob_provider, *staged_commit)
.expect("Error merging staged commit.");
} else {
unreachable!("Expected a StagedCommit.");
}
assert_eq!(
alice_group
.export_secret(alice_provider.crypto(), "", &[], 32)
.unwrap(),
bob_group
.export_secret(bob_provider.crypto(), "", &[], 32)
.unwrap()
);
assert_eq!(
alice_group.export_ratchet_tree(),
bob_group.export_ratchet_tree()
);
let charlie_key_package = generate_key_package(
ciphersuite,
charlie_credential.clone(),
Extensions::default(),
charlie_provider,
&charlie_signature_keys,
);
let (queued_message, welcome, _group_info) = bob_group
.add_members(
bob_provider,
&bob_signature_keys,
core::slice::from_ref(charlie_key_package.key_package()),
)
.unwrap();
let alice_processed_message = alice_group
.process_message(
alice_provider,
queued_message
.into_protocol_message()
.expect("Unexpected message type"),
)
.expect("Could not process message.");
bob_group
.merge_pending_commit(bob_provider)
.expect("error merging pending commit");
if let ProcessedMessageContent::StagedCommitMessage(staged_commit) =
alice_processed_message.into_content()
{
alice_group
.merge_staged_commit(alice_provider, *staged_commit)
.expect("Error merging staged commit.");
} else {
unreachable!("Expected a StagedCommit.");
}
let welcome: MlsMessageIn = welcome.into();
let welcome = welcome
.into_welcome()
.expect("expected the message to be a welcome message");
let mut charlie_group = StagedWelcome::new_from_welcome(
charlie_provider,
mls_group_create_config.join_config(),
welcome,
Some(bob_group.export_ratchet_tree().into()),
)
.expect("Error building StagedWelcome")
.into_group(charlie_provider)
.expect("Error creating group from Welcome");
assert_eq!(
alice_group.export_ratchet_tree(),
bob_group.export_ratchet_tree(),
);
assert_eq!(
alice_group.export_ratchet_tree(),
charlie_group.export_ratchet_tree()
);
let members = alice_group.members().collect::<Vec<Member>>();
let credential0 = members[0].credential.serialized_content();
let credential1 = members[1].credential.serialized_content();
let credential2 = members[2].credential.serialized_content();
assert_eq!(credential0, b"Alice");
assert_eq!(credential1, b"Bob");
assert_eq!(credential2, b"Charlie");
assert_eq!(members.len(), 3);
let message_charlie = b"Hi, I'm Charlie!";
let queued_message = charlie_group
.create_message(charlie_provider, &charlie_signature_keys, message_charlie)
.expect("Error creating application message");
let _alice_processed_message = alice_group
.process_message(
alice_provider,
queued_message
.clone()
.into_protocol_message()
.expect("Unexpected message type"),
)
.expect("Could not process message.");
let _bob_processed_message = bob_group
.process_message(
bob_provider,
queued_message
.into_protocol_message()
.expect("Unexpected message type"),
)
.expect("Could not process message.");
let (queued_message, welcome_option, _group_info) = charlie_group
.self_update(
charlie_provider,
&charlie_signature_keys,
LeafNodeParameters::default(),
)
.unwrap()
.into_contents();
let alice_processed_message = alice_group
.process_message(
alice_provider,
queued_message
.clone()
.into_protocol_message()
.expect("Unexpected message type"),
)
.expect("Could not process message.");
let bob_processed_message = bob_group
.process_message(
bob_provider,
queued_message
.into_protocol_message()
.expect("Unexpected message type"),
)
.expect("Could not process message.");
charlie_group
.merge_pending_commit(charlie_provider)
.expect("error merging pending commit");
if let ProcessedMessageContent::StagedCommitMessage(staged_commit) =
alice_processed_message.into_content()
{
alice_group
.merge_staged_commit(alice_provider, *staged_commit)
.expect("Error merging staged commit.");
} else {
unreachable!("Expected a StagedCommit.");
}
if let ProcessedMessageContent::StagedCommitMessage(staged_commit) =
bob_processed_message.into_content()
{
bob_group
.merge_staged_commit(bob_provider, *staged_commit)
.expect("Error merging staged commit.");
} else {
unreachable!("Expected a StagedCommit.");
}
assert!(welcome_option.is_none());
assert_eq!(
alice_group
.export_secret(alice_provider.crypto(), "", &[], 32)
.unwrap(),
bob_group
.export_secret(bob_provider.crypto(), "", &[], 32)
.unwrap()
);
assert_eq!(
alice_group
.export_secret(alice_provider.crypto(), "", &[], 32)
.unwrap(),
charlie_group
.export_secret(charlie_provider.crypto(), "", &[], 32)
.unwrap()
);
assert_eq!(
alice_group.export_ratchet_tree(),
bob_group.export_ratchet_tree(),
);
assert_eq!(
alice_group.export_ratchet_tree(),
charlie_group.export_ratchet_tree()
);
let charlie_members = charlie_group.members().collect::<Vec<Member>>();
let bob_member = charlie_members
.iter()
.find(
|Member {
index: _,
credential,
..
}| { credential.serialized_content() == b"Bob" },
)
.expect("Couldn't find Bob in the list of group members.");
let bob_cred = bob_member.credential.serialized_content();
let bob_group_cred = bob_group
.own_leaf()
.unwrap()
.credential()
.serialized_content();
assert_eq!(bob_cred, bob_group_cred);
let (mls_message_out, welcome_option, _group_info) = charlie_group
.remove_members(
charlie_provider,
&charlie_signature_keys,
&[bob_member.index],
)
.expect("Could not remove Bob from group.");
assert!(bob_group.is_active());
let alice_processed_message = alice_group
.process_message(
alice_provider,
mls_message_out
.clone()
.into_protocol_message()
.expect("Unexpected message type"),
)
.expect("Could not process message.");
let mut alice_members = alice_group.members();
let sender_leaf_index = match alice_processed_message.sender() {
Sender::Member(index) => index,
_ => panic!("Sender should have been a member"),
};
let sender_credential = alice_processed_message.credential();
assert!(alice_members.any(|Member { index, .. }| &index == sender_leaf_index));
drop(alice_members);
assert_eq!(sender_credential, &charlie_credential.credential);
let bob_processed_message = bob_group
.process_message(
bob_provider,
mls_message_out
.into_protocol_message()
.expect("Unexpected message type"),
)
.expect("Could not process message.");
let charlies_leaf_index = charlie_group.own_leaf_index();
charlie_group
.merge_pending_commit(charlie_provider)
.expect("error merging pending commit");
if let ProcessedMessageContent::StagedCommitMessage(staged_commit) =
alice_processed_message.into_content()
{
let remove = staged_commit
.remove_proposals()
.next()
.expect("Expected a proposal.");
assert_eq!(
remove.remove_proposal().removed(),
bob_group.own_leaf_index()
);
assert!(matches!(
remove.sender(),
Sender::Member(member) if *member == charlies_leaf_index
));
alice_group
.merge_staged_commit(alice_provider, *staged_commit)
.expect("Error merging staged commit.");
}
else {
unreachable!("Expected a StagedCommit.");
}
if let ProcessedMessageContent::StagedCommitMessage(staged_commit) =
bob_processed_message.into_content()
{
let remove_proposal = staged_commit
.remove_proposals()
.next()
.expect("An unexpected error occurred.");
let remove_operation = RemoveOperation::new(remove_proposal, &bob_group)
.expect("An unexpected Error occurred.");
match remove_operation {
RemoveOperation::WeLeft => unreachable!(),
RemoveOperation::WeWereRemovedBy(member) => {
assert!(matches!(member, Sender::Member(member) if member == charlies_leaf_index));
}
RemoveOperation::TheyLeft(_) => unreachable!(),
RemoveOperation::TheyWereRemovedBy(_) => unreachable!(),
RemoveOperation::WeRemovedThem(_) => unreachable!(),
}
bob_group
.merge_staged_commit(bob_provider, *staged_commit)
.expect("Error merging staged commit.");
} else {
unreachable!("Expected a StagedCommit.");
}
assert!(welcome_option.is_none());
assert!(!bob_group.is_active());
let members = bob_group.members().collect::<Vec<Member>>();
assert_eq!(members.len(), 2);
let credential0 = members[0].credential.serialized_content();
let credential1 = members[1].credential.serialized_content();
assert_eq!(credential0, b"Alice");
assert_eq!(credential1, b"Charlie");
assert_eq!(
alice_group.export_ratchet_tree(),
charlie_group.export_ratchet_tree()
);
assert_eq!(alice_group.members().count(), 2);
let members = alice_group.members().collect::<Vec<Member>>();
let credential0 = members[0].credential.serialized_content();
let credential1 = members[1].credential.serialized_content();
assert_eq!(credential0, b"Alice");
assert_eq!(credential1, b"Charlie");
assert!(bob_group
.create_message(bob_provider, &bob_signature_keys, b"Should not go through")
.is_err());
let bob_key_package = generate_key_package(
ciphersuite,
bob_credential.clone(),
Extensions::default(),
bob_provider,
&bob_signature_keys,
);
let (mls_message_out, _proposal_ref) = alice_group
.propose_remove_member(
alice_provider,
&alice_signature_keys,
charlie_group.own_leaf_index(),
)
.expect("Could not create proposal to remove Charlie.");
let charlie_processed_message = charlie_group
.process_message(
charlie_provider,
mls_message_out
.into_protocol_message()
.expect("Unexpected message type"),
)
.expect("Could not process message.");
if let ProcessedMessageContent::ProposalMessage(staged_proposal) =
charlie_processed_message.into_content()
{
if let Proposal::Remove(ref remove_proposal) = staged_proposal.proposal() {
assert_eq!(remove_proposal.removed(), charlie_group.own_leaf_index());
charlie_group
.store_pending_proposal(charlie_provider.storage(), *staged_proposal.clone())
.unwrap();
} else {
unreachable!("Expected a Proposal.");
}
assert!(matches!(
staged_proposal.sender(),
Sender::Member(member) if *member == alice_group.own_leaf_index()
));
} else {
unreachable!("Expected a QueuedProposal.");
}
let (_mls_message_out, proposal_ref) = alice_group
.propose_add_member(
alice_provider,
&alice_signature_keys,
bob_key_package.key_package(),
)
.expect("Could not create proposal to add Bob");
alice_group
.remove_pending_proposal(alice_provider.storage(), &proposal_ref)
.expect("The proposal was not found");
let (mls_message_out, _proposal_ref) = alice_group
.propose_add_member(
alice_provider,
&alice_signature_keys,
bob_key_package.key_package(),
)
.expect("Could not create proposal to add Bob");
let charlie_processed_message = charlie_group
.process_message(
charlie_provider,
mls_message_out
.into_protocol_message()
.expect("Unexpected message type"),
)
.expect("Could not process message.");
if let ProcessedMessageContent::ProposalMessage(staged_proposal) =
charlie_processed_message.into_content()
{
if let Proposal::Add(add_proposal) = staged_proposal.proposal() {
assert_eq!(
add_proposal.key_package().leaf_node().credential(),
&bob_credential.credential
);
} else {
panic!("Expected an AddProposal.");
}
assert!(matches!(
staged_proposal.sender(),
Sender::Member(member) if *member == alice_group.own_leaf_index()
));
charlie_group
.store_pending_proposal(charlie_provider.storage(), *staged_proposal)
.unwrap();
}
else {
unreachable!("Expected a QueuedProposal.");
}
let (queued_message, welcome_option, _group_info) = alice_group
.commit_to_pending_proposals(alice_provider, &alice_signature_keys)
.expect("Could not flush proposals");
let charlie_processed_message = charlie_group
.process_message(
charlie_provider,
queued_message
.into_protocol_message()
.expect("Unexpected message type"),
)
.expect("Could not process message.");
alice_group
.merge_pending_commit(alice_provider)
.expect("error merging pending commit");
if let ProcessedMessageContent::StagedCommitMessage(staged_commit) =
charlie_processed_message.into_content()
{
charlie_group
.merge_staged_commit(charlie_provider, *staged_commit)
.expect("Error merging staged commit.");
} else {
unreachable!("Expected a StagedCommit.");
}
assert_eq!(alice_group.members().count(), 2);
let members = alice_group.members().collect::<Vec<Member>>();
let credential0 = members[0].credential.serialized_content();
let credential1 = members[1].credential.serialized_content();
assert_eq!(credential0, b"Alice");
assert_eq!(credential1, b"Bob");
let welcome: MlsMessageIn = welcome_option.expect("Welcome was not returned").into();
let welcome = welcome
.into_welcome()
.expect("expected the message to be a welcome message");
let processed_welcome = ProcessedWelcome::new_from_welcome(
bob_provider,
mls_group_create_config.join_config(),
welcome,
)
.expect("Error creating ProcessedWelcome");
let mut bob_group = JoinBuilder::new(bob_provider, processed_welcome)
.replace_old_group()
.with_ratchet_tree(alice_group.export_ratchet_tree().into())
.build()
.expect("Error creating group from ProcessedWelcome")
.into_group(bob_provider)
.expect("Error creating group from StagedWelcome");
assert_eq!(alice_group.members().count(), 2);
let members = alice_group.members().collect::<Vec<Member>>();
let credential0 = members[0].credential.serialized_content();
let credential1 = members[1].credential.serialized_content();
assert_eq!(credential0, b"Alice");
assert_eq!(credential1, b"Bob");
assert_eq!(bob_group.members().count(), 2);
let members = bob_group.members().collect::<Vec<Member>>();
let credential0 = members[0].credential.serialized_content();
let credential1 = members[1].credential.serialized_content();
assert_eq!(credential0, b"Alice");
assert_eq!(credential1, b"Bob");
let message_alice = b"Hi, I'm Alice!";
let queued_message = alice_group
.create_message(alice_provider, &alice_signature_keys, message_alice)
.expect("Error creating application message");
let bob_processed_message = bob_group
.process_message(
bob_provider,
queued_message
.into_protocol_message()
.expect("Unexpected message type"),
)
.expect("Could not process message.");
let sender_cred_from_msg = bob_processed_message.credential().clone();
let sender_cred_from_group =
if let Sender::Member(sender_index) = bob_processed_message.sender() {
bob_group
.member(*sender_index)
.expect("Could not find sender in group.")
.clone()
} else {
unreachable!("Expected sender type to be `Member`.")
};
if let ProcessedMessageContent::ApplicationMessage(application_message) =
bob_processed_message.into_content()
{
assert_eq!(application_message.into_bytes(), message_alice);
assert_eq!(sender_cred_from_msg, sender_cred_from_group);
assert_eq!(
&sender_cred_from_msg,
alice_group.credential().expect("Expected a credential.")
);
} else {
unreachable!("Expected an ApplicationMessage.");
}
let queued_message = bob_group
.leave_group(bob_provider, &bob_signature_keys)
.expect("Could not leave group");
let alice_processed_message = alice_group
.process_message(
alice_provider,
queued_message
.into_protocol_message()
.expect("Unexpected message type"),
)
.expect("Could not process message.");
if let ProcessedMessageContent::ProposalMessage(staged_proposal) =
alice_processed_message.into_content()
{
alice_group
.store_pending_proposal(alice_provider.storage(), *staged_proposal)
.unwrap();
} else {
unreachable!("Expected a QueuedProposal.");
}
assert!(matches!(
bob_group.commit_to_pending_proposals(bob_provider, &bob_signature_keys),
Err(CommitToPendingProposalsError::CreateCommitError(
CreateCommitError::CannotRemoveSelf
))
));
let (queued_message, _welcome_option, _group_info) = alice_group
.commit_to_pending_proposals(alice_provider, &alice_signature_keys)
.expect("Could not commit to proposals.");
assert!(bob_group.is_active());
if let Some(staged_commit) = alice_group.pending_commit() {
let remove = staged_commit
.remove_proposals()
.next()
.expect("Expected a proposal.");
assert_eq!(
remove.remove_proposal().removed(),
bob_group.own_leaf_index()
);
assert!(matches!(
remove.sender(),
Sender::Member(member) if *member == bob_group.own_leaf_index()
));
} else {
unreachable!("Expected a StagedCommit.");
}
alice_group
.merge_pending_commit(alice_provider)
.expect("Could not merge Commit.");
let bob_processed_message = bob_group
.process_message(
bob_provider,
queued_message
.into_protocol_message()
.expect("Unexpected message type"),
)
.expect("Could not process message.");
if let ProcessedMessageContent::StagedCommitMessage(staged_commit) =
bob_processed_message.into_content()
{
let remove = staged_commit
.remove_proposals()
.next()
.expect("Expected a proposal.");
assert_eq!(
remove.remove_proposal().removed(),
bob_group.own_leaf_index()
);
assert!(matches!(
remove.sender(),
Sender::Member(member) if *member == bob_group.own_leaf_index()
));
assert!(staged_commit.self_removed());
bob_group
.merge_staged_commit(bob_provider, *staged_commit)
.expect("Error merging staged commit.");
} else {
unreachable!("Expected a StagedCommit.");
}
assert!(!bob_group.is_active());
assert_eq!(alice_group.members().count(), 1);
let members = alice_group.members().collect::<Vec<Member>>();
let credential0 = members[0].credential.serialized_content();
assert_eq!(credential0, b"Alice");
let bob_key_package = generate_key_package(
ciphersuite,
bob_credential.clone(),
Extensions::default(),
bob_provider,
&bob_signature_keys,
);
let proposal =
JoinProposal::new::<<Provider as openmls_traits::OpenMlsProvider>::StorageProvider>(
bob_key_package.key_package().clone(),
alice_group.group_id().clone(),
alice_group.epoch(),
&bob_signature_keys,
)
.expect("Could not create external Add proposal");
bob_group.delete(bob_provider.storage()).unwrap();
let alice_processed_message = alice_group
.process_message(
alice_provider,
proposal
.into_protocol_message()
.expect("Unexpected message type."),
)
.expect("Could not process message.");
match alice_processed_message.into_content() {
ProcessedMessageContent::ExternalJoinProposalMessage(proposal) => {
alice_group
.store_pending_proposal(alice_provider.storage(), *proposal)
.unwrap();
let (_commit, welcome, _group_info) = alice_group
.commit_to_pending_proposals(alice_provider, &alice_signature_keys)
.expect("Could not commit");
assert_eq!(alice_group.members().count(), 1);
alice_group
.merge_pending_commit(alice_provider)
.expect("Could not merge commit");
assert_eq!(alice_group.members().count(), 2);
let welcome: MlsMessageIn = welcome.expect("Welcome was not returned").into();
let welcome = welcome
.into_welcome()
.expect("expected the message to be a welcome message");
let bob_group = StagedWelcome::new_from_welcome(
bob_provider,
mls_group_create_config.join_config(),
welcome,
None,
)
.expect("Bob could not stage the the group join")
.into_group(bob_provider)
.expect("Bob could not join the group");
assert_eq!(bob_group.members().count(), 2);
}
_ => unreachable!(),
}
let bob_index = alice_group
.members()
.find_map(|member| {
let credential = member.credential.serialized_content();
if credential == b"Bob" {
Some(member.index)
} else {
None
}
})
.unwrap();
let proposal = ExternalProposal::new_remove::<Provider>(
bob_index,
alice_group.group_id().clone(),
alice_group.epoch(),
&ds_signature_keys,
SenderExtensionIndex::new(0),
)
.expect("Could not create external Remove proposal");
let alice_processed_message = alice_group
.process_message(
alice_provider,
proposal
.into_protocol_message()
.expect("Unexpected message type."),
)
.expect("Could not process message.");
match alice_processed_message.into_content() {
ProcessedMessageContent::ProposalMessage(proposal) => {
alice_group
.store_pending_proposal(alice_provider.storage(), *proposal)
.unwrap();
assert_eq!(alice_group.members().count(), 2);
alice_group
.commit_to_pending_proposals(alice_provider, &alice_signature_keys)
.expect("Could not commit");
alice_group
.merge_pending_commit(alice_provider)
.expect("Could not merge commit");
assert_eq!(alice_group.members().count(), 1);
}
_ => unreachable!(),
}
let bob_key_package = generate_key_package(
ciphersuite,
bob_credential.clone(),
Extensions::default(),
bob_provider,
&bob_signature_keys,
);
let proposal = ExternalProposal::new_add::<Provider>(
bob_key_package.key_package().clone(),
alice_group.group_id().clone(),
alice_group.epoch(),
&ds_signature_keys,
SenderExtensionIndex::new(0),
)
.expect("Could not create external Add proposal");
let alice_processed_message = alice_group
.process_message(
alice_provider,
proposal
.into_protocol_message()
.expect("Unexpected message type"),
)
.expect("Could not process message.");
if let ProcessedMessageContent::ProposalMessage(staged_proposal) =
alice_processed_message.into_content()
{
alice_group
.store_pending_proposal(alice_provider.storage(), *staged_proposal)
.unwrap();
} else {
unreachable!("Expected a QueuedProposal.");
}
let (_, welcome, _) = alice_group
.commit_to_pending_proposals(alice_provider, &alice_signature_keys)
.expect("Could not commit");
alice_group
.merge_pending_commit(alice_provider)
.expect("error merging pending commit");
bob_group.delete(bob_provider.storage()).unwrap();
let welcome: MlsMessageIn = welcome.unwrap().into();
let welcome = welcome
.into_welcome()
.expect("expected the message to be a welcome message");
let bob_staged_welcome = StagedWelcome::new_from_welcome(
bob_provider,
mls_group_create_config.join_config(),
welcome,
Some(alice_group.export_ratchet_tree().into()),
)
.expect("Could not create StagedWelcome from Welcome");
let mut bob_group = bob_staged_welcome
.into_group(bob_provider)
.expect("Could not create group from StagedWelcome");
assert_eq!(
alice_group
.export_secret(alice_provider.crypto(), "before load", &[], 32)
.unwrap(),
bob_group
.export_secret(alice_provider.crypto(), "before load", &[], 32)
.unwrap()
);
bob_group = MlsGroup::load(alice_provider.storage(), &group_id)
.expect("An error occurred while loading the group")
.expect("No group with provided group id exists");
assert_eq!(
alice_group
.export_secret(alice_provider.crypto(), "after load", &[], 32)
.unwrap(),
bob_group
.export_secret(alice_provider.crypto(), "after load", &[], 32)
.unwrap()
);
}
#[openmls_test]
fn test_empty_input_errors() {
let provider = &Provider::default();
let group_id = GroupId::from_slice(b"Test Group");
let (alice_credential, alice_signature_keys) =
generate_credential("Alice".into(), ciphersuite.signature_algorithm(), provider);
let mls_group_config = MlsGroupCreateConfig::test_default(ciphersuite);
let mut alice_group = MlsGroup::new_with_group_id(
provider,
&alice_signature_keys,
&mls_group_config,
group_id,
alice_credential,
)
.expect("An unexpected error occurred.");
assert!(matches!(
alice_group
.add_members(provider, &alice_signature_keys, &[])
.expect_err("No EmptyInputError when trying to pass an empty slice to `add_members`."),
AddMembersError::EmptyInput(EmptyInputError::AddMembers)
));
assert!(matches!(
alice_group
.remove_members(provider, &alice_signature_keys, &[])
.expect_err(
"No EmptyInputError when trying to pass an empty slice to `remove_members`."
),
RemoveMembersError::EmptyInput(EmptyInputError::RemoveMembers)
));
}
#[openmls_test]
fn custom_proposal_usage() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let (alice_credential_with_key, alice_signer) = generate_credential(
b"alice".into(),
ciphersuite.signature_algorithm(),
alice_provider,
);
let (bob_credential_with_key, bob_signer) = generate_credential(
b"bob".into(),
ciphersuite.signature_algorithm(),
bob_provider,
);
let custom_proposal_type = 0xFFFF;
let capabilities = Capabilities::new(
None,
None,
None,
Some(&[ProposalType::Custom(custom_proposal_type)]),
None,
);
let bob_key_package = KeyPackageBuilder::new()
.leaf_node_capabilities(capabilities.clone())
.build(
ciphersuite,
bob_provider,
&bob_signer,
bob_credential_with_key,
)
.unwrap();
let mut alice_group = MlsGroup::builder()
.with_capabilities(capabilities.clone())
.ciphersuite(ciphersuite)
.build(alice_provider, &alice_signer, alice_credential_with_key)
.unwrap();
let (_mls_message, welcome, _group_info) = alice_group
.add_members(
alice_provider,
&alice_signer,
core::slice::from_ref(bob_key_package.key_package()),
)
.unwrap();
alice_group.merge_pending_commit(alice_provider).unwrap();
let staged_welcome = StagedWelcome::new_from_welcome(
bob_provider,
&MlsGroupJoinConfig::default(),
welcome.into_welcome().unwrap(),
Some(alice_group.export_ratchet_tree().into()),
)
.unwrap();
let mut bob_group = staged_welcome.into_group(bob_provider).unwrap();
let custom_proposal_payload = vec![0, 1, 2, 3];
let custom_proposal =
CustomProposal::new(custom_proposal_type, custom_proposal_payload.clone());
let (custom_proposal_message, _proposal_ref) = alice_group
.propose_custom_proposal_by_reference(
alice_provider,
&alice_signer,
custom_proposal.clone(),
)
.unwrap();
let processed_message = bob_group
.process_message(
bob_provider,
custom_proposal_message.into_protocol_message().unwrap(),
)
.unwrap();
let ProcessedMessageContent::ProposalMessage(proposal) = processed_message.into_content()
else {
panic!("Unexpected message type");
};
bob_group
.store_pending_proposal(bob_provider.storage(), *proposal)
.unwrap();
let (commit, _, _) = alice_group
.commit_to_pending_proposals(alice_provider, &alice_signer)
.unwrap();
let processed_message = bob_group
.process_message(bob_provider, commit.into_protocol_message().unwrap())
.unwrap();
let staged_commit = match processed_message.into_content() {
ProcessedMessageContent::StagedCommitMessage(staged_commit) => staged_commit,
_ => panic!("Unexpected message type"),
};
assert!(staged_commit.queued_proposals().any(|qp| {
let Proposal::Custom(custom_proposal) = qp.proposal() else {
return false;
};
custom_proposal.proposal_type() == custom_proposal_type
&& custom_proposal.payload() == custom_proposal_payload
}));
}
#[openmls_test]
fn commit_builder() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let ds_provider = &Provider::default();
let (alice_credential, alice_signature_keys) = generate_credential(
"Alice".into(),
ciphersuite.signature_algorithm(),
alice_provider,
);
let (bob_credential, bob_signature_keys) = generate_credential(
"Bob".into(),
ciphersuite.signature_algorithm(),
bob_provider,
);
let bob_key_package = generate_key_package(
ciphersuite,
bob_credential.clone(),
Extensions::default(),
bob_provider,
&bob_signature_keys,
);
let (ds_credential_with_key, _) = generate_credential(
"delivery-service".into(),
ciphersuite.signature_algorithm(),
ds_provider,
);
let mls_group_create_config = MlsGroupCreateConfig::builder()
.padding_size(100)
.sender_ratchet_configuration(SenderRatchetConfiguration::new(
10, 2000, ))
.with_group_context_extensions(
Extensions::single(Extension::ExternalSenders(vec![ExternalSender::new(
ds_credential_with_key.signature_key.clone(),
ds_credential_with_key.credential.clone(),
)]))
.expect("error adding external senders extension to group context extensions"),
)
.ciphersuite(ciphersuite)
.capabilities(Capabilities::new(
None, None, Some(&[ExtensionType::Unknown(0xff00)]),
None, Some(&[CredentialType::Basic]),
))
.with_leaf_node_extensions(
Extensions::single(Extension::Unknown(
0xff00,
UnknownExtension(vec![0, 1, 2, 3]),
))
.expect("failed to create single-element extensions list"),
)
.expect("failed to configure leaf extensions")
.use_ratchet_tree_extension(true)
.build();
let mut alice_group = MlsGroup::new(
alice_provider,
&alice_signature_keys,
&mls_group_create_config,
alice_credential.clone(),
)
.expect("An unexpected error occurred.");
let message_bundle = alice_group
.commit_builder()
.propose_adds(Some(bob_key_package.key_package().clone()))
.load_psks(alice_provider.storage())
.expect("error loading psks")
.build(
alice_provider.rand(),
alice_provider.crypto(),
&alice_signature_keys,
|_proposal| true,
)
.expect("error validating data and building commit")
.stage_commit(alice_provider)
.expect("error staging commit");
let (mls_message_out, welcome, group_info) = message_bundle.into_contents();
_ = (mls_message_out, welcome, group_info)
}
#[openmls_test]
fn new_signer() {
let alice_provider = &Provider::default();
let (alice_old_credential, alice_old_signature_keys) = generate_credential(
"Alice".into(),
ciphersuite.signature_algorithm(),
alice_provider,
);
let config = MlsGroupCreateConfig::builder()
.ciphersuite(ciphersuite)
.build();
let mut alice_group = MlsGroup::new(
alice_provider,
&alice_old_signature_keys,
&config,
alice_old_credential.clone(),
)
.expect("An unexpected error occurred.");
let (alice_new_credential, alice_new_signature_keys) = generate_credential(
"Alice".into(),
ciphersuite.signature_algorithm(),
alice_provider,
);
let new_signer_bundle = NewSignerBundle {
signer: &alice_new_signature_keys,
credential_with_key: alice_new_credential,
};
let message_bundle = alice_group
.self_update_with_new_signer(
alice_provider,
&alice_old_signature_keys,
new_signer_bundle,
LeafNodeParameters::default(),
)
.unwrap();
let (mls_message_out, welcome, group_info) = message_bundle.into_contents();
_ = (mls_message_out, welcome, group_info)
}
#[openmls_test]
fn external_commit_builder() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let charlie_provider = &Provider::default();
let (alice_credential_with_key, alice_signer) = generate_credential(
"Alice".into(),
ciphersuite.signature_algorithm(),
alice_provider,
);
let (bob_credential_with_key, bob_signer) = generate_credential(
"Bob".into(),
ciphersuite.signature_algorithm(),
bob_provider,
);
let (charlie_credential_with_key, charlie_signer) = generate_credential(
"Charlie".into(),
ciphersuite.signature_algorithm(),
charlie_provider,
);
let capabilities = Capabilities::builder()
.proposals(vec![ProposalType::SelfRemove])
.build();
const POLICY: WireFormatPolicy = PURE_PLAINTEXT_WIRE_FORMAT_POLICY;
let mut alice_group = MlsGroup::builder()
.ciphersuite(ciphersuite)
.with_wire_format_policy(POLICY)
.with_capabilities(capabilities.clone())
.build(alice_provider, &alice_signer, alice_credential_with_key)
.unwrap();
let verifiable_group_info = alice_group
.export_group_info(alice_provider.crypto(), &alice_signer, false)
.unwrap()
.into_verifiable_group_info()
.unwrap();
let tree_option = alice_group.export_ratchet_tree();
const PADDING_SIZE: usize = 256;
const AAD: &[u8] = b"some additional authenticated data";
let leaf_node_parameters = LeafNodeParameters::builder()
.with_capabilities(capabilities.clone())
.build();
let join_group_config = MlsGroupJoinConfig::builder()
.padding_size(PADDING_SIZE)
.wire_format_policy(POLICY)
.build();
let (mut bob_group, commit_message_bundle) = MlsGroup::external_commit_builder()
.with_ratchet_tree(tree_option.into())
.with_config(join_group_config.clone())
.with_aad(AAD.to_vec())
.build_group(
bob_provider,
verifiable_group_info,
bob_credential_with_key.clone(),
)
.expect("error building group")
.leaf_node_parameters(leaf_node_parameters)
.load_psks(bob_provider.storage())
.expect("error loading psks")
.build(
bob_provider.rand(),
bob_provider.crypto(),
&bob_signer,
|_| true,
)
.expect("error building external commit")
.finalize(bob_provider)
.expect("error finalizing external commit");
assert_eq!(bob_group.configuration().padding_size(), PADDING_SIZE);
let plaintext = commit_message_bundle
.into_commit()
.into_protocol_message()
.unwrap();
alice_group.set_aad(AAD.to_vec());
let processed_message = alice_group
.process_message(alice_provider, plaintext)
.unwrap();
let ProcessedMessageContent::StagedCommitMessage(staged_commit) =
processed_message.into_content()
else {
panic!("Expected a staged commit message.");
};
alice_group
.merge_staged_commit(alice_provider, *staged_commit)
.unwrap();
let msg_out = alice_group
.leave_group_via_self_remove(alice_provider, &alice_signer)
.unwrap();
let ProtocolMessage::PublicMessage(self_remove_proposal) =
msg_out.into_protocol_message().unwrap()
else {
panic!("Expected a public message for the self-remove proposal.");
};
let bob_processed_message = bob_group
.process_message(bob_provider, *self_remove_proposal.clone())
.unwrap();
let ProcessedMessageContent::ProposalMessage(proposal) = bob_processed_message.into_content()
else {
panic!("Expected a proposal message.");
};
bob_group
.store_pending_proposal(bob_provider.storage(), *proposal)
.unwrap();
let verifiable_group_info = bob_group
.export_group_info(bob_provider.crypto(), &bob_signer, false)
.unwrap()
.into_verifiable_group_info()
.unwrap();
let psk_id_bytes = vec![0, 1, 2, 3];
let psk_id = Psk::External(ExternalPsk::new(psk_id_bytes.clone()));
let psk = PreSharedKeyId::new(ciphersuite, bob_provider.rand(), psk_id).unwrap();
let psk_value = vec![4, 5, 6, 7];
psk.store(bob_provider, &psk_value).unwrap();
psk.store(charlie_provider, &psk_value).unwrap();
let (charlie_group, commit_message_bundle) = MlsGroup::external_commit_builder()
.with_proposals(vec![*self_remove_proposal])
.with_ratchet_tree(bob_group.export_ratchet_tree().into())
.build_group(
charlie_provider,
verifiable_group_info,
charlie_credential_with_key.clone(),
)
.unwrap()
.add_psk_proposal(PreSharedKeyProposal::new(psk))
.load_psks(charlie_provider.storage())
.unwrap()
.build(
charlie_provider.rand(),
charlie_provider.crypto(),
&charlie_signer,
|_| true,
)
.unwrap()
.finalize(charlie_provider)
.unwrap();
let plaintext = commit_message_bundle
.into_commit()
.into_protocol_message()
.unwrap();
let bob_processed_message = bob_group.process_message(bob_provider, plaintext).unwrap();
let ProcessedMessageContent::StagedCommitMessage(staged_commit) =
bob_processed_message.into_content()
else {
panic!("Expected a staged commit message.");
};
bob_group
.merge_staged_commit(bob_provider, *staged_commit)
.unwrap();
let members = bob_group.members().collect::<Vec<_>>();
assert_eq!(members, charlie_group.members().collect::<Vec<_>>());
assert_eq!(members.len(), 2);
assert!(members
.iter()
.any(|m| m.credential == bob_credential_with_key.credential));
assert!(members
.iter()
.any(|m| m.credential == charlie_credential_with_key.credential));
}