mod boxed;
mod error;
mod multi;
use std::borrow::Cow;
use bon::Builder;
pub use boxed::{BoxedAeadCipher, BoxedAeadDecryptor, BoxedAeadEncryptor};
pub(crate) use error::InvalidBundleSnafu;
pub use error::UnsealError;
pub use multi::{MultiKeyCipher, MultiKeyDecryptor, MultiKeyDecryptorError};
use snafu::ensure;
use crate::{
crypto::KeyMatchStrength,
platform::{MaybeSend, MaybeSendSync},
};
pub struct AeadOutput {
pub nonce: Vec<u8>,
pub ciphertext: Vec<u8>,
pub tag: Vec<u8>,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Builder)]
pub struct CipherMatch<'a> {
pub enc: Option<&'a str>,
pub kid: Option<&'a str>,
}
pub trait AeadEncryptor: MaybeSendSync {
type Error: crate::Error;
fn enc_algorithm(&self) -> Cow<'_, str>;
fn key_id(&self) -> Option<Cow<'_, str>>;
fn encrypt(
&self,
plaintext: &[u8],
aad: &[u8],
) -> impl Future<Output = Result<AeadOutput, Self::Error>> + MaybeSend;
}
pub trait AeadDecryptor: MaybeSendSync {
type Error: crate::Error;
fn cipher_match(&self, m: &CipherMatch<'_>) -> Option<KeyMatchStrength>;
fn decrypt(
&self,
cipher_match: Option<&CipherMatch<'_>>,
nonce: &[u8],
ciphertext: &[u8],
tag: &[u8],
aad: &[u8],
) -> impl Future<Output = Result<Vec<u8>, Self::Error>> + MaybeSend;
}
pub trait AeadCipherSelector: MaybeSendSync {
type Encryptor: AeadEncryptor;
fn select_cipher(&self) -> Self::Encryptor;
}
pub trait AeadSealer: AeadEncryptor {
fn seal(
&self,
plaintext: &[u8],
aad: &[u8],
) -> impl Future<Output = Result<Vec<u8>, Self::Error>> + MaybeSend;
}
pub trait AeadUnsealer: AeadDecryptor {
fn unseal(
&self,
cipher_match: Option<&CipherMatch<'_>>,
bundle: &[u8],
aad: &[u8],
) -> impl Future<Output = Result<Vec<u8>, Self::Error>> + MaybeSend;
}
pub struct AeadV1Sealer<E: AeadEncryptor>(E);
impl<E: AeadEncryptor> AeadV1Sealer<E> {
pub fn new(encryptor: E) -> Self {
Self(encryptor)
}
}
impl<E: AeadEncryptor> AeadEncryptor for AeadV1Sealer<E> {
type Error = E::Error;
fn enc_algorithm(&self) -> Cow<'_, str> {
self.0.enc_algorithm()
}
fn key_id(&self) -> Option<Cow<'_, str>> {
self.0.key_id()
}
async fn encrypt(&self, plaintext: &[u8], aad: &[u8]) -> Result<AeadOutput, Self::Error> {
self.0.encrypt(plaintext, aad).await
}
}
impl<E: AeadEncryptor> AeadSealer for AeadV1Sealer<E> {
async fn seal(&self, plaintext: &[u8], aad: &[u8]) -> Result<Vec<u8>, Self::Error> {
let output = self.encrypt(plaintext, aad).await?;
let nonce_len: u8 = output
.nonce
.len()
.try_into()
.expect("nonce length exceeds u8::MAX");
let tag_len: u8 = output
.tag
.len()
.try_into()
.expect("tag length exceeds u8::MAX");
let mut bundle =
Vec::with_capacity(3 + output.nonce.len() + output.ciphertext.len() + output.tag.len());
bundle.push(0x01);
bundle.push(nonce_len);
bundle.push(tag_len);
bundle.extend_from_slice(&output.nonce);
bundle.extend_from_slice(&output.ciphertext);
bundle.extend_from_slice(&output.tag);
Ok(bundle)
}
}
pub struct AeadV1Unsealer<D: AeadDecryptor>(D);
impl<D: AeadDecryptor> AeadV1Unsealer<D> {
pub fn new(decryptor: D) -> Self {
Self(decryptor)
}
}
impl<D: AeadDecryptor> AeadDecryptor for AeadV1Unsealer<D> {
type Error = UnsealError<D::Error>;
fn cipher_match(&self, m: &CipherMatch<'_>) -> Option<KeyMatchStrength> {
self.0.cipher_match(m)
}
async fn decrypt(
&self,
cipher_match: Option<&CipherMatch<'_>>,
nonce: &[u8],
ciphertext: &[u8],
tag: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, Self::Error> {
self.0
.decrypt(cipher_match, nonce, ciphertext, tag, aad)
.await
.map_err(Into::into)
}
}
impl<D: AeadDecryptor> AeadUnsealer for AeadV1Unsealer<D> {
async fn unseal(
&self,
cipher_match: Option<&CipherMatch<'_>>,
bundle: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, Self::Error> {
ensure!(bundle.len() >= 3 && bundle[0] == 0x01, InvalidBundleSnafu);
let nonce_len = bundle[1] as usize;
let tag_len = bundle[2] as usize;
ensure!(bundle.len() >= 3 + nonce_len + tag_len, InvalidBundleSnafu);
let nonce = &bundle[3..3 + nonce_len];
let tag = &bundle[bundle.len() - tag_len..];
let ciphertext = &bundle[3 + nonce_len..bundle.len() - tag_len];
self.decrypt(cipher_match, nonce, ciphertext, tag, aad)
.await
}
}
#[cfg(test)]
mod tests {
use std::convert::Infallible;
use super::*;
#[derive(Debug)]
struct MockEncryptor;
impl AeadEncryptor for MockEncryptor {
type Error = Infallible;
fn enc_algorithm(&self) -> Cow<'_, str> {
"mock".into()
}
fn key_id(&self) -> Option<Cow<'_, str>> {
None
}
async fn encrypt(&self, plaintext: &[u8], _aad: &[u8]) -> Result<AeadOutput, Infallible> {
Ok(AeadOutput {
nonce: vec![1, 2, 3],
ciphertext: plaintext.iter().map(|b| b ^ 0xFF).collect(),
tag: vec![4, 5, 6, 7],
})
}
}
#[derive(Debug)]
struct MockDecryptor;
impl AeadDecryptor for MockDecryptor {
type Error = Infallible;
fn cipher_match(&self, _m: &CipherMatch<'_>) -> Option<KeyMatchStrength> {
Some(KeyMatchStrength::ByAlgorithm)
}
async fn decrypt(
&self,
_cipher_match: Option<&CipherMatch<'_>>,
_nonce: &[u8],
ciphertext: &[u8],
_tag: &[u8],
_aad: &[u8],
) -> Result<Vec<u8>, Infallible> {
Ok(ciphertext.iter().map(|b| b ^ 0xFF).collect())
}
}
#[tokio::test]
async fn seal_roundtrip() {
let plaintext = b"hello world";
let aad = b"associated";
let sealer = AeadV1Sealer::new(MockEncryptor);
let bundle = sealer.seal(plaintext, aad).await.unwrap();
let unsealer = AeadV1Unsealer::new(MockDecryptor);
let recovered = unsealer.unseal(None, &bundle, aad).await.unwrap();
assert_eq!(recovered, plaintext);
}
#[tokio::test]
async fn bundle_format() {
let plaintext = b"AB";
let sealer = AeadV1Sealer::new(MockEncryptor);
let bundle = sealer.seal(plaintext, b"").await.unwrap();
assert_eq!(bundle[0], 0x01);
assert_eq!(bundle[1], 3); assert_eq!(bundle[2], 4); assert_eq!(&bundle[3..6], &[1, 2, 3]); assert_eq!(&bundle[6..8], &[0x41 ^ 0xFF, 0x42 ^ 0xFF]); assert_eq!(&bundle[8..12], &[4, 5, 6, 7]); }
#[tokio::test]
async fn unseal_wrong_version() {
let unsealer = AeadV1Unsealer::new(MockDecryptor);
let result = unsealer.unseal(None, &[0x02, 0, 0], b"").await;
assert!(matches!(result, Err(UnsealError::InvalidBundle)));
}
#[tokio::test]
async fn unseal_too_short() {
let unsealer = AeadV1Unsealer::new(MockDecryptor);
let result = unsealer.unseal(None, &[0x01], b"").await;
assert!(matches!(result, Err(UnsealError::InvalidBundle)));
}
#[tokio::test]
async fn unseal_truncated() {
let unsealer = AeadV1Unsealer::new(MockDecryptor);
let result = unsealer.unseal(None, &[0x01, 10, 10, 0x00], b"").await;
assert!(matches!(result, Err(UnsealError::InvalidBundle)));
}
#[tokio::test]
async fn unseal_empty() {
let unsealer = AeadV1Unsealer::new(MockDecryptor);
let result = unsealer.unseal(None, &[], b"").await;
assert!(matches!(result, Err(UnsealError::InvalidBundle)));
}
}