use std::collections::VecDeque;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::crypto::Secret;
use crate::crypto::aead::AeadNonce;
use crate::crypto::hkdf::{HkdfError, hkdf};
pub const MESSAGE_KEY_SIZE: usize = 32;
pub type RatchetKey = Secret<MESSAGE_KEY_SIZE>;
pub type RatchetNonce = AeadNonce;
pub type RatchetKeyMaterial = (RatchetKey, RatchetNonce);
pub type Generation = u32;
#[derive(Debug)]
pub struct RatchetSecret;
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test_utils"), derive(Clone))]
pub struct RatchetSecretState {
secret: Secret<MESSAGE_KEY_SIZE>,
generation: Generation,
}
impl RatchetSecret {
pub fn init(secret: Secret<MESSAGE_KEY_SIZE>) -> RatchetSecretState {
RatchetSecretState {
secret,
generation: 0,
}
}
pub fn ratchet_forward(
mut y: RatchetSecretState,
) -> Result<(RatchetSecretState, Generation, RatchetKeyMaterial), RatchetError> {
let generation = y.generation;
let nonce: AeadNonce = hkdf(b"nonce", y.secret.as_bytes(), None)?;
let key: [u8; MESSAGE_KEY_SIZE] = hkdf(b"key", y.secret.as_bytes(), None)?;
y.generation += 1;
y.secret = Secret::from_bytes(hkdf(b"chain", y.secret.as_bytes(), None)?);
Ok((y, generation, (Secret::from_bytes(key), nonce)))
}
}
#[derive(Debug)]
pub struct DecryptionRatchet;
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test_utils"), derive(Clone))]
pub struct DecryptionRatchetState {
past_secrets: VecDeque<Option<RatchetKeyMaterial>>,
ratchet_head: RatchetSecretState,
}
impl DecryptionRatchet {
pub fn init(secret: Secret<MESSAGE_KEY_SIZE>) -> DecryptionRatchetState {
DecryptionRatchetState {
past_secrets: VecDeque::new(),
ratchet_head: RatchetSecret::init(secret),
}
}
pub fn secret_for_decryption(
mut y: DecryptionRatchetState,
generation: Generation,
maximum_forward_distance: u32,
ooo_tolerance: u32,
) -> Result<(DecryptionRatchetState, RatchetKeyMaterial), RatchetError> {
let generation_head = y.ratchet_head.generation;
if generation_head < u32::MAX - maximum_forward_distance
&& generation > generation_head + maximum_forward_distance
{
return Err(RatchetError::TooDistantInTheFuture);
}
if generation < generation_head && (generation_head - generation) > ooo_tolerance {
return Err(RatchetError::TooDistantInThePast);
}
if generation >= generation_head {
for _ in 0..(generation - generation_head) {
let (y_ratchet_head_i, _, ratchet_secrets) =
RatchetSecret::ratchet_forward(y.ratchet_head)?;
y.ratchet_head = y_ratchet_head_i;
y.past_secrets.push_front(Some(ratchet_secrets));
}
let (y_ratchet_head_i, _, ratchet_secrets) =
RatchetSecret::ratchet_forward(y.ratchet_head)?;
y.ratchet_head = y_ratchet_head_i;
y.past_secrets.push_front(None);
y.past_secrets.truncate(ooo_tolerance as usize);
Ok((y, ratchet_secrets))
} else {
let window_index = ((generation_head - generation) as i32) - 1;
let index = if window_index >= 0 {
window_index as usize
} else {
return Err(RatchetError::TooDistantInThePast);
};
let ratchet_secrets = y
.past_secrets
.get_mut(index)
.ok_or(RatchetError::IndexOutOfBounds)?
.take()
.ok_or(RatchetError::SecretReuse)?;
Ok((y, ratchet_secrets))
}
}
}
#[derive(Debug, Error)]
pub enum RatchetError {
#[error(transparent)]
Hkdf(#[from] HkdfError),
#[error("generation for message ratchet is too far into the future")]
TooDistantInTheFuture,
#[error("generation for message ratchet is too far into the past")]
TooDistantInThePast,
#[error("unknown message secret")]
IndexOutOfBounds,
#[error("tried to re-use secret for same generation")]
SecretReuse,
}
#[cfg(test)]
mod tests {
use crate::Rng;
use crate::crypto::Secret;
use super::{DecryptionRatchet, MESSAGE_KEY_SIZE, RatchetError, RatchetSecret};
#[test]
fn ratchet_forward() {
let rng = Rng::from_seed([1; 32]);
let update_secret = Secret::from_bytes(rng.random_array::<MESSAGE_KEY_SIZE>().unwrap());
let ratchet = RatchetSecret::init(update_secret);
let (ratchet, generation, secret_0) = RatchetSecret::ratchet_forward(ratchet).unwrap();
assert_eq!(generation, 0);
assert_eq!(ratchet.generation, 1);
let (ratchet, generation, secret_1) = RatchetSecret::ratchet_forward(ratchet).unwrap();
assert_eq!(generation, 1);
assert_eq!(ratchet.generation, 2);
assert_ne!(secret_0, secret_1);
}
#[test]
fn forward_secrecy() {
let rng = Rng::from_seed([1; 32]);
let update_secret = Secret::from_bytes(rng.random_array::<MESSAGE_KEY_SIZE>().unwrap());
let ooo_tolerance = 4;
let max_forward = 100;
let ratchet = DecryptionRatchet::init(update_secret);
let (ratchet, secret) =
DecryptionRatchet::secret_for_decryption(ratchet, 0, max_forward, ooo_tolerance)
.unwrap();
assert_eq!(ratchet.ratchet_head.generation, 1);
assert_ne!(ratchet.ratchet_head.secret, secret.0);
assert!(!ratchet.past_secrets.iter().any(|secret| secret.is_some()));
assert!(matches!(
DecryptionRatchet::secret_for_decryption(
ratchet.clone(),
0,
max_forward,
ooo_tolerance
),
Err(RatchetError::SecretReuse),
));
let jump = 10;
let (mut ratchet, _) =
DecryptionRatchet::secret_for_decryption(ratchet, jump, max_forward, ooo_tolerance)
.unwrap();
for generation in jump - ooo_tolerance + 1..jump {
let (ratchet_i, _) = DecryptionRatchet::secret_for_decryption(
ratchet,
generation,
max_forward,
ooo_tolerance,
)
.unwrap();
assert!(matches!(
DecryptionRatchet::secret_for_decryption(
ratchet_i.clone(),
generation,
max_forward,
ooo_tolerance
),
Err(RatchetError::SecretReuse),
));
ratchet = ratchet_i;
}
assert!(!ratchet.past_secrets.iter().any(|secret| secret.is_some()));
}
#[test]
fn out_of_order() {
let rng = Rng::from_seed([1; 32]);
let update_secret = Secret::from_bytes(rng.random_array::<MESSAGE_KEY_SIZE>().unwrap());
let max_forward = 3;
let ooo_tolerance = 3;
let alice = RatchetSecret::init(update_secret.clone());
let bob = DecryptionRatchet::init(update_secret);
let (alice, _, alice_secret_0) = RatchetSecret::ratchet_forward(alice).unwrap();
let (alice, _, _alice_secret_1) = RatchetSecret::ratchet_forward(alice).unwrap();
let (alice, _, alice_secret_2) = RatchetSecret::ratchet_forward(alice).unwrap();
let (alice, _, alice_secret_3) = RatchetSecret::ratchet_forward(alice).unwrap();
let (alice, _, alice_secret_4) = RatchetSecret::ratchet_forward(alice).unwrap();
assert_eq!(alice.generation, 5);
let (bob, bob_secret_0) =
DecryptionRatchet::secret_for_decryption(bob, 0, max_forward, ooo_tolerance).unwrap();
assert_eq!(alice_secret_0, bob_secret_0);
let (bob, bob_secret_4) =
DecryptionRatchet::secret_for_decryption(bob, 4, max_forward, ooo_tolerance).unwrap();
assert_eq!(alice_secret_4, bob_secret_4);
assert_eq!(bob.ratchet_head.generation, 5);
let (bob, bob_secret_3) =
DecryptionRatchet::secret_for_decryption(bob, 3, max_forward, ooo_tolerance).unwrap();
assert_eq!(alice_secret_3, bob_secret_3);
let (bob, bob_secret_2) =
DecryptionRatchet::secret_for_decryption(bob, 2, max_forward, ooo_tolerance).unwrap();
assert_eq!(alice_secret_2, bob_secret_2);
assert!(matches!(
DecryptionRatchet::secret_for_decryption(bob.clone(), 1, max_forward, ooo_tolerance),
Err(RatchetError::TooDistantInThePast)
));
assert!(matches!(
DecryptionRatchet::secret_for_decryption(
bob.clone(),
bob.ratchet_head.generation + max_forward + 1,
max_forward,
ooo_tolerance
),
Err(RatchetError::TooDistantInTheFuture)
));
}
}