use crate::{
CipherSuite,
crypto::{AeadDecrypt, AeadEncrypt, KeyDerivation, Ratcheting},
error::{Result, SframeError},
header::KeyId,
key::{
KeyLookup,
generic::{GenericDecryptionKey, GenericEncryptionKey},
},
ratchet::{
RatchetingKeyId, key_id::RatchetStepDiff, key_material::GenericRatchetingKeyMaterial,
},
};
pub struct GenericRatchetingEncryptionKey<A, D>
where
A: AeadEncrypt,
D: KeyDerivation + Ratcheting,
{
key_id: RatchetingKeyId,
material: GenericRatchetingKeyMaterial<D>,
enc_key: GenericEncryptionKey<A, D>,
ratcheted_from: Option<RatchetingKeyId>,
}
impl<A, D> GenericRatchetingEncryptionKey<A, D>
where
A: AeadEncrypt<Secret = D::Secret>,
D: KeyDerivation + Ratcheting,
{
pub fn derive_from<K, M>(cipher_suite: CipherSuite, key_id: K, key_material: M) -> Result<Self>
where
K: Into<RatchetingKeyId>,
M: AsRef<[u8]>,
{
let key_id = key_id.into();
let enc_key =
GenericEncryptionKey::<A, D>::derive_from(cipher_suite, key_id, &key_material)?;
let material = GenericRatchetingKeyMaterial::derive_from(cipher_suite, key_material)?;
Ok(Self {
key_id,
material,
enc_key,
ratcheted_from: None,
})
}
pub fn key_material(&self) -> &GenericRatchetingKeyMaterial<D> {
&self.material
}
pub fn ratchet(&self) -> Result<Self> {
let key_id = self.key_id.inc_ratchet_step();
let enc_key =
GenericEncryptionKey::derive_from(self.enc_key.cipher_suite(), key_id, &self.material)?;
let material = self.material.ratchet()?;
Ok(Self {
key_id,
material,
enc_key,
ratcheted_from: Some(self.key_id),
})
}
pub fn key_id(&self) -> RatchetingKeyId {
self.key_id
}
pub fn ratcheted_from(&self) -> Option<RatchetingKeyId> {
self.ratcheted_from
}
}
impl<A, D> AsRef<GenericEncryptionKey<A, D>> for GenericRatchetingEncryptionKey<A, D>
where
A: AeadEncrypt<Secret = D::Secret>,
D: KeyDerivation + Ratcheting,
{
fn as_ref(&self) -> &GenericEncryptionKey<A, D> {
&self.enc_key
}
}
pub struct GenericRatchetingDecryptionKey<A, D>
where
A: AeadDecrypt<Secret = D::Secret>,
D: KeyDerivation + Ratcheting,
{
material: GenericRatchetingKeyMaterial<D>,
dec_key: GenericDecryptionKey<A, D>,
key_id: RatchetingKeyId,
ratcheted_from: Option<RatchetingKeyId>,
}
impl<A, D> GenericRatchetingDecryptionKey<A, D>
where
A: AeadDecrypt<Secret = D::Secret>,
D: KeyDerivation + Ratcheting,
{
pub fn derive_from<K, M>(cipher_suite: CipherSuite, key_id: K, key_material: M) -> Result<Self>
where
K: Into<RatchetingKeyId>,
M: AsRef<[u8]>,
{
let key_id = key_id.into();
let dec_key = GenericDecryptionKey::derive_from(cipher_suite, key_id, &key_material)?;
let material = GenericRatchetingKeyMaterial::derive_from(cipher_suite, key_material)?;
Ok(Self {
dec_key,
key_id,
material,
ratcheted_from: None,
})
}
pub fn key_material(&self) -> &GenericRatchetingKeyMaterial<D> {
&self.material
}
pub fn key_id(&self) -> RatchetingKeyId {
self.key_id
}
pub fn ratchet(&self) -> Result<Self>
where
A: Clone,
D::Secret: Clone,
{
self.ratchet_by(RatchetStepDiff::ONE)
}
pub fn ratchet_to(&self, key_id: RatchetingKeyId, max_steps: RatchetStepDiff) -> Result<Self>
where
A: Clone,
D::Secret: Clone,
{
let steps = self.key_id.steps_to(key_id)?;
if steps > max_steps {
return Err(SframeError::RatchetingFailure);
}
self.ratchet_by(steps)
}
fn ratchet_by(&self, steps: RatchetStepDiff) -> Result<Self>
where
A: Clone,
D::Secret: Clone,
{
let mut key_id = self.key_id;
let mut dec_key = self.dec_key.clone();
let mut material = self.material.clone();
for _ in 0..u64::from(steps) {
key_id = key_id.inc_ratchet_step();
dec_key =
GenericDecryptionKey::derive_from(self.dec_key.cipher_suite(), key_id, &material)?;
material = material.ratchet()?;
}
Ok(Self {
dec_key,
material,
ratcheted_from: (key_id != self.key_id).then_some(self.key_id),
key_id,
})
}
pub fn ratcheted_from(&self) -> Option<RatchetingKeyId> {
self.ratcheted_from
}
}
impl<A, D> AsRef<GenericDecryptionKey<A, D>> for GenericRatchetingDecryptionKey<A, D>
where
A: AeadDecrypt<Secret = D::Secret>,
D: KeyDerivation + Ratcheting,
{
fn as_ref(&self) -> &GenericDecryptionKey<A, D> {
&self.dec_key
}
}
impl<A, D> KeyLookup<A, D> for GenericRatchetingDecryptionKey<A, D>
where
A: AeadDecrypt<Secret = D::Secret>,
D: KeyDerivation + Ratcheting,
{
fn get_key<K>(&self, key_id: K) -> Option<&GenericDecryptionKey<A, D>>
where
K: Into<KeyId>,
{
self.dec_key.get_key(key_id)
}
}
#[cfg(all(test, crypto_backend))]
mod test {
use crate::{
CipherSuite,
crypto::{Aead, Kdf},
ratchet::{RatchetBits, RatchetStep, RatchetStepDiff, RatchetingKeyId},
};
use pretty_assertions::assert_eq;
type RatchetingEncryptionKey = super::GenericRatchetingEncryptionKey<Aead, Kdf>;
type RatchetingDecryptionKey = super::GenericRatchetingDecryptionKey<Aead, Kdf>;
const SECRET: &[u8] = b"SuperSecret";
const CIPHER_SUITE: CipherSuite = CipherSuite::AesGcm128Sha256;
fn max_steps() -> RatchetStepDiff {
RatchetStepDiff::from(2)
}
fn key_id() -> RatchetingKeyId {
RatchetingKeyId::new(42u8, RatchetBits::new(4))
}
fn encryption_key() -> RatchetingEncryptionKey {
RatchetingEncryptionKey::derive_from(CIPHER_SUITE, key_id(), SECRET).unwrap()
}
fn decryption_key() -> RatchetingDecryptionKey {
RatchetingDecryptionKey::derive_from(CIPHER_SUITE, key_id(), SECRET).unwrap()
}
fn advanced_key_id(n_steps: u64) -> RatchetingKeyId {
(0..n_steps).fold(key_id(), |key_id, _| key_id.inc_ratchet_step())
}
#[test]
fn derive_encryption_key() {
let key = encryption_key();
assert_eq!(key.key_id().ratchet_step(), RatchetStep::from(0));
assert_eq!(None, key.ratcheted_from());
}
#[test]
fn ratchets_encryption_key() {
let original_key = encryption_key();
let ratcheted_key = original_key.ratchet().unwrap();
assert_eq!(ratcheted_key.key_id().ratchet_step(), RatchetStep::from(1));
assert_eq!(Some(key_id()), ratcheted_key.ratcheted_from());
assert_ne!(original_key.as_ref(), ratcheted_key.as_ref());
assert_ne!(
original_key.key_material().as_ref(),
ratcheted_key.key_material().as_ref()
);
}
#[test]
fn derive_decryption_key() {
let key = decryption_key();
assert_eq!(RatchetStep::from(0), key.key_id().ratchet_step());
assert_eq!(None, key.ratcheted_from());
}
#[test]
fn doesnt_ratchet_decryption_key() {
let original_key = decryption_key();
let key = original_key.ratchet_to(key_id(), max_steps()).unwrap();
assert_eq!(RatchetStep::from(0), key.key_id().ratchet_step());
assert_eq!(original_key.as_ref(), key.as_ref());
assert_eq!(
original_key.key_material().as_ref(),
key.key_material().as_ref()
);
assert_eq!(None, key.ratcheted_from());
}
#[test]
fn ratchets_decryption_key_once() {
let original_key = decryption_key();
let key = original_key.ratchet().unwrap();
assert_eq!(RatchetStep::from(1), key.key_id().ratchet_step());
assert_ne!(original_key.as_ref(), key.as_ref());
assert_ne!(
original_key.key_material().as_ref(),
key.key_material().as_ref()
);
assert_eq!(Some(key_id()), key.ratcheted_from());
}
#[test]
fn ratchets_decryption_key_up_to_a_key_id() {
let original_key = decryption_key();
let key = original_key
.ratchet_to(advanced_key_id(2), max_steps())
.unwrap();
let step_by_step = original_key.ratchet().unwrap().ratchet().unwrap();
assert_eq!(RatchetStep::from(2), key.key_id().ratchet_step());
assert_eq!(step_by_step.as_ref(), key.as_ref());
assert_eq!(
step_by_step.key_material().as_ref(),
key.key_material().as_ref()
);
assert_eq!(Some(key_id()), key.ratcheted_from());
}
#[test]
fn rejects_a_key_id_further_ahead_than_the_maximum() {
let key = decryption_key();
let too_far = key.ratchet_to(advanced_key_id(3), max_steps());
assert!(too_far.is_err());
}
}