#[cfg(feature = "virtual-clients-draft")]
use crate::tree::dual_use_ratchet::DualUseRatchet;
use crate::{
ciphersuite::Secret, test_utils::*, tree::secret_tree::SecretTreeError, tree::sender_ratchet::*,
};
#[openmls_test::openmls_test]
fn test_max_forward_distance() {
let provider = &Provider::default();
let configuration = &SenderRatchetConfiguration::default();
let secret = Secret::random(ciphersuite, provider.rand()).expect("Not enough randomness.");
let mut ratchet1 = DecryptionRatchet::new(secret.clone());
let mut ratchet2 = DecryptionRatchet::new(secret);
let _secret = ratchet1
.secret_for_decryption(
ciphersuite,
provider.crypto(),
configuration.maximum_forward_distance(),
configuration,
)
.expect("Expected decryption secret.");
let err = ratchet2
.secret_for_decryption(
ciphersuite,
provider.crypto(),
configuration.maximum_forward_distance() + 1,
configuration,
)
.expect_err("Expected error.");
assert_eq!(err, SecretTreeError::TooDistantInTheFuture);
ratchet1.ratchet_secret_mut().set_generation(u32::MAX - 5);
ratchet1
.secret_for_decryption(ciphersuite, provider.crypto(), u32::MAX - 1, configuration)
.expect("Error ratcheting to very high generation");
}
#[openmls_test::openmls_test]
fn test_out_of_order_generations() {
let provider = &Provider::default();
let configuration = &SenderRatchetConfiguration::default();
let secret = Secret::random(ciphersuite, provider.rand()).expect("Not enough randomness.");
let mut ratchet1 = DecryptionRatchet::new(secret);
for i in 0..configuration.out_of_order_tolerance() * 2 {
let _secret = ratchet1
.secret_for_decryption(ciphersuite, provider.crypto(), i, configuration)
.expect("Expected decryption secret.");
}
let err = ratchet1
.secret_for_decryption(
ciphersuite,
provider.crypto(),
configuration.out_of_order_tolerance() - 1,
configuration,
)
.expect_err("Expected error.");
assert_eq!(err, SecretTreeError::TooDistantInThePast);
for i in configuration.out_of_order_tolerance()..configuration.out_of_order_tolerance() * 2 {
assert_eq!(
ratchet1
.secret_for_decryption(ciphersuite, provider.crypto(), i, configuration)
.expect_err("Expected decryption secret."),
SecretTreeError::SecretReuseError
);
}
}
#[openmls_test::openmls_test]
fn test_forward_secrecy() {
let provider = &Provider::default();
let configuration = &SenderRatchetConfiguration::default();
let secret = Secret::random(ciphersuite, provider.rand()).expect("Not enough randomness.");
let mut ratchet = DecryptionRatchet::new(secret);
let _ratchet_secrets = ratchet
.secret_for_decryption(ciphersuite, provider.crypto(), 0, configuration)
.expect("Error ratcheting forward.");
assert_eq!(ratchet.generation(), 1);
let err = ratchet
.secret_for_decryption(ciphersuite, provider.crypto(), 0, configuration)
.expect_err("No error when trying to retrieve key outside of tolerance window.");
assert_eq!(err, SecretTreeError::SecretReuseError);
let _ratchet_secrets = ratchet
.secret_for_decryption(ciphersuite, provider.crypto(), 10, configuration)
.expect("Error ratcheting forward.");
let err = ratchet
.secret_for_decryption(ciphersuite, provider.crypto(), 5, configuration)
.expect_err("No error when trying to retrieve key outside of tolerance window.");
assert_eq!(err, SecretTreeError::TooDistantInThePast);
for generation in 10 - configuration.out_of_order_tolerance() + 1..10 {
let keys = ratchet.secret_for_decryption(
ciphersuite,
provider.crypto(),
generation,
configuration,
);
assert!(keys.is_ok());
let err = ratchet
.secret_for_decryption(ciphersuite, provider.crypto(), generation, configuration)
.expect_err("No error when trying to retrieve deleted key.");
assert_eq!(err, SecretTreeError::SecretReuseError);
}
}
#[test]
fn sender_ratchet_generation_overflow() {
let provider = OpenMlsRustCrypto::default();
let ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
let secret = Secret::random(ciphersuite, provider.rand()).expect("Not enough randomness.");
let mut ratchet = RatchetSecret::initial_ratchet_secret(secret);
ratchet.set_generation(u32::MAX - 1);
let _ = ratchet
.ratchet_forward(provider.crypto(), ciphersuite)
.expect("error ratcheting forward");
let err = ratchet
.ratchet_forward(provider.crypto(), ciphersuite)
.expect_err("no error exceeding generation u32::MAX");
assert_eq!(err, SecretTreeError::RatchetTooLong)
}
#[cfg(feature = "virtual-clients-draft")]
#[openmls_test::openmls_test]
fn dual_use_encrypt_confirm_drops_secret() {
let provider = &Provider::default();
let configuration = &SenderRatchetConfiguration::default();
let secret = Secret::random(ciphersuite, provider.rand()).expect("Not enough randomness.");
let mut ratchet = DualUseRatchet::new(secret);
let (generation, _) = ratchet
.secret_for_encryption(ciphersuite, provider.crypto())
.expect("Expected encryption secret.");
assert_eq!(generation, 0);
assert_eq!(ratchet.generation(), 1);
ratchet.delete_secret_for_generation(generation);
let err = ratchet
.secret_for_decryption(ciphersuite, provider.crypto(), generation, configuration)
.expect_err("Confirmed secret should be unavailable.");
assert_eq!(err, SecretTreeError::SecretReuseError);
}
#[cfg(feature = "virtual-clients-draft")]
#[openmls_test::openmls_test]
fn dual_use_encrypt_then_decrypt_own() {
let provider = &Provider::default();
let configuration = &SenderRatchetConfiguration::default();
let secret = Secret::random(ciphersuite, provider.rand()).expect("Not enough randomness.");
let mut ratchet = DualUseRatchet::new(secret);
let (generation, _) = ratchet
.secret_for_encryption(ciphersuite, provider.crypto())
.expect("Expected encryption secret.");
let _decrypted = ratchet
.secret_for_decryption(ciphersuite, provider.crypto(), generation, configuration)
.expect("Expected to decrypt own message.");
let err = ratchet
.secret_for_decryption(ciphersuite, provider.crypto(), generation, configuration)
.expect_err("Reusing the same generation should fail.");
assert_eq!(err, SecretTreeError::SecretReuseError);
}
#[cfg(feature = "virtual-clients-draft")]
#[openmls_test::openmls_test]
fn dual_use_delete_secret_edge_cases() {
let provider = &Provider::default();
let configuration = &SenderRatchetConfiguration::default();
let secret = Secret::random(ciphersuite, provider.rand()).expect("Not enough randomness.");
let mut ratchet = DualUseRatchet::new(secret);
ratchet.delete_secret_for_generation(ratchet.generation());
assert_eq!(ratchet.generation(), 0);
ratchet.delete_secret_for_generation(42);
assert_eq!(ratchet.generation(), 0);
let (gen0, _) = ratchet
.secret_for_encryption(ciphersuite, provider.crypto())
.expect("Expected encryption secret.");
let (_gen1, _) = ratchet
.secret_for_encryption(ciphersuite, provider.crypto())
.expect("Expected encryption secret.");
ratchet.delete_secret_for_generation(gen0);
ratchet.delete_secret_for_generation(gen0);
let err = ratchet
.secret_for_decryption(ciphersuite, provider.crypto(), gen0, configuration)
.expect_err("Deleted secret should be unavailable.");
assert_eq!(err, SecretTreeError::SecretReuseError);
}
#[cfg(feature = "virtual-clients-draft")]
#[openmls_test::openmls_test]
fn dual_use_decrypts_past_out_of_order_tolerance() {
let provider = &Provider::default();
let configuration = &SenderRatchetConfiguration::default();
let secret = Secret::random(ciphersuite, provider.rand()).expect("Not enough randomness.");
let mut ratchet = DualUseRatchet::new(secret);
let send_count = configuration.out_of_order_tolerance() + 2;
let mut first_generation = None;
for _ in 0..send_count {
let (generation, _) = ratchet
.secret_for_encryption(ciphersuite, provider.crypto())
.expect("Expected encryption secret.");
first_generation.get_or_insert(generation);
}
let first_generation = first_generation.unwrap();
assert!(ratchet.generation() - first_generation > configuration.out_of_order_tolerance());
let _decrypted = ratchet
.secret_for_decryption(
ciphersuite,
provider.crypto(),
first_generation,
configuration,
)
.expect("Old encryption secret should still be retrievable for own decryption.");
}
#[cfg(feature = "virtual-clients-draft")]
#[openmls_test::openmls_test]
fn dual_use_confirming_later_messages_keeps_old_unconfirmed_secret() {
let provider = &Provider::default();
let configuration = &SenderRatchetConfiguration::default();
let secret = Secret::random(ciphersuite, provider.rand()).expect("Not enough randomness.");
let mut ratchet = DualUseRatchet::new(secret);
let (first_generation, _) = ratchet
.secret_for_encryption(ciphersuite, provider.crypto())
.expect("Expected encryption secret.");
for _ in 0..configuration.out_of_order_tolerance() + 2 {
let (generation, _) = ratchet
.secret_for_encryption(ciphersuite, provider.crypto())
.expect("Expected encryption secret.");
ratchet.delete_secret_for_generation(generation);
}
assert!(ratchet.generation() - first_generation > configuration.out_of_order_tolerance());
let _decrypted = ratchet
.secret_for_decryption(
ciphersuite,
provider.crypto(),
first_generation,
configuration,
)
.expect("Old unconfirmed secret should still be retrievable for own decryption.");
}
#[cfg(feature = "virtual-clients-draft")]
#[openmls_test::openmls_test]
fn dual_use_decryption_moves_receive_window() {
let provider = &Provider::default();
let configuration = &SenderRatchetConfiguration::default();
let secret = Secret::random(ciphersuite, provider.rand()).expect("Not enough randomness.");
let mut ratchet = DualUseRatchet::new(secret);
let target_generation = configuration.out_of_order_tolerance() * 2;
let _decrypted = ratchet
.secret_for_decryption(
ciphersuite,
provider.crypto(),
target_generation,
configuration,
)
.expect("Expected decryption secret.");
let too_old_generation = target_generation - configuration.out_of_order_tolerance();
let err = ratchet
.secret_for_decryption(
ciphersuite,
provider.crypto(),
too_old_generation,
configuration,
)
.expect_err("Expected the receive window to reject old generations.");
assert_eq!(err, SecretTreeError::TooDistantInThePast);
let retained_generation = target_generation - configuration.out_of_order_tolerance() + 1;
let _decrypted = ratchet
.secret_for_decryption(
ciphersuite,
provider.crypto(),
retained_generation,
configuration,
)
.expect("Expected retained out-of-order secret.");
}
#[cfg(feature = "virtual-clients-draft")]
#[openmls_test::openmls_test]
fn dual_use_local_sends_do_not_advance_receive_window() {
let provider = &Provider::default();
let configuration = &SenderRatchetConfiguration::default();
let secret = Secret::random(ciphersuite, provider.rand()).expect("Not enough randomness.");
let mut ratchet = DualUseRatchet::new(secret);
let first_received_generation = configuration.out_of_order_tolerance();
let _decrypted = ratchet
.secret_for_decryption(
ciphersuite,
provider.crypto(),
first_received_generation,
configuration,
)
.expect("Expected first decryption secret.");
for _ in 0..configuration.out_of_order_tolerance() {
let (generation, _) = ratchet
.secret_for_encryption(ciphersuite, provider.crypto())
.expect("Expected encryption secret.");
ratchet.delete_secret_for_generation(generation);
}
let later_received_generation = ratchet.generation();
let _decrypted = ratchet
.secret_for_decryption(
ciphersuite,
provider.crypto(),
later_received_generation,
configuration,
)
.expect("Expected later decryption secret.");
let pruned_by_later_decryption =
first_received_generation.saturating_sub(configuration.out_of_order_tolerance()) + 1;
let err = ratchet
.secret_for_decryption(
ciphersuite,
provider.crypto(),
pruned_by_later_decryption,
configuration,
)
.expect_err("One old generation should be pruned by the later decryption.");
assert_eq!(err, SecretTreeError::TooDistantInThePast);
let retained_across_local_sends = pruned_by_later_decryption + 1;
let _decrypted = ratchet
.secret_for_decryption(
ciphersuite,
provider.crypto(),
retained_across_local_sends,
configuration,
)
.expect("Local sends should not prune the receive window.");
}