#[cfg(feature = "safe_api")]
use crate::generics::sealed::Data;
pub use crate::hazardous::stream::chacha20::CHACHA_BLOCKSIZE;
pub use crate::hazardous::stream::chacha20::MAX_KEYSTREAM_BYTES;
pub use crate::hazardous::stream::chacha20::SecretKey;
use crate::{
errors::UnknownCryptoError,
generics::{ByteArrayData, Public, TypeSpec, sealed::Sealed},
hazardous::stream::chacha20::{IETF_CHACHA_NONCESIZE, Nonce as IETFNonce},
};
use crate::{
generics::GeneratePublic,
hazardous::stream::chacha20::{ChaCha20, HCHACHA_NONCESIZE},
};
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
pub const XCHACHA_NONCESIZE: usize = 24;
#[derive(Debug, Clone, Copy)]
pub struct XChaCha20Nonce {}
impl Sealed for XChaCha20Nonce {}
impl TypeSpec for XChaCha20Nonce {
const NAME: &'static str = stringify!(Nonce);
type TypeData = ByteArrayData<XCHACHA_NONCESIZE>;
}
impl From<[u8; XCHACHA_NONCESIZE]> for Public<XChaCha20Nonce> {
fn from(value: [u8; XCHACHA_NONCESIZE]) -> Self {
Self::from_data(<XChaCha20Nonce as TypeSpec>::TypeData::from(value))
}
}
impl GeneratePublic for XChaCha20Nonce {
#[cfg(feature = "safe_api")]
#[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))]
fn generate() -> Result<Public<XChaCha20Nonce>, UnknownCryptoError> {
let mut data = Self::TypeData::new(XCHACHA_NONCESIZE)?;
crate::util::secure_rand_bytes(&mut data.bytes)?;
Ok(Public::from_data(data))
}
}
pub type Nonce = Public<XChaCha20Nonce>;
#[derive(Clone, Debug)]
pub struct XChaCha20 {
chacha20: ChaCha20,
}
impl Drop for XChaCha20 {
fn drop(&mut self) {
#[cfg(feature = "zeroize")]
self.chacha20.zeroize();
}
}
impl XChaCha20 {
pub(crate) fn subkey_and_ietf_nonce(sk: &SecretKey, n: &Nonce) -> (SecretKey, IETFNonce) {
let mut hchacha_nonce = [0u8; HCHACHA_NONCESIZE];
hchacha_nonce.copy_from_slice(&n.data.bytes[..16]);
let subkey = SecretKey::from(ChaCha20::hchacha(sk, &hchacha_nonce));
let mut prefixed_nonce = [0u8; IETF_CHACHA_NONCESIZE];
prefixed_nonce[4..IETF_CHACHA_NONCESIZE].copy_from_slice(&n.data.bytes[16..24]);
(subkey, IETFNonce::from(prefixed_nonce))
}
pub fn new(sk: &SecretKey, n: &Nonce) -> Self {
let (subkey, ietf_nonce) = Self::subkey_and_ietf_nonce(sk, n);
Self {
chacha20: ChaCha20::new(&subkey, &ietf_nonce),
}
}
pub fn is_exhausted(&self) -> bool {
self.chacha20.is_exhausted()
}
pub fn next_producible(&self) -> Result<(), UnknownCryptoError> {
self.chacha20.next_producible()
}
pub fn keystream_remaining(&self) -> u64 {
self.chacha20.keystream_remaining()
}
pub fn set_position(&mut self, blockctr: u32) {
self.chacha20.set_position(blockctr);
}
pub fn set_byte_position(&mut self, pos: u64) -> Result<(), UnknownCryptoError> {
self.chacha20.set_byte_position(pos)
}
pub fn position(&self) -> u32 {
self.chacha20.position()
}
pub fn byte_position(&mut self) -> u64 {
self.chacha20.byte_position()
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn xor_keystream_into(&mut self, bytes: &mut [u8]) -> Result<(), UnknownCryptoError> {
self.chacha20.xor_keystream_into(bytes)
}
}
#[cfg(test)]
#[cfg(feature = "safe_api")]
mod public {
use crate::{
hazardous::stream::chacha20::{CHACHA_BLOCKSIZE, CHACHA_KEYSIZE, MAX_KEYSTREAM_BYTES},
test_framework::streamcipher_interface::{StreamcipherTester, TestableStreamCipher},
};
use super::*;
const ZERO_KEY: [u8; CHACHA_KEYSIZE] = [0u8; CHACHA_KEYSIZE];
const ZERO_XNONCE: [u8; XCHACHA_NONCESIZE] = [0u8; XCHACHA_NONCESIZE];
#[test]
fn test_keystream_remaining() {
let sk = SecretKey::from(ZERO_KEY);
let nonce = Nonce::from(ZERO_XNONCE);
let mut ctx = XChaCha20::new(&sk, &nonce);
ctx.set_position(0);
assert_eq!(ctx.position(), 0);
assert_eq!(
ctx.keystream_remaining(),
(CHACHA_BLOCKSIZE as u64) * 2u64.pow(32)
);
ctx.set_position(1);
assert_eq!(ctx.position(), 1);
assert_eq!(
ctx.keystream_remaining(),
crate::hazardous::aead::chacha20poly1305::P_MAX
);
ctx.set_position(u32::MAX);
assert_eq!(ctx.position(), u32::MAX);
assert_eq!(ctx.keystream_remaining(), 64);
}
#[test]
fn test_xchacha20_nonce() {
use super::*;
use crate::test_framework::newtypes::public::PublicNewtype;
PublicNewtype::test_with_generate::<
XCHACHA_NONCESIZE,
XCHACHA_NONCESIZE,
XCHACHA_NONCESIZE,
XChaCha20Nonce,
>();
#[cfg(feature = "serde")]
PublicNewtype::test_serialization::<XCHACHA_NONCESIZE, XChaCha20Nonce>();
}
impl TestableStreamCipher for XChaCha20 {
fn _new(sk: &[u8], n: &[u8]) -> Self {
Self::new(
&SecretKey::try_from(sk).unwrap(),
&Nonce::try_from(n).unwrap(),
)
}
#[cfg(test)]
#[cfg(feature = "safe_api")]
fn _random() -> Self {
use rand::RngExt;
let mut rng = rand::rng();
let rand_sk: bool = rng.random();
let (sk, n) = if rand_sk {
(SecretKey::generate().unwrap(), Nonce::from(ZERO_XNONCE))
} else {
(SecretKey::from(ZERO_KEY), Nonce::generate().unwrap())
};
Self::_new(sk.unprotected_as_ref(), n.as_ref())
}
fn _next_producible(&self) -> Result<(), UnknownCryptoError> {
self.next_producible()
}
fn _keystream_remaining(&self) -> u64 {
self.keystream_remaining()
}
fn _set_position(&mut self, blockctr: u32) {
self.set_position(blockctr);
}
fn _position(&self) -> u32 {
self.position()
}
fn _xor_keystream_into(&mut self, bytes: &mut [u8]) -> Result<(), UnknownCryptoError> {
self.xor_keystream_into(bytes)
}
fn _is_exhausted(&self) -> bool {
self.is_exhausted()
}
fn _set_byte_position(&mut self, pos: u64) -> Result<(), UnknownCryptoError> {
self.set_byte_position(pos)
}
fn _byte_position(&mut self) -> u64 {
self.byte_position()
}
const MAX_KEYSTREAM_BYTES: u64 = MAX_KEYSTREAM_BYTES;
}
#[test]
fn test_streamcipher() {
StreamcipherTester::<XChaCha20>::run_tests::<
CHACHA_BLOCKSIZE,
{ u32::MAX },
MAX_KEYSTREAM_BYTES,
>(&ZERO_KEY, &ZERO_XNONCE, None, None);
}
#[quickcheck]
#[cfg(feature = "safe_api")]
fn prop_streamcipher_interface(input: Vec<u8>) -> bool {
let sk = SecretKey::generate().unwrap();
let n = Nonce::from(ZERO_XNONCE);
StreamcipherTester::<XChaCha20>::run_tests::<
CHACHA_BLOCKSIZE,
{ u32::MAX },
MAX_KEYSTREAM_BYTES,
>(sk.unprotected_as_ref(), n.as_ref(), Some(&input), None);
true
}
}