use aws_lc_rs::aead::{
Aad, LessSafeKey, Nonce, UnboundKey, AES_128_GCM, AES_256_GCM, CHACHA20_POLY1305,
};
use oxicrypto_core::{Aead, CryptoError};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Algo {
Aes128Gcm,
Aes256Gcm,
ChaCha20Poly1305,
}
#[derive(Debug, Clone, Copy)]
pub struct AwsLcAead {
algo: Algo,
}
impl AwsLcAead {
#[must_use]
pub fn aes128_gcm() -> Self {
Self {
algo: Algo::Aes128Gcm,
}
}
#[must_use]
pub fn aes256_gcm() -> Self {
Self {
algo: Algo::Aes256Gcm,
}
}
#[must_use]
pub fn chacha20_poly1305() -> Self {
Self {
algo: Algo::ChaCha20Poly1305,
}
}
fn make_less_safe_key(&self, key: &[u8]) -> Result<LessSafeKey, CryptoError> {
let algo = match self.algo {
Algo::Aes128Gcm => &AES_128_GCM,
Algo::Aes256Gcm => &AES_256_GCM,
Algo::ChaCha20Poly1305 => &CHACHA20_POLY1305,
};
let unbound = UnboundKey::new(algo, key).map_err(|_| CryptoError::InvalidKey)?;
Ok(LessSafeKey::new(unbound))
}
fn make_nonce(nonce: &[u8]) -> Result<Nonce, CryptoError> {
Nonce::try_assume_unique_for_key(nonce).map_err(|_| CryptoError::InvalidNonce)
}
}
impl Aead for AwsLcAead {
fn name(&self) -> &'static str {
match self.algo {
Algo::Aes128Gcm => "AES-128-GCM (aws-lc-rs)",
Algo::Aes256Gcm => "AES-256-GCM (aws-lc-rs)",
Algo::ChaCha20Poly1305 => "ChaCha20-Poly1305 (aws-lc-rs)",
}
}
fn key_len(&self) -> usize {
match self.algo {
Algo::Aes128Gcm => 16,
Algo::Aes256Gcm => 32,
Algo::ChaCha20Poly1305 => 32,
}
}
fn nonce_len(&self) -> usize {
12
}
fn tag_len(&self) -> usize {
16
}
fn seal(
&self,
key: &[u8],
nonce: &[u8],
aad: &[u8],
pt: &[u8],
ct_out: &mut [u8],
) -> Result<usize, CryptoError> {
let required = pt
.len()
.checked_add(self.tag_len())
.ok_or(CryptoError::BadInput)?;
if ct_out.len() < required {
return Err(CryptoError::BufferTooSmall);
}
let less_safe = self.make_less_safe_key(key)?;
let nonce_val = Self::make_nonce(nonce)?;
ct_out[..pt.len()].copy_from_slice(pt);
let tag = less_safe
.seal_in_place_separate_tag(nonce_val, Aad::from(aad), &mut ct_out[..pt.len()])
.map_err(|_| CryptoError::Internal("aws-lc-rs AEAD seal failed"))?;
ct_out[pt.len()..required].copy_from_slice(tag.as_ref());
Ok(required)
}
fn open(
&self,
key: &[u8],
nonce: &[u8],
aad: &[u8],
ct: &[u8],
pt_out: &mut [u8],
) -> Result<usize, CryptoError> {
if ct.len() < self.tag_len() {
return Err(CryptoError::BadInput);
}
let pt_len = ct.len() - self.tag_len();
if pt_out.len() < pt_len {
return Err(CryptoError::BufferTooSmall);
}
let less_safe = self.make_less_safe_key(key)?;
let nonce_val = Self::make_nonce(nonce)?;
let ciphertext = &ct[..pt_len];
let tag = &ct[pt_len..];
less_safe
.open_separate_gather(
nonce_val,
Aad::from(aad),
ciphertext,
tag,
&mut pt_out[..pt_len],
)
.map_err(|_| CryptoError::InvalidTag)?;
Ok(pt_len)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn round_trip(cipher: AwsLcAead, key: &[u8]) {
let nonce = vec![0x11u8; 12];
let aad = b"aws-lc adapter aad";
let pt = b"hello from aws-lc-rs";
let mut ct = vec![0u8; pt.len() + cipher.tag_len()];
let written = cipher.seal(key, &nonce, aad, pt, &mut ct).expect("seal");
assert_eq!(written, pt.len() + 16);
let mut recovered = vec![0u8; pt.len()];
let n = cipher
.open(key, &nonce, aad, &ct[..written], &mut recovered)
.expect("open");
assert_eq!(&recovered[..n], pt.as_ref());
}
#[test]
fn aes128gcm_round_trip() {
round_trip(AwsLcAead::aes128_gcm(), &[0x42u8; 16]);
}
#[test]
fn aes256gcm_round_trip() {
round_trip(AwsLcAead::aes256_gcm(), &[0x42u8; 32]);
}
#[test]
fn chacha20poly1305_round_trip() {
round_trip(AwsLcAead::chacha20_poly1305(), &[0x42u8; 32]);
}
#[test]
fn wrong_tag_fails() {
let cipher = AwsLcAead::aes256_gcm();
let key = [0x55u8; 32];
let nonce = [0x22u8; 12];
let pt = b"secret data";
let mut ct = vec![0u8; pt.len() + 16];
cipher.seal(&key, &nonce, b"", pt, &mut ct).expect("seal");
let last = ct.len() - 1;
ct[last] ^= 0xff;
let mut pt_out = vec![0u8; pt.len()];
assert_eq!(
cipher.open(&key, &nonce, b"", &ct, &mut pt_out),
Err(CryptoError::InvalidTag)
);
}
}