use std::slice::from_ref;
use mls_group::{
tests_and_kats::utils::{flip_last_byte, setup_alice_bob, setup_alice_bob_group, setup_client},
EncryptionKeyPair, GroupEpochSecrets, MessageSecretsStore,
};
use openmls_basic_credential::SignatureKeyPair;
use openmls_test::openmls_test;
use openmls_traits::storage::CURRENT_VERSION;
use signable::Signable;
use tls_codec::{Deserialize, Serialize};
use crate::{
binary_tree::LeafNodeIndex,
credentials::{test_utils::new_credential, NewSignerBundle},
framing::*,
group::{errors::*, *},
key_packages::*,
messages::{
group_info::GroupInfoTBS, proposals::*, EncryptedGroupSecrets, GroupSecretsError, Welcome,
},
prelude::{
ConfirmationTag, ExtensionTypeNotValidInLeafNodeError, InvalidExtensionError, LeafNode,
},
schedule::{ExternalPsk, PreSharedKeyId, Psk},
test_utils::{
frankenstein::{FrankenFramedContentBody, FrankenPublicMessage},
single_group_test_framework::{AddMemberConfig, CorePartyState, GroupState},
test_framework::{
errors::ClientError, noop_authentication_service, ActionType::Commit, CodecUse,
MlsGroupTestSetup,
},
},
tree::sender_ratchet::SenderRatchetConfiguration,
treesync::{
errors::ApplyUpdatePathError, node::leaf_node::Capabilities, LeafNodeParameters, TreeSync,
},
};
#[openmls_test]
fn test_mls_group_persistence<Provider: OpenMlsProvider>() {
let alice_provider = &Provider::default();
let group_id = GroupId::from_slice(b"Test Group");
let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) =
setup_client("Alice", ciphersuite, alice_provider);
let mls_group_config = MlsGroupCreateConfig::test_default(ciphersuite);
let alice_group = MlsGroup::new_with_group_id(
alice_provider,
&alice_signer,
&mls_group_config,
group_id.clone(),
alice_credential_with_key,
)
.expect("An unexpected error occurred.");
let alice_group_deserialized = MlsGroup::load(alice_provider.storage(), &group_id)
.expect("Could not deserialize MlsGroup: error")
.expect("Could not deserialize MlsGroup: doesn't exist");
assert_eq!(
(
alice_group.export_ratchet_tree(),
alice_group
.export_secret(alice_provider.crypto(), "test", &[], 32)
.unwrap()
),
(
alice_group_deserialized.export_ratchet_tree(),
alice_group_deserialized
.export_secret(alice_provider.crypto(), "test", &[], 32)
.unwrap()
)
);
}
#[openmls_test]
fn remover() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let charlie_provider = &Provider::default();
let group_id = GroupId::from_slice(b"Test Group");
let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) =
setup_client("Alice", ciphersuite, alice_provider);
let (_bob_credential, bob_kpb, bob_signer, _bob_pk) =
setup_client("Bob", ciphersuite, bob_provider);
let (_charlie_credential, charlie_kpb, charlie_signer, _charlie_pk) =
setup_client("Charly", ciphersuite, charlie_provider);
let mls_group_create_config = MlsGroupCreateConfig::builder()
.ciphersuite(ciphersuite)
.build();
let mut alice_group = MlsGroup::new_with_group_id(
alice_provider,
&alice_signer,
&mls_group_create_config,
group_id,
alice_credential_with_key,
)
.expect("An unexpected error occurred.");
alice_group
.ensure_persistence(alice_provider.storage())
.unwrap();
let (_queued_message, welcome, _group_info) = alice_group
.add_members(
alice_provider,
&alice_signer,
from_ref(bob_kpb.key_package()),
)
.expect("Could not add member to group.");
alice_group
.ensure_persistence(alice_provider.storage())
.unwrap();
alice_group
.merge_pending_commit(alice_provider)
.expect("error merging pending commit");
alice_group
.ensure_persistence(alice_provider.storage())
.unwrap();
let welcome: MlsMessageIn = welcome.into();
let welcome = welcome.into_welcome().expect("expected a welcome");
let mut bob_group = StagedWelcome::new_from_welcome(
bob_provider,
mls_group_create_config.join_config(),
welcome,
Some(alice_group.export_ratchet_tree().into()),
)
.expect("Error creating staged join from Welcome")
.into_group(bob_provider)
.expect("Error creating group from staged join");
bob_group
.ensure_persistence(bob_provider.storage())
.unwrap();
let (queued_messages, welcome, _group_info) = bob_group
.add_members(
bob_provider,
&bob_signer,
from_ref(charlie_kpb.key_package()),
)
.unwrap();
bob_group
.ensure_persistence(bob_provider.storage())
.unwrap();
let alice_processed_message = alice_group
.process_message(
alice_provider,
queued_messages
.into_protocol_message()
.expect("Unexpected message type"),
)
.expect("Could not process messages.");
if let ProcessedMessageContent::StagedCommitMessage(staged_commit) =
alice_processed_message.into_content()
{
alice_group
.merge_staged_commit(alice_provider, *staged_commit)
.expect("Error merging commit.");
alice_group
.ensure_persistence(alice_provider.storage())
.unwrap();
} else {
unreachable!("Expected a StagedCommit.");
}
bob_group
.merge_pending_commit(bob_provider)
.expect("error merging pending commit");
bob_group
.ensure_persistence(bob_provider.storage())
.unwrap();
let welcome: MlsMessageIn = welcome.into();
let welcome = welcome.into_welcome().expect("expected a welcome");
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 creating group from Welcome")
.into_group(charlie_provider)
.expect("Error creating group from Welcome");
charlie_group
.ensure_persistence(charlie_provider.storage())
.unwrap();
let (queued_messages, _) = alice_group
.propose_remove_member(alice_provider, &alice_signer, LeafNodeIndex::new(1))
.expect("Could not propose removal");
alice_group
.ensure_persistence(alice_provider.storage())
.unwrap();
let charlie_processed_message = charlie_group
.process_message(
charlie_provider,
queued_messages
.into_protocol_message()
.expect("Unexpected message type"),
)
.expect("Could not process messages.");
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(), LeafNodeIndex::new(1));
charlie_group
.store_pending_proposal(charlie_provider.storage(), *staged_proposal.clone())
.unwrap();
charlie_group
.ensure_persistence(charlie_provider.storage())
.unwrap();
}
assert!(matches!(
staged_proposal.sender(),
Sender::Member(member) if member.u32() == 0
));
} else {
unreachable!("Expected a QueuedProposal.");
}
let (_queued_messages, _welcome, _group_info) = charlie_group
.commit_to_pending_proposals(charlie_provider, &charlie_signer)
.expect("Could not commit proposal");
charlie_group
.ensure_persistence(charlie_provider.storage())
.unwrap();
if let Some(staged_commit) = charlie_group.pending_commit() {
let remove = staged_commit
.remove_proposals()
.next()
.expect("Expected a proposal.");
assert_eq!(remove.remove_proposal().removed().u32(), 1);
assert!(matches!(remove.sender(), Sender::Member(member) if member.u32() == 0));
} else {
unreachable!("Expected a StagedCommit.");
};
charlie_group
.merge_pending_commit(charlie_provider)
.expect("error merging pending commit");
}
#[openmls_test]
fn export_secret() {
let alice_provider = &Provider::default();
let group_id = GroupId::from_slice(b"Test Group");
let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) =
setup_client("Alice", ciphersuite, alice_provider);
let mls_group_create_config = MlsGroupCreateConfig::test_default(ciphersuite);
let alice_group = MlsGroup::new_with_group_id(
alice_provider,
&alice_signer,
&mls_group_create_config,
group_id,
alice_credential_with_key,
)
.expect("An unexpected error occurred.");
assert!(
alice_group
.export_secret(
alice_provider.crypto(),
"test1",
&[],
ciphersuite.hash_length()
)
.expect("An unexpected error occurred.")
!= alice_group
.export_secret(
alice_provider.crypto(),
"test2",
&[],
ciphersuite.hash_length()
)
.expect("An unexpected error occurred.")
);
assert!(
alice_group
.export_secret(
alice_provider.crypto(),
"test",
&[0u8],
ciphersuite.hash_length()
)
.expect("An unexpected error occurred.")
!= alice_group
.export_secret(
alice_provider.crypto(),
"test",
&[1u8],
ciphersuite.hash_length()
)
.expect("An unexpected error occurred.")
)
}
#[cfg(feature = "extensions-draft-08")]
#[openmls_test]
fn safe_export_secret() {
use crate::schedule::application_export_tree::ApplicationExportTreeError;
let alice_party = CorePartyState::<Provider>::new("alice");
let bob_party = CorePartyState::<Provider>::new("bob");
let alice_pre_group = alice_party.generate_pre_group(ciphersuite);
let bob_pre_group = bob_party.generate_pre_group(ciphersuite);
let mls_group_create_config = MlsGroupCreateConfig::builder()
.ciphersuite(ciphersuite)
.use_ratchet_tree_extension(true)
.build();
let mls_group_join_config = mls_group_create_config.join_config().clone();
let group_id = GroupId::from_slice(b"test");
let mut group_state =
GroupState::new_from_party(group_id, alice_pre_group, mls_group_create_config).unwrap();
group_state
.add_member(AddMemberConfig {
adder: "alice",
addees: vec![bob_pre_group],
join_config: mls_group_join_config.clone(),
tree: None,
})
.expect("Could not add member");
let [alice_group_state, bob_group_state] = group_state.members_mut(&["alice", "bob"]);
let alice_commit = alice_group_state
.group
.self_update(
&alice_group_state.party.core_state.provider,
&alice_group_state.party.signer,
LeafNodeParameters::default(),
)
.expect("Could not create self update");
let alice_application_secret = alice_group_state
.group
.safe_export_secret_from_pending(
alice_group_state.party.core_state.provider.crypto(),
alice_group_state.party.core_state.provider.storage(),
0x8000,
)
.expect("Could not export secret");
alice_group_state
.group
.merge_pending_commit(&alice_group_state.party.core_state.provider)
.unwrap();
let component_id = 0x8000;
let processed_message = bob_group_state
.group
.process_message(
&bob_group_state.party.core_state.provider,
MlsMessageIn::from(alice_commit.into_commit())
.into_protocol_message()
.unwrap(),
)
.unwrap();
let ProcessedMessageContent::StagedCommitMessage(staged_commit) =
processed_message.into_content()
else {
panic!("Expected a StagedCommitMessage");
};
bob_group_state
.group
.merge_staged_commit(&bob_group_state.party.core_state.provider, *staged_commit)
.unwrap();
let bob_application_secret = bob_group_state
.group
.safe_export_secret(
bob_group_state.party.core_state.provider.crypto(),
bob_group_state.party.core_state.provider.storage(),
component_id,
)
.unwrap();
assert_eq!(alice_application_secret, bob_application_secret);
let differing_component_id = 0x8001;
let alice_differing_application_secret = alice_group_state
.group
.safe_export_secret(
alice_group_state.party.core_state.provider.crypto(),
alice_group_state.party.core_state.provider.storage(),
differing_component_id,
)
.unwrap();
assert_ne!(alice_application_secret, alice_differing_application_secret);
let error = alice_group_state
.group
.safe_export_secret(
alice_group_state.party.core_state.provider.crypto(),
alice_group_state.party.core_state.provider.storage(),
component_id,
)
.expect_err("Expected an error when exporting the same component ID twice");
assert!(matches!(
error,
SafeExportSecretError::ApplicationExportTree(ApplicationExportTreeError::PuncturedInput)
));
}
#[openmls_test]
fn staged_join() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let group_id = GroupId::from_slice(b"Test Group");
let (alice_credential_with_key, alice_kpb, alice_signer, _alice_pk) =
setup_client("Alice", ciphersuite, alice_provider);
let (_bob_credential, bob_kpb, _bob_signer, _bob_pk) =
setup_client("Bob", ciphersuite, bob_provider);
let mls_group_create_config = MlsGroupCreateConfig::test_default(ciphersuite);
let mut alice_group = MlsGroup::new_with_group_id(
alice_provider,
&alice_signer,
&mls_group_create_config,
group_id,
alice_credential_with_key,
)
.expect("An unexpected error occurred.");
let (_queued_message, welcome, _group_info) = alice_group
.add_members(
alice_provider,
&alice_signer,
from_ref(bob_kpb.key_package()),
)
.expect("Could not add member to group.");
alice_group
.merge_pending_commit(alice_provider)
.expect("couldn't merge commit that adds bob");
let join_config = mls_group_create_config.join_config();
let welcome: MlsMessageIn = welcome.into();
let welcome = welcome.into_welcome().expect("expected a welcome");
let staged_bob_group = StagedWelcome::new_from_welcome(
bob_provider,
join_config,
welcome,
Some(alice_group.export_ratchet_tree().into()),
)
.expect("error creating staged mls group");
let welcome_sender = staged_bob_group
.welcome_sender()
.expect("couldn't determine sender of welcome");
assert_eq!(
welcome_sender.credential(),
alice_kpb.key_package().leaf_node().credential()
);
let bob_group = staged_bob_group
.into_group(bob_provider)
.expect("error turning StagedWelcome into MlsGroup");
assert_eq!(
alice_group
.export_secret(
alice_provider.crypto(),
"test",
&[],
ciphersuite.hash_length()
)
.expect("An unexpected error occurred."),
bob_group
.export_secret(
bob_provider.crypto(),
"test",
&[],
ciphersuite.hash_length()
)
.expect("An unexpected error occurred.")
);
}
#[openmls_test]
fn test_invalid_plaintext() {
let mls_group_create_config = MlsGroupCreateConfig::test_default(ciphersuite);
let number_of_clients = 20;
let setup = MlsGroupTestSetup::<Provider>::new(
mls_group_create_config,
number_of_clients,
CodecUse::StructMessages,
);
let group_id = setup
.create_random_group(10, ciphersuite, noop_authentication_service)
.expect("An unexpected error occurred.");
let mut groups = setup.groups.write().expect("An unexpected error occurred.");
let group = groups
.get_mut(&group_id)
.expect("An unexpected error occurred.");
let (_, client_id) = &group
.members()
.find(|(index, _)| index == &0)
.expect("An unexpected error occurred.");
let clients = setup.clients.read().expect("An unexpected error occurred.");
let client = clients
.get(client_id)
.expect("An unexpected error occurred.")
.read()
.expect("An unexpected error occurred.");
let (mls_message, _welcome_option, _group_info) = client
.self_update(Commit, &group_id, LeafNodeParameters::default())
.expect("error creating self update");
let client_groups = client.groups.read().unwrap();
let client_group = client_groups.get(&group_id).unwrap();
let membership_key = client_group.message_secrets().membership_key();
let mut msg_invalid_signature = mls_message.clone();
if let MlsMessageBodyOut::PublicMessage(ref mut pt) = msg_invalid_signature.body {
pt.invalidate_signature()
};
let mut msg_invalid_sender = mls_message;
let random_sender = Sender::build_member(LeafNodeIndex::new(987543210));
match &mut msg_invalid_sender.body {
MlsMessageBodyOut::PublicMessage(pt) => {
pt.set_sender(random_sender);
pt.set_membership_tag(
client.provider.crypto(),
ciphersuite,
membership_key,
client_group.message_secrets().serialized_context(),
)
.unwrap()
}
_ => panic!("This should be a plaintext!"),
};
drop(client_groups);
drop(client);
drop(clients);
let error = setup
.distribute_to_members(
"no_client".as_bytes(),
group,
&msg_invalid_signature.into(),
&noop_authentication_service,
)
.expect_err("No error when distributing message with invalid signature.");
assert_eq!(
ClientError::ProcessMessageError(ProcessMessageError::ValidationError(
ValidationError::InvalidMembershipTag
)),
error
);
let error = setup
.distribute_to_members(
"no_client".as_bytes(),
group,
&msg_invalid_sender.into(),
&noop_authentication_service,
)
.expect_err("No error when distributing message with invalid signature.");
assert_eq!(
ClientError::ProcessMessageError(ProcessMessageError::ValidationError(
ValidationError::UnknownMember
)),
error
);
}
#[openmls_test]
fn test_verify_staged_commit_credentials() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let group_id = GroupId::from_slice(b"Test Group");
let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) =
setup_client("Alice", ciphersuite, alice_provider);
let (_bob_credential, bob_kpb, _bob_signer, _bob_pk) =
setup_client("Bob", ciphersuite, bob_provider);
let mls_group_config = MlsGroupCreateConfig::test_default(ciphersuite);
let mut alice_group = MlsGroup::new_with_group_id(
alice_provider,
&alice_signer,
&mls_group_config,
group_id,
alice_credential_with_key.clone(),
)
.expect("An unexpected error occurred.");
assert!(alice_group.pending_commit().is_none());
let bob_key_package = bob_kpb.key_package();
let (proposal, _) = alice_group
.propose_add_member(alice_provider, &alice_signer, bob_key_package)
.expect("error creating self-update proposal");
let alice_processed_message = alice_group
.process_message(alice_provider, proposal.into_protocol_message().unwrap())
.expect("Could not process messages.");
assert!(alice_group.pending_commit().is_none());
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 StagedCommit.");
}
let (_msg, welcome_option, _group_info) = alice_group
.self_update(alice_provider, &alice_signer, LeafNodeParameters::default())
.expect("error creating self-update commit")
.into_messages();
alice_group
.merge_pending_commit(alice_provider)
.expect("error merging pending commit");
assert!(alice_group.pending_commit().is_none());
assert!(alice_group.pending_proposals().next().is_none());
let welcome: MlsMessageIn = welcome_option.expect("expected a welcome").into();
let welcome = welcome
.into_welcome()
.expect("expected message to be a welcome");
let mut bob_group = StagedWelcome::new_from_welcome(
bob_provider,
mls_group_config.join_config(),
welcome,
Some(alice_group.export_ratchet_tree().into()),
)
.expect("error creating group from welcome")
.into_group(bob_provider)
.expect("error creating group from welcome");
assert_eq!(
bob_group.export_ratchet_tree(),
alice_group.export_ratchet_tree()
);
assert_eq!(
bob_group
.export_secret(
alice_provider.crypto(),
"test",
&[],
ciphersuite.hash_length()
)
.unwrap(),
alice_group
.export_secret(
alice_provider.crypto(),
"test",
&[],
ciphersuite.hash_length()
)
.unwrap()
);
let (commit_msg, _welcome_option, _group_info) = alice_group
.self_update(alice_provider, &alice_signer, LeafNodeParameters::default())
.expect("error creating self-update commit")
.into_contents();
assert!(_welcome_option.is_none());
assert!(_group_info.is_none());
let alice_pending_commit = alice_group
.pending_commit()
.expect("alice should have the self-update as pending commit");
for cred in alice_pending_commit.credentials_to_verify() {
assert_eq!(cred, &alice_credential_with_key.credential);
}
alice_group
.merge_pending_commit(alice_provider)
.expect("alice failed to merge the pending empty commit");
let mut wire_msg = Vec::<u8>::new();
commit_msg
.tls_serialize(&mut wire_msg)
.expect("alice failed serializing her message");
let msg_in = MlsMessageIn::tls_deserialize(&mut &wire_msg[..])
.expect("bob failed deserializing alice's message");
assert!(alice_group.pending_proposals().next().is_none());
assert!(bob_group.pending_proposals().next().is_none());
assert!(bob_group.pending_commit().is_none());
assert!(alice_group.pending_commit().is_none());
let processed_message = bob_group
.process_message(bob_provider, msg_in.try_into_protocol_message().unwrap())
.expect("bob failed processing alice's message");
assert!(matches!(
processed_message.content(),
ProcessedMessageContent::StagedCommitMessage(_)
));
if let ProcessedMessageContent::StagedCommitMessage(staged_commit) =
processed_message.into_content()
{
for cred in staged_commit.credentials_to_verify() {
assert_eq!(cred, &alice_credential_with_key.credential);
}
bob_group
.merge_staged_commit(bob_provider, *staged_commit)
.expect("bob failed merging alice's empty commit (staged)");
assert_eq!(
bob_group.export_ratchet_tree(),
alice_group.export_ratchet_tree()
);
assert_eq!(
bob_group
.export_secret(
bob_provider.crypto(),
"test",
&[],
ciphersuite.hash_length()
)
.unwrap(),
alice_group
.export_secret(
alice_provider.crypto(),
"test",
&[],
ciphersuite.hash_length()
)
.unwrap()
);
} else {
unreachable!()
}
assert!(bob_group.pending_commit().is_none());
assert!(alice_group.pending_commit().is_none());
}
#[openmls_test]
fn test_commit_with_update_path_leaf_node() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let group_id = GroupId::from_slice(b"Test Group");
let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) =
setup_client("Alice", ciphersuite, alice_provider);
let (_bob_credential, bob_kpb, _bob_signer, _bob_pk) =
setup_client("Bob", ciphersuite, bob_provider);
let mls_group_create_config = MlsGroupCreateConfig::test_default(ciphersuite);
let mut alice_group = MlsGroup::new_with_group_id(
alice_provider,
&alice_signer,
&mls_group_create_config,
group_id,
alice_credential_with_key.clone(),
)
.expect("An unexpected error occurred.");
assert!(alice_group.pending_commit().is_none());
let bob_key_package = bob_kpb.key_package();
let (proposal, _) = alice_group
.propose_add_member(alice_provider, &alice_signer, bob_key_package)
.expect("error creating self-update proposal");
let alice_processed_message = alice_group
.process_message(alice_provider, proposal.into_protocol_message().unwrap())
.expect("Could not process messages.");
assert!(alice_group.pending_commit().is_none());
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 StagedCommit.");
}
println!("\nCreating commit with add proposal.");
let (_msg, welcome_option, _group_info) = alice_group
.self_update(alice_provider, &alice_signer, LeafNodeParameters::default())
.expect("error creating self-update commit")
.into_messages();
println!("Done creating commit.");
alice_group
.merge_pending_commit(alice_provider)
.expect("error merging pending commit");
assert!(alice_group.pending_commit().is_none());
assert!(alice_group.pending_proposals().next().is_none());
let welcome: MlsMessageIn = welcome_option.expect("expected a welcome").into();
let welcome = welcome
.into_welcome()
.expect("expected message to be a welcome");
let mut bob_group = StagedWelcome::new_from_welcome(
bob_provider,
mls_group_create_config.join_config(),
welcome,
Some(alice_group.export_ratchet_tree().into()),
)
.expect("error creating group from welcome")
.into_group(bob_provider)
.expect("error creating group from welcome");
assert_eq!(
bob_group.export_ratchet_tree(),
alice_group.export_ratchet_tree()
);
assert_eq!(
bob_group
.export_secret(
bob_provider.crypto(),
"test",
&[],
ciphersuite.hash_length()
)
.unwrap(),
alice_group
.export_secret(
alice_provider.crypto(),
"test",
&[],
ciphersuite.hash_length()
)
.unwrap()
);
println!("\nCreating self-update commit.");
let (commit_msg, _welcome_option, _group_info) = alice_group
.self_update(alice_provider, &alice_signer, LeafNodeParameters::default())
.expect("error creating self-update commit")
.into_messages();
println!("Done creating commit.");
assert!(_welcome_option.is_none());
assert!(_group_info.is_none());
let alice_pending_commit = alice_group
.pending_commit()
.expect("alice should have the self-update as pending commit");
let alice_update_path_leaf_node = alice_pending_commit
.update_path_leaf_node()
.expect("expected alice's staged commit to have an update path");
assert_eq!(
alice_update_path_leaf_node.credential(),
&alice_credential_with_key.credential
);
alice_group
.merge_pending_commit(alice_provider)
.expect("alice failed to merge the pending empty commit");
let mut wire_msg = Vec::<u8>::new();
commit_msg
.tls_serialize(&mut wire_msg)
.expect("alice failed serializing her message");
let msg_in = MlsMessageIn::tls_deserialize(&mut &wire_msg[..])
.expect("bob failed deserializing alice's message");
assert!(alice_group.pending_proposals().next().is_none());
assert!(bob_group.pending_proposals().next().is_none());
assert!(bob_group.pending_commit().is_none());
assert!(alice_group.pending_commit().is_none());
let processed_message = bob_group
.process_message(bob_provider, msg_in.try_into_protocol_message().unwrap())
.expect("bob failed processing alice's message");
assert!(matches!(
processed_message.content(),
ProcessedMessageContent::StagedCommitMessage(_)
));
if let ProcessedMessageContent::StagedCommitMessage(staged_commit) =
processed_message.into_content()
{
let bob_update_path_leaf_node = staged_commit
.update_path_leaf_node()
.expect("staged commit received by bob should carry an update path with a leaf node");
assert_eq!(
bob_update_path_leaf_node.credential(),
&alice_credential_with_key.credential
);
bob_group
.merge_staged_commit(bob_provider, *staged_commit)
.expect("bob failed merging alice's empty commit (staged)");
assert_eq!(
bob_group.export_ratchet_tree(),
alice_group.export_ratchet_tree()
);
assert_eq!(
bob_group
.export_secret(
alice_provider.crypto(),
"test",
&[],
ciphersuite.hash_length()
)
.unwrap(),
alice_group
.export_secret(
alice_provider.crypto(),
"test",
&[],
ciphersuite.hash_length()
)
.unwrap()
);
} else {
unreachable!()
}
assert!(bob_group.pending_commit().is_none());
assert!(alice_group.pending_commit().is_none());
}
#[openmls_test]
fn test_pending_commit_logic() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let group_id = GroupId::from_slice(b"Test Group");
let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) =
setup_client("Alice", ciphersuite, alice_provider);
let (_bob_credential, bob_kpb, bob_signer, _bob_pk) =
setup_client("Bob", ciphersuite, bob_provider);
let mls_group_create_config = MlsGroupCreateConfig::test_default(ciphersuite);
let mut alice_group = MlsGroup::new_with_group_id(
alice_provider,
&alice_signer,
&mls_group_create_config,
group_id,
alice_credential_with_key,
)
.expect("An unexpected error occurred.");
assert!(alice_group.pending_commit().is_none());
let bob_key_package = bob_kpb.key_package();
let (proposal, _) = alice_group
.propose_add_member(bob_provider, &alice_signer, bob_key_package)
.expect("error creating add-bob proposal");
let alice_processed_message = alice_group
.process_message(bob_provider, proposal.into_protocol_message().unwrap())
.expect("Could not process messages.");
assert!(alice_group.pending_commit().is_none());
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 StagedCommit.");
}
assert!(alice_group.pending_commit().is_none());
println!("\nCreating commit with add proposal.");
let (_msg, _welcome_option, _group_info) = alice_group
.self_update(alice_provider, &alice_signer, LeafNodeParameters::default())
.expect("error creating self-update commit")
.into_messages();
println!("Done creating commit.");
assert!(alice_group.pending_commit().is_some());
let error = alice_group
.add_members(alice_provider, &alice_signer, from_ref(bob_key_package))
.expect_err("no error committing while a commit is pending");
assert!(matches!(
error,
AddMembersError::GroupStateError(MlsGroupStateError::PendingCommit)
));
let error = alice_group
.propose_add_member(alice_provider, &alice_signer, bob_key_package)
.expect_err("no error creating a proposal while a commit is pending");
assert!(matches!(
error,
ProposeAddMemberError::GroupStateError(MlsGroupStateError::PendingCommit)
));
let error = alice_group
.remove_members(alice_provider, &alice_signer, &[LeafNodeIndex::new(1)])
.expect_err("no error committing while a commit is pending");
assert!(matches!(
error,
RemoveMembersError::GroupStateError(MlsGroupStateError::PendingCommit)
));
let error = alice_group
.propose_remove_member(alice_provider, &alice_signer, LeafNodeIndex::new(1))
.expect_err("no error creating a proposal while a commit is pending");
assert!(matches!(
error,
ProposeRemoveMemberError::GroupStateError(MlsGroupStateError::PendingCommit)
));
let error = alice_group
.commit_to_pending_proposals(alice_provider, &alice_signer)
.expect_err("no error committing while a commit is pending");
assert!(matches!(
error,
CommitToPendingProposalsError::GroupStateError(MlsGroupStateError::PendingCommit)
));
let error = alice_group
.self_update(alice_provider, &alice_signer, LeafNodeParameters::default())
.expect_err("no error committing while a commit is pending");
assert!(matches!(
error,
SelfUpdateError::GroupStateError(MlsGroupStateError::PendingCommit)
));
let error = alice_group
.propose_self_update(alice_provider, &alice_signer, LeafNodeParameters::default())
.expect_err("no error creating a proposal while a commit is pending");
assert!(matches!(
error,
ProposeSelfUpdateError::GroupStateError(MlsGroupStateError::PendingCommit)
));
alice_group
.clear_pending_commit(alice_provider.storage())
.unwrap();
assert!(alice_group.pending_commit().is_none());
let (_msg, welcome_option, _group_info) = alice_group
.self_update(alice_provider, &alice_signer, LeafNodeParameters::default())
.expect("error creating self-update commit")
.into_messages();
alice_group
.merge_pending_commit(alice_provider)
.expect("error merging pending commit");
assert!(alice_group.pending_commit().is_none());
let welcome: MlsMessageIn = welcome_option.expect("expected a welcome").into();
let welcome = welcome
.into_welcome()
.expect("expected message to be a welcome");
let mut bob_group = StagedWelcome::new_from_welcome(
bob_provider,
mls_group_create_config.join_config(),
welcome,
Some(alice_group.export_ratchet_tree().into()),
)
.expect("error creating group from welcome")
.into_group(bob_provider)
.expect("error creating group from welcome");
assert_eq!(
bob_group.export_ratchet_tree(),
alice_group.export_ratchet_tree()
);
assert_eq!(
bob_group
.export_secret(
bob_provider.crypto(),
"test",
&[],
ciphersuite.hash_length()
)
.unwrap(),
alice_group
.export_secret(
alice_provider.crypto(),
"test",
&[],
ciphersuite.hash_length()
)
.unwrap()
);
let (_msg, _welcome_option, _group_info) = alice_group
.self_update(alice_provider, &alice_signer, LeafNodeParameters::default())
.expect("error creating self-update commit")
.into_messages();
let (msg, _welcome_option, _group_info) = bob_group
.self_update(bob_provider, &bob_signer, LeafNodeParameters::default())
.expect("error creating self-update commit")
.into_messages();
let alice_processed_message = alice_group
.process_message(alice_provider, msg.into_protocol_message().unwrap())
.expect("Could not process messages.");
assert!(alice_group.pending_commit().is_some());
if let ProcessedMessageContent::StagedCommitMessage(staged_commit) =
alice_processed_message.into_content()
{
alice_group
.merge_staged_commit(alice_provider, *staged_commit)
.expect("Error merging commit.");
} else {
unreachable!("Expected a StagedCommit.");
}
assert!(alice_group.pending_commit().is_none());
}
#[openmls_test]
fn key_package_deletion() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let group_id = GroupId::from_slice(b"Test Group");
let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) =
setup_client("Alice", ciphersuite, alice_provider);
let (_bob_credential_with_key, bob_kpb, _bob_signer, _bob_pk) =
setup_client("Bob", ciphersuite, bob_provider);
let bob_key_package = bob_kpb.key_package();
let mls_group_create_config = MlsGroupCreateConfig::builder()
.ciphersuite(ciphersuite)
.build();
let mut alice_group = MlsGroup::new_with_group_id(
alice_provider,
&alice_signer,
&mls_group_create_config,
group_id,
alice_credential_with_key,
)
.expect("An unexpected error occurred.");
let (_queued_message, welcome, _group_info) = alice_group
.add_members(alice_provider, &alice_signer, from_ref(bob_key_package))
.unwrap();
alice_group.merge_pending_commit(alice_provider).unwrap();
let welcome: MlsMessageIn = welcome.into();
let welcome = welcome
.into_welcome()
.expect("expected message to be a welcome");
let _bob_group = StagedWelcome::new_from_welcome(
bob_provider,
mls_group_create_config.join_config(),
welcome,
Some(alice_group.export_ratchet_tree().into()),
)
.expect("Error creating staged join from Welcome")
.into_group(bob_provider)
.expect("Error creating group from staged join");
let result: Option<KeyPackageBundle> = bob_provider
.storage()
.key_package(&bob_key_package.hash_ref(bob_provider.crypto()).unwrap())
.unwrap();
assert!(
result.is_none(),
"The key package is still in the key store after creating a new group from it."
);
}
#[openmls_test]
fn remove_prosposal_by_ref() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let charlie_provider = &Provider::default();
let group_id = GroupId::from_slice(b"Test Group");
let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) =
setup_client("Alice", ciphersuite, alice_provider);
let (_bob_credential_with_key, bob_kpb, _bob_signer, _bob_pk) =
setup_client("Bob", ciphersuite, bob_provider);
let bob_key_package = bob_kpb.key_package().clone();
let (_charlie_credential_with_key, charlie_kpb, _charlie_signer, _charlie_pk) =
setup_client("Charlie", ciphersuite, charlie_provider);
let charlie_key_package = charlie_kpb.key_package();
let mls_group_create_config = MlsGroupCreateConfig::builder()
.ciphersuite(ciphersuite)
.build();
let mut alice_group = MlsGroup::new_with_group_id(
alice_provider,
&alice_signer,
&mls_group_create_config,
group_id,
alice_credential_with_key,
)
.expect("An unexpected error occurred.");
let (_, welcome, _) = alice_group
.add_members(alice_provider, &alice_signer, &[bob_key_package])
.unwrap();
alice_group.merge_pending_commit(alice_provider).unwrap();
let welcome: MlsMessageIn = welcome.into();
let welcome = welcome
.into_welcome()
.expect("expected message to be a welcome");
let mut bob_group = StagedWelcome::new_from_welcome(
bob_provider,
mls_group_create_config.join_config(),
welcome,
Some(alice_group.export_ratchet_tree().into()),
)
.unwrap()
.into_group(bob_provider)
.unwrap();
let (_, reference) = alice_group
.propose_add_member(alice_provider, &alice_signer, charlie_key_package)
.unwrap();
assert_eq!(alice_group.proposal_store().proposals().count(), 1);
alice_group
.remove_pending_proposal(alice_provider.storage(), &reference)
.unwrap();
assert!(alice_group.proposal_store().is_empty());
let err = alice_group
.remove_pending_proposal(alice_provider.storage(), &reference)
.unwrap_err();
assert!(matches!(err, RemoveProposalError::ProposalNotFound));
let (commit, _, _) = alice_group
.commit_to_pending_proposals(alice_provider, &alice_signer)
.unwrap();
let msg = bob_group
.process_message(
bob_provider,
MlsMessageIn::from(commit)
.try_into_protocol_message()
.unwrap(),
)
.unwrap();
match msg.into_content() {
ProcessedMessageContent::StagedCommitMessage(commit) => {
assert!(commit.add_proposals().next().is_none());
assert!(commit.update_proposals().next().is_none());
assert!(commit.remove_proposals().next().is_none());
assert!(commit.psk_proposals().next().is_none());
assert_eq!(alice_group.members().count(), 2);
}
_ => unreachable!("Expected a StagedCommit."),
}
}
#[openmls_test]
fn max_past_epochs_join_config() {
let alice_provider = &Provider::default();
let max_past_epochs = 10;
let create_config = MlsGroupCreateConfig::builder()
.max_past_epochs(max_past_epochs)
.build();
let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) =
setup_client("Alice", ciphersuite, alice_provider);
let alice_group = MlsGroup::new(
alice_provider,
&alice_signer,
&create_config,
alice_credential_with_key,
)
.expect("failed to create group");
assert_eq!(
alice_group.message_secrets_store.max_epochs,
max_past_epochs
);
}
#[openmls_test]
fn builder_pattern() {
let alice_provider = &Provider::default();
let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) =
setup_client("Alice", ciphersuite, alice_provider);
let test_group_id = GroupId::from_slice(b"Test Group");
let test_lifetime = Lifetime::new(3600);
let test_wire_format_policy = PURE_CIPHERTEXT_WIRE_FORMAT_POLICY;
let test_padding_size = 100;
let test_external_senders = Extension::ExternalSenders(vec![ExternalSender::new(
alice_credential_with_key.signature_key.clone(),
alice_credential_with_key.credential.clone(),
)]);
let test_required_capabilities = Extension::RequiredCapabilities(
RequiredCapabilitiesExtension::new(&[ExtensionType::Unknown(0xff00)], &[], &[]),
);
let test_gc_extensions = Extensions::from_vec(vec![
test_external_senders.clone(),
test_required_capabilities.clone(),
])
.expect("error creating group context extensions");
let test_ciphersuite = ciphersuite;
let test_sender_ratchet_config = SenderRatchetConfiguration::new(10, 2000);
let test_max_past_epochs = 10;
let test_number_of_resumption_psks = 5;
let test_capabilities = Capabilities::new(
None,
None,
Some(&[ExtensionType::Unknown(0xff00)]),
None,
None,
);
let test_leaf_extensions = Extensions::single(Extension::Unknown(
0xff00,
UnknownExtension(vec![0x00, 0x01, 0x02]),
))
.expect("failed to create single-element extensions list");
let alice_group = MlsGroup::builder()
.with_group_id(test_group_id.clone())
.padding_size(test_padding_size)
.sender_ratchet_configuration(test_sender_ratchet_config)
.with_group_context_extensions(test_gc_extensions.clone())
.ciphersuite(test_ciphersuite)
.with_wire_format_policy(test_wire_format_policy)
.lifetime(test_lifetime)
.use_ratchet_tree_extension(true)
.max_past_epochs(test_max_past_epochs)
.number_of_resumption_psks(test_number_of_resumption_psks)
.with_capabilities(test_capabilities.clone())
.with_leaf_node_extensions(test_leaf_extensions.clone())
.expect("error adding leaf node extension to builder")
.build(alice_provider, &alice_signer, alice_credential_with_key)
.expect("error creating group using builder");
let group_config = alice_group.configuration();
assert_eq!(group_config.padding_size(), test_padding_size);
assert_eq!(
group_config.sender_ratchet_configuration(),
&test_sender_ratchet_config
);
assert_eq!(group_config.wire_format_policy(), test_wire_format_policy);
assert!(group_config.use_ratchet_tree_extension);
assert_eq!(group_config.max_past_epochs, test_max_past_epochs);
assert_eq!(
group_config.number_of_resumption_psks,
test_number_of_resumption_psks
);
let group_context = alice_group.export_group_context();
assert_eq!(alice_group.group_id(), &test_group_id);
let external_senders = group_context
.extensions()
.external_senders()
.expect("error getting external senders")
.to_vec();
assert_eq!(
Extension::ExternalSenders(external_senders),
test_external_senders
);
assert_eq!(ciphersuite, test_ciphersuite);
assert_eq!(group_context.extensions(), &test_gc_extensions);
let lifetime = alice_group
.own_leaf()
.expect("error getting own leaf")
.life_time()
.expect("leaf doesn't have a lifetime");
assert_eq!(lifetime, &test_lifetime);
let own_leaf = alice_group.own_leaf_node().expect("can't find own leaf");
let capabilities = own_leaf.capabilities();
let filtered_ciphersuites: Vec<_> = capabilities
.ciphersuites()
.iter()
.filter(|cs| !cs.is_grease())
.copied()
.collect();
let filtered_extensions: Vec<_> = capabilities
.extensions()
.iter()
.filter(|ext| !ext.is_grease())
.copied()
.collect();
let filtered_proposals: Vec<_> = capabilities
.proposals()
.iter()
.filter(|prop| !prop.is_grease())
.copied()
.collect();
let filtered_credentials: Vec<_> = capabilities
.credentials()
.iter()
.filter(|cred| !cred.is_grease())
.copied()
.collect();
assert_eq!(filtered_ciphersuites, test_capabilities.ciphersuites());
assert_eq!(filtered_extensions, test_capabilities.extensions());
assert_eq!(filtered_proposals, test_capabilities.proposals());
assert_eq!(filtered_credentials, test_capabilities.credentials());
let leaf_extensions = own_leaf.extensions();
assert_eq!(leaf_extensions, &test_leaf_extensions);
let err = Extensions::<LeafNode>::single(Extension::RequiredCapabilities(
RequiredCapabilitiesExtension::new(&[], &[], &[]),
))
.expect_err(
"should not be able single-element leaf node extensions list with RequiredCapabilities",
);
assert_eq!(
err,
InvalidExtensionError::ExtensionTypeNotValidInLeafNode(
ExtensionTypeNotValidInLeafNodeError(ExtensionType::RequiredCapabilities)
)
);
}
#[openmls_test]
fn update_group_context_with_unknown_extension<Provider: OpenMlsProvider + Default>() {
let alice_provider = &Provider::default();
let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) =
setup_client("Alice", ciphersuite, alice_provider);
const UNKNOWN_EXTENSION_TYPE: u16 = 0xff11;
let unknown_extension_data = vec![1, 2];
let unknown_gc_extension = Extension::Unknown(
UNKNOWN_EXTENSION_TYPE,
UnknownExtension(unknown_extension_data),
);
let required_extension_types = &[ExtensionType::Unknown(UNKNOWN_EXTENSION_TYPE)];
let required_capabilities = Extension::RequiredCapabilities(
RequiredCapabilitiesExtension::new(required_extension_types, &[], &[]),
);
let capabilities = Capabilities::new(None, None, Some(required_extension_types), None, None);
let test_gc_extensions = Extensions::from_vec(vec![
unknown_gc_extension.clone(),
required_capabilities.clone(),
])
.expect("error creating test group context extensions");
let mls_group_create_config = MlsGroupCreateConfig::builder()
.with_group_context_extensions(test_gc_extensions.clone())
.capabilities(capabilities.clone())
.ciphersuite(ciphersuite)
.build();
let mut alice_group = MlsGroup::new(
alice_provider,
&alice_signer,
&mls_group_create_config,
alice_credential_with_key,
)
.expect("error creating group");
let group_context_extensions = alice_group.context().extensions();
let mut extracted_data = None;
for extension in group_context_extensions.iter() {
if let Extension::Unknown(UNKNOWN_EXTENSION_TYPE, UnknownExtension(data)) = extension {
extracted_data = Some(data.clone());
}
}
assert_eq!(
extracted_data.unwrap(),
vec![1, 2],
"The data of Extension::Unknown(0xff11) does not match the expected data"
);
let bob_provider = &Provider::default();
let (bob_credential_with_key, _bob_kpb, bob_signer, _bob_pk) =
setup_client("Bob", ciphersuite, bob_provider);
let bob_key_package = KeyPackage::builder()
.leaf_node_capabilities(capabilities)
.build(
ciphersuite,
bob_provider,
&bob_signer,
bob_credential_with_key,
)
.expect("error building key package");
let (_, welcome, _) = alice_group
.add_members(
alice_provider,
&alice_signer,
from_ref(bob_key_package.key_package()),
)
.unwrap();
alice_group.merge_pending_commit(alice_provider).unwrap();
let welcome: MlsMessageIn = welcome.into();
let welcome = welcome
.into_welcome()
.expect("expected message to be a welcome");
let mut bob_group = StagedWelcome::new_from_welcome(
bob_provider,
&MlsGroupJoinConfig::default(),
welcome,
Some(alice_group.export_ratchet_tree().into()),
)
.expect("Error creating staged join from Welcome")
.into_group(bob_provider)
.expect("Error creating group from staged join");
let group_context_extensions = bob_group.context().extensions();
let mut extracted_data_2 = None;
for extension in group_context_extensions.iter() {
if let Extension::Unknown(UNKNOWN_EXTENSION_TYPE, UnknownExtension(data)) = extension {
extracted_data_2 = Some(data.clone());
}
}
assert_eq!(
extracted_data_2.unwrap(),
vec![1, 2],
"The data of Extension::Unknown(0xff11) does not match the expected data"
);
let updated_unknown_extension_data = vec![3, 4]; let updated_unknown_gc_extension = Extension::Unknown(
UNKNOWN_EXTENSION_TYPE,
UnknownExtension(updated_unknown_extension_data.clone()),
);
let mut updated_extensions = test_gc_extensions.clone();
updated_extensions
.add_or_replace(updated_unknown_gc_extension)
.expect("updated extension should be valid here");
let (update_proposal, _) = alice_group
.propose_group_context_extensions(alice_provider, updated_extensions, &alice_signer)
.expect("failed to propose group context extensions with unknown extension");
assert_eq!(
alice_group.pending_proposals().count(),
1,
"Expected one pending proposal"
);
let (update_commit, _, _) = alice_group
.commit_to_pending_proposals(alice_provider, &alice_signer)
.expect("failed to commit to pending group context extensions");
alice_group
.merge_pending_commit(alice_provider)
.expect("error merging pending commit");
assert_eq!(
bob_group.pending_proposals().count(),
0,
"Expected no pending proposals"
);
let processed_update_message = bob_group
.process_message(
bob_provider,
update_proposal.into_protocol_message().unwrap(),
)
.expect("bob failed processing the update");
match processed_update_message.into_content() {
ProcessedMessageContent::ProposalMessage(msg) => {
bob_group
.store_pending_proposal(bob_provider.storage(), *msg)
.unwrap();
}
other => panic!("expected proposal, got {other:?}"),
}
assert_eq!(
bob_group.pending_proposals().count(),
1,
"Expected one pending proposal"
);
let processed_commit_message = bob_group
.process_message(bob_provider, update_commit.into_protocol_message().unwrap())
.expect("bob failed processing the update");
match processed_commit_message.into_content() {
ProcessedMessageContent::StagedCommitMessage(staged_commit) => bob_group
.merge_staged_commit(bob_provider, *staged_commit)
.expect("error merging group context update commit"),
other => panic!("expected commit, got {other:?}"),
};
let group_context_extensions = alice_group.context().extensions();
let mut extracted_data_updated = None;
for extension in group_context_extensions.iter() {
if let Extension::Unknown(UNKNOWN_EXTENSION_TYPE, UnknownExtension(data)) = extension {
extracted_data_updated = Some(data.clone());
}
}
assert_eq!(
extracted_data_updated.unwrap(),
vec![3, 4],
"The data of Extension::Unknown(0xff11) does not match the expected data"
);
let bob_group_loaded = MlsGroup::load(bob_provider.storage(), bob_group.group_id())
.expect("error loading group")
.expect("no such group");
let group_context_extensions_2 = bob_group_loaded.export_group_context().extensions();
let mut extracted_data_2 = None;
for extension in group_context_extensions_2.iter() {
if let Extension::Unknown(UNKNOWN_EXTENSION_TYPE, UnknownExtension(data)) = extension {
extracted_data_2 = Some(data.clone());
}
}
assert_eq!(
extracted_data_2.unwrap(),
vec![3, 4],
"The data of Extension::Unknown(0xff11) does not match the expected data"
);
}
#[openmls_test]
fn update_proposal_bob() {
let alice_provider = &Provider::default();
let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) =
setup_client("Alice", ciphersuite, alice_provider);
let mls_group_create_config = MlsGroupCreateConfig::builder()
.ciphersuite(ciphersuite)
.build();
let mut alice_group = MlsGroup::new(
alice_provider,
&alice_signer,
&mls_group_create_config,
alice_credential_with_key,
)
.expect("error creating group");
let bob_provider = &Provider::default();
let (bob_credential_with_key, _bob_kpb, bob_signer, _bob_pk) =
setup_client("Bob", ciphersuite, bob_provider);
let bob_key_package = KeyPackage::builder()
.build(
ciphersuite,
bob_provider,
&bob_signer,
bob_credential_with_key,
)
.expect("error building key package");
let (_, welcome, _) = alice_group
.add_members(
alice_provider,
&alice_signer,
from_ref(bob_key_package.key_package()),
)
.unwrap();
alice_group.merge_pending_commit(alice_provider).unwrap();
let welcome: MlsMessageIn = welcome.into();
let welcome = welcome
.into_welcome()
.expect("expected message to be a welcome");
let mut bob_group = StagedWelcome::new_from_welcome(
bob_provider,
&MlsGroupJoinConfig::default(),
welcome,
Some(alice_group.export_ratchet_tree().into()),
)
.expect("Error creating staged join from Welcome")
.into_group(bob_provider)
.expect("Error creating group from staged join");
let (update_proposal, _proposal_reference) = bob_group
.propose_self_update(
bob_provider,
&bob_signer,
LeafNodeParameters::builder().build(),
)
.unwrap();
let processed_message = alice_group
.process_message(
alice_provider,
update_proposal.into_protocol_message().unwrap(),
)
.unwrap();
let ProcessedMessageContent::ProposalMessage(proposal_msg) = processed_message.into_content()
else {
panic!("expected proposal");
};
bob_group
.store_pending_proposal(bob_provider.storage(), *proposal_msg)
.unwrap();
let (commit, _, _) = alice_group
.commit_to_pending_proposals(alice_provider, &alice_signer)
.expect("failed to commit to pending group context extensions");
alice_group
.merge_pending_commit(alice_provider)
.expect("error merging pending commit");
let processed_message = bob_group
.process_message(bob_provider, commit.into_protocol_message().unwrap())
.expect("bob failed processing the update");
let ProcessedMessageContent::StagedCommitMessage(staged_commit) =
processed_message.into_content()
else {
panic!("Expected a commit");
};
bob_group
.merge_staged_commit(bob_provider, *staged_commit)
.expect("error merging commit to own update proposal");
}
#[openmls_test]
fn update_proposal_alice() {
let alice_provider = &Provider::default();
let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) =
setup_client("Alice", ciphersuite, alice_provider);
let mls_group_create_config = MlsGroupCreateConfig::builder()
.ciphersuite(ciphersuite)
.build();
let mut alice_group = MlsGroup::new(
alice_provider,
&alice_signer,
&mls_group_create_config,
alice_credential_with_key,
)
.expect("error creating group");
let bob_provider = &Provider::default();
let (bob_credential_with_key, _bob_kpb, bob_signer, _bob_pk) =
setup_client("Bob", ciphersuite, bob_provider);
let bob_key_package = KeyPackage::builder()
.build(
ciphersuite,
bob_provider,
&bob_signer,
bob_credential_with_key,
)
.expect("error building key package");
let (_, welcome, _) = alice_group
.add_members(
alice_provider,
&alice_signer,
from_ref(bob_key_package.key_package()),
)
.unwrap();
alice_group.merge_pending_commit(alice_provider).unwrap();
let welcome: MlsMessageIn = welcome.into();
let welcome = welcome
.into_welcome()
.expect("expected message to be a welcome");
let mut bob_group = StagedWelcome::new_from_welcome(
bob_provider,
&MlsGroupJoinConfig::default(),
welcome,
Some(alice_group.export_ratchet_tree().into()),
)
.expect("Error creating staged join from Welcome")
.into_group(bob_provider)
.expect("Error creating group from staged join");
let (update_proposal, _proposal_reference) = alice_group
.propose_self_update(
alice_provider,
&alice_signer,
LeafNodeParameters::builder().build(),
)
.unwrap();
let processed_message = bob_group
.process_message(
bob_provider,
update_proposal.into_protocol_message().unwrap(),
)
.unwrap();
let ProcessedMessageContent::ProposalMessage(proposal_msg) = processed_message.into_content()
else {
panic!("expected proposal");
};
bob_group
.store_pending_proposal(bob_provider.storage(), *proposal_msg)
.unwrap();
let (commit, _, _) = bob_group
.commit_to_pending_proposals(bob_provider, &bob_signer)
.expect("failed to commit to pending group context extensions");
bob_group
.merge_pending_commit(bob_provider)
.expect("error merging pending commit");
let processed_message = alice_group
.process_message(alice_provider, commit.into_protocol_message().unwrap())
.expect("bob failed processing the update");
let ProcessedMessageContent::StagedCommitMessage(staged_commit) =
processed_message.into_content()
else {
panic!("Expected a commit");
};
alice_group
.merge_staged_commit(alice_provider, *staged_commit)
.expect("error merging commit to own update proposal");
assert_eq!(
alice_group.epoch_authenticator(),
bob_group.epoch_authenticator()
);
}
#[openmls_test]
fn test_update_group_context_with_unknown_extension_using_update_function<
Provider: OpenMlsProvider + Default,
>() {
let alice_provider = &Provider::default();
let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) =
setup_client("Alice", ciphersuite, alice_provider);
const UNKNOWN_EXTENSION_TYPE: u16 = 0xff11;
let unknown_extension_data = vec![1, 2];
let unknown_gc_extension = Extension::Unknown(
UNKNOWN_EXTENSION_TYPE,
UnknownExtension(unknown_extension_data),
);
let required_extension_types = &[ExtensionType::Unknown(UNKNOWN_EXTENSION_TYPE)];
let required_capabilities = Extension::RequiredCapabilities(
RequiredCapabilitiesExtension::new(required_extension_types, &[], &[]),
);
let capabilities = Capabilities::new(None, None, Some(required_extension_types), None, None);
let test_gc_extensions = Extensions::from_vec(vec![
unknown_gc_extension.clone(),
required_capabilities.clone(),
])
.expect("error creating test group context extensions");
let mls_group_create_config = MlsGroupCreateConfig::builder()
.with_group_context_extensions(test_gc_extensions.clone())
.capabilities(capabilities.clone())
.ciphersuite(ciphersuite)
.build();
let mut alice_group = MlsGroup::new(
alice_provider,
&alice_signer,
&mls_group_create_config,
alice_credential_with_key,
)
.expect("error creating group");
let group_context_extensions = alice_group.context().extensions();
let mut extracted_data = None;
for extension in group_context_extensions.iter() {
if let Extension::Unknown(UNKNOWN_EXTENSION_TYPE, UnknownExtension(data)) = extension {
extracted_data = Some(data.clone());
}
}
assert_eq!(
extracted_data.unwrap(),
vec![1, 2],
"The data of Extension::Unknown(0xff11) does not match the expected data"
);
let updated_unknown_extension_data = vec![3, 4];
let updated_unknown_gc_extension = Extension::Unknown(
UNKNOWN_EXTENSION_TYPE,
UnknownExtension(updated_unknown_extension_data.clone()),
);
let mut updated_extensions = test_gc_extensions.clone();
updated_extensions
.add_or_replace(updated_unknown_gc_extension)
.expect("updated extension should be valid here");
let update_result = alice_group.update_group_context_extensions(
alice_provider,
updated_extensions,
&alice_signer,
);
assert!(
update_result.is_ok(),
"Failed to update group context extensions: {:?}",
update_result.err()
);
alice_group
.clear_pending_commit(alice_provider.storage())
.unwrap();
let group_context_extensions = alice_group.context().extensions();
let mut extracted_data = None;
for extension in group_context_extensions.iter() {
if let Extension::Unknown(UNKNOWN_EXTENSION_TYPE, UnknownExtension(data)) = extension {
extracted_data = Some(data.clone());
}
}
assert_eq!(
extracted_data.unwrap(),
vec![1, 2],
"The data of Extension::Unknown(0xff11) does not match the expected data"
);
let updated_unknown_extension_data = vec![4, 5]; let updated_unknown_gc_extension = Extension::Unknown(
UNKNOWN_EXTENSION_TYPE,
UnknownExtension(updated_unknown_extension_data.clone()),
);
let mut updated_extensions = test_gc_extensions.clone();
updated_extensions
.add_or_replace(updated_unknown_gc_extension)
.expect("updated extension should be valid here");
let update_result = alice_group.update_group_context_extensions(
alice_provider,
updated_extensions,
&alice_signer,
);
assert!(
update_result.is_ok(),
"Failed to update group context extensions: {:?}",
update_result.err()
);
alice_group.merge_pending_commit(alice_provider).unwrap();
let group_context_extensions = alice_group.context().extensions();
let mut extracted_data_updated = None;
for extension in group_context_extensions.iter() {
if let Extension::Unknown(UNKNOWN_EXTENSION_TYPE, UnknownExtension(data)) = extension {
extracted_data_updated = Some(data.clone());
}
}
assert_eq!(
extracted_data_updated.unwrap(),
vec![4, 5],
"The data of Extension::Unknown(0xff11) does not match the expected data"
);
}
#[openmls_test]
fn unknown_extensions() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) =
setup_client("Alice", ciphersuite, alice_provider);
let unknown_gc_extension = Extension::Unknown(0xff00, UnknownExtension(vec![0, 1, 2, 3]));
let unknown_leaf_extension = Extension::Unknown(0xff01, UnknownExtension(vec![4, 5, 6, 7]));
let unknown_kp_extension = Extension::Unknown(0xff02, UnknownExtension(vec![8, 9, 10, 11]));
let required_extensions = &[
ExtensionType::Unknown(0xff00),
ExtensionType::Unknown(0xff01),
];
let required_capabilities =
Extension::RequiredCapabilities(RequiredCapabilitiesExtension::new(&[], &[], &[]));
let capabilities = Capabilities::new(None, None, Some(required_extensions), None, None);
let test_gc_extensions = Extensions::from_vec(vec![
unknown_gc_extension.clone(),
required_capabilities.clone(),
])
.expect("error creating group context extensions");
let test_kp_extensions = Extensions::single(unknown_kp_extension.clone())
.expect("failed to create single-element extensions list");
let mut alice_group = MlsGroup::builder()
.ciphersuite(ciphersuite)
.with_capabilities(capabilities.clone())
.with_leaf_node_extensions(
Extensions::single(unknown_leaf_extension.clone())
.expect("failed to create single-element extensions list"),
)
.expect("error adding unknown leaf extension to builder")
.with_group_context_extensions(test_gc_extensions.clone())
.build(alice_provider, &alice_signer, alice_credential_with_key)
.expect("error creating group using builder");
let group_context_extensions = alice_group.export_group_context().extensions();
assert_eq!(group_context_extensions, &test_gc_extensions);
let leaf_node = alice_group.own_leaf().expect("error getting own leaf");
assert_eq!(
leaf_node.extensions(),
&Extensions::single(unknown_leaf_extension)
.expect("failed to create single-element extensions list")
);
let (bob_credential_with_key, _bob_kpb, bob_signer, _bob_pk) =
setup_client("Bob", ciphersuite, bob_provider);
let bob_key_package = KeyPackage::builder()
.leaf_node_capabilities(capabilities)
.key_package_extensions(test_kp_extensions.clone())
.build(
ciphersuite,
bob_provider,
&bob_signer,
bob_credential_with_key,
)
.expect("error building key package");
assert_eq!(
bob_key_package.key_package().extensions(),
&Extensions::single(unknown_kp_extension)
.expect("failed to create single-element extensions list")
);
let (_, welcome, _) = alice_group
.add_members(
alice_provider,
&alice_signer,
from_ref(bob_key_package.key_package()),
)
.unwrap();
alice_group.merge_pending_commit(alice_provider).unwrap();
let welcome: MlsMessageIn = welcome.into();
let welcome = welcome
.into_welcome()
.expect("expected message to be a welcome");
let _bob_group = StagedWelcome::new_from_welcome(
bob_provider,
&MlsGroupJoinConfig::default(),
welcome,
Some(alice_group.export_ratchet_tree().into()),
)
.expect("Error creating staged join from Welcome")
.into_group(bob_provider)
.expect("Error creating group from staged join");
}
#[openmls_test]
fn join_multiple_groups_last_resort_extension() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let charlie_provider = &Provider::default();
let (alice_credential_with_key, _alice_kpb, alice_signer, _alice_pk) =
setup_client("alice", ciphersuite, alice_provider);
let (bob_credential_with_key, _bob_kpb, bob_signer, _bob_pk) =
setup_client("bob", ciphersuite, bob_provider);
let (charlie_credential_with_key, _charlie_kpb, charlie_signer, _charlie_pk) =
setup_client("charlie", ciphersuite, charlie_provider);
let leaf_capabilities =
Capabilities::new(None, None, Some(&[ExtensionType::LastResort]), None, None);
let keypkg_extensions = Extensions::single(Extension::LastResort(LastResortExtension::new()))
.expect("failed to create single-element extensions list");
let mut alice_group = MlsGroup::builder()
.ciphersuite(ciphersuite)
.use_ratchet_tree_extension(true)
.build(alice_provider, &alice_signer, alice_credential_with_key)
.expect("error creating group for alice using builder");
let mut bob_group = MlsGroup::builder()
.ciphersuite(ciphersuite)
.use_ratchet_tree_extension(true)
.build(bob_provider, &bob_signer, bob_credential_with_key)
.expect("error creating group for bob using builder");
let charlie_keypkg = KeyPackage::builder()
.leaf_node_capabilities(leaf_capabilities)
.key_package_extensions(keypkg_extensions.clone())
.build(
ciphersuite,
charlie_provider,
&charlie_signer,
charlie_credential_with_key,
)
.expect("error building key package for charlie");
let (_, alice_welcome, _) = alice_group
.add_members(
alice_provider,
&alice_signer,
from_ref(charlie_keypkg.key_package()),
)
.expect("error adding charlie to alice's group");
alice_group
.merge_pending_commit(alice_provider)
.expect("error merging commit for alice's group");
let alice_welcome: MlsMessageIn = alice_welcome.into();
let alice_welcome = alice_welcome
.into_welcome()
.expect("expected message to be a welcome");
StagedWelcome::new_from_welcome(
charlie_provider,
&MlsGroupJoinConfig::default(),
alice_welcome,
None,
)
.expect("error creating staged join from welcome")
.into_group(charlie_provider)
.expect("error creating group from staged join");
let (_, bob_welcome, _) = bob_group
.add_members(
bob_provider,
&bob_signer,
from_ref(charlie_keypkg.key_package()),
)
.expect("error adding charlie to bob's group");
bob_group
.merge_pending_commit(bob_provider)
.expect("error merging commit for bob's group");
let bob_welcome: MlsMessageIn = bob_welcome.into();
let bob_welcome = bob_welcome
.into_welcome()
.expect("expected message to be a welcome");
StagedWelcome::new_from_welcome(
charlie_provider,
&MlsGroupJoinConfig::default(),
bob_welcome,
None,
)
.expect("error creating staged join from welcome")
.into_group(charlie_provider)
.expect("error creating group from staged join");
}
#[openmls_test]
fn deletion() {
let alice_provider = &Provider::default();
let (alice_credential_with_key, alice_kpb, alice_signer, alice_pk) =
setup_client("alice", ciphersuite, alice_provider);
<StorageProvider as openmls_traits::storage::StorageProvider<CURRENT_VERSION>>::delete_key_package
(
alice_provider.storage(),
&alice_kpb.key_package().hash_ref(alice_provider.crypto()).unwrap(),
).unwrap();
<StorageProvider as openmls_traits::storage::StorageProvider<CURRENT_VERSION>>::delete_encryption_key_pair
(alice_provider
.storage(),alice_kpb.key_package().leaf_node().encryption_key())
.unwrap();
let mut alice_group = MlsGroup::builder()
.ciphersuite(ciphersuite)
.use_ratchet_tree_extension(true)
.build(alice_provider, &alice_signer, alice_credential_with_key)
.expect("error creating group for alice using builder");
SignatureKeyPair::delete(
alice_provider.storage(),
alice_pk.as_slice(),
ciphersuite.signature_algorithm(),
)
.unwrap();
alice_group.delete(alice_provider.storage()).unwrap();
let storage = alice_provider.storage();
let group_id = alice_group.group_id();
let current_epoch = alice_group.epoch();
let own_leaf_index = alice_group.own_leaf_index();
let all_gone = storage.tree::<_, TreeSync>(group_id).unwrap().is_none()
&& storage
.confirmation_tag::<_, ConfirmationTag>(group_id)
.unwrap()
.is_none()
&& storage
.group_context::<_, GroupContext>(group_id)
.unwrap()
.is_none()
&& storage
.interim_transcript_hash::<_, InterimTranscriptHash>(group_id)
.unwrap()
.is_none()
&& storage
.own_leaf_index::<_, LeafNodeIndex>(group_id)
.unwrap()
.is_none()
&& storage
.group_epoch_secrets::<_, GroupEpochSecrets>(group_id)
.unwrap()
.is_none()
&& storage
.message_secrets::<_, MessageSecretsStore>(group_id)
.unwrap()
.is_none()
&& storage
.mls_group_join_config::<_, MlsGroupJoinConfig>(group_id)
.unwrap()
.is_none()
&& storage
.own_leaf_nodes::<_, LeafNode>(group_id)
.unwrap()
.is_empty()
&& storage
.group_state::<MlsGroupState, _>(group_id)
.unwrap()
.is_none()
&& storage
.encryption_epoch_key_pairs::<_, _, EncryptionKeyPair>(
group_id,
¤t_epoch,
own_leaf_index.u32(),
)
.unwrap()
.is_empty()
&& alice_group.proposal_store().is_empty();
assert!(all_gone);
}
#[openmls_test::openmls_test]
fn failed_groupinfo_decryption() {
let provider = &Provider::default();
let epoch = 123;
let group_id = GroupId::random(provider.rand());
let tree_hash = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
let confirmed_transcript_hash = vec![1, 1, 1];
let extensions = Extensions::empty();
let confirmation_tag = ConfirmationTag(Mac {
mac_value: vec![1, 2, 3, 4, 5, 6, 7, 8, 9].into(),
});
let (alice_credential_with_key, alice_signature_keys) =
new_credential(provider, b"Alice", ciphersuite.signature_algorithm());
let key_package_bundle = KeyPackageBundle::generate(
provider,
&alice_signature_keys,
ciphersuite,
alice_credential_with_key,
);
let group_info_tbs = {
let group_context = GroupContext::new(
ciphersuite,
group_id,
epoch,
tree_hash,
confirmed_transcript_hash,
Extensions::empty(),
);
GroupInfoTBS::new(
group_context,
extensions,
confirmation_tag,
LeafNodeIndex::new(0),
)
.unwrap()
};
let welcome_key = AeadKey::random(ciphersuite, provider.rand());
let welcome_nonce = AeadNonce::random(provider.rand());
let receiver_key_pair = provider
.crypto()
.derive_hpke_keypair(
ciphersuite.hpke_config(),
Secret::random(ciphersuite, provider.rand())
.expect("Not enough randomness.")
.as_slice(),
)
.expect("error deriving receiver hpke key pair");
let hpke_context = b"group info welcome test info";
let group_secrets = b"these should be the group secrets";
let mut encrypted_group_secrets = hpke::encrypt_with_label(
receiver_key_pair.public.as_slice(),
"Welcome",
hpke_context,
group_secrets,
ciphersuite,
provider.crypto(),
)
.unwrap();
let group_info = group_info_tbs
.sign(&alice_signature_keys)
.expect("Error signing group info");
flip_last_byte(&mut encrypted_group_secrets);
let broken_secrets = vec![EncryptedGroupSecrets::new(
key_package_bundle
.key_package
.hash_ref(provider.crypto())
.expect("Could not hash KeyPackage."),
encrypted_group_secrets,
)];
let encrypted_group_info = welcome_key
.aead_seal(
provider.crypto(),
&group_info
.tls_serialize_detached()
.expect("An unexpected error occurred."),
&[],
&welcome_nonce,
)
.expect("An unexpected error occurred.");
let broken_welcome = Welcome::new(ciphersuite, broken_secrets, encrypted_group_info);
let error = StagedWelcome::new_from_welcome(
provider,
&MlsGroupJoinConfig::default(),
broken_welcome,
None,
)
.and_then(|staged_join| staged_join.into_group(provider))
.expect_err("Creation of mls group from a broken Welcome was successful.");
assert!(matches!(
error,
WelcomeError::GroupSecrets(GroupSecretsError::DecryptionFailed)
))
}
#[openmls_test::openmls_test]
fn update_path() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let (
mut alice_group,
_alice_signature_keys,
mut bob_group,
bob_signature_keys,
_alice_credential_with_key,
_bob_credential_with_key,
) = setup_alice_bob_group(ciphersuite, alice_provider, bob_provider);
let mut bob_new_leaf_node = bob_group.own_leaf_node().unwrap().clone();
bob_new_leaf_node
.update(
ciphersuite,
bob_provider,
&bob_signature_keys,
bob_group.group_id().clone(),
bob_group.own_leaf_index(),
LeafNodeParameters::default(),
)
.unwrap();
let (update_bob, _welcome_option, _group_info_option) = bob_group
.self_update(
bob_provider,
&bob_signature_keys,
LeafNodeParameters::default(),
)
.expect("Could not create proposal.")
.into_contents();
let pm = match update_bob.body {
mls_group::MlsMessageBodyOut::PublicMessage(pm) => pm,
_ => panic!("Wrong message type"),
};
let franken_pm = FrankenPublicMessage::from(pm.clone());
let mut content = franken_pm.content.clone();
let FrankenFramedContentBody::Commit(ref mut commit) = content.body else {
panic!("Unexpected content type");
};
let Some(ref mut path) = commit.path else {
panic!("No path in commit.");
};
for node in &mut path.nodes {
for eps in &mut node.encrypted_path_secrets {
let mut eps_ctxt_vec = Vec::<u8>::from(eps.ciphertext.clone());
eps_ctxt_vec[0] ^= 0xff;
eps.ciphertext = eps_ctxt_vec.into();
}
}
let group_context = bob_group.export_group_context().clone();
let membership_key = bob_group.message_secrets().membership_key().as_slice();
let broken_message = FrankenPublicMessage::auth(
bob_provider,
ciphersuite,
&bob_signature_keys,
content,
Some(&group_context.into()),
Some(membership_key),
Some(pm.confirmation_tag().unwrap().0.mac_value.clone()),
);
let protocol_message = ProtocolMessage::from(PublicMessage::from(broken_message));
let result = alice_group.process_message(alice_provider, protocol_message);
assert_eq!(
result.expect_err("Successful processing of a broken commit."),
ProcessMessageError::InvalidCommit(StageCommitError::UpdatePathError(
ApplyUpdatePathError::UnableToDecrypt
))
);
}
#[openmls_test::openmls_test]
fn psks() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let (
alice_credential_with_key,
alice_signature_keys,
bob_key_package_bundle,
bob_signature_keys,
) = setup_alice_bob(ciphersuite, alice_provider, bob_provider);
let psk_id = vec![1u8, 2, 3];
let secret =
Secret::random(ciphersuite, alice_provider.rand()).expect("Not enough randomness.");
let external_psk = ExternalPsk::new(psk_id);
let preshared_key_id = PreSharedKeyId::new(
ciphersuite,
alice_provider.rand(),
Psk::External(external_psk),
)
.expect("An unexpected error occured.");
preshared_key_id
.store(alice_provider, secret.as_slice())
.unwrap();
preshared_key_id
.store(bob_provider, secret.as_slice())
.unwrap();
let mut alice_group = MlsGroup::builder()
.ciphersuite(ciphersuite)
.with_wire_format_policy(PURE_PLAINTEXT_WIRE_FORMAT_POLICY)
.build(
alice_provider,
&alice_signature_keys,
alice_credential_with_key,
)
.expect("Error creating group.");
log::info!(" >>> Creating psk proposal ...");
let (_psk_proposal, _proposal_ref) = alice_group
.propose_external_psk(alice_provider, &alice_signature_keys, preshared_key_id)
.expect("Could not create PSK proposal");
let (_commit, welcome, _group_info_option) = alice_group
.add_members(
alice_provider,
&alice_signature_keys,
from_ref(bob_key_package_bundle.key_package()),
)
.expect("Could not create commit");
log::info!(" >>> Merging commit ...");
alice_group
.merge_pending_commit(alice_provider)
.expect("Could not merge commit");
let ratchet_tree = alice_group.export_ratchet_tree();
let mut bob_group = StagedWelcome::new_from_welcome(
bob_provider,
&MlsGroupJoinConfig::default(),
welcome.into_welcome().unwrap(),
Some(ratchet_tree.into()),
)
.expect("Could not stage welcome")
.into_group(bob_provider)
.expect("Could not create group from welcome");
let (_commit, _welcome_option, _group_info_option) = bob_group
.self_update(
bob_provider,
&bob_signature_keys,
LeafNodeParameters::default(),
)
.expect("An unexpected error occurred.")
.into_contents();
}
#[openmls_test::openmls_test]
fn staged_commit_creation() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let (alice_credential_with_key, alice_signature_keys, bob_key_package_bundle, _) =
setup_alice_bob(ciphersuite, alice_provider, bob_provider);
let mut alice_group = MlsGroup::builder()
.ciphersuite(ciphersuite)
.with_wire_format_policy(PURE_PLAINTEXT_WIRE_FORMAT_POLICY)
.build(
alice_provider,
&alice_signature_keys,
alice_credential_with_key,
)
.expect("Error creating group.");
let (_commit, welcome, _group_info_option) = alice_group
.add_members(
alice_provider,
&alice_signature_keys,
from_ref(bob_key_package_bundle.key_package()),
)
.expect("Could not create commit");
alice_group
.merge_pending_commit(alice_provider)
.expect("Could not merge commit");
let ratchet_tree = alice_group.export_ratchet_tree();
let bob_group = StagedWelcome::new_from_welcome(
bob_provider,
&MlsGroupJoinConfig::default(),
welcome.into_welcome().unwrap(),
Some(ratchet_tree.into()),
)
.expect("Could not stage welcome")
.into_group(bob_provider)
.expect("Could not create group from welcome");
assert_eq!(
bob_group.epoch_authenticator(),
alice_group.epoch_authenticator()
);
assert_eq!(
bob_group.export_ratchet_tree(),
alice_group.export_ratchet_tree()
)
}
#[openmls_test::openmls_test]
fn own_commit_processing() {
let alice_provider = &Provider::default();
let (alice_credential_with_key, alice_signature_keys) =
new_credential(alice_provider, b"Alice", ciphersuite.signature_algorithm());
let mut alice_group = MlsGroup::builder()
.ciphersuite(ciphersuite)
.with_wire_format_policy(PURE_PLAINTEXT_WIRE_FORMAT_POLICY)
.build(
alice_provider,
&alice_signature_keys,
alice_credential_with_key,
)
.expect("Error creating group.");
let (commit_out, _welcome_option, _group_info_option) = alice_group
.self_update(
alice_provider,
&alice_signature_keys,
LeafNodeParameters::default(),
)
.expect("Could not create commit")
.into_contents();
let commit_in = MlsMessageIn::from(commit_out);
let error = alice_group
.process_message(alice_provider, commit_in.into_protocol_message().unwrap())
.expect_err("no error while processing own commit");
assert_eq!(
error,
ProcessMessageError::InvalidCommit(StageCommitError::OwnCommit)
);
}
#[openmls_test::openmls_test]
fn proposal_application_after_self_was_removed() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let charlie_provider = &Provider::default();
let (alice_credential_with_key, _, alice_signature_keys, _pk) =
setup_client("Alice", ciphersuite, alice_provider);
let (_, bob_kpb, _, _) = setup_client("Bob", ciphersuite, bob_provider);
let (_, charlie_kpb, _, _) = setup_client("Charlie", ciphersuite, charlie_provider);
let join_group_config = MlsGroupJoinConfig::builder()
.wire_format_policy(PURE_PLAINTEXT_WIRE_FORMAT_POLICY)
.build();
let mut alice_group = MlsGroup::builder()
.ciphersuite(ciphersuite)
.with_wire_format_policy(PURE_PLAINTEXT_WIRE_FORMAT_POLICY)
.build(
alice_provider,
&alice_signature_keys,
alice_credential_with_key,
)
.expect("Error creating group.");
let (_commit, welcome, _group_info_option) = alice_group
.add_members(
alice_provider,
&alice_signature_keys,
from_ref(bob_kpb.key_package()),
)
.expect("Could not create commit");
alice_group
.merge_pending_commit(alice_provider)
.expect("Could not merge commit");
let ratchet_tree = alice_group.export_ratchet_tree();
let mut bob_group = StagedWelcome::new_from_welcome(
bob_provider,
&join_group_config,
welcome.into_welcome().unwrap(),
Some(ratchet_tree.into()),
)
.expect("Could not stage welcome")
.into_group(bob_provider)
.expect("Could not create group from welcome");
let bob_index = alice_group
.members()
.find(
|Member {
index: _,
credential,
..
}| { credential.serialized_content() == b"Bob" },
)
.expect("Couldn't find Bob in tree.")
.index;
assert_eq!(bob_index.u32(), 1);
let (bob_remove_proposal, _bob_remove_proposal_ref) = alice_group
.propose_remove_member(alice_provider, &alice_signature_keys, bob_index)
.expect("Could not create proposal");
let processed_message = bob_group
.process_message(
bob_provider,
bob_remove_proposal.into_protocol_message().unwrap(),
)
.unwrap();
let staged_proposal = match processed_message.into_content() {
ProcessedMessageContent::ProposalMessage(proposal) => *proposal,
_ => panic!("Wrong message type"),
};
bob_group
.store_pending_proposal(bob_provider.storage(), staged_proposal)
.expect("Error storing proposal");
let (commit, welcome, _group_info_option) = alice_group
.add_members(
alice_provider,
&alice_signature_keys,
from_ref(charlie_kpb.key_package()),
)
.expect("Could not create commit");
alice_group
.merge_pending_commit(alice_provider)
.expect("Could not merge commit");
println!("Bob processes the commit");
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(commit) => *commit,
_ => panic!("Wrong message type"),
};
bob_group
.merge_staged_commit(bob_provider, staged_commit)
.expect("Error merging commit.");
println!("Charlie processes the commit");
let ratchet_tree = alice_group.export_ratchet_tree();
let charlie_group = StagedWelcome::new_from_welcome(
charlie_provider,
&join_group_config,
welcome.into_welcome().unwrap(),
Some(ratchet_tree.into()),
)
.expect("Error staging welcome.")
.into_group(charlie_provider)
.expect("Error creating group from welcome.");
let alice_members = alice_group.members();
let bob_members = bob_group.members();
let charlie_members = charlie_group.members();
for (alice_member, (bob_member, charlie_member)) in
alice_members.zip(bob_members.zip(charlie_members))
{
assert_eq!(alice_member.index, bob_member.index);
let alice_id = alice_member.credential.serialized_content();
let bob_id = bob_member.credential.serialized_content();
let charlie_id = charlie_member.credential.serialized_content();
assert_eq!(alice_id, bob_id);
assert_eq!(alice_member.signature_key, bob_member.signature_key);
assert_eq!(charlie_member.index, bob_member.index);
assert_eq!(charlie_id, bob_id);
assert_eq!(charlie_member.signature_key, bob_member.signature_key);
assert_eq!(charlie_member.encryption_key, alice_member.encryption_key);
}
let mut bob_members = bob_group.members();
let member = bob_members.next().unwrap();
let bob_next_id = member.credential.serialized_content();
assert_eq!(bob_next_id, b"Alice");
let member = bob_members.next().unwrap();
let bob_next_id = member.credential.serialized_content();
assert_eq!(bob_next_id, b"Charlie");
}
#[openmls_test::openmls_test]
fn proposal_application_after_self_was_removed_ref() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let charlie_provider = &Provider::default();
let (alice_credential_with_key, _, alice_signature_keys, _pk) =
setup_client("Alice", ciphersuite, alice_provider);
let (_, bob_kpb, _, _) = setup_client("Bob", ciphersuite, bob_provider);
let (_, charlie_kpb, _, _) = setup_client("Charlie", ciphersuite, charlie_provider);
let join_group_config = MlsGroupJoinConfig::builder()
.wire_format_policy(PURE_PLAINTEXT_WIRE_FORMAT_POLICY)
.build();
let mut alice_group = MlsGroup::builder()
.ciphersuite(ciphersuite)
.with_wire_format_policy(PURE_PLAINTEXT_WIRE_FORMAT_POLICY)
.build(
alice_provider,
&alice_signature_keys,
alice_credential_with_key,
)
.expect("Error creating group.");
let (_commit, welcome, _group_info_option) = alice_group
.add_members(
alice_provider,
&alice_signature_keys,
from_ref(bob_kpb.key_package()),
)
.expect("Could not create commit");
alice_group
.merge_pending_commit(alice_provider)
.expect("Could not merge commit");
let ratchet_tree = alice_group.export_ratchet_tree();
let mut bob_group = StagedWelcome::new_from_welcome(
bob_provider,
&join_group_config,
welcome.into_welcome().unwrap(),
Some(ratchet_tree.into()),
)
.expect("Could not stage welcome")
.into_group(bob_provider)
.expect("Could not create group from welcome");
let bob_index = alice_group
.members()
.find(
|Member {
index: _,
credential,
..
}| { credential.serialized_content() == b"Bob" },
)
.expect("Couldn't find Bob in tree.")
.index;
assert_eq!(bob_index.u32(), 1);
let (bob_remove_proposal, _bob_remove_proposal_ref) = alice_group
.propose_remove_member(alice_provider, &alice_signature_keys, bob_index)
.expect("Could not create proposal");
let (charlie_add_proposal, _charlie_add_proposal_ref) = alice_group
.propose_add_member(
alice_provider,
&alice_signature_keys,
charlie_kpb.key_package(),
)
.expect("Could not create proposal");
let processed_message = bob_group
.process_message(
bob_provider,
bob_remove_proposal.into_protocol_message().unwrap(),
)
.unwrap();
let staged_proposal = match processed_message.into_content() {
ProcessedMessageContent::ProposalMessage(proposal) => *proposal,
_ => panic!("Wrong message type"),
};
bob_group
.store_pending_proposal(bob_provider.storage(), staged_proposal)
.expect("Error storing proposal");
let processed_message = bob_group
.process_message(
bob_provider,
charlie_add_proposal.into_protocol_message().unwrap(),
)
.unwrap();
let staged_proposal = match processed_message.into_content() {
ProcessedMessageContent::ProposalMessage(proposal) => *proposal,
_ => panic!("Wrong message type"),
};
bob_group
.store_pending_proposal(bob_provider.storage(), staged_proposal)
.expect("Error storing proposal");
alice_group.print_ratchet_tree("Alice's tree before commit\n");
let alice_rt_before = alice_group.export_ratchet_tree();
let (commit, welcome, _group_info_option) = alice_group
.commit_to_pending_proposals(alice_provider, &alice_signature_keys)
.expect("Could not create commit");
alice_group
.merge_pending_commit(alice_provider)
.expect("Could not merge commit");
alice_group.print_ratchet_tree("Alice's tree after commit\n");
println!("Bob processes the commit");
bob_group.print_ratchet_tree("Bob's tree before processing the commit\n");
let bob_rt_before = bob_group.export_ratchet_tree();
assert_eq!(alice_rt_before, bob_rt_before);
let processed_message = bob_group
.process_message(bob_provider, commit.into_protocol_message().unwrap())
.unwrap();
println!("Bob finished processesing the commit");
let staged_commit = match processed_message.into_content() {
ProcessedMessageContent::StagedCommitMessage(commit) => *commit,
_ => panic!("Wrong message type"),
};
bob_group
.merge_staged_commit(bob_provider, staged_commit)
.expect("Error merging commit.");
println!("Charlie processes the commit");
let ratchet_tree = alice_group.export_ratchet_tree();
let charlie_group = StagedWelcome::new_from_welcome(
charlie_provider,
&join_group_config,
welcome.unwrap().into_welcome().unwrap(),
Some(ratchet_tree.into()),
)
.expect("Error staging welcome.")
.into_group(charlie_provider)
.expect("Error creating group from welcome.");
let alice_members = alice_group.members();
let bob_members = bob_group.members();
let charlie_members = charlie_group.members();
for (alice_member, (bob_member, charlie_member)) in
alice_members.zip(bob_members.zip(charlie_members))
{
assert_eq!(alice_member.index, bob_member.index);
let alice_id = alice_member.credential.serialized_content();
let bob_id = bob_member.credential.serialized_content();
let charlie_id = charlie_member.credential.serialized_content();
assert_eq!(alice_id, bob_id);
assert_eq!(alice_member.signature_key, bob_member.signature_key);
assert_eq!(charlie_member.index, bob_member.index);
assert_eq!(charlie_id, bob_id);
assert_eq!(charlie_member.signature_key, bob_member.signature_key);
assert_eq!(charlie_member.encryption_key, alice_member.encryption_key);
}
let mut bob_members = bob_group.members();
let member = bob_members.next().unwrap();
let bob_next_id = member.credential.serialized_content();
assert_eq!(bob_next_id, b"Alice");
let member = bob_members.next().unwrap();
let bob_next_id = member.credential.serialized_content();
assert_eq!(bob_next_id, b"Charlie");
}
#[openmls_test::openmls_test]
fn signature_key_rotation() {
let alice_party = CorePartyState::<Provider>::new("alice");
let bob_party = CorePartyState::<Provider>::new("bob");
let alice_pre_group = alice_party.generate_pre_group(ciphersuite);
let old_credential_with_key = alice_pre_group.credential_with_key.clone();
let bob_pre_group = bob_party.generate_pre_group(ciphersuite);
let mls_group_create_config = MlsGroupCreateConfig::builder()
.ciphersuite(ciphersuite)
.use_ratchet_tree_extension(true)
.build();
let mls_group_join_config = mls_group_create_config.join_config().clone();
let group_id = GroupId::from_slice(b"test");
let mut group_state =
GroupState::new_from_party(group_id, alice_pre_group, mls_group_create_config).unwrap();
group_state
.add_member(AddMemberConfig {
adder: "alice",
addees: vec![bob_pre_group],
join_config: mls_group_join_config.clone(),
tree: None,
})
.expect("Could not add member");
let new_pre_group_state = alice_party.generate_pre_group(ciphersuite);
let [alice_group_state] = group_state.members_mut(&["alice"]);
let old_signature_key = alice_group_state
.party
.credential_with_key
.signature_key
.clone();
let new_signature_key = new_pre_group_state
.credential_with_key
.signature_key
.clone();
assert_ne!(old_signature_key, new_signature_key);
let leaf_node_parameters = LeafNodeParameters::builder()
.with_credential_with_key(old_credential_with_key)
.build();
let new_signer = NewSignerBundle {
signer: &new_pre_group_state.signer,
credential_with_key: new_pre_group_state.credential_with_key,
};
let err = alice_group_state
.group
.self_update_with_new_signer(
&alice_group_state.party.core_state.provider,
&alice_group_state.party.signer,
new_signer.clone(),
leaf_node_parameters,
)
.unwrap_err();
assert_eq!(
err,
SelfUpdateError::CreateCommitError(CreateCommitError::InvalidLeafNodeParameters)
);
let bundle = alice_group_state
.group
.self_update_with_new_signer(
&alice_group_state.party.core_state.provider,
&alice_group_state.party.signer,
new_signer,
LeafNodeParameters::default(),
)
.unwrap();
alice_group_state
.group
.merge_pending_commit(&alice_group_state.party.core_state.provider)
.unwrap();
group_state
.deliver_and_apply_if(bundle.into_commit().into(), |state| {
state.party.core_state.name != "alice"
})
.unwrap();
let [bob_group_state] = group_state.members_mut(&["bob"]);
let alice_signature_key = bob_group_state
.group
.members()
.find(|m| m.index == LeafNodeIndex::new(0))
.unwrap()
.signature_key;
assert_eq!(alice_signature_key.as_slice(), new_signature_key.as_slice());
let [alice_group_state] = group_state.members_mut(&["alice"]);
let bundle = alice_group_state
.group
.self_update(
&alice_group_state.party.core_state.provider,
&new_pre_group_state.signer,
LeafNodeParameters::default(),
)
.unwrap();
group_state
.deliver_and_apply_if(bundle.into_commit().into(), |state| {
state.party.core_state.name != "alice"
})
.unwrap();
}
#[openmls_test::openmls_test]
fn group_replacement() {
let alice_party = CorePartyState::<Provider>::new("alice");
let bob_party = CorePartyState::<Provider>::new("bob");
let alice_pre_group = alice_party.generate_pre_group(ciphersuite);
let alice_credential_with_key = alice_pre_group.credential_with_key.clone();
let alice_signer = alice_pre_group.signer.clone();
let bob_pre_group = bob_party.generate_pre_group(ciphersuite);
let bob_kpb = KeyPackageBundle::generate(
&bob_party.provider,
&bob_pre_group.signer,
ciphersuite,
bob_pre_group.credential_with_key.clone(),
);
let bob_kpb2 = KeyPackageBundle::generate(
&bob_party.provider,
&bob_pre_group.signer,
ciphersuite,
bob_pre_group.credential_with_key.clone(),
);
let mls_group_create_config = MlsGroupCreateConfig::builder()
.ciphersuite(ciphersuite)
.use_ratchet_tree_extension(true)
.build();
let mls_group_join_config = mls_group_create_config.join_config().clone();
let group_id = GroupId::from_slice(b"test");
let mut group_state =
GroupState::new_from_party(group_id.clone(), alice_pre_group, mls_group_create_config)
.unwrap();
group_state
.add_member(AddMemberConfig {
adder: "alice",
addees: vec![bob_pre_group],
join_config: mls_group_join_config.clone(),
tree: None,
})
.expect("Could not add member");
let err = MlsGroup::builder()
.ciphersuite(ciphersuite)
.with_group_id(group_id.clone())
.use_ratchet_tree_extension(true)
.build(
&alice_party.provider,
&alice_signer,
alice_credential_with_key.clone(),
)
.expect_err("Creating a group with an existing ID succeeded unexpectedly.");
assert_eq!(err, NewGroupError::GroupAlreadyExists);
let mut alice_group = MlsGroup::builder()
.replace_old_group()
.ciphersuite(ciphersuite)
.with_group_id(group_id.clone())
.use_ratchet_tree_extension(true)
.build(
&alice_party.provider,
&alice_signer,
alice_credential_with_key.clone(),
)
.expect("Group creation failed despite replace flag");
let (_commit, welcome, _group_info_option) = alice_group
.add_members(
&alice_party.provider,
&alice_signer,
&[bob_kpb.key_package().clone()],
)
.unwrap();
let welcome = welcome.into_welcome().unwrap();
let processed_welcome = ProcessedWelcome::new_from_welcome(
&bob_party.provider,
&mls_group_join_config,
welcome.clone(),
)
.unwrap();
let err = JoinBuilder::new(&bob_party.provider, processed_welcome)
.build()
.expect_err("Bob joined the new group unexpectedly.");
assert_eq!(err, WelcomeError::GroupAlreadyExists);
let mut alice_group = MlsGroup::builder()
.replace_old_group()
.ciphersuite(ciphersuite)
.with_group_id(group_id)
.use_ratchet_tree_extension(true)
.build(
&alice_party.provider,
&alice_signer,
alice_credential_with_key,
)
.expect("Group creation failed despite replace flag");
let (_commit, welcome, _group_info_option) = alice_group
.add_members(
&alice_party.provider,
&alice_signer,
&[bob_kpb2.key_package().clone()],
)
.unwrap();
let welcome = welcome.into_welcome().unwrap();
let processed_welcome = ProcessedWelcome::new_from_welcome(
&bob_party.provider,
&mls_group_join_config,
welcome.clone(),
)
.unwrap();
let _ = JoinBuilder::new(&bob_party.provider, processed_welcome)
.replace_old_group()
.build()
.expect("Bob failed to join the new group despite replace flag.");
}