pub mod states;
io_impls! {
#[doc(hidden)]
pub mod io;
#[doc(inline)]
pub use io::Aad as IoAad;
#[doc(inline)]
pub use io::Data as IoData;
}
use wolf_crypto_sys::{
ChaChaPoly_Aead,
wc_ChaCha20Poly1305_UpdateData, wc_ChaCha20Poly1305_UpdateAad,
wc_ChaCha20Poly1305_Init, wc_ChaCha20Poly1305_Final,
wc_ChaCha20Poly1305_Decrypt, wc_ChaCha20Poly1305_Encrypt,
CHACHA20_POLY1305_AEAD_DECRYPT, CHACHA20_POLY1305_AEAD_ENCRYPT,
};
#[cfg(feature = "llvm-assume")]
use wolf_crypto_sys::{
CHACHA20_POLY1305_STATE_READY, CHACHA20_POLY1305_STATE_AAD, CHACHA20_POLY1305_STATE_DATA,
byte
};
use states::{
State, Init, CanUpdate, CanSetAad, CanUpdateAad,
Updating, UpdatingAad,
EncryptMaybeAad, DecryptMaybeAad,
EncryptAad, DecryptAad,
};
#[doc(inline)]
pub use states::{Decrypt, Encrypt};
use core::mem::MaybeUninit;
use core::marker::PhantomData;
use core::ptr::addr_of_mut;
use crate::aead::{Aad, Tag};
use crate::buf::{GenericIv, U12};
use crate::mac::poly1305::GenericKey;
use crate::opaque_res::Res;
use crate::{can_cast_u32, const_can_cast_u32, Unspecified};
#[doc(inline)]
pub use crate::mac::poly1305::{Key, KeyRef};
opaque_dbg! { ChaCha20Poly1305<Init> }
opaque_dbg! { ChaCha20Poly1305<EncryptMaybeAad> }
opaque_dbg! { ChaCha20Poly1305<DecryptMaybeAad> }
opaque_dbg! { ChaCha20Poly1305<EncryptAad> }
opaque_dbg! { ChaCha20Poly1305<DecryptAad> }
opaque_dbg! { ChaCha20Poly1305<Encrypt> }
opaque_dbg! { ChaCha20Poly1305<Decrypt> }
#[inline(always)]
#[must_use]
fn oneshot_predicate<A: Aad>(plain: &[u8], out: &[u8], aad: &A) -> bool {
can_cast_u32(plain.len()) && out.len() >= plain.len() && aad.is_valid_size()
}
pub fn encrypt<K, IV, A>(
key: K, iv: IV,
plain: &[u8], out: &mut [u8],
aad: A
) -> Result<Tag, Unspecified>
where
K: GenericKey,
IV: GenericIv<Size = U12>,
A: Aad
{
if !oneshot_predicate(plain, out, &aad) { return Err(Unspecified) }
let mut res = Res::new();
let mut tag = Tag::new_zeroed();
unsafe {
res.ensure_0(wc_ChaCha20Poly1305_Encrypt(
key.ptr(),
iv.as_slice().as_ptr(),
aad.ptr(),
aad.size(),
plain.as_ptr(),
plain.len() as u32,
out.as_mut_ptr(),
tag.as_mut_ptr()
));
}
res.unit_err(tag)
}
pub fn encrypt_in_place<K, IV, A>(key: K, iv: IV, in_out: &mut [u8], aad: A) -> Result<Tag, Unspecified>
where
K: GenericKey,
IV: GenericIv<Size = U12>,
A: Aad
{
if !(can_cast_u32(in_out.len()) && aad.is_valid_size()) { return Err(Unspecified) }
let mut res = Res::new();
let mut tag = Tag::new_zeroed();
unsafe {
res.ensure_0(wc_ChaCha20Poly1305_Encrypt(
key.ptr(),
iv.as_slice().as_ptr(),
aad.ptr(),
aad.size(),
in_out.as_ptr(),
in_out.len() as u32,
in_out.as_ptr().cast_mut(),
tag.as_mut_ptr()
));
}
res.unit_err(tag)
}
pub fn decrypt<K, IV, A>(
key: K, iv: IV,
cipher: &[u8], out: &mut [u8],
aad: A, tag: Tag
) -> Result<(), Unspecified>
where
K: GenericKey,
IV: GenericIv<Size = U12>,
A: Aad
{
if !oneshot_predicate(cipher, out, &aad) { return Err(Unspecified) }
let mut res = Res::new();
unsafe {
res.ensure_0(wc_ChaCha20Poly1305_Decrypt(
key.ptr(),
iv.as_slice().as_ptr(),
aad.ptr(),
aad.size(),
cipher.as_ptr(),
cipher.len() as u32,
tag.as_ptr(),
out.as_mut_ptr()
));
}
res.unit_err(())
}
pub fn decrypt_in_place<K, IV, A>(
key: K, iv: IV,
in_out: &mut [u8],
aad: A, tag: Tag
) -> Result<(), Unspecified>
where
K: GenericKey,
IV: GenericIv<Size = U12>,
A: Aad
{
if !(can_cast_u32(in_out.len()) && aad.is_valid_size()) { return Err(Unspecified) }
let mut res = Res::new();
unsafe {
res.ensure_0(wc_ChaCha20Poly1305_Decrypt(
key.ptr(),
iv.as_slice().as_ptr(),
aad.ptr(),
aad.size(),
in_out.as_ptr(),
in_out.len() as u32,
tag.as_ptr(),
in_out.as_ptr().cast_mut()
));
}
res.unit_err(())
}
#[must_use]
#[repr(transparent)]
pub struct ChaCha20Poly1305<S: State = Init> {
inner: ChaChaPoly_Aead,
_state: PhantomData<S>
}
impl ChaCha20Poly1305<Init> {
fn new_with_dir<K, IV, S>(key: K, iv: IV, dir: core::ffi::c_int) -> ChaCha20Poly1305<S>
where
K: GenericKey,
IV: GenericIv<Size = U12>,
S: State
{
debug_assert!(matches!(
dir as core::ffi::c_uint,
CHACHA20_POLY1305_AEAD_ENCRYPT | CHACHA20_POLY1305_AEAD_DECRYPT
));
let mut inner = MaybeUninit::<ChaChaPoly_Aead>::uninit();
unsafe {
let _res = wc_ChaCha20Poly1305_Init(
inner.as_mut_ptr(),
key.ptr(),
iv.as_slice().as_ptr(),
dir
);
debug_assert_eq!(_res, 0);
ChaCha20Poly1305::<S> {
inner: inner.assume_init(),
_state: PhantomData
}
}
}
#[inline]
pub fn new<Mode: Updating>(key: impl GenericKey, iv: impl GenericIv<Size = U12>) -> ChaCha20Poly1305<Mode::InitState> {
Self::new_with_dir(key, iv, Mode::direction())
}
#[inline]
pub fn new_encrypt<K, IV>(key: K, iv: IV) -> ChaCha20Poly1305<<Encrypt as Updating>::InitState>
where
K: GenericKey,
IV: GenericIv<Size = U12>
{
Self::new::<Encrypt>(key, iv)
}
#[inline]
pub fn new_decrypt<K, IV>(key: K, iv: IV) -> ChaCha20Poly1305<<Decrypt as Updating>::InitState>
where
K: GenericKey,
IV: GenericIv<Size = U12>
{
Self::new::<Decrypt>(key, iv)
}
}
impl<S: State> ChaCha20Poly1305<S> {
#[inline]
pub(crate) const fn with_state<N: State>(self) -> ChaCha20Poly1305<N> {
unsafe { core::mem::transmute(self) }
}
}
impl<S: CanUpdateAad> ChaCha20Poly1305<S> {
#[cfg_attr(debug_assertions, track_caller)]
#[inline]
unsafe fn update_aad_unchecked<A: Aad>(&mut self, aad: A) {
debug_assert!(aad.is_valid_size());
#[cfg(feature = "llvm-assume")] {
core::hint::assert_unchecked(
self.inner.state == CHACHA20_POLY1305_STATE_READY as byte ||
self.inner.state == CHACHA20_POLY1305_STATE_AAD as byte
);
core::hint::assert_unchecked(
self.inner.state != CHACHA20_POLY1305_STATE_DATA as byte
);
}
let _res = wc_ChaCha20Poly1305_UpdateAad(
addr_of_mut!(self.inner),
aad.ptr(),
aad.size()
);
debug_assert_eq!(_res, 0);
}
io_impls! {
#[cfg_attr(all(feature = "embedded-io", not(feature = "std")), doc = "use embedded_io::Write;")]
#[cfg_attr(feature = "std", doc = "use std::io::Write;")]
#[cfg_attr(
all(feature = "embedded-io", not(feature = "std")),
doc = "# fn main() -> Result<(), wolf_crypto::Unspecified> {"
)]
#[cfg_attr(feature = "std", doc = "# fn main() -> Result<(), Box<dyn std::error::Error>> {")]
#[inline]
pub const fn aad_io<IO>(self, io: IO) -> IoAad<S::Updating, IO> {
io::Aad::new(self.with_state(), io)
}
}
#[inline]
pub fn update_aad<A: Aad>(mut self, aad: A) -> Result<ChaCha20Poly1305<S::Updating>, Self> {
if !aad.is_valid_size() { return Err(self) }
unsafe { self.update_aad_unchecked(aad); }
Ok(self.with_state())
}
}
impl<S: CanUpdate> ChaCha20Poly1305<S> {
#[inline]
unsafe fn update_in_place_unchecked(&mut self, data: &mut [u8]) {
debug_assert!(can_cast_u32(data.len()));
#[cfg(feature = "llvm-assume")]
core::hint::assert_unchecked(!( self.inner.state != CHACHA20_POLY1305_STATE_READY as byte
&& self.inner.state != CHACHA20_POLY1305_STATE_AAD as byte
&& self.inner.state != CHACHA20_POLY1305_STATE_DATA as byte
));
let _res = wc_ChaCha20Poly1305_UpdateData(
addr_of_mut!(self.inner),
data.as_ptr(),
data.as_ptr().cast_mut(),
data.len() as u32
);
debug_assert_eq!(_res, 0);
}
#[inline]
unsafe fn update_unchecked(&mut self, data: &[u8], output: &mut [u8]) {
debug_assert!(data.len() <= output.len());
debug_assert!(can_cast_u32(data.len()));
#[cfg(feature = "llvm-assume")] {
core::hint::assert_unchecked(
self.inner.state == CHACHA20_POLY1305_STATE_READY as byte
|| self.inner.state == CHACHA20_POLY1305_STATE_AAD as byte
|| self.inner.state == CHACHA20_POLY1305_STATE_DATA as byte
);
}
let _res = wc_ChaCha20Poly1305_UpdateData(
addr_of_mut!(self.inner),
data.as_ptr(),
output.as_mut_ptr(),
data.len() as u32
);
debug_assert_eq!(_res, 0);
}
#[inline]
#[must_use]
const fn update_predicate(input: &[u8], output: &[u8]) -> bool {
can_cast_u32(input.len()) && output.len() >= input.len()
}
#[inline]
pub fn update_in_place(mut self, in_out: &mut [u8]) -> Result<ChaCha20Poly1305<S::Mode>, Self> {
if can_cast_u32(in_out.len()) {
unsafe { self.update_in_place_unchecked(in_out) };
Ok(self.with_state())
} else {
Err(self)
}
}
#[inline]
pub fn update_in_place_sized<const C: usize>(mut self, in_out: &mut [u8; C]) -> Result<ChaCha20Poly1305<S::Mode>, Self> {
if const_can_cast_u32::<C>() {
unsafe { self.update_in_place_unchecked(in_out) };
Ok(self.with_state())
} else {
Err(self)
}
}
pub fn update(mut self, data: &[u8], output: &mut [u8]) -> Result<ChaCha20Poly1305<S::Mode>, Self> {
if Self::update_predicate(data, output) {
unsafe { self.update_unchecked(data, output) };
Ok(self.with_state())
} else {
Err(self)
}
}
io_impls! {
#[cfg_attr(all(feature = "embedded-io", not(feature = "std")), doc = "use embedded_io::Read;")]
#[cfg_attr(feature = "std", doc = "use std::io::Read;")]
#[cfg_attr(
all(feature = "embedded-io", not(feature = "std")),
doc = "# fn main() -> Result<(), wolf_crypto::Unspecified> {"
)]
#[cfg_attr(feature = "std", doc = "# fn main() -> Result<(), Box<dyn std::error::Error>> {")]
pub const fn data_io<IO>(self, io: IO) -> IoData<S::Mode, IO> {
io::Data::new(self.with_state(), io)
}
}
}
impl<S: CanSetAad> ChaCha20Poly1305<S> {
#[inline]
pub fn set_aad<A: Aad>(
mut self,
aad: A
) -> Result<ChaCha20Poly1305<<S as CanSetAad>::Mode>, Self>
{
if aad.is_valid_size() {
unsafe { self.update_aad_unchecked(aad); }
Ok(self.with_state())
} else {
Err(self)
}
}
}
impl<S: UpdatingAad> ChaCha20Poly1305<S> {
pub const fn finish(self) -> ChaCha20Poly1305<S::Mode> {
self.with_state()
}
pub fn update_aad_streaming<A: Aad>(&mut self, aad: A) -> Result<(), Unspecified> {
if aad.is_valid_size() {
unsafe { self.update_aad_unchecked(aad); }
Ok(())
} else {
Err(Unspecified)
}
}
}
impl<S: Updating> ChaCha20Poly1305<S> {
#[inline]
pub fn finalize(mut self) -> Tag {
let mut tag = Tag::new_zeroed();
unsafe {
let _res = wc_ChaCha20Poly1305_Final(
addr_of_mut!(self.inner),
tag.as_mut_ptr()
);
debug_assert_eq!(_res, 0);
}
tag
}
pub fn update_in_place_streaming<'io>(&mut self, in_out: &'io mut [u8]) -> Result<&'io mut [u8], Unspecified> {
if can_cast_u32(in_out.len()) {
unsafe { self.update_in_place_unchecked(in_out) };
Ok(in_out)
} else {
Err(Unspecified)
}
}
pub fn update_streaming(&mut self, input: &[u8], output: &mut [u8]) -> Result<(), Unspecified> {
if Self::update_predicate(input, output) {
unsafe { self.update_unchecked(input, output) };
Ok(())
} else {
Err(Unspecified)
}
}
}
#[cfg(test)]
mod tests {
use crate::mac::poly1305::Key;
use core::{
slice,
};
use super::*;
#[test]
fn type_state_machine() {
let key = Key::new([0u8; 32]);
let mut cipher = [69, 69, 69, 69];
let tag = ChaCha20Poly1305::new::<Encrypt>(key.as_ref(), [0u8; 12])
.set_aad(Some(Some(Some(())))).unwrap()
.update_in_place(cipher.as_mut_slice()).unwrap()
.finalize();
let new_tag = ChaCha20Poly1305::new::<Decrypt>(key.as_ref(), [0u8; 12])
.set_aad(()).unwrap()
.update_in_place(cipher.as_mut_slice()).unwrap()
.finalize();
assert_eq!(tag, new_tag);
assert_eq!(cipher, [69, 69, 69, 69]);
}
macro_rules! bogus_slice {
($size:expr) => {{
let src = b"hello world";
unsafe { slice::from_raw_parts(src.as_ptr(), $size) }
}};
}
#[test]
fn oneshot_size_predicate_fail() {
let slice = bogus_slice!(u32::MAX as usize + 1);
let out = slice;
assert!(!oneshot_predicate(slice, out, &()))
}
#[test]
fn oneshot_size_predicate() {
let slice = bogus_slice!(u32::MAX as usize - 1);
let out = slice;
assert!(oneshot_predicate(slice, out, &()))
}
#[test]
fn oneshot_size_predicate_too_small_out() {
let slice = bogus_slice!(u32::MAX as usize - 1);
let out = bogus_slice!(u32::MAX as usize - 2);
assert!(!oneshot_predicate(slice, out, &()));
}
}
#[cfg(test)]
mod property_tests {
use super::*;
use crate::aes::test_utils::{BoundList};
use crate::buf::Nonce;
use crate::mac::poly1305::Key;
use proptest::{prelude::*, proptest};
proptest! {
#![proptest_config(ProptestConfig::with_cases(5_000))]
#[test]
fn bijectivity(
input in any::<BoundList<1024>>(),
key in any::<Key>(),
iv in any::<Nonce>()
) {
let mut output = input.create_self();
let tag = ChaCha20Poly1305::new::<Encrypt>(key.as_ref(), iv.copy())
.update(input.as_slice(), output.as_mut_slice()).unwrap()
.finalize();
if output.len() >= 6 {
prop_assert_ne!(output.as_slice(), input.as_slice());
}
let mut decrypted = output.create_self();
let d_tag = ChaCha20Poly1305::new::<Decrypt>(key.as_ref(), iv)
.update(output.as_slice(), decrypted.as_mut_slice()).unwrap()
.finalize();
prop_assert_eq!(tag, d_tag);
prop_assert_eq!(decrypted.as_slice(), input.as_slice());
}
#[test]
fn bijectivity_with_aad(
input in any::<BoundList<1024>>(),
key in any::<Key>(),
iv in any::<Nonce>(),
aad in any::<Option<String>>()
) {
let mut output = input.create_self();
let tag = ChaCha20Poly1305::new::<Encrypt>(key.as_ref(), iv.copy())
.set_aad(aad.as_ref()).unwrap()
.update(input.as_slice(), output.as_mut_slice()).unwrap()
.finalize();
if output.len() >= 6 {
prop_assert_ne!(output.as_slice(), input.as_slice());
}
let mut decrypted = output.create_self();
let d_tag = ChaCha20Poly1305::new::<Decrypt>(key.as_ref(), iv)
.set_aad(aad.as_ref()).unwrap()
.update(output.as_slice(), decrypted.as_mut_slice()).unwrap()
.finalize();
prop_assert_eq!(tag, d_tag);
prop_assert_eq!(decrypted.as_slice(), input.as_slice());
}
#[test]
fn oneshot_bijectivity(
input in any::<BoundList<1024>>(),
key in any::<Key>(),
iv in any::<Nonce>()
) {
let mut output = input.create_self();
let tag = encrypt(
key.as_ref(), iv.copy(),
input.as_slice(), output.as_mut_slice(),
()
).unwrap();
if output.len() >= 6 {
prop_assert_ne!(output.as_slice(), input.as_slice());
}
let mut decrypted = output.create_self();
prop_assert!(decrypt(
key.as_ref(), iv,
output.as_slice(), decrypted.as_mut_slice(),
(), tag
).is_ok());
prop_assert_eq!(input.as_slice(), decrypted.as_slice());
}
#[test]
fn oneshot_bijectivity_with_aad(
input in any::<BoundList<1024>>(),
key in any::<Key>(),
iv in any::<Nonce>(),
aad in any::<Option<String>>()
) {
let mut output = input.create_self();
let tag = encrypt(
key.as_ref(), iv.copy(),
input.as_slice(), output.as_mut_slice(),
aad.as_ref()
).unwrap();
if output.len() >= 6 {
prop_assert_ne!(output.as_slice(), input.as_slice());
}
let mut decrypted = output.create_self();
prop_assert!(decrypt(
key.as_ref(), iv,
output.as_slice(), decrypted.as_mut_slice(),
aad.as_ref(), tag
).is_ok());
prop_assert_eq!(input.as_slice(), decrypted.as_slice());
}
}
}