#![cfg(feature = "fork-resolution")]
use openmls::{
group::{JoinBuilder, ProcessedWelcome},
prelude::*,
};
use openmls_basic_credential::SignatureKeyPair;
use openmls_test::openmls_test;
use openmls_traits::{signatures::Signer, types::SignatureScheme};
#[openmls_test]
fn book_example_readd() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let charlie_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 bob_kpb = generate_key_package(
ciphersuite,
bob_credential.clone(),
Extensions::empty(),
bob_provider,
&bob_signature_keys,
);
let mls_group_create_config = MlsGroupCreateConfig::builder()
.padding_size(100)
.sender_ratchet_configuration(SenderRatchetConfiguration::new(
10, 2000, ))
.ciphersuite(ciphersuite)
.use_ratchet_tree_extension(true)
.build();
let mls_group_config = mls_group_create_config.join_config();
let mut alice_group = MlsGroup::new(
alice_provider,
&alice_signature_keys,
&mls_group_create_config,
alice_credential.clone(),
)
.unwrap();
let add_bob_messages = alice_group
.commit_builder()
.propose_adds(vec![bob_kpb.key_package().clone()])
.load_psks(alice_provider.storage())
.unwrap()
.build(
alice_provider.rand(),
alice_provider.crypto(),
&alice_signature_keys,
|_| true,
)
.unwrap()
.stage_commit(alice_provider)
.unwrap();
alice_group.merge_pending_commit(alice_provider).unwrap();
let welcome = add_bob_messages.into_welcome().unwrap();
let mut bob_group =
StagedWelcome::new_from_welcome(bob_provider, mls_group_config, welcome.clone(), None)
.unwrap()
.into_group(bob_provider)
.unwrap();
let charlie_kpb = generate_key_package(
ciphersuite,
charlie_credential,
Extensions::empty(),
charlie_provider,
&charlie_signature_keys,
);
let add_charlie_messages = alice_group
.commit_builder()
.propose_adds(vec![charlie_kpb.key_package().clone()])
.load_psks(alice_provider.storage())
.unwrap()
.build(
alice_provider.rand(),
alice_provider.crypto(),
&alice_signature_keys,
|_| true,
)
.unwrap()
.stage_commit(alice_provider)
.unwrap();
bob_group
.commit_builder()
.propose_adds(vec![charlie_kpb.key_package().clone()])
.load_psks(bob_provider.storage())
.unwrap()
.build(
bob_provider.rand(),
bob_provider.crypto(),
&bob_signature_keys,
|_| true,
)
.unwrap()
.stage_commit(bob_provider)
.unwrap();
alice_group.merge_pending_commit(alice_provider).unwrap();
bob_group.merge_pending_commit(bob_provider).unwrap();
let welcome = add_charlie_messages.into_welcome().unwrap();
let mut charlie_group =
StagedWelcome::new_from_welcome(charlie_provider, mls_group_config, welcome, None)
.unwrap()
.into_group(charlie_provider)
.unwrap();
assert_eq!(
alice_group.confirmation_tag(),
charlie_group.confirmation_tag()
);
assert_ne!(bob_group.confirmation_tag(), alice_group.confirmation_tag());
assert_ne!(
bob_group.confirmation_tag(),
charlie_group.confirmation_tag()
);
let bob_new_kpb = generate_key_package(
ciphersuite,
bob_credential,
Extensions::empty(),
bob_provider,
&bob_signature_keys,
);
let our_partition = &[alice_group.own_leaf_index(), charlie_group.own_leaf_index()];
let builder = alice_group.recover_fork_by_readding(our_partition).unwrap();
let readded_key_packages = builder
.complement_partition()
.iter()
.map(|member| {
let basic_credential = BasicCredential::try_from(member.credential.clone()).unwrap();
match basic_credential.identity() {
b"Bob" => bob_new_kpb.key_package().clone(),
other => panic!(
"we only expect bob to be re-added, but found {:?}",
String::from_utf8(other.to_vec()).unwrap()
),
}
})
.collect();
let readd_messages = builder
.provide_key_packages(readded_key_packages)
.load_psks(alice_provider.storage())
.unwrap()
.build(
alice_provider.rand(),
alice_provider.crypto(),
&alice_signature_keys,
|_| true,
)
.unwrap()
.stage_commit(alice_provider)
.unwrap();
let (commit, welcome, _) = readd_messages.into_contents();
let welcome = welcome.unwrap();
let processed_welcome =
ProcessedWelcome::new_from_welcome(bob_provider, &mls_group_config, welcome).unwrap();
let bob_group = JoinBuilder::new(bob_provider, processed_welcome)
.replace_old_group()
.build()
.unwrap()
.into_group(bob_provider)
.unwrap();
alice_group.merge_pending_commit(alice_provider).unwrap();
if let ProcessedMessageContent::StagedCommitMessage(staged_commit) = charlie_group
.process_message(charlie_provider, commit.into_protocol_message().unwrap())
.unwrap()
.into_content()
{
charlie_group
.merge_staged_commit(charlie_provider, *staged_commit)
.unwrap()
} else {
panic!("expected a commit")
}
assert_eq!(alice_group.confirmation_tag(), bob_group.confirmation_tag());
assert_eq!(
alice_group.confirmation_tag(),
charlie_group.confirmation_tag()
);
assert_eq!(
charlie_group.confirmation_tag(),
bob_group.confirmation_tag()
);
}
#[openmls_test]
fn book_example_reboot() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let charlie_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 bob_kpb = generate_key_package(
ciphersuite,
bob_credential.clone(),
Extensions::empty(),
bob_provider,
&bob_signature_keys,
);
let mls_group_create_config = MlsGroupCreateConfig::builder()
.padding_size(100)
.sender_ratchet_configuration(SenderRatchetConfiguration::new(
10, 2000, ))
.ciphersuite(ciphersuite)
.use_ratchet_tree_extension(true)
.build();
let mls_group_config = mls_group_create_config.join_config();
let mut alice_group = MlsGroup::new(
alice_provider,
&alice_signature_keys,
&mls_group_create_config,
alice_credential.clone(),
)
.unwrap();
let add_bob_messages = alice_group
.commit_builder()
.propose_adds(vec![bob_kpb.key_package().clone()])
.load_psks(alice_provider.storage())
.unwrap()
.build(
alice_provider.rand(),
alice_provider.crypto(),
&alice_signature_keys,
|_| true,
)
.unwrap()
.stage_commit(alice_provider)
.unwrap();
alice_group.merge_pending_commit(alice_provider).unwrap();
let welcome = add_bob_messages.into_welcome().unwrap();
let mut bob_group =
StagedWelcome::new_from_welcome(bob_provider, mls_group_config, welcome, None)
.unwrap()
.into_group(bob_provider)
.unwrap();
let charlie_kpb = generate_key_package(
ciphersuite,
charlie_credential.clone(),
Extensions::empty(),
charlie_provider,
&charlie_signature_keys,
);
let add_charlie_messages = alice_group
.commit_builder()
.propose_adds(vec![charlie_kpb.key_package().clone()])
.load_psks(alice_provider.storage())
.unwrap()
.build(
alice_provider.rand(),
alice_provider.crypto(),
&alice_signature_keys,
|_| true,
)
.unwrap()
.stage_commit(alice_provider)
.unwrap();
bob_group
.commit_builder()
.propose_adds(vec![charlie_kpb.key_package().clone()])
.load_psks(bob_provider.storage())
.unwrap()
.build(
bob_provider.rand(),
bob_provider.crypto(),
&bob_signature_keys,
|_| true,
)
.unwrap()
.stage_commit(bob_provider)
.unwrap();
alice_group.merge_pending_commit(alice_provider).unwrap();
bob_group.merge_pending_commit(bob_provider).unwrap();
let welcome = add_charlie_messages.into_welcome().unwrap();
let charlie_group =
StagedWelcome::new_from_welcome(charlie_provider, mls_group_config, welcome, None)
.unwrap()
.into_group(charlie_provider)
.unwrap();
assert_eq!(
alice_group.confirmation_tag(),
charlie_group.confirmation_tag()
);
assert_ne!(bob_group.confirmation_tag(), alice_group.confirmation_tag());
assert_ne!(
bob_group.confirmation_tag(),
charlie_group.confirmation_tag()
);
let bob_new_kpb = generate_key_package(
ciphersuite,
bob_credential,
Extensions::empty(),
bob_provider,
&bob_signature_keys,
);
let charlie_new_kpb = generate_key_package(
ciphersuite,
charlie_credential,
Extensions::empty(),
charlie_provider,
&charlie_signature_keys,
);
let new_group_id: GroupId = GroupId::from_slice(
alice_group
.group_id()
.as_slice()
.iter()
.copied()
.chain(b"-new".iter().copied())
.collect::<Vec<_>>()
.as_slice(),
);
let (mut alice_group, reboot_messages) = alice_group
.reboot(new_group_id)
.finish(
Extensions::empty(),
vec![
bob_new_kpb.key_package().clone(),
charlie_new_kpb.key_package().clone(),
],
|builder| builder,
alice_provider,
&alice_signature_keys,
alice_credential,
)
.unwrap();
alice_group.merge_pending_commit(alice_provider).unwrap();
let welcome = reboot_messages.into_welcome().unwrap();
let bob_group =
StagedWelcome::new_from_welcome(bob_provider, mls_group_config, welcome.clone(), None)
.unwrap()
.into_group(bob_provider)
.unwrap();
assert_eq!(bob_group.own_leaf_index(), LeafNodeIndex::new(1));
let charlie_group =
StagedWelcome::new_from_welcome(charlie_provider, mls_group_config, welcome, None)
.unwrap()
.into_group(charlie_provider)
.unwrap();
assert_eq!(charlie_group.own_leaf_index(), LeafNodeIndex::new(2));
assert_eq!(alice_group.confirmation_tag(), bob_group.confirmation_tag());
assert_eq!(
alice_group.confirmation_tag(),
charlie_group.confirmation_tag()
);
assert_eq!(
bob_group.confirmation_tag(),
charlie_group.confirmation_tag()
);
}
fn generate_credential(
identity: Vec<u8>,
signature_algorithm: SignatureScheme,
provider: &impl openmls::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 openmls::storage::OpenMlsProvider,
signer: &impl Signer,
) -> KeyPackageBundle {
KeyPackage::builder()
.key_package_extensions(extensions)
.build(ciphersuite, provider, signer, credential_with_key)
.unwrap()
}