use core::mem::ManuallyDrop;
use crate::Error;
use crate::low::{Zeroable, zeroise_value};
use crate::mid::aes_gcm::AesGcm;
use crate::mid::chacha20poly1305::ChaCha20Poly1305;
use crate::mid::rng::{RandomSource, SystemRandom};
use crate::mid::xchacha20poly1305::XChaCha20Poly1305;
pub mod quic;
const NONCE_LEN: usize = 12;
const TAG_LEN: usize = 16;
#[derive(Debug)]
pub struct Algorithm {
id: AlgorithmId,
}
#[derive(Debug)]
enum AlgorithmId {
Aes128Gcm,
Aes256Gcm,
ChaCha20Poly1305,
}
impl Algorithm {
pub fn key_len(&self) -> usize {
match self.id {
AlgorithmId::Aes128Gcm => 16,
AlgorithmId::Aes256Gcm | AlgorithmId::ChaCha20Poly1305 => 32,
}
}
pub fn tag_len(&self) -> usize {
TAG_LEN
}
pub fn nonce_len(&self) -> usize {
NONCE_LEN
}
}
pub static AES_128_GCM: Algorithm = Algorithm {
id: AlgorithmId::Aes128Gcm,
};
pub static AES_256_GCM: Algorithm = Algorithm {
id: AlgorithmId::Aes256Gcm,
};
pub static CHACHA20_POLY1305: Algorithm = Algorithm {
id: AlgorithmId::ChaCha20Poly1305,
};
pub struct Key(Inner);
impl Key {
pub fn new(algorithm: &'static Algorithm, key: &[u8]) -> Result<Self, Error> {
if key.len() != algorithm.key_len() {
return Err(Error::WrongLength);
}
Ok(Self(match algorithm.id {
AlgorithmId::Aes128Gcm | AlgorithmId::Aes256Gcm => {
Inner::AesGcm(ManuallyDrop::new(AesGcm::new(key)?))
}
AlgorithmId::ChaCha20Poly1305 => Inner::ChaCha20Poly1305(ManuallyDrop::new(
ChaCha20Poly1305::new(key.try_into().map_err(|_| Error::WrongLength)?),
)),
}))
}
pub fn seal_in_place_separate_tag(
&self,
nonce: Nonce,
aad: &[u8],
in_out: &mut [u8],
) -> Result<Tag, Error> {
let mut tag = [0u8; TAG_LEN];
match &self.0 {
Inner::AesGcm(key) => key.encrypt(&nonce.0, aad, in_out, &mut tag),
Inner::ChaCha20Poly1305(key) => key.encrypt(&nonce.0, aad, in_out, &mut tag),
}?;
Ok(Tag(tag))
}
pub fn seal_in_place_append_tag<InOut>(
&self,
nonce: Nonce,
aad: &[u8],
in_out: &mut InOut,
) -> Result<(), Error>
where
InOut: AsMut<[u8]> + for<'a> Extend<&'a u8>,
{
let tag = self.seal_in_place_separate_tag(nonce, aad, in_out.as_mut())?;
in_out.extend(tag.as_ref());
Ok(())
}
pub fn open_in_place<'a>(
&self,
nonce: Nonce,
aad: &[u8],
in_out: &'a mut [u8],
) -> Result<&'a mut [u8], Error> {
let (in_out, tag) = in_out.split_last_chunk_mut().ok_or(Error::DecryptFailed)?;
self.open_in_place_separate_tag(nonce, aad, Tag(*tag), in_out)
}
pub fn open_in_place_separate_tag<'a>(
&self,
nonce: Nonce,
aad: &[u8],
tag: Tag,
in_out: &'a mut [u8],
) -> Result<&'a mut [u8], Error> {
match &self.0 {
Inner::AesGcm(key) => key.decrypt(&nonce.0, aad, in_out, &tag.0),
Inner::ChaCha20Poly1305(key) => key.decrypt(&nonce.0, aad, in_out, &tag.0),
}?;
Ok(in_out)
}
}
#[allow(clippy::large_enum_variant)]
#[repr(u8)]
enum Inner {
AesGcm(ManuallyDrop<AesGcm>),
ChaCha20Poly1305(ManuallyDrop<ChaCha20Poly1305>),
}
impl Drop for Inner {
fn drop(&mut self) {
zeroise_value(self);
}
}
impl Zeroable for Inner {}
pub struct Nonce([u8; NONCE_LEN]);
impl Nonce {
pub fn assume_unique_for_key(nonce: [u8; NONCE_LEN]) -> Self {
Self(nonce)
}
}
#[derive(Clone, Copy, Debug)]
pub struct Tag([u8; TAG_LEN]);
impl AsRef<[u8]> for Tag {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl TryFrom<&[u8]> for Tag {
type Error = Error;
fn try_from(tag: &[u8]) -> Result<Self, Error> {
tag.try_into().map(Self).map_err(|_| Error::WrongLength)
}
}
pub struct RandomizedNonceKey(XChaCha20Poly1305);
impl RandomizedNonceKey {
pub fn new(key: &[u8; 32]) -> Self {
Self(XChaCha20Poly1305::new(*key))
}
pub fn seal_in_place_append_tag<InOut>(
&self,
aad: &[u8],
in_out: &mut InOut,
) -> Result<RandomNonce, Error>
where
InOut: AsMut<[u8]> + for<'a> Extend<&'a u8>,
{
let mut nonce = [0u8; 24];
SystemRandom.fill(&mut nonce)?;
let mut tag = [0u8; TAG_LEN];
self.0.encrypt(&nonce, aad, in_out.as_mut(), &mut tag)?;
in_out.extend(&tag);
Ok(RandomNonce(nonce))
}
pub fn open_in_place<'a>(
&self,
nonce: RandomNonce,
aad: &[u8],
in_out: &'a mut [u8],
) -> Result<&'a mut [u8], Error> {
let (in_out, tag) = in_out.split_last_chunk_mut().ok_or(Error::DecryptFailed)?;
self.0.decrypt(&nonce.0, aad, in_out, tag)?;
Ok(in_out)
}
}
#[derive(Clone, Copy, Debug)]
pub struct RandomNonce([u8; 24]);
impl AsRef<[u8]> for RandomNonce {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl From<[u8; 24]> for RandomNonce {
fn from(nonce: [u8; 24]) -> Self {
Self(nonce)
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
const ALGORITHMS: [&Algorithm; 3] = [&AES_128_GCM, &AES_256_GCM, &CHACHA20_POLY1305];
#[test]
fn key_length_must_match_algorithm() {
let key = [0u8; 33];
for algorithm in ALGORITHMS {
for len in 0..=key.len() {
assert_eq!(
Key::new(algorithm, &key[..len]).is_ok(),
len == algorithm.key_len(),
"{algorithm:?} len {len}"
);
}
}
}
#[test]
fn seal_and_open() {
for algorithm in ALGORITHMS {
let key = Key::new(algorithm, &[7u8; 32][..algorithm.key_len()]).unwrap();
let nonce = || Nonce::assume_unique_for_key([9; 12]);
let mut sealed = b"hello".to_vec();
key.seal_in_place_append_tag(nonce(), b"aad", &mut sealed)
.unwrap();
assert_eq!(sealed.len(), 5 + algorithm.tag_len());
let mut separate = b"hello".to_vec();
let tag = key
.seal_in_place_separate_tag(nonce(), b"aad", &mut separate)
.unwrap();
assert_eq!([&separate[..], tag.as_ref()].concat(), sealed);
let mut opened = sealed.clone();
assert_eq!(
key.open_in_place(nonce(), b"aad", &mut opened).unwrap(),
b"hello"
);
assert_eq!(
key.open_in_place_separate_tag(nonce(), b"aad", tag, &mut separate)
.unwrap(),
b"hello"
);
let mut wrong = sealed.clone();
assert_eq!(
key.open_in_place(nonce(), &[], &mut wrong),
Err(Error::DecryptFailed)
);
assert_eq!(wrong[..5], [0; 5]);
assert_eq!(wrong[5..], sealed[5..]);
}
}
#[test]
fn open_refuses_input_shorter_than_tag() {
for algorithm in ALGORITHMS {
let key = Key::new(algorithm, &[7u8; 32][..algorithm.key_len()]).unwrap();
let mut short = [0xaau8; TAG_LEN - 1];
assert_eq!(
key.open_in_place(Nonce::assume_unique_for_key([0; 12]), &[], &mut short),
Err(Error::DecryptFailed)
);
assert_eq!(short, [0xaa; TAG_LEN - 1]);
}
}
#[test]
fn tag_must_be_whole() {
let bytes = [0u8; TAG_LEN + 1];
for len in 0..=bytes.len() {
assert_eq!(
Tag::try_from(&bytes[..len]).is_ok(),
len == TAG_LEN,
"len {len}"
);
}
}
#[test]
fn randomized_nonce_key() {
let key = RandomizedNonceKey::new(&[3u8; 32]);
let mut first = b"ticket".to_vec();
let nonce = key.seal_in_place_append_tag(b"name", &mut first).unwrap();
let mut second = b"ticket".to_vec();
let other = key.seal_in_place_append_tag(b"name", &mut second).unwrap();
assert_ne!(nonce.as_ref(), other.as_ref());
assert_ne!(first, second);
let received = RandomNonce::from(<[u8; 24]>::try_from(nonce.as_ref()).unwrap());
assert_eq!(
key.open_in_place(received, b"name", &mut first).unwrap(),
b"ticket"
);
assert_eq!(
key.open_in_place(nonce, b"name", &mut second),
Err(Error::DecryptFailed)
);
assert_eq!(
key.open_in_place(nonce, &[], &mut [0u8; TAG_LEN - 1]),
Err(Error::DecryptFailed)
);
}
}