use std::fmt;
use ::aes_gcm::{
aead::{Aead, Nonce, Payload},
Aes128Gcm, Aes256Gcm, KeyInit,
};
use crate::{iana, Encryptor, Error, Key, Label};
const NONCE_LEN: usize = 12;
#[derive(Clone)]
pub struct AesGcmEncryptor {
alg: i64,
kid: Option<Vec<u8>>,
cipher: AesGcmCipher,
raw_key: Vec<u8>,
base_iv: Option<Vec<u8>>,
}
#[derive(Clone)]
enum AesGcmCipher {
Aes128(Box<Aes128Gcm>),
Aes256(Box<Aes256Gcm>),
}
impl AesGcmEncryptor {
pub fn new(alg: i64, key: &[u8], kid: Option<Vec<u8>>) -> Result<Self, Error> {
let cipher = match alg {
iana::AlgorithmA128GCM => AesGcmCipher::Aes128(Box::new(
Aes128Gcm::new_from_slice(key)
.map_err(|_| Error::custom("invalid A128GCM key length"))?,
)),
iana::AlgorithmA256GCM => AesGcmCipher::Aes256(Box::new(
Aes256Gcm::new_from_slice(key)
.map_err(|_| Error::custom("invalid A256GCM key length"))?,
)),
_ => return Err(unsupported_alg(alg)),
};
Ok(Self {
alg,
kid,
cipher,
raw_key: key.to_vec(),
base_iv: None,
})
}
pub fn from_cose_key(key: &Key) -> Result<Self, Error> {
require_kty(key, iana::KeyTypeSymmetric)?;
let alg = required_alg(key)?;
let mut encryptor = Self::new(
alg,
required_bytes(key, iana::SymmetricKeyParameterK, "k")?,
key_kid(key)?,
)?;
encryptor.base_iv = key.base_iv()?.map(ToOwned::to_owned);
Ok(encryptor)
}
pub fn with_base_iv(mut self, base_iv: impl Into<Vec<u8>>) -> Self {
self.base_iv = Some(base_iv.into());
self
}
pub fn to_cose_key(&self) -> Result<Key, Error> {
let mut key = Key::new();
key.set_kty(iana::KeyTypeSymmetric).set_alg(self.alg);
if let Some(kid) = &self.kid {
key.set_kid(kid.clone());
}
key.insert(iana::SymmetricKeyParameterK, self.raw_key.clone());
if let Some(base_iv) = &self.base_iv {
key.insert(iana::KeyParameterBaseIV, base_iv.clone());
}
Ok(key)
}
pub fn algorithm(&self) -> i64 {
self.alg
}
}
impl fmt::Debug for AesGcmEncryptor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AesGcmEncryptor")
.field("alg", &self.alg)
.field("kid", &self.kid)
.field("base_iv", &self.base_iv)
.finish_non_exhaustive()
}
}
impl Encryptor for AesGcmEncryptor {
fn alg(&self) -> Option<Label> {
Some(self.alg.into())
}
fn kid(&self) -> Option<&[u8]> {
self.kid.as_deref()
}
fn nonce_size(&self) -> usize {
NONCE_LEN
}
fn base_iv(&self) -> Option<&[u8]> {
self.base_iv.as_deref()
}
fn encrypt(&self, nonce: &[u8], plaintext: &[u8], aad: &[u8]) -> Result<Vec<u8>, Error> {
let payload = Payload {
msg: plaintext,
aad,
};
match &self.cipher {
AesGcmCipher::Aes128(c) => aead_seal(c.as_ref(), nonce, payload),
AesGcmCipher::Aes256(c) => aead_seal(c.as_ref(), nonce, payload),
}
}
fn decrypt(&self, nonce: &[u8], ciphertext: &[u8], aad: &[u8]) -> Result<Vec<u8>, Error> {
let payload = Payload {
msg: ciphertext,
aad,
};
match &self.cipher {
AesGcmCipher::Aes128(c) => aead_open(c.as_ref(), nonce, payload),
AesGcmCipher::Aes256(c) => aead_open(c.as_ref(), nonce, payload),
}
}
}
fn aead_seal<C: Aead>(cipher: &C, nonce: &[u8], payload: Payload) -> Result<Vec<u8>, Error> {
let nonce =
Nonce::<C>::try_from(nonce).map_err(|_| Error::custom("invalid AEAD nonce length"))?;
cipher
.encrypt(&nonce, payload)
.map_err(|_| Error::custom("AEAD encryption failed"))
}
fn aead_open<C: Aead>(cipher: &C, nonce: &[u8], payload: Payload) -> Result<Vec<u8>, Error> {
let nonce =
Nonce::<C>::try_from(nonce).map_err(|_| Error::custom("invalid AEAD nonce length"))?;
cipher
.decrypt(&nonce, payload)
.map_err(|_| Error::verify("AEAD authentication failed"))
}
fn required_alg(key: &Key) -> Result<i64, Error> {
match key.alg()? {
Some(Label::Int(alg)) => Ok(alg),
Some(Label::Text(_)) => Err(Error::custom(
"the built-in AES-GCM backend does not support text-string algorithms",
)),
None => Err(Error::custom("COSE_Key is missing alg")),
}
}
fn require_kty(key: &Key, expected: i64) -> Result<(), Error> {
match key.kty()? {
Some(Label::Int(kty)) if kty == expected => Ok(()),
Some(other) => Err(Error::custom(format!(
"COSE_Key kty mismatch, expected {}, got {}",
Label::from(expected),
other
))),
None => Err(Error::custom("COSE_Key is missing kty")),
}
}
fn required_bytes<'a>(key: &'a Key, label: i64, name: &str) -> Result<&'a [u8], Error> {
key.get_bytes(label)?
.ok_or_else(|| Error::custom(format!("COSE_Key is missing {name}")))
}
fn key_kid(key: &Key) -> Result<Option<Vec<u8>>, Error> {
Ok(key.kid()?.map(ToOwned::to_owned))
}
fn unsupported_alg(alg: i64) -> Error {
Error::custom(format!(
"unsupported AEAD algorithm {} for the built-in AES-GCM backend",
Label::from(alg)
))
}