use aes_gcm::aead::consts::{U12, U16};
use aes_gcm::aes::{Aes128, Aes192, Aes256};
use aes_gcm::{AeadCore, AeadInOut, AesGcm, KeyInit, Nonce, Tag};
use zeroize::{Zeroize, Zeroizing};
use super::content::cek_len;
use crate::jwa::ContentEncAlg;
use crate::{Error, Result};
const IV_LEN: usize = 12;
const TAG_LEN: usize = 16;
type Aes192Gcm = AesGcm<Aes192, U12>;
type Aes128Gcm = AesGcm<Aes128, U12>;
type Aes256Gcm = AesGcm<Aes256, U12>;
pub(super) fn encrypt(
enc: &ContentEncAlg,
cek: &[u8],
iv: &[u8],
aad: &[u8],
plaintext: &[u8],
) -> Result<(Vec<u8>, Vec<u8>)> {
let seal = match enc {
ContentEncAlg::A128GCM => seal::<Aes128Gcm>,
ContentEncAlg::A192GCM => seal::<Aes192Gcm>,
ContentEncAlg::A256GCM => seal::<Aes256Gcm>,
_ => return Err(Error::UnsupportedAlgorithm),
};
if cek.len() != cek_len(enc)? {
return Err(Error::InvalidKey("CEK length does not match enc"));
}
if iv.len() != IV_LEN {
return Err(Error::InvalidFormat("content IV length does not match enc"));
}
seal(cek, iv, aad, plaintext)
}
pub(super) fn decrypt(
enc: &ContentEncAlg,
cek: &[u8],
iv: &[u8],
aad: &[u8],
ciphertext: &[u8],
tag: &[u8],
) -> Result<Zeroizing<Vec<u8>>> {
let open = match enc {
ContentEncAlg::A128GCM => open::<Aes128Gcm>,
ContentEncAlg::A192GCM => open::<Aes192Gcm>,
ContentEncAlg::A256GCM => open::<Aes256Gcm>,
_ => return Err(Error::DecryptionFailed),
};
if iv.len() != IV_LEN || tag.len() != TAG_LEN || Ok(cek.len()) != cek_len(enc) {
return Err(Error::DecryptionFailed);
}
open(cek, iv, aad, ciphertext, tag)
}
fn seal<C>(cek: &[u8], iv: &[u8], aad: &[u8], plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>)>
where
C: KeyInit + AeadInOut + AeadCore<NonceSize = U12, TagSize = U16>,
{
let cipher =
C::new_from_slice(cek).map_err(|_| Error::InvalidKey("CEK length does not match enc"))?;
let nonce = Nonce::<U12>::try_from(iv)
.map_err(|_| Error::InvalidFormat("content IV length does not match enc"))?;
let mut buffer = plaintext.to_vec();
match cipher.encrypt_inout_detached(&nonce, aad, buffer.as_mut_slice().into()) {
Ok(tag) => Ok((buffer, tag.to_vec())),
Err(_) => {
buffer.zeroize();
Err(Error::InvalidFormat(
"plaintext or AAD too long for AES-GCM",
))
}
}
}
fn open<C>(
cek: &[u8],
iv: &[u8],
aad: &[u8],
ciphertext: &[u8],
tag: &[u8],
) -> Result<Zeroizing<Vec<u8>>>
where
C: KeyInit + AeadInOut + AeadCore<NonceSize = U12, TagSize = U16>,
{
let cipher = C::new_from_slice(cek).map_err(|_| Error::DecryptionFailed)?;
let nonce = Nonce::<U12>::try_from(iv).map_err(|_| Error::DecryptionFailed)?;
let tag = Tag::<U16>::try_from(tag).map_err(|_| Error::DecryptionFailed)?;
let mut buffer = Zeroizing::new(ciphertext.to_vec());
cipher
.decrypt_inout_detached(&nonce, aad, buffer.as_mut_slice().into(), &tag)
.map_err(|_| Error::DecryptionFailed)?;
Ok(buffer)
}
#[cfg(test)]
mod tests {
use super::*;
const GCM: [ContentEncAlg; 3] = [
ContentEncAlg::A128GCM,
ContentEncAlg::A192GCM,
ContentEncAlg::A256GCM,
];
const NOT_GCM: [ContentEncAlg; 3] = [
ContentEncAlg::A128CbcHs256,
ContentEncAlg::A192CbcHs384,
ContentEncAlg::A256CbcHs512,
];
fn key(enc: &ContentEncAlg) -> Vec<u8> {
(0..cek_len(enc).unwrap()).map(|i| i as u8 ^ 0x5A).collect()
}
const IV: [u8; 12] = [7; 12];
const AAD: &[u8] = b"eyJlbmMiOiJBMTI4R0NNIn0";
const MSG: &[u8] = b"Live long and prosper.";
#[test]
fn round_trips_with_a_detached_16_byte_tag() {
for enc in &GCM {
let (ct, tag) = encrypt(enc, &key(enc), &IV, AAD, MSG).unwrap();
assert_eq!((ct.len(), tag.len()), (MSG.len(), TAG_LEN), "{enc}");
assert_ne!(ct, MSG);
let pt = decrypt(enc, &key(enc), &IV, AAD, &ct, &tag).unwrap();
assert_eq!(pt.as_slice(), MSG, "{enc}");
}
let (ct, tag) = encrypt(&GCM[0], &key(&GCM[0]), &IV, b"", b"").unwrap();
assert_eq!((ct.len(), tag.len()), (0, TAG_LEN));
let pt = decrypt(&GCM[0], &key(&GCM[0]), &IV, b"", &ct, &tag).unwrap();
assert!(pt.is_empty());
}
#[test]
fn the_three_key_sizes_are_three_different_ciphers() {
let long = key(&ContentEncAlg::A256GCM);
let outs: Vec<_> = GCM
.iter()
.map(|enc| {
let k = &long[..cek_len(enc).unwrap()];
encrypt(enc, k, &IV, AAD, MSG).unwrap()
})
.collect();
assert_ne!(outs[0], outs[1]);
assert_ne!(outs[1], outs[2]);
assert_ne!(outs[0], outs[2]);
}
#[test]
fn decrypt_rejects_every_wrong_iv_or_tag_length() {
for enc in &GCM {
let k = key(enc);
let (ct, tag) = encrypt(enc, &k, &IV, AAD, MSG).unwrap();
for n in [0, 1, 8, 11, 13, 16, 32] {
let iv = vec![7u8; n];
assert_eq!(
decrypt(enc, &k, &iv, AAD, &ct, &tag),
Err(Error::DecryptionFailed),
"{enc}: IV of {n} bytes"
);
}
for n in [0, 1, 4, 8, 12, 15] {
assert_eq!(
decrypt(enc, &k, &IV, AAD, &ct, &tag[..n]),
Err(Error::DecryptionFailed),
"{enc}: tag prefix of {n} bytes"
);
}
let mut long_tag = tag.clone();
long_tag.push(0);
assert_eq!(
decrypt(enc, &k, &IV, AAD, &ct, &long_tag),
Err(Error::DecryptionFailed),
"{enc}: 17-byte tag"
);
}
}
#[test]
fn decrypt_rejects_a_cek_of_another_size() {
for enc in &GCM {
let (ct, tag) = encrypt(enc, &key(enc), &IV, AAD, MSG).unwrap();
for n in [0, 15, 16, 17, 24, 31, 32, 33, 64] {
if n == cek_len(enc).unwrap() {
continue;
}
assert_eq!(
decrypt(enc, &vec![0x5A; n], &IV, AAD, &ct, &tag),
Err(Error::DecryptionFailed),
"{enc}: CEK of {n} bytes"
);
}
}
}
#[test]
fn decrypt_rejects_a_tag_mismatch_without_plaintext() {
for enc in &GCM {
let k = key(enc);
let (ct, tag) = encrypt(enc, &k, &IV, AAD, MSG).unwrap();
let mut bad_tag = tag.clone();
bad_tag[15] ^= 1;
let mut bad_ct = ct.clone();
bad_ct[0] ^= 0x80;
let mut bad_key = k.clone();
bad_key[0] ^= 1;
let cases: [[&[u8]; 5]; 5] = [
[&k, &IV, AAD, &ct, &bad_tag],
[&k, &IV, AAD, &bad_ct, &tag],
[&k, &[8; 12], AAD, &ct, &tag],
[&k, &IV, b"eyJlbmMiOiJBMTI4R0NNIn1", &ct, &tag],
[&bad_key, &IV, AAD, &ct, &tag],
];
for [key, iv, aad, ct, tag] in cases {
assert_eq!(
decrypt(enc, key, iv, aad, ct, tag),
Err(Error::DecryptionFailed),
"{enc}"
);
}
}
}
#[test]
fn non_gcm_enc_is_refused_before_any_work() {
for enc in NOT_GCM.iter().chain([&ContentEncAlg::from("A128CTR")]) {
assert_eq!(
encrypt(enc, &[0; 32], &[0; 16], b"", MSG),
Err(Error::UnsupportedAlgorithm),
"{enc}"
);
assert_eq!(
decrypt(enc, &[0; 32], &IV, b"", MSG, &[0; 16]),
Err(Error::DecryptionFailed),
"{enc}"
);
}
}
#[test]
fn encrypt_rejects_a_wrong_cek_or_iv_length() {
for enc in &GCM {
let n = cek_len(enc).unwrap();
for bad in [n - 1, n + 1, n + 8] {
assert_eq!(
encrypt(enc, &vec![0; bad], &IV, AAD, MSG),
Err(Error::InvalidKey("CEK length does not match enc")),
"{enc}: CEK of {bad} bytes"
);
}
for bad in [0, 11, 13, 16] {
assert_eq!(
encrypt(enc, &key(enc), &vec![0; bad], AAD, MSG),
Err(Error::InvalidFormat("content IV length does not match enc")),
"{enc}: IV of {bad} bytes"
);
}
}
}
}