use std::fmt::Debug;
use hmac::{Hmac, Mac};
use sha2::Sha256;
mod private {
pub trait Sealed {}
impl Sealed for super::Deterministic {}
impl Sealed for super::Randomized {}
}
pub trait Strategy: private::Sealed + Debug {
fn generate_nonce_for(payload: &[u8], key: &[u8; 32]) -> [u8; 12];
}
#[derive(Debug, PartialEq, Eq)]
pub struct Deterministic;
impl Strategy for Deterministic {
fn generate_nonce_for(payload: &[u8], key: &[u8; 32]) -> [u8; 12] {
let mut mac = Hmac::<Sha256>::new_from_slice(key).unwrap();
mac.update(payload);
mac.finalize().into_bytes()[0..12].try_into().unwrap()
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Randomized;
impl Strategy for Randomized {
fn generate_nonce_for(_payload: &[u8], _key: &[u8; 32]) -> [u8; 12] {
rand::random()
}
}
#[cfg(test)]
mod tests {
use super::*;
use secrecy::ExposeSecret as _;
use crate::{
config::Config as _,
testing::{TestConfigDeterministic, TestConfigRandomized},
utilities::base64,
};
mod deterministic {
use super::*;
#[test]
fn nonce_is_deterministic() {
let key = TestConfigDeterministic.primary_key();
let nonce = Deterministic::generate_nonce_for("rigo is cool".as_bytes(), key.expose_secret());
assert_eq!(nonce.len(), 12);
assert_eq!(nonce, *base64::decode("Ts2jGkMEW9NFsQZX").unwrap());
}
}
mod randomized {
use super::*;
#[test]
fn nonce_is_randomized() {
let payload = "much secret much secure".as_bytes();
let key = TestConfigRandomized.primary_key();
let first_nonce = Randomized::generate_nonce_for(payload, key.expose_secret());
let second_nonce = Randomized::generate_nonce_for(payload, key.expose_secret());
assert_eq!(first_nonce.len(), 12);
assert_eq!(second_nonce.len(), 12);
assert_ne!(first_nonce, second_nonce);
}
}
}