use openmls_traits::signatures::Signer;
use crate::{
binary_tree::LeafNodeIndex,
group::past_secrets::MessageSecretsStore,
prelude::{tests_and_kats::utils::generate_credential_with_key, *},
schedule::message_secrets::MessageSecrets,
};
use std::time::Duration;
#[cfg(not(target_arch = "wasm32"))]
use std::time::{Instant, SystemTime};
#[cfg(target_arch = "wasm32")]
use web_time::{Instant, SystemTime};
const INTERVAL: Duration = Duration::from_millis(100);
fn setup<Provider: OpenMlsProvider + Default>(
ciphersuite: Ciphersuite,
policy: PastEpochDeletionPolicy,
) -> (Provider, impl Signer, MlsGroup) {
let alice_provider = Provider::default();
let alice_credential_with_keys = generate_credential_with_key(
b"Alice".to_vec(),
ciphersuite.signature_algorithm(),
&alice_provider,
);
let alice_signer = alice_credential_with_keys.signer;
let mls_group_create_config = MlsGroupCreateConfig::builder()
.set_past_epoch_deletion_policy(policy)
.ciphersuite(ciphersuite)
.build();
let alice_group = MlsGroup::new(
&alice_provider,
&alice_signer,
&mls_group_create_config,
alice_credential_with_keys.credential_with_key.clone(),
)
.expect("An unexpected error occurred.");
(alice_provider, alice_signer, alice_group)
}
fn apply_and_merge_commits<Provider: OpenMlsProvider>(
num_commits: usize,
provider: &Provider,
signer: &impl Signer,
group: &mut MlsGroup,
policy: PastEpochDeletionPolicy,
) {
for _ in 0..num_commits {
group
.update_group_context_extensions(provider, Extensions::empty(), signer)
.expect("error building commit");
group
.merge_pending_commit(provider)
.expect("error merging commit");
if let PastEpochDeletionPolicy::MaxEpochs(epochs) = policy {
assert!(group.message_secrets_store().num_past_epoch_trees() <= epochs);
} else {
assert_eq!(
group.message_secrets_store().num_past_epoch_trees(),
group.epoch().as_u64() as usize
);
}
}
}
#[openmls_test::openmls_test]
fn max_epochs_with_duration<Provider: crate::storage::OpenMlsProvider>(
ciphersuite: Ciphersuite,
provider: &Provider,
) {
let policy = PastEpochDeletionPolicy::MaxEpochs(2);
let (alice_provider, alice_signer, mut alice_group) =
setup::<Provider>(ciphersuite, policy.clone());
let start = Instant::now();
apply_and_merge_commits(4, &alice_provider, &alice_signer, &mut alice_group, policy);
std::thread::sleep(INTERVAL + start.elapsed());
alice_group
.delete_past_epoch_secrets(
&alice_provider,
PastEpochDeletion::older_than_duration(INTERVAL),
)
.expect("error deleting past epoch secrets");
assert_eq!(
alice_group.message_secrets_store().num_past_epoch_trees(),
0
);
let alice_group_stored = MlsGroup::load(alice_provider.storage(), alice_group.group_id())
.expect("error loading group")
.expect("no group for id");
assert_eq!(
alice_group_stored
.message_secrets_store()
.num_past_epoch_trees(),
0
);
}
#[openmls_test::openmls_test]
fn max_epochs_policy_with_timestamp<Provider: crate::storage::OpenMlsProvider>() {
let policy = PastEpochDeletionPolicy::MaxEpochs(2);
let (alice_provider, alice_signer, mut alice_group) =
setup::<Provider>(ciphersuite, policy.clone());
let start = SystemTime::now();
apply_and_merge_commits(4, &alice_provider, &alice_signer, &mut alice_group, policy);
alice_group
.delete_past_epoch_secrets(
&alice_provider,
PastEpochDeletion::before_timestamp(start).max_past_epochs(3),
)
.expect("error deleting past epoch secrets");
assert_eq!(
alice_group.message_secrets_store().num_past_epoch_trees(),
2
);
alice_group
.delete_past_epoch_secrets(
&alice_provider,
PastEpochDeletion::before_timestamp(start).max_past_epochs(1),
)
.expect("error deleting past epoch secrets");
assert_eq!(
alice_group.message_secrets_store().num_past_epoch_trees(),
1
);
alice_group
.delete_past_epoch_secrets(
&alice_provider,
PastEpochDeletion::before_timestamp(SystemTime::now()),
)
.expect("error deleting past epoch secrets");
assert_eq!(
alice_group.message_secrets_store().num_past_epoch_trees(),
0
);
let alice_group_stored = MlsGroup::load(alice_provider.storage(), alice_group.group_id())
.expect("error loading group")
.expect("no group for id");
assert_eq!(
alice_group_stored
.message_secrets_store()
.num_past_epoch_trees(),
0
);
}
#[openmls_test::openmls_test]
fn keep_all_policy_with_duration<Provider: crate::storage::OpenMlsProvider>(
ciphersuite: Ciphersuite,
provider: &Provider,
) {
let policy = PastEpochDeletionPolicy::KeepAll;
let (alice_provider, alice_signer, mut alice_group) =
setup::<Provider>(ciphersuite, policy.clone());
let start = Instant::now();
apply_and_merge_commits(4, &alice_provider, &alice_signer, &mut alice_group, policy);
std::thread::sleep(INTERVAL + start.elapsed());
alice_group
.delete_past_epoch_secrets(
&alice_provider,
PastEpochDeletion::older_than_duration(INTERVAL),
)
.expect("error deleting past epoch secrets");
assert_eq!(
alice_group.message_secrets_store().num_past_epoch_trees(),
0
);
let alice_group_stored = MlsGroup::load(alice_provider.storage(), alice_group.group_id())
.expect("error loading group")
.expect("no group for id");
assert_eq!(
alice_group_stored
.message_secrets_store()
.num_past_epoch_trees(),
0
);
}
#[openmls_test::openmls_test]
fn keep_all_policy_with_timestamp<Provider: crate::storage::OpenMlsProvider>(
ciphersuite: Ciphersuite,
provider: &Provider,
) {
let policy = PastEpochDeletionPolicy::KeepAll;
let (alice_provider, alice_signer, mut alice_group) =
setup::<Provider>(ciphersuite, policy.clone());
apply_and_merge_commits(4, &alice_provider, &alice_signer, &mut alice_group, policy);
alice_group
.delete_past_epoch_secrets(
&alice_provider,
PastEpochDeletion::before_timestamp(SystemTime::UNIX_EPOCH).max_past_epochs(5),
)
.expect("error deleting past epoch secrets");
assert_eq!(
alice_group.message_secrets_store().num_past_epoch_trees(),
4
);
alice_group
.delete_past_epoch_secrets(
&alice_provider,
PastEpochDeletion::before_timestamp(SystemTime::UNIX_EPOCH).max_past_epochs(3),
)
.expect("error deleting past epoch secrets");
assert_eq!(
alice_group.message_secrets_store().num_past_epoch_trees(),
3
);
alice_group
.delete_past_epoch_secrets(
&alice_provider,
PastEpochDeletion::before_timestamp(SystemTime::UNIX_EPOCH).max_past_epochs(1),
)
.expect("error deleting past epoch secrets");
assert_eq!(
alice_group.message_secrets_store().num_past_epoch_trees(),
1
);
alice_group
.delete_past_epoch_secrets(
&alice_provider,
PastEpochDeletion::before_timestamp(SystemTime::now()),
)
.expect("error deleting past epoch secrets");
assert_eq!(
alice_group.message_secrets_store().num_past_epoch_trees(),
0
);
let alice_group_stored = MlsGroup::load(alice_provider.storage(), alice_group.group_id())
.expect("error loading group")
.expect("no group for id");
assert_eq!(
alice_group_stored
.message_secrets_store()
.num_past_epoch_trees(),
0
);
}
#[openmls_test::openmls_test]
fn delete_all<Provider: crate::storage::OpenMlsProvider>() {
for policy in [
PastEpochDeletionPolicy::KeepAll,
PastEpochDeletionPolicy::MaxEpochs(2),
PastEpochDeletionPolicy::MaxEpochs(0),
] {
let (alice_provider, alice_signer, mut alice_group) =
setup::<Provider>(ciphersuite, policy.clone());
apply_and_merge_commits(4, &alice_provider, &alice_signer, &mut alice_group, policy);
alice_group
.delete_past_epoch_secrets(&alice_provider, PastEpochDeletion::delete_all())
.expect("error deleting past epoch secrets");
assert_eq!(
alice_group.message_secrets_store().num_past_epoch_trees(),
0
);
let alice_group_stored = MlsGroup::load(alice_provider.storage(), alice_group.group_id())
.expect("error loading group")
.expect("no group for id");
assert_eq!(
alice_group_stored
.message_secrets_store()
.num_past_epoch_trees(),
0
);
}
}
fn setup_tree_store_with_timestamps<Provider: OpenMlsProvider>(
ciphersuite: Ciphersuite,
provider: &Provider,
entries: &[Option<SystemTime>],
) -> MessageSecretsStore {
let mut message_secrets_store = MessageSecretsStore::new_with_secret(
&PastEpochDeletionPolicy::KeepAll,
MessageSecrets::random(ciphersuite, provider.rand(), LeafNodeIndex::new(0)),
);
for (epoch, timestamp) in entries.iter().enumerate() {
let message_secrets = MessageSecrets::random(
ciphersuite,
provider.rand(),
LeafNodeIndex::new(epoch as u32),
);
let message_secrets = match timestamp {
Some(timestamp) => message_secrets.with_timestamp(*timestamp),
None => message_secrets.without_timestamp(),
};
message_secrets_store.add_past_epoch_tree(epoch as u64, message_secrets, Vec::new());
}
message_secrets_store
}
#[openmls_test::openmls_test]
fn test_update_policy_persistence<Provider: OpenMlsProvider>(ciphersuite: Ciphersuite) {
let initial_policy = PastEpochDeletionPolicy::MaxEpochs(3);
let (alice_provider, _alice_signer, mut alice_group) =
setup::<Provider>(ciphersuite, initial_policy.clone());
let alice_group_stored = MlsGroup::load(alice_provider.storage(), alice_group.group_id())
.expect("error loading group")
.expect("no group for id");
assert_eq!(
alice_group_stored.past_epoch_deletion_policy(),
&initial_policy
);
for new_policy in [
PastEpochDeletionPolicy::KeepAll,
PastEpochDeletionPolicy::MaxEpochs(3),
PastEpochDeletionPolicy::MaxEpochs(2),
] {
alice_group
.set_past_epoch_deletion_policy(&alice_provider, new_policy.clone())
.unwrap();
let alice_group_stored = MlsGroup::load(alice_provider.storage(), alice_group.group_id())
.expect("error loading group")
.expect("no group for id");
assert_eq!(alice_group_stored.past_epoch_deletion_policy(), &new_policy);
}
}
#[openmls_test::openmls_test]
fn test_update_policy<Provider: OpenMlsProvider>(ciphersuite: Ciphersuite) {
let policy = PastEpochDeletionPolicy::MaxEpochs(3);
let (alice_provider, alice_signer, mut alice_group) =
setup::<Provider>(ciphersuite, policy.clone());
apply_and_merge_commits(4, &alice_provider, &alice_signer, &mut alice_group, policy);
assert_eq!(
alice_group.message_secrets_store().num_past_epoch_trees(),
3
);
let new_policy = PastEpochDeletionPolicy::MaxEpochs(5);
alice_group
.set_past_epoch_deletion_policy(&alice_provider, new_policy.clone())
.expect("error updating policy");
for _ in 0..10 {
alice_group
.update_group_context_extensions(&alice_provider, Extensions::empty(), &alice_signer)
.expect("error building commit");
alice_group
.merge_pending_commit(&alice_provider)
.expect("error merging commit");
}
assert_eq!(alice_group.past_epoch_deletion_policy(), &new_policy);
assert_eq!(
alice_group.message_secrets_store().num_past_epoch_trees(),
5
);
let new_policy = PastEpochDeletionPolicy::MaxEpochs(2);
alice_group
.set_past_epoch_deletion_policy(&alice_provider, new_policy.clone())
.expect("error updating policy");
assert_eq!(alice_group.past_epoch_deletion_policy(), &new_policy);
assert_eq!(
alice_group.message_secrets_store().num_past_epoch_trees(),
2
);
let new_policy = PastEpochDeletionPolicy::KeepAll;
alice_group
.set_past_epoch_deletion_policy(&alice_provider, new_policy.clone())
.expect("error updating policy");
for _ in 0..8 {
alice_group
.update_group_context_extensions(&alice_provider, Extensions::empty(), &alice_signer)
.expect("error building commit");
alice_group
.merge_pending_commit(&alice_provider)
.expect("error merging commit");
}
assert_eq!(alice_group.past_epoch_deletion_policy(), &new_policy);
assert_eq!(
alice_group.message_secrets_store().num_past_epoch_trees(),
10
);
alice_group
.delete_past_epoch_secrets(&alice_provider, PastEpochDeletion::delete_all())
.expect("error deleting past epoch secrets");
assert_eq!(
alice_group.message_secrets_store().num_past_epoch_trees(),
0
);
alice_group
.update_group_context_extensions(&alice_provider, Extensions::empty(), &alice_signer)
.expect("error building commit");
alice_group
.merge_pending_commit(&alice_provider)
.expect("error merging commit");
assert_eq!(
alice_group.message_secrets_store().num_past_epoch_trees(),
1
);
let new_policy = PastEpochDeletionPolicy::MaxEpochs(2);
alice_group
.set_past_epoch_deletion_policy(&alice_provider, new_policy.clone())
.expect("error updating policy");
assert_eq!(
alice_group.message_secrets_store().num_past_epoch_trees(),
1
);
let alice_group_stored = MlsGroup::load(alice_provider.storage(), alice_group.group_id())
.expect("error loading group")
.expect("no group for id");
assert_eq!(
alice_group_stored
.message_secrets_store()
.num_past_epoch_trees(),
1
);
assert_eq!(alice_group_stored.past_epoch_deletion_policy(), &new_policy);
}
#[openmls_test::openmls_test]
fn test_update_policy_via_set_configuration<Provider: OpenMlsProvider>(ciphersuite: Ciphersuite) {
let policy = PastEpochDeletionPolicy::MaxEpochs(3);
let (alice_provider, alice_signer, mut alice_group) =
setup::<Provider>(ciphersuite, policy.clone());
apply_and_merge_commits(4, &alice_provider, &alice_signer, &mut alice_group, policy);
assert_eq!(
alice_group.message_secrets_store().num_past_epoch_trees(),
3
);
let new_policy = PastEpochDeletionPolicy::MaxEpochs(5);
let mut new_config = alice_group.configuration().clone();
new_config.past_epoch_deletion_policy = new_policy.clone();
alice_group
.set_configuration(alice_provider.storage(), &new_config)
.expect("error updating configuration");
for _ in 0..10 {
alice_group
.update_group_context_extensions(&alice_provider, Extensions::empty(), &alice_signer)
.expect("error building commit");
alice_group
.merge_pending_commit(&alice_provider)
.expect("error merging commit");
}
assert_eq!(alice_group.past_epoch_deletion_policy(), &new_policy);
assert_eq!(
alice_group.message_secrets_store().num_past_epoch_trees(),
5
);
let new_policy = PastEpochDeletionPolicy::MaxEpochs(2);
let mut new_config = alice_group.configuration().clone();
new_config.past_epoch_deletion_policy = new_policy.clone();
alice_group
.set_configuration(alice_provider.storage(), &new_config)
.expect("error updating configuration");
assert_eq!(alice_group.past_epoch_deletion_policy(), &new_policy);
assert_eq!(
alice_group.message_secrets_store().num_past_epoch_trees(),
2
);
let alice_group_stored = MlsGroup::load(alice_provider.storage(), alice_group.group_id())
.expect("error loading group")
.expect("no group for id");
assert_eq!(
alice_group_stored
.message_secrets_store()
.num_past_epoch_trees(),
2
);
assert_eq!(alice_group_stored.past_epoch_deletion_policy(), &new_policy);
}
#[openmls_test::openmls_test]
fn test_secret_tree_store_migration_next_epoch_timestamp() {
let provider = &Provider::default();
let mut message_secrets_store = setup_tree_store_with_timestamps(
ciphersuite,
provider,
&[
None, None, ],
);
message_secrets_store
.delete_past_epoch_secrets(PastEpochDeletion::before_timestamp(SystemTime::UNIX_EPOCH));
assert_eq!(message_secrets_store.num_past_epoch_trees(), 2);
message_secrets_store
.delete_past_epoch_secrets(PastEpochDeletion::before_timestamp(SystemTime::now()));
assert_eq!(message_secrets_store.num_past_epoch_trees(), 0);
}
#[openmls_test::openmls_test]
fn test_secret_tree_store_migration_next_epoch_duration() {
let provider = &Provider::default();
let mut message_secrets_store = setup_tree_store_with_timestamps(
ciphersuite,
provider,
&[
None, None, ],
);
message_secrets_store.delete_past_epoch_secrets(PastEpochDeletion::older_than_duration(
Duration::from_hours(2),
));
assert_eq!(message_secrets_store.num_past_epoch_trees(), 2);
message_secrets_store.delete_past_epoch_secrets(PastEpochDeletion::older_than_duration(
Duration::from_nanos(0),
));
assert_eq!(message_secrets_store.num_past_epoch_trees(), 0);
}
#[openmls_test::openmls_test]
fn test_secret_tree_store_mixed_delete_by_timestamp() {
let provider = &Provider::default();
let timestamp_before = SystemTime::now();
let mut message_secrets_store = setup_tree_store_with_timestamps(
ciphersuite,
provider,
&[
None, Some(SystemTime::UNIX_EPOCH), None, Some(SystemTime::UNIX_EPOCH), None, Some(SystemTime::now()), None, ],
);
message_secrets_store
.delete_past_epoch_secrets(PastEpochDeletion::before_timestamp(timestamp_before));
assert_eq!(message_secrets_store.num_past_epoch_trees(), 3);
assert!(message_secrets_store.secrets_for_epoch(4).is_some());
assert!(message_secrets_store.secrets_for_epoch(5).is_some());
assert!(message_secrets_store.secrets_for_epoch(6).is_some());
}
#[openmls_test::openmls_test]
fn test_secret_tree_store_mixed_delete_by_duration() {
let provider = &Provider::default();
let mut message_secrets_store = setup_tree_store_with_timestamps(
ciphersuite,
provider,
&[
None, Some(SystemTime::UNIX_EPOCH), None, Some(SystemTime::UNIX_EPOCH), None, Some(SystemTime::now()), None, ],
);
message_secrets_store.delete_past_epoch_secrets(PastEpochDeletion::older_than_duration(
Duration::from_hours(3),
));
assert_eq!(message_secrets_store.num_past_epoch_trees(), 3);
assert!(message_secrets_store.secrets_for_epoch(4).is_some());
assert!(message_secrets_store.secrets_for_epoch(5).is_some());
assert!(message_secrets_store.secrets_for_epoch(6).is_some());
}
#[openmls_test::openmls_test]
fn test_secret_tree_store() {
let provider = &Provider::default();
let mut message_secrets_store = MessageSecretsStore::new_with_secret(
&PastEpochDeletionPolicy::MaxEpochs(3),
MessageSecrets::random(ciphersuite, provider.rand(), LeafNodeIndex::new(0)),
);
message_secrets_store.add_past_epoch_tree(
0,
MessageSecrets::random(ciphersuite, provider.rand(), LeafNodeIndex::new(0))
.with_timestamp(SystemTime::now()),
Vec::new(),
);
assert!(message_secrets_store.secrets_for_epoch_mut(0).is_some());
for i in 1..6u64 {
message_secrets_store.add_past_epoch_tree(
i,
MessageSecrets::random(ciphersuite, provider.rand(), LeafNodeIndex::new(0))
.with_timestamp(SystemTime::now()),
Vec::new(),
);
}
assert!(message_secrets_store.secrets_for_epoch_mut(3).is_some());
assert!(message_secrets_store.secrets_for_epoch_mut(4).is_some());
assert!(message_secrets_store.secrets_for_epoch_mut(5).is_some());
assert!(message_secrets_store.secrets_for_epoch_mut(0).is_none());
assert!(message_secrets_store.secrets_for_epoch_mut(1).is_none());
assert!(message_secrets_store.secrets_for_epoch_mut(2).is_none());
assert!(message_secrets_store.secrets_for_epoch_mut(6).is_none());
}
#[openmls_test::openmls_test]
fn test_empty_secret_tree_store() {
let provider = &Provider::default();
let mut message_secrets_store = MessageSecretsStore::new_with_secret(
&PastEpochDeletionPolicy::MaxEpochs(0),
MessageSecrets::random(ciphersuite, provider.rand(), LeafNodeIndex::new(0)),
);
message_secrets_store.add_past_epoch_tree(
0,
MessageSecrets::random(ciphersuite, provider.rand(), LeafNodeIndex::new(0))
.with_timestamp(SystemTime::now()),
Vec::new(),
);
assert!(message_secrets_store.secrets_for_epoch_mut(0).is_none());
}