use subtle::ConstantTimeEq;
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
use crate::classic::crypto_core::{HChaCha20Key, crypto_core_hchacha20};
use crate::constants::{
CRYPTO_CORE_HCHACHA20_INPUTBYTES, CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES,
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES,
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES,
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES,
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES,
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX,
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY, CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES,
CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES,
};
use crate::error::*;
use crate::rng::copy_randombytes;
use crate::types::*;
use crate::utils::{increment_bytes, pad16, xor_buf};
pub type Key = [u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES];
pub type Nonce = [u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES];
pub type Header = [u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES];
#[derive(PartialEq, Eq, Clone, Default, Zeroize, ZeroizeOnDrop)]
pub struct State {
k: Key,
nonce: Nonce,
}
impl State {
pub fn new() -> Self {
Self::default()
}
}
pub fn crypto_secretstream_xchacha20poly1305_keygen(key: &mut Key) {
copy_randombytes(key);
}
fn state_counter(nonce: &mut Nonce) -> &mut [u8] {
&mut nonce[..CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES]
}
fn state_inonce(nonce: &mut Nonce) -> &mut [u8] {
&mut nonce[CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES
..CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES
+ CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES]
}
fn secretstream_length_block(associated_data_len: usize, message_len: usize) -> [u8; 16] {
let mut lengths = [0u8; 16];
lengths[..8].copy_from_slice(&(associated_data_len as u64).to_le_bytes());
lengths[8..].copy_from_slice(&(64u64 + message_len as u64).to_le_bytes());
lengths
}
fn _crypto_secretstream_xchacha20poly1305_counter_reset(state: &mut State) {
let counter = state_counter(&mut state.nonce);
counter.fill(0);
counter[0] = 1;
}
pub fn crypto_secretstream_xchacha20poly1305_init_push(
state: &mut State,
header: &mut Header,
key: &Key,
) {
copy_randombytes(header);
let mut k = Zeroizing::new(HChaCha20Key::default());
crypto_core_hchacha20(
k.as_mut_array(),
ByteArray::as_array(&header[..16]),
key,
None,
);
state.k.copy_from_slice(&*k);
_crypto_secretstream_xchacha20poly1305_counter_reset(state);
let inonce = state_inonce(&mut state.nonce);
inonce.copy_from_slice(
&header[CRYPTO_CORE_HCHACHA20_INPUTBYTES
..(CRYPTO_CORE_HCHACHA20_INPUTBYTES
+ CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES)],
);
}
pub fn crypto_secretstream_xchacha20poly1305_init_pull(
state: &mut State,
header: &Header,
key: &Key,
) {
let mut k = Zeroizing::new(HChaCha20Key::default());
crypto_core_hchacha20(
k.as_mut_array(),
ByteArray::as_array(&header[0..16]),
key,
None,
);
state.k.copy_from_slice(&*k);
_crypto_secretstream_xchacha20poly1305_counter_reset(state);
let inonce = state_inonce(&mut state.nonce);
inonce.copy_from_slice(
&header[CRYPTO_CORE_HCHACHA20_INPUTBYTES
..(CRYPTO_CORE_HCHACHA20_INPUTBYTES
+ CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES)],
);
}
pub fn crypto_secretstream_xchacha20poly1305_rekey(state: &mut State) {
use chacha20::ChaCha20;
use chacha20::cipher::{KeyIvInit, StreamCipher};
let mut new_state = Zeroizing::new(
[0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES
+ CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES],
);
new_state[..CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES].copy_from_slice(&state.k);
new_state[CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES..]
.copy_from_slice(state_inonce(&mut state.nonce));
let mut cipher = ChaCha20::new((&state.k).into(), (&state.nonce).into());
cipher.apply_keystream(&mut *new_state);
state
.k
.copy_from_slice(&new_state[0..CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES]);
state_inonce(&mut state.nonce)
.copy_from_slice(&new_state[CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES..]);
_crypto_secretstream_xchacha20poly1305_counter_reset(state);
}
pub fn crypto_secretstream_xchacha20poly1305_push(
state: &mut State,
ciphertext: &mut [u8],
message: &[u8],
associated_data: Option<&[u8]>,
tag: u8,
) -> Result<(), Error> {
use chacha20::ChaCha20;
use chacha20::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
use crate::poly1305::Poly1305;
if message.len() > CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX {
return Err(length_error!(
crate::ErrorContext::Message,
message.len(),
max CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX
));
}
let expected_ciphertext_len = message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES;
if ciphertext.len() != expected_ciphertext_len {
return Err(length_error!(
crate::ErrorContext::Ciphertext,
ciphertext.len(),
exact expected_ciphertext_len
));
}
let associated_data = associated_data.unwrap_or(&[]);
let mut mac_key = crate::poly1305::Key::new();
let _pad0 = [0u8; 16];
let mut cipher = ChaCha20::new((&state.k).into(), (&state.nonce).into());
cipher.apply_keystream(&mut mac_key);
let mut mac = Zeroizing::new(Poly1305::new(&mac_key));
mac_key.zeroize();
mac.update(associated_data);
mac.update(&_pad0[..pad16(associated_data.len())]);
let mut block = Zeroizing::new([0u8; 64]);
block[0] = tag;
cipher.seek(64);
cipher.apply_keystream(&mut *block);
mac.update(&*block);
let mlen = message.len();
ciphertext[0] = block[0];
ciphertext[1..(1 + mlen)].copy_from_slice(message);
cipher.seek(128);
cipher.apply_keystream(&mut ciphertext[1..(1 + mlen)]);
let size_data = secretstream_length_block(associated_data.len(), mlen);
mac.update(&ciphertext[1..(1 + mlen)]);
let buffer_mac_pad = ((0x10 - block.len() as i64 + mlen as i64) & 0xf) as usize;
mac.update(&_pad0[0..buffer_mac_pad]);
mac.update(&size_data);
mac.finalize(&mut ciphertext[1 + mlen..]);
let inonce = state_inonce(&mut state.nonce);
xor_buf(inonce, &ciphertext[1 + mlen..]);
let counter = state_counter(&mut state.nonce);
increment_bytes(counter);
if tag & CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY
== CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY
|| state_counter(&mut state.nonce)
.ct_eq(&[0u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES])
.unwrap_u8()
== 1
{
crypto_secretstream_xchacha20poly1305_rekey(state);
}
Ok(())
}
pub fn crypto_secretstream_xchacha20poly1305_pull(
state: &mut State,
message: &mut [u8],
tag: &mut u8,
ciphertext: &[u8],
associated_data: Option<&[u8]>,
) -> Result<usize, Error> {
use chacha20::ChaCha20;
use chacha20::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
use crate::poly1305::Poly1305;
let _pad0 = [0u8; 16];
if ciphertext.len() < CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES {
return Err(length_error!(
crate::ErrorContext::Ciphertext,
ciphertext.len(),
min CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
));
}
let mlen = ciphertext.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES;
if mlen > CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX {
return Err(length_error!(
crate::ErrorContext::Ciphertext,
ciphertext.len(),
max CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX
+ CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
));
}
if message.len() < mlen {
return Err(length_error!(
crate::ErrorContext::Message,
message.len(),
min mlen
));
}
let associated_data = associated_data.unwrap_or(&[]);
let mut mac_key = crate::poly1305::Key::new();
let mut cipher = ChaCha20::new((&state.k).into(), (&state.nonce).into());
cipher.apply_keystream(&mut mac_key);
let mut mac = Zeroizing::new(Poly1305::new(&mac_key));
mac_key.zeroize();
mac.update(associated_data);
mac.update(&_pad0[..pad16(associated_data.len())]);
let mut block = Zeroizing::new([0u8; 64]);
block[0] = ciphertext[0];
cipher.seek(64);
cipher.apply_keystream(&mut *block);
let decrypted_tag = block[0];
block[0] = ciphertext[0];
mac.update(&*block);
let buffer_mac_pad = ((0x10 - block.len() as i64 + mlen as i64) & 0xf) as usize;
mac.update(&ciphertext[1..1 + mlen]);
mac.update(&_pad0[..buffer_mac_pad]);
let size_data = secretstream_length_block(associated_data.len(), mlen);
mac.update(&size_data);
let mac = Zeroizing::new(mac.finalize_to_array());
if ciphertext[1 + mlen..].ct_eq(mac.as_slice()).unwrap_u8() == 0 {
return Err(Error::AuthenticationFailed);
}
message[..mlen].copy_from_slice(&ciphertext[1..1 + mlen]);
cipher.seek(128);
cipher.apply_keystream(&mut message[..mlen]);
*tag = decrypted_tag;
let inonce = state_inonce(&mut state.nonce);
xor_buf(inonce, &*mac);
let counter = state_counter(&mut state.nonce);
increment_bytes(counter);
if decrypted_tag & CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY
== CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY
|| state_counter(&mut state.nonce)
.ct_eq(&[0u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES])
.unwrap_u8()
== 1
{
crypto_secretstream_xchacha20poly1305_rekey(state);
}
Ok(mlen)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dryocstream::Tag;
#[test]
fn push_and_pull_reject_invalid_buffer_lengths() {
let mut state = State::new();
let mut tag = 0;
let error = crypto_secretstream_xchacha20poly1305_push(
&mut state,
&mut [],
b"message",
None,
Tag::MESSAGE.bits(),
)
.expect_err("ciphertext must include secretstream overhead");
assert!(matches!(
error,
Error::InvalidLength {
context: crate::ErrorContext::Ciphertext,
..
}
));
let short_ciphertext = [0u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES - 1];
let error = crypto_secretstream_xchacha20poly1305_pull(
&mut state,
&mut [],
&mut tag,
&short_ciphertext,
None,
)
.expect_err("ciphertext must include secretstream overhead");
assert!(matches!(
error,
Error::InvalidLength {
context: crate::ErrorContext::Ciphertext,
..
}
));
let ciphertext = [0u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES + 1];
let error = crypto_secretstream_xchacha20poly1305_pull(
&mut state,
&mut [],
&mut tag,
&ciphertext,
None,
)
.expect_err("the message buffer must hold the plaintext");
assert!(matches!(
error,
Error::InvalidLength {
context: crate::ErrorContext::Message,
..
}
));
}
#[test]
fn pull_authenticates_before_mutating_outputs_or_state() {
let key = Key::default();
let mut push_state = State::new();
let mut header = Header::default();
crypto_secretstream_xchacha20poly1305_init_push(&mut push_state, &mut header, &key);
let plaintext = b"do not publish unauthenticated plaintext";
let mut ciphertext =
vec![0u8; plaintext.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
crypto_secretstream_xchacha20poly1305_push(
&mut push_state,
&mut ciphertext,
plaintext,
Some(b"associated data"),
Tag::FINAL.bits(),
)
.expect("push failed");
let mut pull_state = State::new();
crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &header, &key);
let original_state = pull_state.clone();
let mut tampered = ciphertext.clone();
*tampered.last_mut().expect("authentication tag") ^= 1;
let mut output = vec![0xa5; plaintext.len()];
let original_output = output.clone();
let mut tag = 0x5a;
assert!(matches!(
crypto_secretstream_xchacha20poly1305_pull(
&mut pull_state,
&mut output,
&mut tag,
&tampered,
Some(b"associated data"),
),
Err(Error::AuthenticationFailed)
));
assert_eq!(output, original_output);
assert_eq!(tag, 0x5a);
assert!(pull_state == original_state);
crypto_secretstream_xchacha20poly1305_pull(
&mut pull_state,
&mut output,
&mut tag,
&ciphertext,
Some(b"associated data"),
)
.expect("state must remain usable after authentication failure");
assert_eq!(output, plaintext);
assert_eq!(tag, Tag::FINAL.bits());
}
#[test]
fn length_block_uses_fixed_width_little_endian_values() {
let lengths = secretstream_length_block(0x0102_0304, 0x0506_0708);
assert_eq!(&lengths[..8], &0x0102_0304u64.to_le_bytes());
assert_eq!(&lengths[8..], &(64u64 + 0x0506_0708).to_le_bytes());
}
#[test]
fn test_sizes() {
use crate::constants::*;
const _: () = assert!(
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES
== CRYPTO_CORE_HCHACHA20_INPUTBYTES
+ CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES
);
const _: () = assert!(
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES
== CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES
);
const _: () = assert!(
CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES
== CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES
+ CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES
);
const _: () = assert!(
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX
<= CRYPTO_AEAD_CHACHA20POLY1305_IETF_MESSAGEBYTES_MAX
);
const _: () = assert!(
CRYPTO_ONETIMEAUTH_POLY1305_BYTES >= CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES
);
#[cfg(target_pointer_width = "32")]
{
assert_eq!(
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX,
usize::MAX - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
);
assert_eq!(
CRYPTO_AEAD_CHACHA20POLY1305_IETF_MESSAGEBYTES_MAX,
usize::MAX - CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES
);
assert_eq!(
CRYPTO_SECRETBOX_MESSAGEBYTES_MAX,
usize::MAX - CRYPTO_SECRETBOX_MACBYTES
);
}
}
#[test]
fn test_secretstream_large_aad() {
let mut key = Key::default();
crypto_secretstream_xchacha20poly1305_keygen(&mut key);
let mut push_state = State::new();
let mut push_header = Header::default();
crypto_secretstream_xchacha20poly1305_init_push(&mut push_state, &mut push_header, &key);
let message = b"hello world";
let large_aad = vec![0x42u8; 328];
let mut ciphertext =
vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
crypto_secretstream_xchacha20poly1305_push(
&mut push_state,
&mut ciphertext,
message,
Some(&large_aad),
Tag::MESSAGE.bits(),
)
.expect("push failed");
let mut pull_state = State::new();
crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &push_header, &key);
let mut decrypted = vec![0u8; message.len()];
let mut tag = 0u8;
crypto_secretstream_xchacha20poly1305_pull(
&mut pull_state,
&mut decrypted,
&mut tag,
&ciphertext,
Some(&large_aad),
)
.expect("pull failed");
assert_eq!(message.as_slice(), decrypted.as_slice());
assert_eq!(tag, Tag::MESSAGE.bits());
let mut wrong_aad = large_aad.clone();
wrong_aad[100] = 0x43;
let mut wrong_aad_pull_state = State::new();
crypto_secretstream_xchacha20poly1305_init_pull(
&mut wrong_aad_pull_state,
&push_header,
&key,
);
let mut decrypted = vec![0u8; message.len()];
let mut tag = 0u8;
assert!(
crypto_secretstream_xchacha20poly1305_pull(
&mut wrong_aad_pull_state,
&mut decrypted,
&mut tag,
&ciphertext,
Some(&wrong_aad),
)
.is_err()
);
}
#[test]
fn test_secretstream_small_aad() {
let mut key = Key::default();
crypto_secretstream_xchacha20poly1305_keygen(&mut key);
let mut push_state = State::new();
let mut push_header = Header::default();
crypto_secretstream_xchacha20poly1305_init_push(&mut push_state, &mut push_header, &key);
let message = b"hello world";
let small_aad = b"abc";
let mut ciphertext =
vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
crypto_secretstream_xchacha20poly1305_push(
&mut push_state,
&mut ciphertext,
message,
Some(small_aad),
Tag::MESSAGE.bits(),
)
.expect("push failed");
let mut pull_state = State::new();
crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &push_header, &key);
let mut decrypted = vec![0u8; message.len()];
let mut tag = 0u8;
crypto_secretstream_xchacha20poly1305_pull(
&mut pull_state,
&mut decrypted,
&mut tag,
&ciphertext,
Some(small_aad),
)
.expect("pull failed");
assert_eq!(message.as_slice(), decrypted.as_slice());
assert_eq!(tag, Tag::MESSAGE.bits());
let wrong_aad = b"xyz";
let mut wrong_aad_pull_state = State::new();
crypto_secretstream_xchacha20poly1305_init_pull(
&mut wrong_aad_pull_state,
&push_header,
&key,
);
let mut decrypted = vec![0u8; message.len()];
let mut tag = 0u8;
assert!(
crypto_secretstream_xchacha20poly1305_pull(
&mut wrong_aad_pull_state,
&mut decrypted,
&mut tag,
&ciphertext,
Some(wrong_aad),
)
.is_err()
);
}
#[cfg(dryoc_native_tests)]
mod native_tests {
use super::*;
#[test]
fn test_secretstream_basic_push() {
use base64::Engine as _;
use base64::engine::general_purpose;
use libsodium_sys::{
crypto_secretstream_xchacha20poly1305_init_pull as so_crypto_secretstream_xchacha20poly1305_init_pull,
crypto_secretstream_xchacha20poly1305_pull as so_crypto_secretstream_xchacha20poly1305_pull,
crypto_secretstream_xchacha20poly1305_push as so_crypto_secretstream_xchacha20poly1305_push,
crypto_secretstream_xchacha20poly1305_state,
};
use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
use crate::dryocstream::Tag;
let mut key = Key::default();
crypto_secretstream_xchacha20poly1305_keygen(&mut key);
let mut push_state = State::new();
let mut push_header = Header::default();
crypto_secretstream_xchacha20poly1305_init_push(
&mut push_state,
&mut push_header,
&key,
);
let push_state_init = push_state.clone();
let message = b"hello";
let mut output =
vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
let aad = b"";
let tag = Tag::MESSAGE.bits();
crypto_secretstream_xchacha20poly1305_push(
&mut push_state,
&mut output,
message,
Some(aad),
tag,
)
.expect("push failed");
let mut so_output = output.clone();
unsafe {
use libc::{c_uchar, c_ulonglong};
let mut so_state = crypto_secretstream_xchacha20poly1305_state {
k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
_pad: [0u8; 8],
};
so_state.k.copy_from_slice(&push_state_init.k);
so_state.nonce.copy_from_slice(&push_state_init.nonce);
let mut clen_p: c_ulonglong = 0;
let ret = so_crypto_secretstream_xchacha20poly1305_push(
&mut so_state,
so_output.as_mut_ptr(),
&mut clen_p,
message.as_ptr(),
message.len() as u64,
aad.as_ptr(),
aad.len() as u64,
0,
);
assert_eq!(ret, 0);
so_output.resize(clen_p as usize, 0);
assert_eq!(
general_purpose::STANDARD.encode(&so_output),
general_purpose::STANDARD.encode(&output)
);
assert_eq!(
general_purpose::STANDARD.encode(so_state.k),
general_purpose::STANDARD.encode(push_state.k)
);
assert_eq!(
general_purpose::STANDARD.encode(so_state.nonce),
general_purpose::STANDARD.encode(push_state.nonce)
);
let mut so_state = crypto_secretstream_xchacha20poly1305_state {
k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
_pad: [0u8; 8],
};
let mut mlen_p: c_ulonglong = 0;
let mut tag_p: c_uchar = 0;
let ret = so_crypto_secretstream_xchacha20poly1305_init_pull(
&mut so_state,
push_header.as_ptr(),
key.as_ptr(),
);
assert_eq!(ret, 0);
assert_eq!(
general_purpose::STANDARD.encode(so_state.k),
general_purpose::STANDARD.encode(push_state_init.k)
);
assert_eq!(
general_purpose::STANDARD.encode(so_state.nonce),
general_purpose::STANDARD.encode(push_state_init.nonce)
);
assert!(so_output.len() >= CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES);
let ret = so_crypto_secretstream_xchacha20poly1305_pull(
&mut so_state,
so_output.as_mut_ptr(),
&mut mlen_p,
&mut tag_p,
output.as_ptr(),
output.len() as u64,
aad.as_ptr(),
aad.len() as u64,
);
assert_eq!(ret, 0);
so_output.resize(mlen_p as usize, 0);
}
assert_eq!(
general_purpose::STANDARD.encode(message),
general_purpose::STANDARD.encode(&so_output)
);
let mut pull_state = State::default();
crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &push_header, &key);
assert_eq!(
general_purpose::STANDARD.encode(pull_state.k),
general_purpose::STANDARD.encode(push_state_init.k)
);
assert_eq!(
general_purpose::STANDARD.encode(pull_state.nonce),
general_purpose::STANDARD.encode(push_state_init.nonce)
);
let mut pull_result_message =
vec![0u8; output.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
let mut pull_result_tag = 0u8;
crypto_secretstream_xchacha20poly1305_pull(
&mut pull_state,
&mut pull_result_message,
&mut pull_result_tag,
&output,
Some(&[]),
)
.expect("pull failed");
assert_eq!(Tag::MESSAGE, Tag::from_bits(tag).expect("tag"));
assert_eq!(
general_purpose::STANDARD.encode(&pull_result_message),
general_purpose::STANDARD.encode(message)
);
}
#[test]
fn test_rekey() {
use base64::Engine as _;
use base64::engine::general_purpose;
use libsodium_sys::{
crypto_secretstream_xchacha20poly1305_rekey as so_crypto_secretstream_xchacha20poly1305_rekey,
crypto_secretstream_xchacha20poly1305_state,
};
use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
let mut key = Key::default();
crypto_secretstream_xchacha20poly1305_keygen(&mut key);
let mut push_state = State::default();
let mut push_header: Header = Header::default();
crypto_secretstream_xchacha20poly1305_init_push(
&mut push_state,
&mut push_header,
&key,
);
let push_state_init = push_state.clone();
crypto_secretstream_xchacha20poly1305_rekey(&mut push_state);
let mut so_state = crypto_secretstream_xchacha20poly1305_state {
k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
_pad: [0u8; 8],
};
so_state.k.copy_from_slice(&push_state_init.k);
so_state.nonce.copy_from_slice(&push_state_init.nonce);
unsafe {
so_crypto_secretstream_xchacha20poly1305_rekey(&mut so_state);
}
assert_eq!(
general_purpose::STANDARD.encode(so_state.k),
general_purpose::STANDARD.encode(push_state.k)
);
assert_eq!(
general_purpose::STANDARD.encode(so_state.nonce),
general_purpose::STANDARD.encode(push_state.nonce)
);
}
#[test]
fn test_secretstream_lots_of_messages_push() {
use base64::Engine as _;
use base64::engine::general_purpose;
use libc::{c_uchar, c_ulonglong};
use libsodium_sys::{
crypto_secretstream_xchacha20poly1305_init_pull as so_crypto_secretstream_xchacha20poly1305_init_pull,
crypto_secretstream_xchacha20poly1305_pull as so_crypto_secretstream_xchacha20poly1305_pull,
crypto_secretstream_xchacha20poly1305_state,
};
use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
use crate::dryocstream::Tag;
let mut key = Key::default();
crypto_secretstream_xchacha20poly1305_keygen(&mut key);
let mut push_state = State::new();
let mut push_header = Header::default();
crypto_secretstream_xchacha20poly1305_init_push(
&mut push_state,
&mut push_header,
&key,
);
let push_state_init = push_state.clone();
let mut pull_state = State::default();
crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &push_header, &key);
assert_eq!(
general_purpose::STANDARD.encode(pull_state.k),
general_purpose::STANDARD.encode(push_state_init.k)
);
assert_eq!(
general_purpose::STANDARD.encode(pull_state.nonce),
general_purpose::STANDARD.encode(push_state_init.nonce)
);
let mut so_state = crypto_secretstream_xchacha20poly1305_state {
k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
_pad: [0u8; 8],
};
so_state.k.copy_from_slice(&push_state_init.k);
so_state.nonce.copy_from_slice(&push_state_init.nonce);
let mut so_state = crypto_secretstream_xchacha20poly1305_state {
k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
_pad: [0u8; 8],
};
let mut mlen_p: c_ulonglong = 0;
let mut tag_p: c_uchar = 0;
unsafe {
let ret = so_crypto_secretstream_xchacha20poly1305_init_pull(
&mut so_state,
push_header.as_ptr(),
key.as_ptr(),
);
assert_eq!(ret, 0);
}
assert_eq!(
general_purpose::STANDARD.encode(so_state.k),
general_purpose::STANDARD.encode(push_state_init.k)
);
assert_eq!(
general_purpose::STANDARD.encode(so_state.nonce),
general_purpose::STANDARD.encode(push_state_init.nonce)
);
for i in 0..100 {
let message = format!("hello {}", i);
let aad = format!("aad {}", i);
let tag = if i % 7 == 0 { Tag::REKEY } else { Tag::MESSAGE };
let mut output =
vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
crypto_secretstream_xchacha20poly1305_push(
&mut push_state,
&mut output,
message.as_bytes(),
Some(aad.as_bytes()),
tag.bits(),
)
.expect("push failed");
let mut so_output = output.clone();
unsafe {
assert!(so_output.len() >= CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES);
let ret = so_crypto_secretstream_xchacha20poly1305_pull(
&mut so_state,
so_output.as_mut_ptr(),
&mut mlen_p,
&mut tag_p,
output.as_ptr(),
output.len() as u64,
aad.as_ptr(),
aad.len() as u64,
);
assert_eq!(ret, 0);
so_output.resize(mlen_p as usize, 0);
}
assert_eq!(
general_purpose::STANDARD.encode(&message),
general_purpose::STANDARD.encode(&so_output)
);
let mut pull_result_message =
vec![0u8; output.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
let mut pull_result_tag = 0u8;
crypto_secretstream_xchacha20poly1305_pull(
&mut pull_state,
&mut pull_result_message,
&mut pull_result_tag,
&output,
Some(aad.as_bytes()),
)
.expect("pull failed");
assert_eq!(tag, Tag::from_bits(pull_result_tag).expect("tag"));
assert_eq!(
general_purpose::STANDARD.encode(&pull_result_message),
general_purpose::STANDARD.encode(&message)
);
}
}
#[test]
fn test_secretstream_basic_pull() {
use base64::Engine as _;
use base64::engine::general_purpose;
use libc::c_ulonglong;
use libsodium_sys::{
crypto_secretstream_xchacha20poly1305_init_push as so_crypto_secretstream_xchacha20poly1305_init_push,
crypto_secretstream_xchacha20poly1305_push as so_crypto_secretstream_xchacha20poly1305_push,
crypto_secretstream_xchacha20poly1305_state,
};
use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
let mut key = Key::default();
crypto_secretstream_xchacha20poly1305_keygen(&mut key);
let mut so_state = crypto_secretstream_xchacha20poly1305_state {
k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
_pad: [0u8; 8],
};
let mut so_header = Header::default();
unsafe {
so_crypto_secretstream_xchacha20poly1305_init_push(
&mut so_state,
so_header.as_mut_ptr(),
key.as_ptr(),
);
}
let mut pull_state = State::new();
crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &so_header, &key);
let message = b"hello";
let aad = b"aad";
let mut so_output =
vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
let mut clen_p: c_ulonglong = 0;
unsafe {
let ret = so_crypto_secretstream_xchacha20poly1305_push(
&mut so_state,
so_output.as_mut_ptr(),
&mut clen_p,
message.as_ptr(),
message.len() as u64,
aad.as_ptr(),
aad.len() as u64,
0,
);
assert_eq!(ret, 0);
so_output.resize(clen_p as usize, 0);
}
let mut output = vec![0u8; so_output.len()];
let mut tag = 0u8;
let mlen = crypto_secretstream_xchacha20poly1305_pull(
&mut pull_state,
&mut output,
&mut tag,
&so_output,
Some(aad),
)
.expect("decrypt failed");
output.resize(mlen, 0);
assert_eq!(
general_purpose::STANDARD.encode(&output),
general_purpose::STANDARD.encode(message)
);
assert_eq!(tag, 0);
}
#[test]
fn test_secretstream_lots_of_messages_pull() {
use base64::Engine as _;
use base64::engine::general_purpose;
use libc::c_ulonglong;
use libsodium_sys::{
crypto_secretstream_xchacha20poly1305_init_push as so_crypto_secretstream_xchacha20poly1305_init_push,
crypto_secretstream_xchacha20poly1305_push as so_crypto_secretstream_xchacha20poly1305_push,
crypto_secretstream_xchacha20poly1305_state,
};
use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
use crate::dryocstream::Tag;
let mut key = Key::default();
crypto_secretstream_xchacha20poly1305_keygen(&mut key);
let mut so_state = crypto_secretstream_xchacha20poly1305_state {
k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
_pad: [0u8; 8],
};
let mut so_header = Header::default();
unsafe {
so_crypto_secretstream_xchacha20poly1305_init_push(
&mut so_state,
so_header.as_mut_ptr(),
key.as_ptr(),
);
}
let mut pull_state = State::new();
crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &so_header, &key);
for i in 0..100 {
let message = format!("hello {}", i);
let aad = format!("aad {}", i);
let mut so_output =
vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
let mut clen_p: c_ulonglong = 0;
let tag = if i % 7 == 0 { Tag::REKEY } else { Tag::MESSAGE };
unsafe {
let ret = so_crypto_secretstream_xchacha20poly1305_push(
&mut so_state,
so_output.as_mut_ptr(),
&mut clen_p,
message.as_ptr(),
message.len() as u64,
aad.as_ptr(),
aad.len() as u64,
tag.bits(),
);
assert_eq!(ret, 0);
so_output.resize(clen_p as usize, 0);
}
let mut output =
vec![0u8; so_output.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
let mut outtag = 0u8;
crypto_secretstream_xchacha20poly1305_pull(
&mut pull_state,
&mut output,
&mut outtag,
&so_output,
Some(aad.as_bytes()),
)
.expect("decrypt failed");
assert_eq!(
general_purpose::STANDARD.encode(so_state.k),
general_purpose::STANDARD.encode(pull_state.k)
);
assert_eq!(
general_purpose::STANDARD.encode(so_state.nonce),
general_purpose::STANDARD.encode(pull_state.nonce)
);
assert_eq!(
general_purpose::STANDARD.encode(&output),
general_purpose::STANDARD.encode(&message)
);
assert_eq!(outtag, tag.bits());
}
}
}
}