use std::collections::HashMap;
use crate::{
crypto::{
aead::AeadDecrypt,
key_derivation::{KeyDerivation, Ratcheting},
},
error::SframeError,
header::KeyId,
key::KeyStore,
ratchet::{
key::GenericRatchetingDecryptionKey,
key_id::{Generation, RatchetBits, RatchetStepDiff, RatchetingKeyId},
},
};
pub struct GenericRatchetingKeyStore<A, D>
where
A: AeadDecrypt<Secret = D::Secret>,
D: KeyDerivation + Ratcheting,
{
keys: HashMap<Generation, GenericRatchetingDecryptionKey<A, D>>,
n_ratchet_bits: RatchetBits,
max_ratchet_steps: RatchetStepDiff,
}
impl<A, D> GenericRatchetingKeyStore<A, D>
where
A: AeadDecrypt<Secret = D::Secret>,
D: KeyDerivation + Ratcheting,
{
pub fn new(n_ratchet_bits: RatchetBits, max_ratchet_steps: RatchetStepDiff) -> Self {
Self {
keys: HashMap::default(),
n_ratchet_bits,
max_ratchet_steps,
}
}
pub fn insert(&mut self, key: GenericRatchetingDecryptionKey<A, D>) -> bool {
self.keys.insert(key.key_id().generation(), key).is_some()
}
pub fn remove(&mut self, generation: Generation) -> bool {
self.keys.remove(&generation).is_some()
}
pub fn get(&self, generation: Generation) -> Option<&GenericRatchetingDecryptionKey<A, D>> {
self.keys.get(&generation)
}
fn ratcheted_key(
&self,
key_id: RatchetingKeyId,
) -> Result<GenericRatchetingDecryptionKey<A, D>, RatchetingKeyStoreError>
where
A: Clone,
D::Secret: Clone,
{
let stored = self.keys.get(&key_id.generation()).ok_or(
RatchetingKeyStoreError::UnknownGeneration(key_id.generation()),
)?;
let can_be_told_apart = self
.n_ratchet_bits
.max_distinguishable_steps()
.max(RatchetStepDiff::ONE);
let max_ratchet_steps = self.max_ratchet_steps.min(can_be_told_apart);
stored
.ratchet_to(key_id, max_ratchet_steps)
.map_err(RatchetingKeyStoreError::RatchetingFailed)
}
}
#[derive(Debug, thiserror::Error)]
pub enum RatchetingKeyStoreError {
#[error("No key is stored for Key Generation {0}")]
UnknownGeneration(Generation),
#[error("{0}")]
RatchetingFailed(#[source] SframeError),
}
impl<A, D> KeyStore<A, D> for &mut GenericRatchetingKeyStore<A, D>
where
A: AeadDecrypt<Secret = D::Secret> + Clone,
D: KeyDerivation + Ratcheting,
D::Secret: Clone,
{
type Key = GenericRatchetingDecryptionKey<A, D>;
type Error = RatchetingKeyStoreError;
fn lookup(&self, key_id: KeyId) -> Result<Self::Key, Self::Error> {
self.ratcheted_key(RatchetingKeyId::from_key_id(key_id, self.n_ratchet_bits))
}
fn record(&mut self, key: Self::Key) {
self.insert(key);
}
}
#[cfg(all(test, crypto_backend))]
mod test {
use crate::{
CipherSuite,
crypto::{Aead, Kdf},
header::KeyId,
key::{GenericDecryptionKey, KeyStore},
ratchet::{Generation, RatchetBits, RatchetStepDiff, RatchetingKeyId},
};
use pretty_assertions::assert_eq;
use super::RatchetingKeyStoreError;
type RatchetingKeyStore = super::GenericRatchetingKeyStore<Aead, Kdf>;
type RatchetingDecryptionKey = super::GenericRatchetingDecryptionKey<Aead, Kdf>;
const KEY_MATERIAL: &[u8] = b"SuperSecret";
const CIPHER_SUITE: CipherSuite = CipherSuite::AesGcm256Sha512;
fn n_ratchet_bits() -> RatchetBits {
RatchetBits::new(4)
}
fn distinguishable_steps() -> u64 {
n_ratchet_bits().max_distinguishable_steps().into()
}
fn generation() -> Generation {
Generation::from(42)
}
fn key_id(n_steps: u64) -> RatchetingKeyId {
let key_id = RatchetingKeyId::new(generation(), n_ratchet_bits());
(0..n_steps).fold(key_id, |key_id, _| key_id.inc_ratchet_step())
}
fn key_store() -> RatchetingKeyStore {
key_store_with_max_steps(n_ratchet_bits().max_distinguishable_steps())
}
fn key_store_with_max_steps(max_ratchet_steps: RatchetStepDiff) -> RatchetingKeyStore {
let mut key_store = RatchetingKeyStore::new(n_ratchet_bits(), max_ratchet_steps);
let key =
RatchetingDecryptionKey::derive_from(CIPHER_SUITE, key_id(0), KEY_MATERIAL).unwrap();
key_store.insert(key);
key_store
}
fn stored_key(key_store: &RatchetingKeyStore) -> GenericDecryptionKey<Aead, Kdf> {
key_store.get(generation()).unwrap().as_ref().clone()
}
fn lookup(
key_store: &mut RatchetingKeyStore,
key_id: RatchetingKeyId,
) -> Result<RatchetingDecryptionKey, RatchetingKeyStoreError> {
key_store.lookup(KeyId::from(key_id))
}
fn lookup_and_record(
mut key_store: &mut RatchetingKeyStore,
key_id: RatchetingKeyId,
) -> Result<GenericDecryptionKey<Aead, Kdf>, RatchetingKeyStoreError> {
let key = key_store.lookup(KeyId::from(key_id))?;
let used = key.as_ref().clone();
key_store.record(key);
Ok(used)
}
#[test]
fn inserts_and_gets_a_key_per_generation() {
let key_store = key_store();
let key = key_store.get(generation()).unwrap();
assert_eq!(key_id(0), key.key_id());
}
#[test]
fn replaces_the_key_of_a_generation_on_insert() {
let mut replaced = key_store();
let key = RatchetingDecryptionKey::derive_from(CIPHER_SUITE, key_id(0), b"other").unwrap();
let was_replaced = replaced.insert(key);
assert!(was_replaced);
assert_ne!(stored_key(&key_store()), stored_key(&replaced));
}
#[test]
fn returns_none_for_an_unknown_generation() {
let key_store = key_store();
assert!(key_store.get(Generation::from(1)).is_none());
}
#[test]
fn removes_a_key() {
let mut key_store = key_store();
assert!(key_store.remove(generation()));
assert!(!key_store.remove(generation()));
assert!(key_store.get(generation()).is_none());
}
#[test]
fn looks_the_stored_key_up() {
let mut key_store = key_store();
let stored = stored_key(&key_store);
let looked_up = lookup(&mut key_store, key_id(0)).unwrap();
assert_eq!(stored, *looked_up.as_ref());
}
#[test]
fn follows_a_single_step_with_one_ratchet_bit() {
let n_ratchet_bits = RatchetBits::new(1);
let key_id = RatchetingKeyId::new(generation(), n_ratchet_bits);
let mut key_store = RatchetingKeyStore::new(n_ratchet_bits, RatchetStepDiff::ONE);
key_store.insert(
RatchetingDecryptionKey::derive_from(CIPHER_SUITE, key_id, KEY_MATERIAL).unwrap(),
);
let stored = stored_key(&key_store);
let used = lookup_and_record(&mut key_store, key_id.inc_ratchet_step()).unwrap();
assert_ne!(stored, used);
}
#[test]
fn ratchets_the_stored_key_forward_to_the_key_id() {
let mut key_store = key_store();
let stored = stored_key(&key_store);
let used = lookup_and_record(&mut key_store, key_id(2)).unwrap();
assert_eq!(KeyId::from(key_id(2)), used.key_id());
assert_ne!(stored, used);
assert_eq!(used, stored_key(&key_store));
}
#[test]
fn does_not_keep_a_key_which_was_not_recorded() {
let mut key_store = key_store();
let stored = stored_key(&key_store);
let ratcheted = lookup(&mut key_store, key_id(2)).unwrap();
drop(ratcheted);
assert_eq!(stored, stored_key(&key_store));
}
#[test]
fn fails_for_an_unknown_generation() {
let mut key_store = RatchetingKeyStore::new(n_ratchet_bits(), RatchetStepDiff::NONE);
let result = lookup(&mut key_store, key_id(0));
assert!(matches!(
result,
Err(RatchetingKeyStoreError::UnknownGeneration(missing)) if missing == generation()
));
}
#[test]
fn rejects_a_ratchet_step_which_was_already_passed() {
let mut key_store = key_store();
lookup_and_record(&mut key_store, key_id(2)).unwrap();
let ratcheted = stored_key(&key_store);
let result = lookup(&mut key_store, key_id(1));
assert!(result.is_err());
assert_eq!(ratcheted, stored_key(&key_store));
}
#[test]
fn catches_up_at_most_max_ratchet_steps() {
let mut key_store = key_store_with_max_steps(RatchetStepDiff::ONE);
let stored = stored_key(&key_store);
let result = lookup(&mut key_store, key_id(2));
assert!(result.is_err());
assert_eq!(stored, stored_key(&key_store));
}
#[test]
fn never_catches_up_more_steps_than_can_be_told_apart() {
let too_many = distinguishable_steps() + 1;
let mut key_store = key_store_with_max_steps(RatchetStepDiff::from(too_many));
let stored = stored_key(&key_store);
let result = lookup(&mut key_store, key_id(too_many));
assert!(result.is_err());
assert_eq!(stored, stored_key(&key_store));
}
#[test]
fn rejects_more_ratchet_steps_than_can_be_told_apart() {
let mut key_store = key_store();
let stored = stored_key(&key_store);
let ambiguous = distinguishable_steps() + 1;
let result = lookup(&mut key_store, key_id(ambiguous));
assert!(result.is_err());
assert_eq!(stored, stored_key(&key_store));
}
}