use std::slice::from_ref;
use crate::{
storage::OpenMlsProvider,
test_utils::frankenstein::*,
treesync::{errors::LeafNodeValidationError, LeafNodeParameters},
};
use openmls_traits::{prelude::openmls_types::*, signatures::Signer};
use proposal_store::QueuedProposal;
use tls_codec::{Deserialize, Serialize};
use crate::group::tests_and_kats::utils::{
generate_credential_with_key, generate_key_package, resign_message, CredentialWithKeyAndSigner,
};
use crate::{
binary_tree::LeafNodeIndex,
ciphersuite::hash_ref::ProposalRef,
credentials::*,
framing::{
mls_content::FramedContentBody, validation::ProcessedMessageContent, AuthenticatedContent,
FramedContent, MlsMessageIn, MlsMessageOut, ProtocolMessage, PublicMessage, Sender,
},
group::*,
key_packages::{errors::*, *},
messages::{
proposals::{
AddProposal, CustomProposal, Proposal, ProposalOrRef, ProposalType, RemoveProposal,
UpdateProposal,
},
Commit, Welcome,
},
prelude::{Capabilities, MlsMessageBodyIn},
test_utils::frankenstein::FrankenKeyPackage,
treesync::errors::ApplyUpdatePathError,
versions::ProtocolVersion,
};
fn generate_credential_with_key_and_key_package(
identity: Vec<u8>,
ciphersuite: Ciphersuite,
provider: &impl OpenMlsProvider,
) -> (CredentialWithKeyAndSigner, KeyPackageBundle) {
let credential_with_key_and_signer =
generate_credential_with_key(identity, ciphersuite.signature_algorithm(), provider);
let key_package = generate_key_package(
ciphersuite,
Extensions::empty(),
provider,
credential_with_key_and_signer.clone(),
);
(credential_with_key_and_signer, key_package)
}
fn create_group_with_members<Provider: OpenMlsProvider>(
ciphersuite: Ciphersuite,
alice_credential_with_key_and_signer: &CredentialWithKeyAndSigner,
member_key_packages: &[KeyPackage],
provider: &Provider,
) -> Result<(MlsMessageIn, Welcome), AddMembersError<<Provider as OpenMlsProvider>::StorageError>> {
let mut alice_group = MlsGroup::new_with_group_id(
provider,
&alice_credential_with_key_and_signer.signer,
&MlsGroupCreateConfig::builder()
.ciphersuite(ciphersuite)
.build(),
GroupId::random(provider.rand()),
alice_credential_with_key_and_signer
.credential_with_key
.clone(),
)
.expect("An unexpected error occurred.");
alice_group
.add_members(
provider,
&alice_credential_with_key_and_signer.signer,
member_key_packages,
)
.map(|(msg, welcome, _group_info)| {
(
msg.into(),
welcome.into_welcome().expect("Unexpected message type."),
)
})
}
struct ProposalValidationTestSetup {
alice_group: MlsGroup,
alice_credential_with_key_and_signer: CredentialWithKeyAndSigner,
bob_group: MlsGroup,
bob_credential_with_key_and_signer: CredentialWithKeyAndSigner,
}
fn new_test_group(
identity: &str,
wire_format_policy: WireFormatPolicy,
ciphersuite: Ciphersuite,
provider: &impl OpenMlsProvider,
) -> (MlsGroup, CredentialWithKeyAndSigner) {
let group_id = GroupId::random(provider.rand());
let credential_with_key_and_signer =
generate_credential_with_key(identity.into(), ciphersuite.signature_algorithm(), provider);
let mls_group_create_config = MlsGroupCreateConfig::builder()
.wire_format_policy(wire_format_policy)
.ciphersuite(ciphersuite)
.build();
(
MlsGroup::new_with_group_id(
provider,
&credential_with_key_and_signer.signer,
&mls_group_create_config,
group_id,
credential_with_key_and_signer.credential_with_key.clone(),
)
.unwrap(),
credential_with_key_and_signer,
)
}
fn validation_test_setup(
wire_format_policy: WireFormatPolicy,
ciphersuite: Ciphersuite,
alice_provider: &impl OpenMlsProvider,
bob_provider: &impl OpenMlsProvider,
) -> ProposalValidationTestSetup {
let (mut alice_group, alice_credential_with_key_and_signer) =
new_test_group("Alice", wire_format_policy, ciphersuite, alice_provider);
let bob_credential_with_key_and_signer = generate_credential_with_key(
"Bob".into(),
ciphersuite.signature_algorithm(),
bob_provider,
);
let bob_key_package = generate_key_package(
ciphersuite,
Extensions::empty(),
bob_provider,
bob_credential_with_key_and_signer.clone(),
);
let (_message, welcome, _group_info) = alice_group
.add_members(
alice_provider,
&alice_credential_with_key_and_signer.signer,
from_ref(bob_key_package.key_package()),
)
.unwrap();
alice_group.merge_pending_commit(alice_provider).unwrap();
let mls_group_config = MlsGroupJoinConfig::builder()
.wire_format_policy(wire_format_policy)
.build();
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_config,
welcome,
Some(alice_group.export_ratchet_tree().into()),
)
.unwrap()
.into_group(bob_provider)
.unwrap();
ProposalValidationTestSetup {
alice_group,
alice_credential_with_key_and_signer,
bob_group,
bob_credential_with_key_and_signer,
}
}
fn insert_proposal_and_resign(
provider: &impl OpenMlsProvider,
ciphersuite: Ciphersuite,
mut proposal_or_ref: Vec<ProposalOrRef>,
mut plaintext: PublicMessage,
original_plaintext: &PublicMessage,
committer_group: &MlsGroup,
signer: &impl Signer,
) -> PublicMessage {
let mut commit_content = if let FramedContentBody::Commit(commit) = plaintext.content() {
commit.clone()
} else {
panic!("Unexpected content type.");
};
commit_content.proposals.append(&mut proposal_or_ref);
plaintext.set_content(FramedContentBody::Commit(commit_content));
let mut signed_plaintext = resign_message(
committer_group,
plaintext,
original_plaintext,
provider,
signer,
ciphersuite,
);
let membership_key = committer_group.message_secrets().membership_key();
signed_plaintext
.set_membership_tag(
provider.crypto(),
ciphersuite,
membership_key,
committer_group.message_secrets().serialized_context(),
)
.expect("error refreshing membership tag");
signed_plaintext
}
enum KeyUniqueness {
PositiveDifferentKey,
NegativeSameKey,
PositiveSameKeyWithRemove,
}
#[openmls_test::openmls_test]
fn test_valsem101a() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let charlie_provider = &Provider::default();
for bob_and_charlie_share_keys in [
KeyUniqueness::NegativeSameKey,
KeyUniqueness::PositiveDifferentKey,
] {
let (alice_credential_with_keys, _) = generate_credential_with_key_and_key_package(
"Alice".into(),
ciphersuite,
alice_provider,
);
let bob_credential_with_keys = generate_credential_with_key(
b"Bob".to_vec(),
ciphersuite.signature_algorithm(),
bob_provider,
);
let mut charlie_credential_with_keys = generate_credential_with_key(
b"Charlie".to_vec(),
ciphersuite.signature_algorithm(),
charlie_provider,
);
match bob_and_charlie_share_keys {
KeyUniqueness::NegativeSameKey => {
let charlie_credential = charlie_credential_with_keys
.credential_with_key
.credential
.clone();
charlie_credential_with_keys = bob_credential_with_keys.clone();
charlie_credential_with_keys.credential_with_key.credential = charlie_credential;
}
KeyUniqueness::PositiveDifferentKey => {
}
KeyUniqueness::PositiveSameKeyWithRemove => unreachable!(),
}
let bob_key_package = generate_key_package(
ciphersuite,
Extensions::empty(),
bob_provider,
bob_credential_with_keys.clone(),
);
let charlie_key_package = generate_key_package(
ciphersuite,
Extensions::empty(),
charlie_provider,
charlie_credential_with_keys.clone(),
);
let res = create_group_with_members(
ciphersuite,
&alice_credential_with_keys,
&[
bob_key_package.key_package().clone(),
charlie_key_package.key_package().clone(),
],
alice_provider,
);
match bob_and_charlie_share_keys {
KeyUniqueness::NegativeSameKey => {
let err = res.expect_err("was able to add users with the same signature key!");
assert!(matches!(
err,
AddMembersError::CreateCommitError(CreateCommitError::ProposalValidationError(
ProposalValidationError::DuplicateSignatureKey
))
));
}
KeyUniqueness::PositiveDifferentKey => {
let _ = res.expect("failed to add users with different signature keypairs!");
}
KeyUniqueness::PositiveSameKeyWithRemove => unreachable!(),
}
}
let ProposalValidationTestSetup {
mut alice_group,
alice_credential_with_key_and_signer,
mut bob_group,
..
} = validation_test_setup(
PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
ciphersuite,
alice_provider,
bob_provider,
);
let (charlie_credential_with_key, charlie_key_package) =
generate_credential_with_key_and_key_package(
"Charlie".into(),
ciphersuite,
charlie_provider,
);
let serialized_update = alice_group
.add_members(
alice_provider,
&alice_credential_with_key_and_signer.signer,
from_ref(charlie_key_package.key_package()),
)
.expect("Error creating self-update")
.tls_serialize_detached()
.expect("Could not serialize message.");
let plaintext = MlsMessageIn::tls_deserialize(&mut serialized_update.as_slice())
.expect("Could not deserialize message.")
.into_plaintext()
.expect("Message was not a plaintext.");
let original_plaintext = plaintext.clone();
let dave_key_package = KeyPackage::builder()
.build(
ciphersuite,
charlie_provider,
&charlie_credential_with_key.signer,
CredentialWithKey {
credential: BasicCredential::new(b"Dave".to_vec()).into(),
signature_key: charlie_credential_with_key
.credential_with_key
.signature_key,
},
)
.unwrap();
let second_add_proposal = Proposal::add(AddProposal {
key_package: dave_key_package.key_package().clone(),
});
let verifiable_plaintext = insert_proposal_and_resign(
alice_provider,
ciphersuite,
vec![ProposalOrRef::proposal(second_add_proposal)],
plaintext,
&original_plaintext,
&alice_group,
&alice_credential_with_key_and_signer.signer,
);
let update_message_in = ProtocolMessage::from(verifiable_plaintext);
let err = bob_group
.process_message(bob_provider, update_message_in)
.expect_err("Could process message despite modified public key in path.");
assert!(matches!(
err,
ProcessMessageError::InvalidCommit(StageCommitError::ProposalValidationError(
ProposalValidationError::DuplicateSignatureKey
))
));
let original_update_plaintext =
MlsMessageIn::tls_deserialize(&mut serialized_update.as_slice())
.expect("Could not deserialize message.");
bob_group
.process_message(
bob_provider,
original_update_plaintext
.try_into_protocol_message()
.unwrap(),
)
.expect("Unexpected error.");
}
#[openmls_test::openmls_test]
fn test_valsem102() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let charlie_provider = &Provider::default();
for bob_and_charlie_share_keys in [
KeyUniqueness::NegativeSameKey,
KeyUniqueness::PositiveDifferentKey,
] {
let (alice_credential_with_key, _) = generate_credential_with_key_and_key_package(
"Alice".into(),
ciphersuite,
alice_provider,
);
let (bob_credential_with_key, mut bob_key_package) =
generate_credential_with_key_and_key_package("Bob".into(), ciphersuite, bob_provider);
let (_charlie_credential_with_key, charlie_key_package) =
generate_credential_with_key_and_key_package(
"Charlie".into(),
ciphersuite,
charlie_provider,
);
match bob_and_charlie_share_keys {
KeyUniqueness::NegativeSameKey => {
let encryption_private_key = bob_key_package.encryption_private_key().clone();
let mut franken_key_package = FrankenKeyPackage::from(bob_key_package);
franken_key_package.init_key = charlie_key_package
.key_package()
.hpke_init_key()
.as_slice()
.to_owned()
.into();
franken_key_package.resign(&bob_credential_with_key.signer);
bob_key_package = {
let kp = KeyPackage::from(franken_key_package.clone());
KeyPackageBundle::new(
kp,
charlie_key_package.init_private_key().clone(),
encryption_private_key.into(),
)
};
}
KeyUniqueness::PositiveDifferentKey => {
}
KeyUniqueness::PositiveSameKeyWithRemove => unreachable!(),
}
let res = create_group_with_members(
ciphersuite,
&alice_credential_with_key,
&[
bob_key_package.key_package().clone(),
charlie_key_package.key_package().clone(),
],
alice_provider,
);
match bob_and_charlie_share_keys {
KeyUniqueness::NegativeSameKey => {
let err = res.expect_err("was able to add users with the same HPKE init key!");
assert!(matches!(
err,
AddMembersError::CreateCommitError(CreateCommitError::ProposalValidationError(
ProposalValidationError::DuplicateInitKey
))
));
}
KeyUniqueness::PositiveDifferentKey => {
let _ = res.expect("failed to add users with different HPKE init keys!");
}
KeyUniqueness::PositiveSameKeyWithRemove => unreachable!(),
}
}
let ProposalValidationTestSetup {
mut alice_group,
alice_credential_with_key_and_signer,
mut bob_group,
..
} = validation_test_setup(
PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
ciphersuite,
alice_provider,
bob_provider,
);
let (_charlie_credential_with_key, charlie_key_package) =
generate_credential_with_key_and_key_package(
"Charlie".into(),
ciphersuite,
charlie_provider,
);
let serialized_update = alice_group
.add_members(
alice_provider,
&alice_credential_with_key_and_signer.signer,
from_ref(charlie_key_package.key_package()),
)
.expect("Error creating self-update")
.tls_serialize_detached()
.expect("Could not serialize message.");
let plaintext = MlsMessageIn::tls_deserialize(&mut serialized_update.as_slice())
.expect("Could not deserialize message.")
.into_plaintext()
.expect("Message was not a plaintext.");
let original_plaintext = plaintext.clone();
let dave_provider = &Provider::default();
let (dave_credential_with_key_and_signer, dave_key_package) =
generate_credential_with_key_and_key_package("Dave".into(), ciphersuite, dave_provider);
let mut franken_key_package = FrankenKeyPackage::from(dave_key_package);
franken_key_package.init_key = charlie_key_package
.key_package()
.hpke_init_key()
.as_slice()
.to_owned()
.into();
franken_key_package.resign(&dave_credential_with_key_and_signer.signer);
let dave_key_package = KeyPackage::from(franken_key_package);
let second_add_proposal = Proposal::add(AddProposal {
key_package: dave_key_package,
});
let verifiable_plaintext = insert_proposal_and_resign(
alice_provider,
ciphersuite,
vec![ProposalOrRef::proposal(second_add_proposal)],
plaintext,
&original_plaintext,
&alice_group,
&alice_credential_with_key_and_signer.signer,
);
let update_message_in = ProtocolMessage::from(verifiable_plaintext);
let err = bob_group
.process_message(bob_provider, update_message_in)
.expect_err("Could process message despite modified encryption key in path.");
assert!(matches!(
err,
ProcessMessageError::InvalidCommit(StageCommitError::ProposalValidationError(
ProposalValidationError::DuplicateInitKey
))
));
let original_update_plaintext =
MlsMessageIn::tls_deserialize(&mut serialized_update.as_slice())
.expect("Could not deserialize message.");
bob_group
.process_message(
bob_provider,
original_update_plaintext
.try_into_protocol_message()
.unwrap(),
)
.expect("Unexpected error.");
}
#[openmls_test::openmls_test]
fn test_valsem101b() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
for alice_and_bob_share_keys in [
KeyUniqueness::NegativeSameKey,
KeyUniqueness::PositiveDifferentKey,
KeyUniqueness::PositiveSameKeyWithRemove,
] {
let new_kp = || {
openmls_basic_credential::SignatureKeyPair::new(ciphersuite.signature_algorithm())
.unwrap()
};
let shared_signature_keypair = new_kp();
let [alice_credential_with_key, bob_credential_with_key, target_credential_with_key] =
match alice_and_bob_share_keys {
KeyUniqueness::NegativeSameKey => [
("Alice", shared_signature_keypair.clone()),
("Bob", new_kp()),
("Charlie", shared_signature_keypair.clone()),
],
KeyUniqueness::PositiveDifferentKey => [
("Alice", new_kp()),
("Bob", new_kp()),
("Charlie", new_kp()),
],
KeyUniqueness::PositiveSameKeyWithRemove => [
("Alice", new_kp()),
("Bob", shared_signature_keypair.clone()),
("Charlie", shared_signature_keypair.clone()),
],
}
.map(|(name, keypair)| CredentialWithKeyAndSigner {
credential_with_key: CredentialWithKey {
credential: BasicCredential::new(name.into()).into(),
signature_key: keypair.to_public_vec().into(),
},
signer: keypair,
});
let bob_key_package = generate_key_package(
ciphersuite,
Extensions::empty(),
bob_provider,
bob_credential_with_key.clone(),
);
let target_provider = &Provider::default();
let target_key_package = generate_key_package(
ciphersuite,
Extensions::empty(),
target_provider,
target_credential_with_key.clone(),
);
let mut alice_group = MlsGroup::new_with_group_id(
alice_provider,
&alice_credential_with_key.signer,
&MlsGroupCreateConfig::builder()
.ciphersuite(ciphersuite)
.build(),
GroupId::random(alice_provider.rand()),
alice_credential_with_key.credential_with_key.clone(),
)
.unwrap();
match alice_and_bob_share_keys {
KeyUniqueness::NegativeSameKey => {
let err = alice_group
.add_members(
alice_provider,
&alice_credential_with_key.signer,
&[
bob_key_package.key_package().clone(),
target_key_package.key_package().clone(),
],
)
.expect_err("was able to add user with same signature key as a group member!");
assert!(matches!(
err,
AddMembersError::CreateCommitError(CreateCommitError::ProposalValidationError(
ProposalValidationError::DuplicateSignatureKey
))
));
}
KeyUniqueness::PositiveDifferentKey => {
alice_group
.add_members(
alice_provider,
&alice_credential_with_key.signer,
&[
bob_key_package.key_package().clone(),
target_key_package.key_package().clone(),
],
)
.expect("failed to add user with different signature keypair!");
}
KeyUniqueness::PositiveSameKeyWithRemove => {
alice_group
.add_members(
alice_provider,
&alice_credential_with_key.signer,
from_ref(bob_key_package.key_package()),
)
.unwrap();
alice_group.merge_pending_commit(alice_provider).unwrap();
let bob_index = alice_group
.members()
.find_map(|member| {
if member.credential.serialized_content() == b"Bob" {
Some(member.index)
} else {
None
}
})
.unwrap();
alice_group
.propose_remove_member(
alice_provider,
&alice_credential_with_key.signer,
bob_index,
)
.unwrap();
alice_group
.add_members(alice_provider, &alice_credential_with_key.signer, from_ref(target_key_package.key_package()))
.expect(
"failed to add a user with the same identity as someone in the group (with a remove proposal)!",
);
}
}
}
}
#[openmls_test::openmls_test]
fn test_valsem103_valsem104() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
for alice_and_bob_share_keys in [
KeyUniqueness::NegativeSameKey,
KeyUniqueness::PositiveDifferentKey,
] {
let (alice_credential_with_key, _) = generate_credential_with_key_and_key_package(
"Alice".into(),
ciphersuite,
alice_provider,
);
let (bob_credential_with_key, bob_key_package) =
generate_credential_with_key_and_key_package("Bob".into(), ciphersuite, bob_provider);
let bob_key_package = match alice_and_bob_share_keys {
KeyUniqueness::NegativeSameKey => {
let mut franken_key_package = FrankenKeyPackage::from(bob_key_package);
franken_key_package.init_key = franken_key_package.leaf_node.encryption_key.clone();
franken_key_package.resign(&bob_credential_with_key.signer);
KeyPackage::from(franken_key_package)
}
KeyUniqueness::PositiveDifferentKey => {
bob_key_package.key_package().clone()
}
KeyUniqueness::PositiveSameKeyWithRemove => unreachable!(),
};
let res = create_group_with_members(
ciphersuite,
&alice_credential_with_key,
&[bob_key_package],
alice_provider,
);
match alice_and_bob_share_keys {
KeyUniqueness::NegativeSameKey => {
let err =
res.expect_err("was able to add user with colliding init and encryption keys!");
assert!(matches!(
err,
AddMembersError::CreateCommitError(CreateCommitError::ProposalValidationError(
ProposalValidationError::InitEncryptionKeyCollision
))
));
}
KeyUniqueness::PositiveDifferentKey => {
let _ = res.expect("failed to add user with different HPKE init key!");
}
KeyUniqueness::PositiveSameKeyWithRemove => unreachable!(),
}
}
let ProposalValidationTestSetup {
mut alice_group,
alice_credential_with_key_and_signer,
mut bob_group,
..
} = validation_test_setup(
PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
ciphersuite,
alice_provider,
bob_provider,
);
let serialized_update = alice_group
.self_update(
alice_provider,
&alice_credential_with_key_and_signer.signer,
LeafNodeParameters::default(),
)
.expect("Error creating self-update")
.into_contents()
.tls_serialize_detached()
.expect("Could not serialize message.");
let plaintext = MlsMessageIn::tls_deserialize(&mut serialized_update.as_slice())
.expect("Could not deserialize message.")
.into_plaintext()
.expect("Message was not a plaintext.");
let original_plaintext = plaintext.clone();
let bob_encryption_key = bob_group
.own_leaf_node()
.expect("No own leaf")
.encryption_key()
.clone();
let dave_provider = &Provider::default();
let (dave_credential_with_key, dave_key_package) =
generate_credential_with_key_and_key_package("Dave".into(), ciphersuite, dave_provider);
let mut franken_key_package = FrankenKeyPackage::from(dave_key_package);
franken_key_package.leaf_node.encryption_key = bob_encryption_key.as_slice().to_owned().into();
franken_key_package.resign(&dave_credential_with_key.signer);
let dave_key_package = KeyPackage::from(franken_key_package);
let add_proposal = Proposal::add(AddProposal {
key_package: dave_key_package,
});
let verifiable_plaintext = insert_proposal_and_resign(
dave_provider,
ciphersuite,
vec![ProposalOrRef::proposal(add_proposal)],
plaintext,
&original_plaintext,
&alice_group,
&alice_credential_with_key_and_signer.signer,
);
let update_message_in = ProtocolMessage::from(verifiable_plaintext);
let err = bob_group
.process_message(bob_provider, update_message_in)
.expect_err("Could process message despite modified public key in path.");
assert!(matches!(
err,
ProcessMessageError::InvalidCommit(StageCommitError::ProposalValidationError(
ProposalValidationError::DuplicateEncryptionKey
))
));
let original_update_plaintext =
MlsMessageIn::tls_deserialize(&mut serialized_update.as_slice())
.expect("Could not deserialize message.");
bob_group
.process_message(
bob_provider,
original_update_plaintext
.try_into_protocol_message()
.unwrap(),
)
.expect("Unexpected error.");
}
#[derive(Debug)]
enum KeyPackageTestVersion {
WrongCiphersuite,
WrongVersion,
UnsupportedVersion,
UnsupportedCiphersuite,
ValidTestCase,
}
enum ProposalInclusion {
ByValue,
ByReference,
}
#[openmls_test::openmls_test]
fn test_valsem105() {
let _ = pretty_env_logger::try_init();
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let charlie_provider = &Provider::default();
for key_package_version in [
KeyPackageTestVersion::WrongCiphersuite,
KeyPackageTestVersion::WrongVersion,
KeyPackageTestVersion::UnsupportedVersion,
KeyPackageTestVersion::UnsupportedCiphersuite,
KeyPackageTestVersion::ValidTestCase,
] {
println!("# running test {key_package_version:?}");
let ProposalValidationTestSetup {
mut alice_group,
alice_credential_with_key_and_signer,
mut bob_group,
..
} = validation_test_setup(
PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
ciphersuite,
alice_provider,
bob_provider,
);
let (charlie_credential_with_key, charlie_key_package) =
generate_credential_with_key_and_key_package(
"Charlie".into(),
ciphersuite,
charlie_provider,
);
let mut franken_key_package = FrankenKeyPackage::from(charlie_key_package.clone());
let kpi = KeyPackageIn::from(charlie_key_package.clone());
kpi.validate(charlie_provider.crypto(), ProtocolVersion::Mls10)
.unwrap();
let wrong_ciphersuite = match ciphersuite {
Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 => {
Ciphersuite::MLS_128_DHKEMP256_AES128GCM_SHA256_P256
}
_ => Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519,
} as u16;
match key_package_version {
KeyPackageTestVersion::WrongCiphersuite => {
franken_key_package.ciphersuite = wrong_ciphersuite;
}
KeyPackageTestVersion::WrongVersion => {
franken_key_package.protocol_version = 999;
}
KeyPackageTestVersion::UnsupportedVersion => {
franken_key_package.leaf_node.capabilities.versions = vec![999];
}
KeyPackageTestVersion::UnsupportedCiphersuite => {
franken_key_package.leaf_node.capabilities.ciphersuites =
vec![Ciphersuite::MLS_256_DHKEMX448_CHACHA20POLY1305_SHA512_Ed448.into()];
}
KeyPackageTestVersion::ValidTestCase => (),
};
franken_key_package.resign(&charlie_credential_with_key.signer);
let test_kp = KeyPackage::from(franken_key_package);
let test_kp_2 = {
let (charlie_credential_with_key, charlie_key_package) =
generate_credential_with_key_and_key_package(
"Charlie".into(),
ciphersuite,
charlie_provider,
);
let mut franken_key_package = FrankenKeyPackage::from(charlie_key_package.clone());
let wrong_ciphersuite = match ciphersuite {
Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 => {
Ciphersuite::MLS_128_DHKEMP256_AES128GCM_SHA256_P256
}
_ => Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519,
} as u16;
match key_package_version {
KeyPackageTestVersion::WrongCiphersuite => {
franken_key_package.ciphersuite = wrong_ciphersuite;
}
KeyPackageTestVersion::WrongVersion => {
franken_key_package.protocol_version = 999;
}
KeyPackageTestVersion::UnsupportedVersion => {
franken_key_package.leaf_node.capabilities.versions = vec![999];
}
KeyPackageTestVersion::UnsupportedCiphersuite => {
franken_key_package.leaf_node.capabilities.ciphersuites =
vec![Ciphersuite::MLS_256_DHKEMX448_CHACHA20POLY1305_SHA512_Ed448.into()];
}
KeyPackageTestVersion::ValidTestCase => (),
};
franken_key_package.resign(&charlie_credential_with_key.signer);
KeyPackage::from(franken_key_package)
};
for proposal_inclusion in [ProposalInclusion::ByReference, ProposalInclusion::ByValue] {
match proposal_inclusion {
ProposalInclusion::ByReference => {
let _proposal = alice_group
.propose_add_member(
alice_provider,
&alice_credential_with_key_and_signer.signer,
&test_kp,
)
.unwrap();
let result = alice_group.commit_to_pending_proposals(
alice_provider,
&alice_credential_with_key_and_signer.signer,
);
match key_package_version {
KeyPackageTestVersion::ValidTestCase => {
result.unwrap();
}
_ => {
matches!(
result.unwrap_err(),
CommitToPendingProposalsError::CreateCommitError(_)
);
}
}
}
ProposalInclusion::ByValue => {
let result = alice_group.add_members(
alice_provider,
&alice_credential_with_key_and_signer.signer,
from_ref(&test_kp_2),
);
match key_package_version {
KeyPackageTestVersion::ValidTestCase => {
result.unwrap();
}
_ => {
matches!(result.unwrap_err(), AddMembersError::CreateCommitError(_));
}
}
}
};
alice_group
.clear_pending_commit(alice_provider.storage())
.unwrap();
}
alice_group
.clear_pending_proposals(alice_provider.storage())
.unwrap();
let serialized_update = alice_group
.self_update(
alice_provider,
&alice_credential_with_key_and_signer.signer,
LeafNodeParameters::default(),
)
.unwrap()
.into_messages()
.tls_serialize_detached()
.unwrap();
let plaintext = MlsMessageIn::tls_deserialize(&mut serialized_update.as_slice())
.unwrap()
.into_plaintext()
.unwrap();
let original_plaintext = plaintext.clone();
let add_proposal = Proposal::add(AddProposal {
key_package: test_kp,
});
for proposal_inclusion in [ProposalInclusion::ByValue, ProposalInclusion::ByReference] {
let proposal_or_ref = match proposal_inclusion {
ProposalInclusion::ByValue => ProposalOrRef::proposal(add_proposal.clone()),
ProposalInclusion::ByReference => ProposalOrRef::reference(
ProposalRef::from_raw_proposal(
ciphersuite,
alice_provider.crypto(),
&add_proposal,
)
.unwrap(),
),
};
let verifiable_plaintext = insert_proposal_and_resign(
alice_provider,
ciphersuite,
vec![proposal_or_ref],
plaintext.clone(),
&original_plaintext,
&alice_group,
&alice_credential_with_key_and_signer.signer,
);
let update_message_in = ProtocolMessage::from(verifiable_plaintext);
if matches!(proposal_inclusion, ProposalInclusion::ByReference) {
bob_group
.store_pending_proposal(
bob_provider.storage(),
QueuedProposal::from_proposal_and_sender(
ciphersuite,
bob_provider.crypto(),
add_proposal.clone(),
&Sender::build_member(alice_group.own_leaf_index()),
)
.unwrap(),
)
.unwrap()
}
let err = bob_group
.process_message(bob_provider, update_message_in)
.expect_err("Could process message despite injected add proposal.");
match key_package_version {
KeyPackageTestVersion::ValidTestCase => {
assert!(matches!(
err,
ProcessMessageError::InvalidCommit(StageCommitError::UpdatePathError(
ApplyUpdatePathError::PathLengthMismatch,
),)
));
}
KeyPackageTestVersion::WrongCiphersuite => {
assert!(
matches!(
err,
ProcessMessageError::InvalidCommit(
StageCommitError::ProposalValidationError(
ProposalValidationError::InvalidAddProposalCiphersuiteOrVersion,
),
)
) || matches!(
err,
ProcessMessageError::ValidationError(
ValidationError::KeyPackageVerifyError(
KeyPackageVerifyError::InvalidLeafNodeSignature,
),
)
) || matches!(
err,
ProcessMessageError::ValidationError(
ValidationError::InvalidAddProposalCiphersuite,
)
)
);
}
KeyPackageTestVersion::WrongVersion => {
assert!(
matches!(
err,
ProcessMessageError::InvalidCommit(
StageCommitError::ProposalValidationError(
ProposalValidationError::InvalidAddProposalCiphersuiteOrVersion,
),
)
) || matches!(
err,
ProcessMessageError::ValidationError(
ValidationError::KeyPackageVerifyError(
KeyPackageVerifyError::InvalidProtocolVersion,
),
)
)
);
}
KeyPackageTestVersion::UnsupportedVersion => {
assert!(
matches!(
err,
ProcessMessageError::ValidationError(
ValidationError::KeyPackageVerifyError(
KeyPackageVerifyError::InvalidProtocolVersion,
),
)
) || matches!(
err,
ProcessMessageError::InvalidCommit(
StageCommitError::ProposalValidationError(
ProposalValidationError::LeafNodeValidation(
LeafNodeValidationError::CiphersuiteNotInCapabilities
),
),
)
),
"unexpected error: {err:?}"
);
}
KeyPackageTestVersion::UnsupportedCiphersuite => {
assert!(
matches!(
err,
ProcessMessageError::InvalidCommit(
StageCommitError::ProposalValidationError(
ProposalValidationError::LeafNodeValidation(
LeafNodeValidationError::CiphersuiteNotInCapabilities
),
),
)
),
"unexpected error: {err:?}"
);
}
};
let original_update_plaintext =
MlsMessageIn::tls_deserialize(&mut serialized_update.as_slice())
.expect("Could not deserialize message.");
bob_group
.process_message(
bob_provider,
original_update_plaintext
.try_into_protocol_message()
.unwrap(),
)
.unwrap();
}
alice_group
.clear_pending_commit(alice_provider.storage())
.unwrap();
}
}
#[openmls_test::openmls_test]
fn test_valsem107() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
fn unwrap_specific_commit(commit_ref_remove: MlsMessageOut) -> Commit {
let serialized_message = commit_ref_remove.tls_serialize_detached().unwrap();
let plaintext = MlsMessageIn::tls_deserialize(&mut serialized_message.as_slice())
.unwrap()
.into_plaintext()
.unwrap();
let commit_content = if let FramedContentBody::Commit(commit) = plaintext.content() {
commit.clone()
} else {
panic!("Unexpected content type.");
};
assert_eq!(commit_content.proposals.len(), 1);
*commit_content
}
let ProposalValidationTestSetup {
mut alice_group,
alice_credential_with_key_and_signer,
bob_group,
..
} = validation_test_setup(
PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
ciphersuite,
alice_provider,
bob_provider,
);
let bob_leaf_index = bob_group.own_leaf_index();
let ref_propose = {
let (ref_propose1, _) = alice_group
.propose_remove_member(
alice_provider,
&alice_credential_with_key_and_signer.signer,
bob_leaf_index,
)
.unwrap();
let (ref_propose2, _) = alice_group
.propose_remove_member(
alice_provider,
&alice_credential_with_key_and_signer.signer,
bob_leaf_index,
)
.unwrap();
assert_eq!(ref_propose1, ref_propose2);
ref_propose1
};
let (commit_ref_remove, _welcome, _group_info) = alice_group
.commit_to_pending_proposals(alice_provider, &alice_credential_with_key_and_signer.signer)
.expect("error while trying to commit to colliding remove proposals");
alice_group
.clear_pending_commit(alice_provider.storage())
.unwrap();
let (commit_inline_remove, _welcome, _group_info) = alice_group
.remove_members(
alice_provider,
&alice_credential_with_key_and_signer.signer,
&[bob_leaf_index, bob_leaf_index],
)
.expect("error while trying to remove the same member twice");
{
let commit_content = unwrap_specific_commit(commit_ref_remove);
let expected = {
let mls_message_in = MlsMessageIn::from(ref_propose);
let authenticated_content = match mls_message_in.body {
MlsMessageBodyIn::PublicMessage(ref public) => AuthenticatedContent::new(
mls_message_in.wire_format(),
FramedContent::from(public.content.clone()),
public.auth.clone(),
),
_ => panic!(),
};
ProposalOrRef::reference(
ProposalRef::from_authenticated_content_by_ref(
alice_provider.crypto(),
ciphersuite,
&authenticated_content,
)
.unwrap(),
)
};
let got = commit_content
.proposals
.as_slice()
.last()
.expect("expected remove proposal");
assert_eq!(expected, *got);
}
{
let commit_content = unwrap_specific_commit(commit_inline_remove);
let expected = ProposalOrRef::proposal(Proposal::remove(RemoveProposal {
removed: bob_leaf_index,
}));
let got = commit_content
.proposals
.as_slice()
.last()
.expect("expected remove proposal");
assert_eq!(expected, *got);
}
}
#[openmls_test::openmls_test]
fn test_valsem108() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let ProposalValidationTestSetup {
mut alice_group,
alice_credential_with_key_and_signer,
mut bob_group,
..
} = validation_test_setup(
PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
ciphersuite,
alice_provider,
bob_provider,
);
let fake_leaf_index = LeafNodeIndex::new(9238754);
let _remove_proposal1 = alice_group
.propose_remove_member(
alice_provider,
&alice_credential_with_key_and_signer.signer,
fake_leaf_index,
)
.expect_err("Successfully created remove proposal for leaf not in the tree");
let _ = alice_group
.commit_to_pending_proposals(alice_provider, &alice_credential_with_key_and_signer.signer)
.expect("No error while committing empty proposals");
alice_group
.clear_pending_commit(alice_provider.storage())
.unwrap();
let err = alice_group
.propose_remove_member(
alice_provider,
&alice_credential_with_key_and_signer.signer,
fake_leaf_index,
)
.expect_err("Successfully created remove proposal for unknown member");
assert!(matches!(err, ProposeRemoveMemberError::UnknownMember));
alice_group
.clear_pending_commit(alice_provider.storage())
.unwrap();
alice_group
.clear_pending_proposals(alice_provider.storage())
.unwrap();
let err = alice_group
.remove_members(
alice_provider,
&alice_credential_with_key_and_signer.signer,
&[fake_leaf_index],
)
.expect_err("no error while trying to remove non-group-member");
assert!(matches!(
err,
RemoveMembersError::CreateCommitError(CreateCommitError::ProposalValidationError(
ProposalValidationError::UnknownMemberRemoval
))
));
let serialized_update = alice_group
.self_update(
alice_provider,
&alice_credential_with_key_and_signer.signer,
LeafNodeParameters::default(),
)
.expect("Error creating self-update")
.into_messages()
.tls_serialize_detached()
.expect("Could not serialize message.");
let plaintext = MlsMessageIn::tls_deserialize(&mut serialized_update.as_slice())
.expect("Could not deserialize message.")
.into_plaintext()
.expect("Message was not a plaintext.");
let original_plaintext = plaintext.clone();
let remove_proposal = Proposal::remove(RemoveProposal {
removed: LeafNodeIndex::new(987),
});
let verifiable_plaintext = insert_proposal_and_resign(
alice_provider,
ciphersuite,
vec![ProposalOrRef::proposal(remove_proposal)],
plaintext,
&original_plaintext,
&alice_group,
&alice_credential_with_key_and_signer.signer,
);
let update_message_in = ProtocolMessage::from(verifiable_plaintext);
let err = bob_group
.process_message(bob_provider, update_message_in)
.expect_err("Could process message despite modified public key in path.");
assert!(matches!(
err,
ProcessMessageError::InvalidCommit(StageCommitError::ProposalValidationError(
ProposalValidationError::UnknownMemberRemoval
))
));
let original_update_plaintext =
MlsMessageIn::tls_deserialize(&mut serialized_update.as_slice())
.expect("Could not deserialize message.");
bob_group
.process_message(
bob_provider,
original_update_plaintext
.try_into_protocol_message()
.unwrap(),
)
.expect("Unexpected error.");
}
#[openmls_test::openmls_test]
fn test_valsem110() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let ProposalValidationTestSetup {
mut alice_group,
alice_credential_with_key_and_signer,
mut bob_group,
bob_credential_with_key_and_signer,
} = validation_test_setup(
PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
ciphersuite,
alice_provider,
bob_provider,
);
let bob_leaf_node = bob_group
.own_leaf_node()
.expect("error getting own leaf node")
.clone();
let alice_encryption_key = alice_group
.own_leaf_node()
.unwrap()
.encryption_key()
.clone();
let update_proposal: MlsMessageIn = bob_group
.propose_self_update(
bob_provider,
&bob_credential_with_key_and_signer.signer,
LeafNodeParameters::default(),
)
.map(|(out, _)| MlsMessageIn::from(out))
.expect("error while creating remove proposal");
let mut franken_leaf_node = FrankenLeafNode::from(bob_leaf_node.clone());
franken_leaf_node.encryption_key = alice_encryption_key.key().clone();
franken_leaf_node.leaf_node_source = FrankenLeafNodeSource::Update;
franken_leaf_node.resign(
Some(bob_group.own_tree_position().into()),
&bob_credential_with_key_and_signer.signer,
);
let franken_message_in = FrankenMlsMessage::from(MlsMessageOut::from(update_proposal.clone()));
let mut content =
if let FrankenMlsMessageBody::PublicMessage(public_message) = franken_message_in.body {
public_message.content
} else {
panic!("Unexpected message type");
};
match content.body {
FrankenFramedContentBody::Proposal(FrankenProposal::Update(ref mut update)) => {
let update_proposal = FrankenUpdateProposal {
leaf_node: franken_leaf_node.clone(),
};
*update = update_proposal;
}
_ => {
panic!("Unexpected content type");
}
}
let group_context = bob_group.export_group_context().clone();
let membership_key = bob_group.message_secrets().membership_key().as_slice();
let new_public_message = FrankenPublicMessage::auth(
bob_provider,
ciphersuite,
&bob_credential_with_key_and_signer.signer,
content,
Some(&group_context.into()),
Some(membership_key),
None,
);
let protocol_message = ProtocolMessage::from(PublicMessage::from(new_public_message));
if let ProcessedMessageContent::ProposalMessage(proposal) = alice_group
.process_message(alice_provider, protocol_message)
.expect("error processing proposal")
.into_content()
{
alice_group
.store_pending_proposal(alice_provider.storage(), *proposal)
.unwrap()
} else {
panic!("Unexpected message type");
};
let err = alice_group
.commit_to_pending_proposals(alice_provider, &alice_credential_with_key_and_signer.signer)
.expect_err("no error while trying to commit to update proposal with differing identity");
assert!(matches!(
err,
CommitToPendingProposalsError::CreateCommitError(
CreateCommitError::ProposalValidationError(
ProposalValidationError::DuplicateEncryptionKey
)
)
));
alice_group
.clear_pending_commit(alice_provider.storage())
.unwrap();
alice_group
.clear_pending_proposals(alice_provider.storage())
.unwrap();
let serialized_update = alice_group
.self_update(
alice_provider,
&alice_credential_with_key_and_signer.signer,
LeafNodeParameters::default(),
)
.expect("Error creating self-update")
.into_contents()
.tls_serialize_detached()
.expect("Could not serialize message.");
let plaintext = MlsMessageIn::tls_deserialize(&mut serialized_update.as_slice())
.expect("Could not deserialize message.")
.into_plaintext()
.expect("Message was not a plaintext.");
let original_plaintext = plaintext.clone();
let update_proposal = Proposal::update(UpdateProposal {
leaf_node: franken_leaf_node.into(),
});
let verifiable_plaintext = insert_proposal_and_resign(
alice_provider,
ciphersuite,
vec![ProposalOrRef::proposal(update_proposal)],
plaintext,
&original_plaintext,
&alice_group,
&alice_credential_with_key_and_signer.signer,
);
let update_message_in = ProtocolMessage::from(verifiable_plaintext);
let leaf_keypair = alice_group
.read_epoch_keypairs(alice_provider.storage())
.unwrap()
.into_iter()
.find(|keypair| keypair.public_key() == &alice_encryption_key)
.unwrap();
leaf_keypair.write(alice_provider.storage()).unwrap();
let err = bob_group
.process_message(bob_provider, update_message_in)
.expect_err("Could process message despite modified public key in path.");
assert!(matches!(
err,
ProcessMessageError::ValidationError(ValidationError::CommitterIncludedOwnUpdate)
));
}
#[openmls_test::openmls_test]
fn test_valsem111() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let ProposalValidationTestSetup {
mut alice_group,
alice_credential_with_key_and_signer,
mut bob_group,
..
} = validation_test_setup(
PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
ciphersuite,
alice_provider,
bob_provider,
);
let update_kp = generate_key_package(
ciphersuite,
Extensions::empty(),
alice_provider,
alice_credential_with_key_and_signer.clone(),
);
let update_proposal = Proposal::update(UpdateProposal {
leaf_node: update_kp.key_package().leaf_node().clone(),
});
let commit_bundle = alice_group
.self_update(
alice_provider,
&alice_credential_with_key_and_signer.signer,
LeafNodeParameters::default(),
)
.expect("Error creating self-update");
let (msg, welcome, group_info) = commit_bundle.contents();
let serialized_message = (msg, welcome.cloned(), group_info.cloned())
.tls_serialize_detached()
.expect("error serializing plaintext");
let plaintext = MlsMessageIn::tls_deserialize(&mut serialized_message.as_slice())
.expect("Could not deserialize message.")
.into_plaintext()
.expect("Message was not a plaintext.");
let commit_content = if let FramedContentBody::Commit(commit) = plaintext.content() {
commit.clone()
} else {
panic!("Unexpected content type.");
};
assert_eq!(commit_content.proposals.len(), 0);
let (msg, welcome, group_info) = commit_bundle.contents();
let serialized_update = (msg, welcome.cloned(), group_info.cloned())
.tls_serialize_detached()
.expect("Could not serialize message.");
let plaintext = MlsMessageIn::tls_deserialize(&mut serialized_update.as_slice())
.expect("Could not deserialize message.")
.into_plaintext()
.expect("Message was not a plaintext.");
let original_plaintext = plaintext.clone();
let verifiable_plaintext = insert_proposal_and_resign(
alice_provider,
ciphersuite,
vec![ProposalOrRef::proposal(update_proposal.clone())],
plaintext,
&original_plaintext,
&alice_group,
&alice_credential_with_key_and_signer.signer,
);
let update_message_in = ProtocolMessage::from(verifiable_plaintext);
let err = bob_group
.process_message(bob_provider, update_message_in)
.expect_err("Could process message despite modified public key in path.");
assert!(matches!(
err,
ProcessMessageError::ValidationError(ValidationError::CommitterIncludedOwnUpdate)
));
bob_group
.store_pending_proposal(
bob_provider.storage(),
QueuedProposal::from_proposal_and_sender(
ciphersuite,
bob_provider.crypto(),
update_proposal.clone(),
&Sender::build_member(alice_group.own_leaf_index()),
)
.expect("error creating queued proposal"),
)
.expect("error writing to storage");
alice_group
.clear_pending_commit(alice_provider.storage())
.unwrap();
let commit = alice_group
.self_update(
alice_provider,
&alice_credential_with_key_and_signer.signer,
LeafNodeParameters::default(),
)
.expect("Error creating self-update");
let serialized_update = commit
.into_contents()
.tls_serialize_detached()
.expect("Could not serialize message.");
let plaintext = MlsMessageIn::tls_deserialize(&mut serialized_update.as_slice())
.expect("Could not deserialize message.")
.into_plaintext()
.expect("Message was not a plaintext.");
let original_plaintext = plaintext.clone();
let verifiable_plaintext = insert_proposal_and_resign(
alice_provider,
ciphersuite,
vec![ProposalOrRef::reference(
ProposalRef::from_raw_proposal(ciphersuite, alice_provider.crypto(), &update_proposal)
.expect("error creating hash reference"),
)],
plaintext,
&original_plaintext,
&alice_group,
&alice_credential_with_key_and_signer.signer,
);
let update_message_in = ProtocolMessage::from(verifiable_plaintext);
let err = bob_group
.process_message(bob_provider, update_message_in)
.expect_err("Could process message despite modified public key in path.");
assert!(matches!(
err,
ProcessMessageError::InvalidCommit(StageCommitError::ProposalValidationError(
ProposalValidationError::CommitterIncludedOwnUpdate
))
));
let original_update_plaintext =
MlsMessageIn::tls_deserialize(&mut serialized_update.as_slice())
.expect("Could not deserialize message.");
bob_group
.process_message(
bob_provider,
original_update_plaintext
.try_into_protocol_message()
.unwrap(),
)
.expect("Unexpected error.");
}
#[openmls_test::openmls_test]
fn test_valsem112() {
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let ProposalValidationTestSetup {
mut alice_group,
alice_credential_with_key_and_signer,
mut bob_group,
..
} = validation_test_setup(
PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
ciphersuite,
alice_provider,
bob_provider,
);
let commit = alice_group
.propose_self_update(
alice_provider,
&alice_credential_with_key_and_signer.signer,
LeafNodeParameters::default(),
)
.expect("Error creating self-update");
let serialized_update = commit
.tls_serialize_detached()
.expect("error serializing plaintext");
let mut plaintext = MlsMessageIn::tls_deserialize(&mut serialized_update.as_slice())
.expect("Could not deserialize message.")
.into_plaintext()
.expect("Message was not a plaintext.");
assert!(plaintext.sender().is_member());
let original_plaintext = plaintext.clone();
plaintext.set_sender(Sender::NewMemberCommit);
let update_message_in = ProtocolMessage::from(plaintext.clone());
let err = bob_group
.process_message(bob_provider, update_message_in)
.expect_err("Could parse message despite modified public key in path.");
assert!(matches!(
err,
ProcessMessageError::ValidationError(ValidationError::NotACommit)
));
bob_group
.process_message(bob_provider, ProtocolMessage::from(original_plaintext))
.expect("Unexpected error.");
}
#[openmls_test::openmls_test]
fn valsem113() {
#[derive(Debug)]
enum TestMode {
Unsupported,
Supported,
}
let alice_provider = &Provider::default();
let bob_provider = &Provider::default();
let custom_proposal_type = 0xFFFF;
let custom_proposal_payload = vec![0, 1, 2, 3];
let custom_proposal =
CustomProposal::new(custom_proposal_type, custom_proposal_payload.clone());
let capabilities_with_support = Capabilities::new(
None,
None,
None,
Some(&[ProposalType::Custom(custom_proposal_type)]),
None,
);
let mls_group_config = MlsGroupJoinConfig::default();
let alice_credential_with_keys = generate_credential_with_key(
b"alice".into(),
ciphersuite.signature_algorithm(),
alice_provider,
);
let bob_credential_with_keys = generate_credential_with_key(
b"bob".into(),
ciphersuite.signature_algorithm(),
bob_provider,
);
for test_mode in [TestMode::Unsupported, TestMode::Supported] {
let bob_key_package = if matches!(test_mode, TestMode::Unsupported) {
KeyPackageBuilder::new()
} else {
KeyPackageBuilder::new().leaf_node_capabilities(capabilities_with_support.clone())
}
.build(
ciphersuite,
bob_provider,
&bob_credential_with_keys.signer,
bob_credential_with_keys.credential_with_key.clone(),
)
.unwrap();
let mut alice_group = if matches!(test_mode, TestMode::Unsupported) {
MlsGroup::builder()
} else {
MlsGroup::builder().with_capabilities(capabilities_with_support.clone())
}
.ciphersuite(ciphersuite)
.build(
alice_provider,
&alice_credential_with_keys.signer,
alice_credential_with_keys.credential_with_key.clone(),
)
.unwrap();
let (_mls_message, welcome, _group_info) = alice_group
.add_members(
alice_provider,
&alice_credential_with_keys.signer,
from_ref(bob_key_package.key_package()),
)
.unwrap();
alice_group.merge_pending_commit(alice_provider).unwrap();
let staged_welcome = StagedWelcome::new_from_welcome(
bob_provider,
&mls_group_config,
welcome.into_welcome().unwrap(),
Some(alice_group.export_ratchet_tree().into()),
)
.unwrap();
let mut bob_group = staged_welcome.into_group(bob_provider).unwrap();
let (custom_proposal_message, _proposal_ref) = alice_group
.propose_custom_proposal_by_reference(
alice_provider,
&alice_credential_with_keys.signer,
custom_proposal.clone(),
)
.unwrap();
let result = alice_group
.commit_to_pending_proposals(alice_provider, &alice_credential_with_keys.signer);
let (commit, _, _) = if matches!(test_mode, TestMode::Unsupported) {
assert!(matches!(
result,
Err(CommitToPendingProposalsError::CreateCommitError(
CreateCommitError::ProposalValidationError(
ProposalValidationError::UnsupportedProposalType
)
))
));
continue;
} else {
result.expect("Error creating commit")
};
let processed_message = bob_group
.process_message(
bob_provider,
custom_proposal_message.into_protocol_message().unwrap(),
)
.unwrap();
if let ProcessedMessageContent::ProposalMessage(proposal) = processed_message.into_content()
{
bob_group
.store_pending_proposal(bob_provider.storage(), *proposal)
.unwrap();
} else {
panic!("Unexpected message type");
}
let result =
bob_group.process_message(bob_provider, commit.into_protocol_message().unwrap());
let _processed_message = if matches!(test_mode, TestMode::Unsupported) {
assert!(matches!(
result,
Err(ProcessMessageError::InvalidCommit(
StageCommitError::ProposalValidationError(
ProposalValidationError::UnsupportedProposalType
)
))
));
continue;
} else {
result.expect("Error processing commit")
};
}
}