use aes_gcm::aead::{Aead, KeyInit, Payload};
use aes_gcm::{Aes256Gcm, Key, Nonce};
use crate::message::SesameError;
pub const KEY_LEN: usize = 32;
pub const IV_LEN: usize = 12;
#[cfg(feature = "rng")]
pub fn random_iv() -> [u8; IV_LEN] {
use rand::rngs::OsRng;
use rand::RngCore;
let mut iv = [0u8; IV_LEN];
OsRng.fill_bytes(&mut iv);
iv
}
pub fn seal(
key: &[u8; KEY_LEN],
iv: &[u8; IV_LEN],
aad: &[u8],
plaintext: &[u8],
) -> Result<Vec<u8>, SesameError> {
let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(key));
cipher
.encrypt(
Nonce::from_slice(iv),
Payload {
msg: plaintext,
aad,
},
)
.map_err(|_| SesameError::DecryptFailed)
}
pub fn open(
key: &[u8; KEY_LEN],
iv: &[u8; IV_LEN],
aad: &[u8],
ciphertext: &[u8],
) -> Result<Vec<u8>, SesameError> {
let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(key));
cipher
.decrypt(
Nonce::from_slice(iv),
Payload {
msg: ciphertext,
aad,
},
)
.map_err(|_| SesameError::DecryptFailed)
}
pub fn aad_for_headers(
version: &str,
key_id: &str,
timestamp: &str,
nonce: &str,
scope: Option<&str>,
) -> Vec<u8> {
let mut s = String::new();
s.push_str(version);
s.push('\n');
s.push_str(key_id);
s.push('\n');
s.push_str(timestamp);
s.push('\n');
s.push_str(nonce);
if let Some(scope) = scope {
s.push('\n');
s.push_str(scope);
}
s.into_bytes()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::message::hex_decode;
#[test]
fn seal_open_roundtrip() {
let key = [7u8; KEY_LEN];
let iv = random_iv();
let aad = b"aad";
let pt = b"<SignalProcessingEvent/>";
let ct = seal(&key, &iv, aad, pt).unwrap();
assert_ne!(&ct[..], &pt[..]); assert_eq!(ct.len(), pt.len() + 16); assert_eq!(open(&key, &iv, aad, &ct).unwrap(), pt);
}
#[test]
fn tampered_tag_rejected() {
let key = [1u8; KEY_LEN];
let iv = random_iv();
let mut ct = seal(&key, &iv, b"", b"hello").unwrap();
let last = ct.len() - 1;
ct[last] ^= 0x01; assert_eq!(open(&key, &iv, b"", &ct), Err(SesameError::DecryptFailed));
}
#[test]
fn wrong_aad_rejected() {
let key = [2u8; KEY_LEN];
let iv = random_iv();
let ct = seal(&key, &iv, b"headers-A", b"x").unwrap();
assert_eq!(
open(&key, &iv, b"headers-B", &ct),
Err(SesameError::DecryptFailed)
);
}
#[test]
fn fresh_iv_each_call() {
assert_ne!(random_iv(), random_iv());
}
#[test]
fn nist_gcm_known_answer() {
let key =
hex_decode("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308").unwrap();
let mut k = [0u8; KEY_LEN];
k.copy_from_slice(&key);
let iv = hex_decode("cafebabefacedbaddecaf888").unwrap();
let mut nonce = [0u8; IV_LEN];
nonce.copy_from_slice(&iv);
let pt = hex_decode(
"d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a72\
1c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
)
.unwrap();
let aad = hex_decode("feedfacedeadbeeffeedfacedeadbeefabaddad2").unwrap();
let expected = hex_decode(
"522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa\
8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662\
76fc6ece0f4e1768cddf8853bb2d551b",
)
.unwrap();
let ct = seal(&k, &nonce, &aad, &pt).unwrap();
assert_eq!(
ct, expected,
"AES-256-GCM ciphertext||tag must match NIST vector"
);
}
}