use zeroize::Zeroize;
use crate::classic::crypto_secretstream_xchacha20poly1305::{
State, crypto_secretstream_xchacha20poly1305_init_pull,
crypto_secretstream_xchacha20poly1305_init_push, crypto_secretstream_xchacha20poly1305_pull,
crypto_secretstream_xchacha20poly1305_push, crypto_secretstream_xchacha20poly1305_rekey,
};
use crate::constants::{
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES,
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES, CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES,
};
use crate::error::Error;
pub use crate::types::*;
mod tag;
pub use tag::{Tag, TagIter, TagIterNames};
pub trait Mode {}
pub struct Push;
pub struct Pull;
impl Mode for Push {}
impl Mode for Pull {}
pub type Key = StackByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES>;
pub type Nonce = StackByteArray<CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES>;
pub type Header = StackByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES>;
#[cfg(any(all(feature = "protected", any(unix, windows)), all(doc, not(doctest))))]
#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "protected")))]
pub mod protected {
use super::*;
pub use crate::protected::*;
pub type Key = HeapByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES>;
pub type Nonce = HeapByteArray<CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES>;
pub type Header = HeapByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES>;
}
#[derive(PartialEq, Eq, Clone, Zeroize)]
pub struct DryocStream<Mode> {
state: State,
phantom: std::marker::PhantomData<Mode>,
}
impl<Mode> Drop for DryocStream<Mode> {
fn drop(&mut self) {
self.state.zeroize()
}
}
impl<M> DryocStream<M> {
pub fn rekey(&mut self) {
crypto_secretstream_xchacha20poly1305_rekey(&mut self.state)
}
}
impl DryocStream<Push> {
pub fn init_push<
Key: ByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES>,
Header: NewByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES>,
>(
key: &Key,
) -> (Self, Header) {
let mut state = State::new();
let mut header = Header::new_byte_array();
crypto_secretstream_xchacha20poly1305_init_push(
&mut state,
header.as_mut_array(),
key.as_array(),
);
(
Self {
state,
phantom: std::marker::PhantomData,
},
header,
)
}
pub fn push<Input: Bytes, Output: NewBytes + ResizableBytes>(
&mut self,
message: &Input,
associated_data: Option<&Input>,
tag: Tag,
) -> Result<Output, Error> {
use crate::constants::{
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES,
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX,
};
Tag::try_from(tag.bits())?;
let message_len = message.as_slice().len();
if message_len > CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX {
return Err(length_error!(
crate::ErrorContext::Message,
message_len,
max CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX
));
}
let mut ciphertext = Output::new_bytes();
ciphertext.resize(
message_len + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES,
0,
);
crypto_secretstream_xchacha20poly1305_push(
&mut self.state,
ciphertext.as_mut_slice(),
message.as_slice(),
associated_data.map(|aad| aad.as_slice()),
tag.bits(),
)?;
Ok(ciphertext)
}
pub fn push_to_vec<Input: Bytes>(
&mut self,
message: &Input,
associated_data: Option<&Input>,
tag: Tag,
) -> Result<Vec<u8>, Error> {
self.push(message, associated_data, tag)
}
}
impl DryocStream<Pull> {
pub fn init_pull<
Key: ByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES>,
Header: ByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES>,
>(
key: &Key,
header: &Header,
) -> Self {
let mut state = State::new();
crypto_secretstream_xchacha20poly1305_init_pull(
&mut state,
header.as_array(),
key.as_array(),
);
Self {
state,
phantom: std::marker::PhantomData,
}
}
pub fn pull<Input: Bytes, Output: MutBytes + Default + ResizableBytes>(
&mut self,
ciphertext: &Input,
associated_data: Option<&Input>,
) -> Result<(Output, Tag), Error> {
use crate::constants::{
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES,
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX,
};
if ciphertext.as_slice().len() < CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES {
return Err(length_error!(
crate::ErrorContext::Ciphertext,
ciphertext.as_slice().len(),
min CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
));
}
let message_len =
ciphertext.as_slice().len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES;
if message_len > CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX {
return Err(length_error!(
crate::ErrorContext::Ciphertext,
ciphertext.as_slice().len(),
max CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX
+ CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
));
}
let mut message = Output::default();
message.resize(message_len, 0);
let mut tag = 0u8;
let mut next_state = self.state.clone();
crypto_secretstream_xchacha20poly1305_pull(
&mut next_state,
message.as_mut_slice(),
&mut tag,
ciphertext.as_slice(),
associated_data.map(|aad| aad.as_slice()),
)?;
let tag = match Tag::try_from(tag) {
Ok(tag) => tag,
Err(error) => {
message.as_mut_slice().zeroize();
return Err(error);
}
};
self.state = next_state;
Ok((message, tag))
}
pub fn pull_to_vec<Input: Bytes>(
&mut self,
ciphertext: &Input,
associated_data: Option<&Input>,
) -> Result<(Vec<u8>, Tag), Error> {
self.pull(ciphertext, associated_data)
}
}
#[cfg(test)]
mod validation_tests {
use super::*;
use crate::constants::CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES;
#[test]
fn rustaceous_push_rejects_unknown_tag_without_advancing_state() {
let key = Key::generate();
let (mut push_stream, _header): (_, Header) = DryocStream::init_push(&key);
let original_state = push_stream.state.clone();
let invalid_tag = Tag::from_bits_retain(0x80);
let result: Result<Vec<u8>, Error> = push_stream.push_to_vec(b"message", None, invalid_tag);
assert!(matches!(
result,
Err(Error::InvalidValue {
context: crate::ErrorContext::Tag,
..
})
));
assert!(push_stream.state == original_state);
}
#[test]
fn rustaceous_pull_rejects_unknown_tag_without_advancing_state() {
let key = Key::generate();
let mut invalid_push_state = State::new();
let mut raw_header =
[0u8; crate::constants::CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES];
crypto_secretstream_xchacha20poly1305_init_push(
&mut invalid_push_state,
&mut raw_header,
key.as_array(),
);
let mut valid_push_state = invalid_push_state.clone();
let header = Header::try_from(raw_header.as_slice()).expect("header conversion failed");
let mut pull_stream = DryocStream::init_pull(&key, &header);
let original_pull_state = pull_stream.state.clone();
let message = b"authenticated unknown tag";
let mut invalid_ciphertext =
vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
crypto_secretstream_xchacha20poly1305_push(
&mut invalid_push_state,
&mut invalid_ciphertext,
message,
None,
0x80,
)
.expect("classic push failed");
let error = pull_stream
.pull_to_vec(&invalid_ciphertext, None)
.expect_err("unknown tag must be rejected");
assert!(matches!(
error,
Error::InvalidValue {
context: crate::ErrorContext::Tag,
..
}
));
assert!(pull_stream.state == original_pull_state);
let mut valid_ciphertext =
vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
crypto_secretstream_xchacha20poly1305_push(
&mut valid_push_state,
&mut valid_ciphertext,
message,
None,
Tag::MESSAGE.bits(),
)
.expect("classic push failed");
let (decrypted, tag) = pull_stream
.pull_to_vec(&valid_ciphertext, None)
.expect("state must remain usable after rejection");
assert_eq!(decrypted, message);
assert_eq!(tag, Tag::MESSAGE);
}
}
#[cfg(all(test, dryoc_native_tests))]
mod tests {
use super::*;
#[test]
fn test_stream_push() {
use sodiumoxide::crypto::secretstream::{
Header as SOHeader, Key as SOKey, Stream as SOStream, Tag as SOTag,
};
let message1 = b"Arbitrary data to encrypt";
let message2 = b"split into";
let message3 = b"three messages";
let key = Key::generate();
let (mut push_stream, header): (_, Header) = DryocStream::init_push(&key);
let c1: Vec<u8> = push_stream
.push(message1, None, Tag::MESSAGE)
.expect("Encrypt failed");
let c2: Vec<u8> = push_stream
.push(message2, None, Tag::MESSAGE)
.expect("Encrypt failed");
let c3: Vec<u8> = push_stream
.push(message3, None, Tag::FINAL)
.expect("Encrypt failed");
let mut so_stream_pull = SOStream::init_pull(
&SOHeader::from_slice(header.as_slice()).expect("header failed"),
&SOKey::from_slice(key.as_slice()).expect("key failed"),
)
.expect("pull init failed");
let (m1, tag1) = so_stream_pull.pull(&c1, None).expect("decrypt failed");
let (m2, tag2) = so_stream_pull.pull(&c2, None).expect("decrypt failed");
let (m3, tag3) = so_stream_pull.pull(&c3, None).expect("decrypt failed");
assert_eq!(message1, m1.as_slice());
assert_eq!(message2, m2.as_slice());
assert_eq!(message3, m3.as_slice());
assert_eq!(tag1, SOTag::Message);
assert_eq!(tag2, SOTag::Message);
assert_eq!(tag3, SOTag::Final);
}
#[test]
fn test_stream_pull() {
use std::convert::TryFrom;
use sodiumoxide::crypto::secretstream::{Key as SOKey, Stream as SOStream, Tag as SOTag};
let message1 = b"Arbitrary data to encrypt";
let message2 = b"split into";
let message3 = b"three messages";
let key = Key::generate();
let (mut so_push_stream, so_header) =
SOStream::init_push(&SOKey::from_slice(key.as_slice()).expect("key failed"))
.expect("init push failed");
let c1: Vec<u8> = so_push_stream
.push(message1, None, SOTag::Message)
.expect("Encrypt failed");
let c2: Vec<u8> = so_push_stream
.push(message2, None, SOTag::Message)
.expect("Encrypt failed");
let c3: Vec<u8> = so_push_stream
.push(message3, None, SOTag::Final)
.expect("Encrypt failed");
let mut pull_stream =
DryocStream::init_pull(&key, &Header::try_from(so_header.as_ref()).expect("header"));
let (m1, tag1): (Vec<u8>, Tag) = pull_stream.pull(&c1, None).expect("Decrypt failed");
let (m2, tag2): (Vec<u8>, Tag) = pull_stream.pull(&c2, None).expect("Decrypt failed");
let (m3, tag3): (Vec<u8>, Tag) = pull_stream.pull(&c3, None).expect("Decrypt failed");
assert_eq!(message1, m1.as_slice());
assert_eq!(message2, m2.as_slice());
assert_eq!(message3, m3.as_slice());
assert_eq!(tag1, Tag::MESSAGE);
assert_eq!(tag2, Tag::MESSAGE);
assert_eq!(tag3, Tag::FINAL);
}
#[cfg(all(feature = "protected", any(unix, windows)))]
#[test]
fn test_protected_memory() {
use crate::protected::*;
let message1 = b"Arbitrary data to encrypt";
let message2 = b"split into";
let message3 = b"three messages";
let key = protected::Key::generate_locked().expect("generate locked");
let (mut push_stream, header): (_, Header) = DryocStream::init_push(&key);
let key = key
.munlock()
.expect("munlock")
.mprotect_noaccess()
.expect("mprotect");
let c1: Locked<HeapBytes> = push_stream
.push(message1, None, Tag::MESSAGE)
.expect("Encrypt failed");
let c2: Vec<u8> = push_stream
.push(message2, None, Tag::MESSAGE)
.expect("Encrypt failed");
let c3: Vec<u8> = push_stream
.push(message3, None, Tag::FINAL)
.expect("Encrypt failed");
let key = key.mprotect_readonly().expect("mprotect");
let mut pull_stream = DryocStream::init_pull(&key, &header);
let _key = key.mprotect_noaccess().expect("mprotect");
let (m1, tag1): (Locked<HeapBytes>, Tag) =
pull_stream.pull(&c1, None).expect("Decrypt failed");
let (m2, tag2): (Locked<HeapBytes>, Tag) =
pull_stream.pull(&c2, None).expect("Decrypt failed");
let (m3, tag3): (Locked<HeapBytes>, Tag) =
pull_stream.pull(&c3, None).expect("Decrypt failed");
assert_eq!(message1, m1.as_slice());
assert_eq!(message2, m2.as_slice());
assert_eq!(message3, m3.as_slice());
assert_eq!(tag1, Tag::MESSAGE);
assert_eq!(tag2, Tag::MESSAGE);
assert_eq!(tag3, Tag::FINAL);
}
}