use openmls_traits::crypto::OpenMlsCrypto;
use std::collections::VecDeque;
use openmls_traits::types::Ciphersuite;
use crate::ciphersuite::AeadNonce;
use crate::tree::secret_tree::*;
use super::*;
pub(crate) type Generation = u32;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SenderRatchetConfiguration {
out_of_order_tolerance: Generation,
maximum_forward_distance: Generation,
}
impl SenderRatchetConfiguration {
pub fn new(out_of_order_tolerance: Generation, maximum_forward_distance: Generation) -> Self {
Self {
out_of_order_tolerance,
maximum_forward_distance,
}
}
pub fn out_of_order_tolerance(&self) -> Generation {
self.out_of_order_tolerance
}
pub fn maximum_forward_distance(&self) -> Generation {
self.maximum_forward_distance
}
}
impl Default for SenderRatchetConfiguration {
fn default() -> Self {
Self::new(5, 1000)
}
}
pub(crate) type RatchetKeyMaterial = (AeadKey, AeadNonce);
#[derive(Serialize, Deserialize)]
#[cfg_attr(any(feature = "test-utils", test), derive(PartialEq, Clone))]
#[cfg_attr(any(feature = "crypto-debug", test), derive(Debug))]
pub(crate) enum SenderRatchet {
EncryptionRatchet(RatchetSecret),
DecryptionRatchet(DecryptionRatchet),
}
impl SenderRatchet {
#[cfg(test)]
pub(crate) fn generation(&self) -> Generation {
match self {
SenderRatchet::EncryptionRatchet(enc_ratchet) => enc_ratchet.generation(),
SenderRatchet::DecryptionRatchet(dec_ratchet) => dec_ratchet.generation(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[cfg_attr(any(feature = "test-utils", test), derive(PartialEq))]
pub(crate) struct RatchetSecret {
secret: Secret,
generation: Generation,
}
impl RatchetSecret {
pub(crate) fn initial_ratchet_secret(secret: Secret) -> Self {
Self {
secret,
generation: 0,
}
}
pub(crate) fn generation(&self) -> Generation {
self.generation
}
pub(crate) fn ratchet_forward(
&mut self,
crypto: &impl OpenMlsCrypto,
ciphersuite: Ciphersuite,
) -> Result<(Generation, RatchetKeyMaterial), SecretTreeError> {
log::trace!("Ratcheting forward in generation {}.", self.generation);
log_crypto!(trace, " with secret {:x?}", self.secret);
if self.generation == u32::MAX {
return Err(SecretTreeError::RatchetTooLong);
}
let nonce = derive_tree_secret(
ciphersuite,
&self.secret,
"nonce",
self.generation,
ciphersuite.aead_nonce_length(),
crypto,
)?;
let key = derive_tree_secret(
ciphersuite,
&self.secret,
"key",
self.generation,
ciphersuite.aead_key_length(),
crypto,
)?;
self.secret = derive_tree_secret(
ciphersuite,
&self.secret,
"secret",
self.generation,
ciphersuite.hash_length(),
crypto,
)?;
let generation = self.generation;
self.generation += 1;
Ok((
generation,
(
AeadKey::from_secret(key, ciphersuite),
AeadNonce::from_secret(nonce),
),
))
}
#[cfg(test)]
pub(crate) fn set_generation(&mut self, generation: Generation) {
self.generation = generation
}
}
#[derive(Serialize, Deserialize)]
#[cfg_attr(any(feature = "test-utils", test), derive(PartialEq, Clone))]
#[cfg_attr(any(feature = "crypto-debug", test), derive(Debug))]
pub struct DecryptionRatchet {
past_secrets: VecDeque<Option<RatchetKeyMaterial>>,
ratchet_head: RatchetSecret,
}
impl DecryptionRatchet {
pub(crate) fn new(secret: Secret) -> Self {
Self {
past_secrets: VecDeque::new(),
ratchet_head: RatchetSecret::initial_ratchet_secret(secret),
}
}
fn prune_past_secrets(&mut self, configuration: &SenderRatchetConfiguration) {
self.past_secrets
.truncate(configuration.out_of_order_tolerance() as usize)
}
#[cfg(test)]
pub(crate) fn generation(&self) -> Generation {
self.ratchet_head.generation()
}
#[cfg(test)]
pub(crate) fn ratchet_secret_mut(&mut self) -> &mut RatchetSecret {
&mut self.ratchet_head
}
pub(crate) fn peek(
&self,
ciphersuite: Ciphersuite,
crypto: &impl OpenMlsCrypto,
generation: Generation,
configuration: &SenderRatchetConfiguration,
) -> Result<(RatchetKeyMaterial, PeekedGeneration), SecretTreeError> {
log::debug!("peek decryption secret");
let head_generation = self.ratchet_head.generation();
if head_generation < u32::MAX - configuration.maximum_forward_distance()
&& generation > head_generation + configuration.maximum_forward_distance()
{
return Err(SecretTreeError::TooDistantInTheFuture);
}
if generation < head_generation
&& (head_generation - generation) > configuration.out_of_order_tolerance()
{
log::error!(" Generation is too far in the past (broke out of order tolerance ({}) {generation} < {head_generation}).", configuration.out_of_order_tolerance());
return Err(SecretTreeError::TooDistantInThePast);
}
if generation >= head_generation {
let mut ratchet_head = self.ratchet_head.clone();
let mut skipped_secrets = VecDeque::new();
for _ in 0..(generation - head_generation) {
let ratchet_secrets = ratchet_head
.ratchet_forward(crypto, ciphersuite)
.map(|(_, key_material)| key_material)?;
skipped_secrets.push_front(ratchet_secrets);
skipped_secrets.truncate(configuration.out_of_order_tolerance() as usize);
}
let ratchet_secrets = ratchet_head
.ratchet_forward(crypto, ciphersuite)
.map(|(_, key_material)| key_material)?;
Ok((
ratchet_secrets,
PeekedGeneration::Forward {
from_head: head_generation,
ratchet_head,
skipped_secrets,
},
))
} else {
let index = self.past_secret_index(generation)?;
let ratchet_secrets = self
.past_secrets
.get(index)
.ok_or(SecretTreeError::IndexOutOfBounds)?
.clone()
.ok_or(SecretTreeError::SecretReuseError)?;
Ok((ratchet_secrets, PeekedGeneration::Past { generation }))
}
}
pub(crate) fn consume(
&mut self,
peeked: PeekedGeneration,
configuration: &SenderRatchetConfiguration,
) -> Result<(), SecretTreeError> {
match peeked {
PeekedGeneration::Forward {
from_head,
ratchet_head,
skipped_secrets,
} => {
if from_head != self.ratchet_head.generation() {
log::error!("The ratchet moved since the generation was peeked.");
return Err(SecretTreeError::LibraryError);
}
self.ratchet_head = ratchet_head;
for ratchet_secrets in skipped_secrets.into_iter().rev() {
self.past_secrets.push_front(Some(ratchet_secrets));
}
self.past_secrets.push_front(None);
self.prune_past_secrets(configuration);
Ok(())
}
PeekedGeneration::Past { generation } => {
let index = self.past_secret_index(generation)?;
self.past_secrets
.get_mut(index)
.ok_or(SecretTreeError::IndexOutOfBounds)?
.take()
.map(|_| ())
.ok_or(SecretTreeError::SecretReuseError)
}
}
}
#[cfg(test)]
pub(crate) fn secret_for_decryption(
&mut self,
ciphersuite: Ciphersuite,
crypto: &impl OpenMlsCrypto,
generation: Generation,
configuration: &SenderRatchetConfiguration,
) -> Result<RatchetKeyMaterial, SecretTreeError> {
let (ratchet_secrets, peeked) =
self.peek(ciphersuite, crypto, generation, configuration)?;
self.consume(peeked, configuration)?;
Ok(ratchet_secrets)
}
fn past_secret_index(&self, generation: Generation) -> Result<usize, SecretTreeError> {
let window_index = i64::from(self.ratchet_head.generation()) - i64::from(generation) - 1;
usize::try_from(window_index).map_err(|_| {
log::error!(" Generation is too far in the past (not in the window).");
SecretTreeError::TooDistantInThePast
})
}
}
pub(crate) enum PeekedGeneration {
Forward {
from_head: Generation,
ratchet_head: RatchetSecret,
skipped_secrets: VecDeque<RatchetKeyMaterial>,
},
Past { generation: Generation },
}