use aes_gcm::aead::{Aead, KeyInit, Payload};
use aes_gcm::{Aes256Gcm, Nonce};
use rand::RngCore;
use zeroize::Zeroizing;
use super::SealError;
pub const KEY_LEN: usize = 32;
pub const NONCE_LEN: usize = 12;
#[must_use]
pub fn fresh_nonce() -> [u8; NONCE_LEN] {
let mut nonce = [0u8; NONCE_LEN];
rand::rngs::OsRng.fill_bytes(&mut nonce);
nonce
}
#[must_use]
pub fn fresh_key() -> Zeroizing<[u8; KEY_LEN]> {
let mut key = Zeroizing::new([0u8; KEY_LEN]);
rand::rngs::OsRng.fill_bytes(key.as_mut());
key
}
pub fn seal(
key: &[u8; KEY_LEN],
nonce: &[u8; NONCE_LEN],
aad: &[u8],
plaintext: &[u8],
) -> Result<Vec<u8>, SealError> {
let cipher = Aes256Gcm::new(key.into());
let nonce = Nonce::from(*nonce);
cipher
.encrypt(
&nonce,
Payload {
msg: plaintext,
aad,
},
)
.map_err(|_| SealError::Crypto("aes-256-gcm seal failed".into()))
}
pub fn open(
key: &[u8; KEY_LEN],
nonce: &[u8; NONCE_LEN],
aad: &[u8],
ciphertext: &[u8],
) -> Result<Zeroizing<Vec<u8>>, SealError> {
let cipher = Aes256Gcm::new(key.into());
let nonce = Nonce::from(*nonce);
cipher
.decrypt(
&nonce,
Payload {
msg: ciphertext,
aad,
},
)
.map(Zeroizing::new)
.map_err(|_| SealError::AuthFailed)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip() {
let key = fresh_key();
let nonce = fresh_nonce();
let aad = b"header-bytes";
let msg = b"the master kek or cred map";
let ct = seal(&key, &nonce, aad, msg).unwrap();
let pt = open(&key, &nonce, aad, &ct).unwrap();
assert_eq!(pt.as_slice(), msg);
}
#[test]
fn wrong_aad_fails_closed() {
let key = fresh_key();
let nonce = fresh_nonce();
let ct = seal(&key, &nonce, b"aad-a", b"msg").unwrap();
let err = open(&key, &nonce, b"aad-b", &ct).unwrap_err();
assert!(matches!(err, SealError::AuthFailed));
}
#[test]
fn wrong_key_fails_closed() {
let key = fresh_key();
let other = fresh_key();
let nonce = fresh_nonce();
let ct = seal(&key, &nonce, b"aad", b"msg").unwrap();
assert!(matches!(
open(&other, &nonce, b"aad", &ct),
Err(SealError::AuthFailed)
));
}
}