use crate::{Cipher, Secret};
use commonware_math::algebra::Random;
use commonware_utils::sequence::FixedBytes;
use rand_core::CryptoRng;
use zeroize::Zeroizing;
const TAG_SIZE: usize = 16;
const NONCE_SIZE_BYTES: usize = 12;
const KEY_SIZE_BYTES: usize = 32;
cfg_if::cfg_if! {
if #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] {
use aws_lc_rs::aead::{self, CHACHA20_POLY1305, LessSafeKey, UnboundKey};
struct Key(LessSafeKey);
impl Key {
fn from_key(key: &[u8; KEY_SIZE_BYTES]) -> Self {
let unbound_key = UnboundKey::new(&CHACHA20_POLY1305, key)
.expect("key size should match algorithm");
Self(LessSafeKey::new(unbound_key))
}
fn encrypt(
&self,
nonce: &[u8; NONCE_SIZE_BYTES],
aad: &[u8],
data: &mut [u8],
) -> Option<[u8; TAG_SIZE]> {
let nonce = aead::Nonce::assume_unique_for_key(*nonce);
let tag = self
.0
.seal_in_place_separate_tag(nonce, aead::Aad::from(aad), data)
.ok()?;
tag.as_ref().try_into().ok()
}
fn decrypt(
&self,
nonce: &[u8; NONCE_SIZE_BYTES],
aad: &[u8],
data: &mut [u8],
tag: &[u8; TAG_SIZE],
) -> Option<()> {
let nonce = aead::Nonce::assume_unique_for_key(*nonce);
self.0
.open_in_place_separate_tag(nonce, aead::Aad::from(aad), tag, data)
.ok()
.map(|_| ())
}
}
} else {
use chacha20poly1305::{ChaCha20Poly1305 as Aead, KeyInit as _, aead::AeadInOut};
struct Key(Aead);
impl Key {
fn from_key(key: &[u8; KEY_SIZE_BYTES]) -> Self {
Self(Aead::new(key.into()))
}
fn encrypt(
&self,
nonce: &[u8; NONCE_SIZE_BYTES],
aad: &[u8],
data: &mut [u8],
) -> Option<[u8; TAG_SIZE]> {
self.0
.encrypt_inout_detached(nonce.into(), aad, data.into())
.ok()
.map(Into::into)
}
fn decrypt(
&self,
nonce: &[u8; NONCE_SIZE_BYTES],
aad: &[u8],
data: &mut [u8],
tag: &[u8; TAG_SIZE],
) -> Option<()> {
self.0
.decrypt_inout_detached(nonce.into(), aad, data.into(), tag.into())
.ok()
}
}
}
}
pub struct ChaCha20Poly1305 {
nonce: u128,
key: Secret<Key>,
}
impl ChaCha20Poly1305 {
fn next_nonce(&mut self) -> Option<[u8; NONCE_SIZE_BYTES]> {
if self.nonce >= 1 << (8 * NONCE_SIZE_BYTES) {
return None;
}
let out = self.nonce.to_le_bytes();
self.nonce += 1;
let mut nonce = [0u8; NONCE_SIZE_BYTES];
nonce.copy_from_slice(&out[..NONCE_SIZE_BYTES]);
Some(nonce)
}
}
impl Random for ChaCha20Poly1305 {
fn random(mut rng: impl CryptoRng) -> Self {
let mut key_bytes = Zeroizing::new([0u8; KEY_SIZE_BYTES]);
rng.fill_bytes(key_bytes.as_mut());
Self {
nonce: 0,
key: Secret::new(Key::from_key(&key_bytes)),
}
}
}
impl Cipher for ChaCha20Poly1305 {
type Tag = FixedBytes<TAG_SIZE>;
#[inline]
fn seal(mut self, aad: &[u8], data: &mut [u8]) -> Option<(Self, Self::Tag)> {
let nonce = self.next_nonce()?;
let tag = self.key.expose(|key| key.encrypt(&nonce, aad, data))?;
Some((self, FixedBytes::new(tag)))
}
#[inline]
fn open(mut self, aad: &[u8], data: &mut [u8], tag: &Self::Tag) -> Option<Self> {
let nonce = self.next_nonce()?;
let tag: &[u8; TAG_SIZE] = tag.as_ref().try_into().expect("tag size is fixed");
self.key.expose(|key| key.decrypt(&nonce, aad, data, tag))?;
Some(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use commonware_utils::{TestRng, test_rng};
fn seal(
cipher: ChaCha20Poly1305,
aad: &[u8],
msg: &[u8],
) -> (ChaCha20Poly1305, Vec<u8>, FixedBytes<TAG_SIZE>) {
let mut data = msg.to_vec();
let (cipher, tag) = cipher.seal(aad, &mut data).unwrap();
(cipher, data, tag)
}
#[test]
fn test_seal_open_roundtrip() {
let mut send = ChaCha20Poly1305::random(test_rng());
let mut recv = ChaCha20Poly1305::random(test_rng());
for msg in [&b""[..], b"hello", b"world"] {
let (next, mut data, tag) = seal(send, b"aad", msg);
send = next;
if !msg.is_empty() {
assert_ne!(data, msg);
}
recv = recv.open(b"aad", &mut data, &tag).unwrap();
assert_eq!(data, msg);
}
}
#[test]
fn test_open_wrong_key_fails() {
let (_, mut data, tag) = seal(ChaCha20Poly1305::random(test_rng()), b"", b"hello");
let recv = ChaCha20Poly1305::random(TestRng::new(1));
assert!(recv.open(b"", &mut data, &tag).is_none());
}
#[test]
fn test_open_wrong_aad_fails() {
let (_, mut data, tag) = seal(ChaCha20Poly1305::random(test_rng()), b"aad", b"hello");
let recv = ChaCha20Poly1305::random(test_rng());
assert!(recv.open(b"other", &mut data, &tag).is_none());
}
#[test]
fn test_open_out_of_order_fails() {
let (send, _, _) = seal(ChaCha20Poly1305::random(test_rng()), b"", b"first");
let (_, mut second, tag) = seal(send, b"", b"second");
let recv = ChaCha20Poly1305::random(test_rng());
assert!(recv.open(b"", &mut second, &tag).is_none());
}
#[test]
fn test_open_corrupted_fails() {
let (_, mut data, tag) = seal(ChaCha20Poly1305::random(test_rng()), b"", b"hello");
data[0] ^= 0xFF;
let recv = ChaCha20Poly1305::random(test_rng());
assert!(recv.open(b"", &mut data, &tag).is_none());
}
#[test]
fn test_open_corrupted_tag_fails() {
let (_, mut data, tag) = seal(ChaCha20Poly1305::random(test_rng()), b"", b"hello");
let mut tag: [u8; TAG_SIZE] = tag.as_ref().try_into().unwrap();
tag[0] ^= 0xFF;
let recv = ChaCha20Poly1305::random(test_rng());
assert!(recv.open(b"", &mut data, &FixedBytes::new(tag)).is_none());
}
#[test]
fn test_exhausted_counter_fails() {
let exhausted = |mut cipher: ChaCha20Poly1305| {
cipher.nonce = 1 << (8 * NONCE_SIZE_BYTES);
cipher
};
let send = exhausted(ChaCha20Poly1305::random(test_rng()));
assert!(send.seal(b"", &mut []).is_none());
let recv = exhausted(ChaCha20Poly1305::random(test_rng()));
let tag = FixedBytes::new([0u8; TAG_SIZE]);
assert!(recv.open(b"", &mut [], &tag).is_none());
}
#[cfg(feature = "arbitrary")]
mod conformance {
use super::*;
use commonware_codec::Encode;
use commonware_conformance::{Conformance, conformance_tests};
use rand::RngExt as _;
struct Seal;
impl Conformance for Seal {
async fn commit(seed: u64) -> Vec<u8> {
let mut rng = TestRng::new(seed);
let mut log = Vec::new();
let mut cipher = ChaCha20Poly1305::random(&mut rng);
for _ in 0..4 {
let mut aad = vec![0u8; rng.random_range(0..32)];
rng.fill(&mut aad[..]);
let mut data = vec![0u8; rng.random_range(0..256)];
rng.fill(&mut data[..]);
let (next, tag) = cipher.seal(&aad, &mut data).unwrap();
cipher = next;
log.extend(aad.encode());
log.extend(data.encode());
log.extend_from_slice(&tag);
}
log
}
}
conformance_tests! {
Seal => 4096,
}
}
}