1use zeroize::Zeroize;
78
79use crate::classic::crypto_secretstream_xchacha20poly1305::{
80 State, crypto_secretstream_xchacha20poly1305_init_pull,
81 crypto_secretstream_xchacha20poly1305_init_push, crypto_secretstream_xchacha20poly1305_pull,
82 crypto_secretstream_xchacha20poly1305_push, crypto_secretstream_xchacha20poly1305_rekey,
83};
84use crate::constants::{
85 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES,
86 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES, CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES,
87};
88use crate::error::Error;
89pub use crate::types::*;
90
91mod tag;
92pub use tag::{Tag, TagIter, TagIterNames};
93
94pub trait Mode {}
96pub struct Push;
98pub struct Pull;
100
101impl Mode for Push {}
102impl Mode for Pull {}
103
104pub type Key = StackByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES>;
106pub type Nonce = StackByteArray<CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES>;
108pub type Header = StackByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES>;
110
111#[cfg(any(all(feature = "protected", any(unix, windows)), all(doc, not(doctest))))]
112#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "protected")))]
113pub mod protected {
114 use super::*;
165 pub use crate::protected::*;
166
167 pub type Key = HeapByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES>;
170 pub type Nonce = HeapByteArray<CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES>;
173 pub type Header = HeapByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES>;
176}
177
178#[derive(PartialEq, Eq, Clone, Zeroize)]
180pub struct DryocStream<Mode> {
181 state: State,
182 phantom: std::marker::PhantomData<Mode>,
183}
184
185impl<Mode> Drop for DryocStream<Mode> {
186 fn drop(&mut self) {
187 self.state.zeroize()
188 }
189}
190
191impl<M> DryocStream<M> {
192 pub fn rekey(&mut self) {
201 crypto_secretstream_xchacha20poly1305_rekey(&mut self.state)
202 }
203}
204
205impl DryocStream<Push> {
206 pub fn init_push<
208 Key: ByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES>,
209 Header: NewByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES>,
210 >(
211 key: &Key,
212 ) -> (Self, Header) {
213 let mut state = State::new();
214 let mut header = Header::new_byte_array();
215 crypto_secretstream_xchacha20poly1305_init_push(
216 &mut state,
217 header.as_mut_array(),
218 key.as_array(),
219 );
220 (
221 Self {
222 state,
223 phantom: std::marker::PhantomData,
224 },
225 header,
226 )
227 }
228
229 pub fn push<Input: Bytes, Output: NewBytes + ResizableBytes>(
238 &mut self,
239 message: &Input,
240 associated_data: Option<&Input>,
241 tag: Tag,
242 ) -> Result<Output, Error> {
243 use crate::constants::{
244 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES,
245 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX,
246 };
247 Tag::try_from(tag.bits())?;
248
249 let message_len = message.as_slice().len();
250 if message_len > CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX {
251 return Err(length_error!(
252 crate::ErrorContext::Message,
253 message_len,
254 max CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX
255 ));
256 }
257
258 let mut ciphertext = Output::new_bytes();
259 ciphertext.resize(
260 message_len + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES,
261 0,
262 );
263 crypto_secretstream_xchacha20poly1305_push(
264 &mut self.state,
265 ciphertext.as_mut_slice(),
266 message.as_slice(),
267 associated_data.map(|aad| aad.as_slice()),
268 tag.bits(),
269 )?;
270 Ok(ciphertext)
271 }
272
273 pub fn push_to_vec<Input: Bytes>(
281 &mut self,
282 message: &Input,
283 associated_data: Option<&Input>,
284 tag: Tag,
285 ) -> Result<Vec<u8>, Error> {
286 self.push(message, associated_data, tag)
287 }
288}
289
290impl DryocStream<Pull> {
291 pub fn init_pull<
293 Key: ByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES>,
294 Header: ByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES>,
295 >(
296 key: &Key,
297 header: &Header,
298 ) -> Self {
299 let mut state = State::new();
300 crypto_secretstream_xchacha20poly1305_init_pull(
301 &mut state,
302 header.as_array(),
303 key.as_array(),
304 );
305 Self {
306 state,
307 phantom: std::marker::PhantomData,
308 }
309 }
310
311 pub fn pull<Input: Bytes, Output: MutBytes + Default + ResizableBytes>(
323 &mut self,
324 ciphertext: &Input,
325 associated_data: Option<&Input>,
326 ) -> Result<(Output, Tag), Error> {
327 use crate::constants::{
328 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES,
329 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX,
330 };
331 if ciphertext.as_slice().len() < CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES {
332 return Err(length_error!(
333 crate::ErrorContext::Ciphertext,
334 ciphertext.as_slice().len(),
335 min CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
336 ));
337 }
338
339 let message_len =
340 ciphertext.as_slice().len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES;
341 if message_len > CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX {
342 return Err(length_error!(
343 crate::ErrorContext::Ciphertext,
344 ciphertext.as_slice().len(),
345 max CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX
346 + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
347 ));
348 }
349
350 let mut message = Output::default();
351 message.resize(message_len, 0);
352 let mut tag = 0u8;
353 let mut next_state = self.state.clone();
354 crypto_secretstream_xchacha20poly1305_pull(
355 &mut next_state,
356 message.as_mut_slice(),
357 &mut tag,
358 ciphertext.as_slice(),
359 associated_data.map(|aad| aad.as_slice()),
360 )?;
361
362 let tag = match Tag::try_from(tag) {
363 Ok(tag) => tag,
364 Err(error) => {
365 message.as_mut_slice().zeroize();
366 return Err(error);
367 }
368 };
369 self.state = next_state;
370
371 Ok((message, tag))
372 }
373
374 pub fn pull_to_vec<Input: Bytes>(
384 &mut self,
385 ciphertext: &Input,
386 associated_data: Option<&Input>,
387 ) -> Result<(Vec<u8>, Tag), Error> {
388 self.pull(ciphertext, associated_data)
389 }
390}
391
392#[cfg(test)]
393mod validation_tests {
394 use super::*;
395 use crate::constants::CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES;
396
397 #[test]
398 fn rustaceous_push_rejects_unknown_tag_without_advancing_state() {
399 let key = Key::generate();
400 let (mut push_stream, _header): (_, Header) = DryocStream::init_push(&key);
401 let original_state = push_stream.state.clone();
402 let invalid_tag = Tag::from_bits_retain(0x80);
403
404 let result: Result<Vec<u8>, Error> = push_stream.push_to_vec(b"message", None, invalid_tag);
405 assert!(matches!(
406 result,
407 Err(Error::InvalidValue {
408 context: crate::ErrorContext::Tag,
409 ..
410 })
411 ));
412 assert!(push_stream.state == original_state);
413 }
414
415 #[test]
416 fn rustaceous_pull_rejects_unknown_tag_without_advancing_state() {
417 let key = Key::generate();
418 let mut invalid_push_state = State::new();
419 let mut raw_header =
420 [0u8; crate::constants::CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES];
421 crypto_secretstream_xchacha20poly1305_init_push(
422 &mut invalid_push_state,
423 &mut raw_header,
424 key.as_array(),
425 );
426 let mut valid_push_state = invalid_push_state.clone();
427 let header = Header::try_from(raw_header.as_slice()).expect("header conversion failed");
428 let mut pull_stream = DryocStream::init_pull(&key, &header);
429 let original_pull_state = pull_stream.state.clone();
430 let message = b"authenticated unknown tag";
431
432 let mut invalid_ciphertext =
433 vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
434 crypto_secretstream_xchacha20poly1305_push(
435 &mut invalid_push_state,
436 &mut invalid_ciphertext,
437 message,
438 None,
439 0x80,
440 )
441 .expect("classic push failed");
442
443 let error = pull_stream
444 .pull_to_vec(&invalid_ciphertext, None)
445 .expect_err("unknown tag must be rejected");
446 assert!(matches!(
447 error,
448 Error::InvalidValue {
449 context: crate::ErrorContext::Tag,
450 ..
451 }
452 ));
453 assert!(pull_stream.state == original_pull_state);
454
455 let mut valid_ciphertext =
456 vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
457 crypto_secretstream_xchacha20poly1305_push(
458 &mut valid_push_state,
459 &mut valid_ciphertext,
460 message,
461 None,
462 Tag::MESSAGE.bits(),
463 )
464 .expect("classic push failed");
465 let (decrypted, tag) = pull_stream
466 .pull_to_vec(&valid_ciphertext, None)
467 .expect("state must remain usable after rejection");
468 assert_eq!(decrypted, message);
469 assert_eq!(tag, Tag::MESSAGE);
470 }
471}
472
473#[cfg(all(test, dryoc_native_tests))]
474mod tests {
475 use super::*;
476
477 #[test]
478 fn test_stream_push() {
479 use sodiumoxide::crypto::secretstream::{
480 Header as SOHeader, Key as SOKey, Stream as SOStream, Tag as SOTag,
481 };
482
483 let message1 = b"Arbitrary data to encrypt";
484 let message2 = b"split into";
485 let message3 = b"three messages";
486
487 let key = Key::generate();
489
490 let (mut push_stream, header): (_, Header) = DryocStream::init_push(&key);
492 let c1: Vec<u8> = push_stream
494 .push(message1, None, Tag::MESSAGE)
495 .expect("Encrypt failed");
496 let c2: Vec<u8> = push_stream
497 .push(message2, None, Tag::MESSAGE)
498 .expect("Encrypt failed");
499 let c3: Vec<u8> = push_stream
500 .push(message3, None, Tag::FINAL)
501 .expect("Encrypt failed");
502
503 let mut so_stream_pull = SOStream::init_pull(
505 &SOHeader::from_slice(header.as_slice()).expect("header failed"),
506 &SOKey::from_slice(key.as_slice()).expect("key failed"),
507 )
508 .expect("pull init failed");
509
510 let (m1, tag1) = so_stream_pull.pull(&c1, None).expect("decrypt failed");
511 let (m2, tag2) = so_stream_pull.pull(&c2, None).expect("decrypt failed");
512 let (m3, tag3) = so_stream_pull.pull(&c3, None).expect("decrypt failed");
513
514 assert_eq!(message1, m1.as_slice());
515 assert_eq!(message2, m2.as_slice());
516 assert_eq!(message3, m3.as_slice());
517
518 assert_eq!(tag1, SOTag::Message);
519 assert_eq!(tag2, SOTag::Message);
520 assert_eq!(tag3, SOTag::Final);
521 }
522
523 #[test]
524 fn test_stream_pull() {
525 use std::convert::TryFrom;
526
527 use sodiumoxide::crypto::secretstream::{Key as SOKey, Stream as SOStream, Tag as SOTag};
528
529 let message1 = b"Arbitrary data to encrypt";
530 let message2 = b"split into";
531 let message3 = b"three messages";
532
533 let key = Key::generate();
535
536 let (mut so_push_stream, so_header) =
538 SOStream::init_push(&SOKey::from_slice(key.as_slice()).expect("key failed"))
539 .expect("init push failed");
540 let c1: Vec<u8> = so_push_stream
542 .push(message1, None, SOTag::Message)
543 .expect("Encrypt failed");
544 let c2: Vec<u8> = so_push_stream
545 .push(message2, None, SOTag::Message)
546 .expect("Encrypt failed");
547 let c3: Vec<u8> = so_push_stream
548 .push(message3, None, SOTag::Final)
549 .expect("Encrypt failed");
550
551 let mut pull_stream =
553 DryocStream::init_pull(&key, &Header::try_from(so_header.as_ref()).expect("header"));
554
555 let (m1, tag1): (Vec<u8>, Tag) = pull_stream.pull(&c1, None).expect("Decrypt failed");
557 let (m2, tag2): (Vec<u8>, Tag) = pull_stream.pull(&c2, None).expect("Decrypt failed");
558 let (m3, tag3): (Vec<u8>, Tag) = pull_stream.pull(&c3, None).expect("Decrypt failed");
559
560 assert_eq!(message1, m1.as_slice());
561 assert_eq!(message2, m2.as_slice());
562 assert_eq!(message3, m3.as_slice());
563
564 assert_eq!(tag1, Tag::MESSAGE);
565 assert_eq!(tag2, Tag::MESSAGE);
566 assert_eq!(tag3, Tag::FINAL);
567 }
568
569 #[cfg(all(feature = "protected", any(unix, windows)))]
570 #[test]
571 fn test_protected_memory() {
572 use crate::protected::*;
573
574 let message1 = b"Arbitrary data to encrypt";
575 let message2 = b"split into";
576 let message3 = b"three messages";
577
578 let key = protected::Key::generate_locked().expect("generate locked");
580
581 let (mut push_stream, header): (_, Header) = DryocStream::init_push(&key);
583
584 let key = key
586 .munlock()
587 .expect("munlock")
588 .mprotect_noaccess()
589 .expect("mprotect");
590
591 let c1: Locked<HeapBytes> = push_stream
593 .push(message1, None, Tag::MESSAGE)
594 .expect("Encrypt failed");
595 let c2: Vec<u8> = push_stream
596 .push(message2, None, Tag::MESSAGE)
597 .expect("Encrypt failed");
598 let c3: Vec<u8> = push_stream
599 .push(message3, None, Tag::FINAL)
600 .expect("Encrypt failed");
601
602 let key = key.mprotect_readonly().expect("mprotect");
604
605 let mut pull_stream = DryocStream::init_pull(&key, &header);
607
608 let _key = key.mprotect_noaccess().expect("mprotect");
610
611 let (m1, tag1): (Locked<HeapBytes>, Tag) =
613 pull_stream.pull(&c1, None).expect("Decrypt failed");
614 let (m2, tag2): (Locked<HeapBytes>, Tag) =
615 pull_stream.pull(&c2, None).expect("Decrypt failed");
616 let (m3, tag3): (Locked<HeapBytes>, Tag) =
617 pull_stream.pull(&c3, None).expect("Decrypt failed");
618
619 assert_eq!(message1, m1.as_slice());
620 assert_eq!(message2, m2.as_slice());
621 assert_eq!(message3, m3.as_slice());
622
623 assert_eq!(tag1, Tag::MESSAGE);
624 assert_eq!(tag2, Tag::MESSAGE);
625 assert_eq!(tag3, Tag::FINAL);
626 }
627}