1use subtle::ConstantTimeEq;
90use zeroize::{Zeroize, ZeroizeOnDrop};
91
92use crate::chacha20::ChaCha20;
93use crate::classic::crypto_core::crypto_core_hchacha20;
94use crate::constants::{
95 CRYPTO_CORE_HCHACHA20_INPUTBYTES, CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES,
96 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES,
97 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES,
98 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES,
99 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES,
100 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX,
101 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY, CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES,
102 CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES,
103};
104use crate::error::*;
105use crate::poly1305::Poly1305;
106use crate::rng::copy_randombytes;
107use crate::types::*;
108use crate::utils::{WideZeroizing, increment_bytes, pad16, verify_ct, xor_buf, zeroize_bytes};
109
110pub type Key = [u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES];
112pub type Nonce = [u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES];
114pub type Header = [u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES];
116
117#[derive(Clone, Default)]
121pub struct State {
122 k: Key,
123 nonce: Nonce,
124}
125
126impl Zeroize for State {
129 fn zeroize(&mut self) {
130 zeroize_bytes(&mut self.k);
131 zeroize_bytes(&mut self.nonce);
132 }
133}
134
135impl Drop for State {
136 fn drop(&mut self) {
137 self.zeroize();
138 }
139}
140
141impl ZeroizeOnDrop for State {}
142
143impl PartialEq for State {
144 fn eq(&self, other: &Self) -> bool {
145 (self.k.ct_eq(&other.k) & self.nonce.ct_eq(&other.nonce)).into()
146 }
147}
148
149impl Eq for State {}
150
151impl State {
152 #[must_use]
154 pub fn new() -> Self {
155 Self::default()
156 }
157}
158
159pub fn crypto_secretstream_xchacha20poly1305_keygen(key: &mut Key) {
161 copy_randombytes(key);
162}
163
164fn state_counter(nonce: &mut Nonce) -> &mut [u8] {
165 &mut nonce[..CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES]
166}
167
168fn state_inonce(nonce: &mut Nonce) -> &mut [u8] {
169 &mut nonce[CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES
170 ..CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES
171 + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES]
172}
173
174pub(crate) fn ciphertext_len_from_message_len(message_len: usize) -> Result<usize, Error> {
177 validate_length!(
178 max CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX,
179 message_len,
180 crate::ErrorContext::Message
181 );
182 Ok(message_len + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES)
183}
184
185pub(crate) fn message_len_from_ciphertext_len(ciphertext_len: usize) -> Result<usize, Error> {
189 validate_length!(
190 min CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES,
191 ciphertext_len,
192 crate::ErrorContext::Ciphertext
193 );
194 validate_length!(
195 max CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX
196 + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES,
197 ciphertext_len,
198 crate::ErrorContext::Ciphertext
199 );
200 Ok(ciphertext_len - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES)
201}
202
203fn secretstream_length_block(associated_data_len: usize, message_len: usize) -> [u8; 16] {
204 let mut lengths = [0u8; 16];
205 lengths[..8].copy_from_slice(&(associated_data_len as u64).to_le_bytes());
206 lengths[8..].copy_from_slice(&(64u64 + message_len as u64).to_le_bytes());
207 lengths
208}
209
210#[inline]
217fn secretstream_init_mac(
218 state: &State,
219 block: &mut [u8; 64],
220 associated_data: &[u8],
221) -> (ChaCha20, Poly1305) {
222 let mut cipher = ChaCha20::ietf(&state.k, &state.nonce, 0);
223
224 let mut block0 = WideZeroizing::new([0u8; 64]);
226 cipher.apply_keystream_with_head(&mut block0, block);
227 let mut mac_key = crate::poly1305::Key::default();
228 mac_key.copy_from_slice(&block0[..mac_key.len()]);
229 let mut mac = Poly1305::new(&mac_key);
230 mac_key.zeroize();
231 drop(block0);
232
233 mac.update(associated_data);
234 mac.update(&[0u8; 16][..pad16(associated_data.len())]);
235
236 (cipher, mac)
237}
238
239#[inline]
243fn secretstream_advance(state: &mut State, mac: &[u8], tag: u8) {
244 let inonce = state_inonce(&mut state.nonce);
245 xor_buf(inonce, mac);
246
247 let counter = state_counter(&mut state.nonce);
248 increment_bytes(counter);
249
250 if tag & CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY
251 == CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY
252 || state_counter(&mut state.nonce)
253 .ct_eq(&[0u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES])
254 .unwrap_u8()
255 == 1
256 {
257 crypto_secretstream_xchacha20poly1305_rekey(state);
258 }
259}
260
261fn _crypto_secretstream_xchacha20poly1305_counter_reset(state: &mut State) {
262 let counter = state_counter(&mut state.nonce);
263 counter.fill(0);
264 counter[0] = 1;
265}
266
267pub fn crypto_secretstream_xchacha20poly1305_init_push(
276 state: &mut State,
277 header: &mut Header,
278 key: &Key,
279) {
280 copy_randombytes(header);
281 secretstream_init(state, header, key);
282}
283
284pub fn crypto_secretstream_xchacha20poly1305_init_pull(
292 state: &mut State,
293 header: &Header,
294 key: &Key,
295) {
296 secretstream_init(state, header, key);
297}
298
299fn secretstream_init(state: &mut State, header: &Header, key: &Key) {
300 crypto_core_hchacha20(
301 &mut state.k,
302 header.first_chunk::<16>().expect("16-byte prefix"),
303 key,
304 None,
305 );
306 _crypto_secretstream_xchacha20poly1305_counter_reset(state);
307
308 let inonce = state_inonce(&mut state.nonce);
309 inonce.copy_from_slice(
310 &header[CRYPTO_CORE_HCHACHA20_INPUTBYTES
311 ..(CRYPTO_CORE_HCHACHA20_INPUTBYTES
312 + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES)],
313 );
314}
315
316pub fn crypto_secretstream_xchacha20poly1305_rekey(state: &mut State) {
321 let mut new_state = WideZeroizing::new(
322 [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES
323 + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES],
324 );
325
326 new_state[..CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES].copy_from_slice(&state.k);
327 new_state[CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES..]
328 .copy_from_slice(state_inonce(&mut state.nonce));
329
330 ChaCha20::ietf(&state.k, &state.nonce, 0).apply_keystream(&mut *new_state);
331
332 state
333 .k
334 .copy_from_slice(&new_state[0..CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES]);
335 state_inonce(&mut state.nonce)
336 .copy_from_slice(&new_state[CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES..]);
337
338 _crypto_secretstream_xchacha20poly1305_counter_reset(state);
339}
340
341pub fn crypto_secretstream_xchacha20poly1305_push(
356 state: &mut State,
357 ciphertext: &mut [u8],
358 message: &[u8],
359 associated_data: Option<&[u8]>,
360 tag: u8,
361) -> Result<(), Error> {
362 let expected_ciphertext_len = ciphertext_len_from_message_len(message.len())?;
363 validate_length!(
364 exact expected_ciphertext_len,
365 ciphertext.len(),
366 crate::ErrorContext::Ciphertext
367 );
368
369 let associated_data = associated_data.unwrap_or(&[]);
370 let _pad0 = [0u8; 16];
371
372 let mut block = WideZeroizing::new([0u8; 64]);
375 block[0] = tag;
376 let (mut cipher, mut mac) = secretstream_init_mac(state, &mut block, associated_data);
377 mac.update(&*block);
378
379 let mlen = message.len();
380 ciphertext[0] = block[0];
381 cipher.apply_keystream_and_mac(Some(message), &mut ciphertext[1..(1 + mlen)], &mut mac);
382
383 let size_data = secretstream_length_block(associated_data.len(), mlen);
384
385 let buffer_mac_pad = ((0x10 - block.len() as i64 + mlen as i64) & 0xf) as usize;
389 mac.update(&_pad0[0..buffer_mac_pad]);
390 mac.update(&size_data);
391
392 mac.finalize(&mut ciphertext[1 + mlen..]);
393
394 secretstream_advance(state, &ciphertext[1 + mlen..], tag);
395
396 Ok(())
397}
398
399pub fn crypto_secretstream_xchacha20poly1305_pull(
418 state: &mut State,
419 message: &mut [u8],
420 tag: &mut u8,
421 ciphertext: &[u8],
422 associated_data: Option<&[u8]>,
423) -> Result<usize, Error> {
424 let _pad0 = [0u8; 16];
425
426 let mlen = message_len_from_ciphertext_len(ciphertext.len())?;
427
428 validate_length!(min mlen, message.len(), crate::ErrorContext::Message);
429
430 let associated_data = associated_data.unwrap_or(&[]);
431
432 let mut block = WideZeroizing::new([0u8; 64]);
435 block[0] = ciphertext[0];
436 let (mut cipher, mut mac) = secretstream_init_mac(state, &mut block, associated_data);
437
438 let decrypted_tag = block[0];
439 block[0] = ciphertext[0];
440
441 mac.update(&*block);
442
443 let buffer_mac_pad = ((0x10 - block.len() as i64 + mlen as i64) & 0xf) as usize;
447 mac.update(&ciphertext[1..1 + mlen]);
448 mac.update(&_pad0[..buffer_mac_pad]);
449
450 let size_data = secretstream_length_block(associated_data.len(), mlen);
451 mac.update(&size_data);
452 let computed = WideZeroizing::new(mac.finalize_to_array());
453 mac.zeroize();
456
457 verify_ct(&ciphertext[1 + mlen..], computed.as_slice())?;
458
459 cipher.apply_keystream_b2b(&ciphertext[1..1 + mlen], &mut message[..mlen]);
460 *tag = decrypted_tag;
461
462 secretstream_advance(state, &*computed, decrypted_tag);
463
464 Ok(mlen)
465}
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470 use crate::dryocstream::Tag;
471 #[cfg(dryoc_native_tests)]
472 use crate::test_prelude::*;
473
474 #[test]
480 fn length_checks_accept_exactly_messagebytes_max() {
481 const MAX: usize = CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX;
482 const ABYTES: usize = CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES;
483
484 #[cfg(target_pointer_width = "64")]
485 assert_eq!(MAX, 64 * ((1 << 32) - 2));
486 #[cfg(not(target_pointer_width = "64"))]
487 assert_eq!(MAX, usize::MAX - ABYTES);
488
489 assert!(matches!(ciphertext_len_from_message_len(0), Ok(ABYTES)));
490 assert!(matches!(
491 ciphertext_len_from_message_len(MAX),
492 Ok(len) if len == MAX + ABYTES
493 ));
494 assert!(matches!(
495 ciphertext_len_from_message_len(MAX + 1),
496 Err(Error::InvalidLength {
497 context: crate::ErrorContext::Message,
498 actual,
499 constraint: LengthConstraint::AtMost(max),
500 }) if actual == MAX + 1 && max == MAX
501 ));
502
503 assert!(matches!(
504 message_len_from_ciphertext_len(ABYTES - 1),
505 Err(Error::InvalidLength {
506 context: crate::ErrorContext::Ciphertext,
507 actual,
508 constraint: LengthConstraint::AtLeast(ABYTES),
509 }) if actual == ABYTES - 1
510 ));
511 assert!(matches!(message_len_from_ciphertext_len(ABYTES), Ok(0)));
512 assert!(matches!(
513 message_len_from_ciphertext_len(MAX + ABYTES),
514 Ok(len) if len == MAX
515 ));
516 if let Some(too_long) = (MAX + ABYTES).checked_add(1) {
519 assert!(matches!(
520 message_len_from_ciphertext_len(too_long),
521 Err(Error::InvalidLength {
522 context: crate::ErrorContext::Ciphertext,
523 actual,
524 constraint: LengthConstraint::AtMost(max),
525 }) if actual == too_long && max == MAX + ABYTES
526 ));
527 }
528 }
529
530 #[test]
535 fn push_and_pull_reject_invalid_buffer_lengths_without_mutation() {
536 let key = Key::default();
537 let mut state = State::new();
538 let mut header = Header::default();
539 crypto_secretstream_xchacha20poly1305_init_push(&mut state, &mut header, &key);
540 let original_state = state.clone();
541 let message = b"message";
542
543 for len in [
544 0,
545 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES,
546 message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES - 1,
547 message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES + 1,
548 ] {
549 let mut ciphertext = vec![0xa5u8; len];
550 let error = crypto_secretstream_xchacha20poly1305_push(
551 &mut state,
552 &mut ciphertext,
553 message,
554 None,
555 Tag::Message.bits(),
556 )
557 .expect_err("ciphertext must be exactly the message plus overhead");
558 assert!(
559 matches!(
560 error,
561 Error::InvalidLength {
562 context: crate::ErrorContext::Ciphertext,
563 ..
564 }
565 ),
566 "push into {len} bytes"
567 );
568 assert_eq!(ciphertext, vec![0xa5u8; len], "push into {len} bytes");
569 assert!(state == original_state, "push into {len} bytes");
570 }
571
572 let mut ciphertext =
573 vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
574 crypto_secretstream_xchacha20poly1305_push(
575 &mut state,
576 &mut ciphertext,
577 message,
578 None,
579 Tag::Message.bits(),
580 )
581 .expect("state must remain usable");
582
583 let mut state = State::new();
584 crypto_secretstream_xchacha20poly1305_init_pull(&mut state, &header, &key);
585 let original_state = state.clone();
586 let mut tag = 0x5a;
587
588 let short_ciphertext = [0u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES - 1];
589 let mut output = [0xa5u8; 8];
590 let error = crypto_secretstream_xchacha20poly1305_pull(
591 &mut state,
592 &mut output,
593 &mut tag,
594 &short_ciphertext,
595 None,
596 )
597 .expect_err("ciphertext must include secretstream overhead");
598 assert!(matches!(
599 error,
600 Error::InvalidLength {
601 context: crate::ErrorContext::Ciphertext,
602 ..
603 }
604 ));
605 assert_eq!((output, tag), ([0xa5u8; 8], 0x5a));
606 assert!(state == original_state);
607
608 let mut output = vec![0xa5u8; message.len() - 1];
609 let error = crypto_secretstream_xchacha20poly1305_pull(
610 &mut state,
611 &mut output,
612 &mut tag,
613 &ciphertext,
614 None,
615 )
616 .expect_err("the message buffer must hold the plaintext");
617 assert!(matches!(
618 error,
619 Error::InvalidLength {
620 context: crate::ErrorContext::Message,
621 ..
622 }
623 ));
624 assert_eq!(output, vec![0xa5u8; message.len() - 1]);
625 assert_eq!(tag, 0x5a);
626 assert!(state == original_state);
627
628 let mut output = vec![0u8; message.len()];
629 crypto_secretstream_xchacha20poly1305_pull(
630 &mut state,
631 &mut output,
632 &mut tag,
633 &ciphertext,
634 None,
635 )
636 .expect("state must remain usable");
637 assert_eq!(
638 (output.as_slice(), tag),
639 (&message[..], Tag::Message.bits())
640 );
641 }
642
643 #[test]
644 fn pull_authenticates_before_mutating_outputs_or_state() {
645 let key = Key::default();
646 let mut push_state = State::new();
647 let mut header = Header::default();
648 crypto_secretstream_xchacha20poly1305_init_push(&mut push_state, &mut header, &key);
649
650 let plaintext = b"do not publish unauthenticated plaintext";
651 let mut ciphertext =
652 vec![0u8; plaintext.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
653 crypto_secretstream_xchacha20poly1305_push(
654 &mut push_state,
655 &mut ciphertext,
656 plaintext,
657 Some(b"associated data"),
658 Tag::Final.bits(),
659 )
660 .expect("push failed");
661
662 let mut pull_state = State::new();
663 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &header, &key);
664 let original_state = pull_state.clone();
665 let mut tampered = ciphertext.clone();
666 *tampered.last_mut().expect("authentication tag") ^= 1;
667 let mut output = vec![0xa5; plaintext.len()];
668 let original_output = output.clone();
669 let mut tag = 0x5a;
670
671 assert!(matches!(
672 crypto_secretstream_xchacha20poly1305_pull(
673 &mut pull_state,
674 &mut output,
675 &mut tag,
676 &tampered,
677 Some(b"associated data"),
678 ),
679 Err(Error::AuthenticationFailed)
680 ));
681 assert_eq!(output, original_output);
682 assert_eq!(tag, 0x5a);
683 assert!(pull_state == original_state);
684
685 crypto_secretstream_xchacha20poly1305_pull(
686 &mut pull_state,
687 &mut output,
688 &mut tag,
689 &ciphertext,
690 Some(b"associated data"),
691 )
692 .expect("state must remain usable after authentication failure");
693 assert_eq!(output, plaintext);
694 assert_eq!(tag, Tag::Final.bits());
695 }
696
697 #[test]
698 fn length_block_uses_fixed_width_little_endian_values() {
699 let lengths = secretstream_length_block(0x0102_0304, 0x0506_0708);
700
701 assert_eq!(&lengths[..8], &0x0102_0304u64.to_le_bytes());
702 assert_eq!(&lengths[8..], &(64u64 + 0x0506_0708).to_le_bytes());
703 }
704
705 #[test]
706 fn test_sizes() {
707 use crate::constants::*;
708
709 const _: () = assert!(
710 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES
711 == CRYPTO_CORE_HCHACHA20_INPUTBYTES
712 + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES
713 );
714
715 const _: () = assert!(
716 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES
717 == CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES
718 );
719
720 const _: () = assert!(
721 CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES
722 == CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES
723 + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES
724 );
725
726 const _: () = assert!(
727 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX
728 <= CRYPTO_AEAD_CHACHA20POLY1305_IETF_MESSAGEBYTES_MAX
729 );
730
731 const _: () = assert!(
732 CRYPTO_ONETIMEAUTH_POLY1305_BYTES >= CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES
733 );
734
735 #[cfg(target_pointer_width = "32")]
736 {
737 assert_eq!(
738 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX,
739 usize::MAX - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
740 );
741 assert_eq!(
742 CRYPTO_AEAD_CHACHA20POLY1305_IETF_MESSAGEBYTES_MAX,
743 usize::MAX - CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES
744 );
745 assert_eq!(
746 CRYPTO_SECRETBOX_MESSAGEBYTES_MAX,
747 usize::MAX - CRYPTO_SECRETBOX_MACBYTES
748 );
749 }
750 }
751
752 #[test]
753 fn test_secretstream_large_aad() {
754 let mut key = Key::default();
755 crypto_secretstream_xchacha20poly1305_keygen(&mut key);
756
757 let mut push_state = State::new();
758 let mut push_header = Header::default();
759 crypto_secretstream_xchacha20poly1305_init_push(&mut push_state, &mut push_header, &key);
760
761 let message = b"hello world";
762 let large_aad = vec![0x42u8; 328]; let mut ciphertext =
765 vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
766 crypto_secretstream_xchacha20poly1305_push(
767 &mut push_state,
768 &mut ciphertext,
769 message,
770 Some(&large_aad),
771 Tag::Message.bits(),
772 )
773 .expect("push failed");
774
775 let mut pull_state = State::new();
776 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &push_header, &key);
777
778 let mut decrypted = vec![0u8; message.len()];
779 let mut tag = 0u8;
780
781 crypto_secretstream_xchacha20poly1305_pull(
782 &mut pull_state,
783 &mut decrypted,
784 &mut tag,
785 &ciphertext,
786 Some(&large_aad),
787 )
788 .expect("pull failed");
789
790 assert_eq!(message.as_slice(), decrypted.as_slice());
791 assert_eq!(tag, Tag::Message.bits());
792
793 let mut wrong_aad = large_aad.clone();
795 wrong_aad[100] = 0x43; let mut wrong_aad_pull_state = State::new();
798 crypto_secretstream_xchacha20poly1305_init_pull(
799 &mut wrong_aad_pull_state,
800 &push_header,
801 &key,
802 );
803
804 let mut decrypted = vec![0u8; message.len()];
805 let mut tag = 0u8;
806
807 assert!(
808 crypto_secretstream_xchacha20poly1305_pull(
809 &mut wrong_aad_pull_state,
810 &mut decrypted,
811 &mut tag,
812 &ciphertext,
813 Some(&wrong_aad),
814 )
815 .is_err()
816 );
817 }
818
819 #[test]
820 fn test_secretstream_small_aad() {
821 let mut key = Key::default();
822 crypto_secretstream_xchacha20poly1305_keygen(&mut key);
823
824 let mut push_state = State::new();
825 let mut push_header = Header::default();
826 crypto_secretstream_xchacha20poly1305_init_push(&mut push_state, &mut push_header, &key);
827
828 let message = b"hello world";
829 let small_aad = b"abc"; let mut ciphertext =
832 vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
833 crypto_secretstream_xchacha20poly1305_push(
834 &mut push_state,
835 &mut ciphertext,
836 message,
837 Some(small_aad),
838 Tag::Message.bits(),
839 )
840 .expect("push failed");
841
842 let mut pull_state = State::new();
843 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &push_header, &key);
844
845 let mut decrypted = vec![0u8; message.len()];
846 let mut tag = 0u8;
847
848 crypto_secretstream_xchacha20poly1305_pull(
849 &mut pull_state,
850 &mut decrypted,
851 &mut tag,
852 &ciphertext,
853 Some(small_aad),
854 )
855 .expect("pull failed");
856
857 assert_eq!(message.as_slice(), decrypted.as_slice());
858 assert_eq!(tag, Tag::Message.bits());
859
860 let wrong_aad = b"xyz"; let mut wrong_aad_pull_state = State::new();
864 crypto_secretstream_xchacha20poly1305_init_pull(
865 &mut wrong_aad_pull_state,
866 &push_header,
867 &key,
868 );
869
870 let mut decrypted = vec![0u8; message.len()];
871 let mut tag = 0u8;
872
873 assert!(
874 crypto_secretstream_xchacha20poly1305_pull(
875 &mut wrong_aad_pull_state,
876 &mut decrypted,
877 &mut tag,
878 &ciphertext,
879 Some(wrong_aad),
880 )
881 .is_err()
882 );
883 }
884
885 #[cfg(dryoc_native_tests)]
886 mod native_tests {
887 use super::*;
888 use crate::constants::{
889 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL,
890 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE,
891 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH,
892 };
893
894 fn so_state(state: &State) -> libsodium_sys::crypto_secretstream_xchacha20poly1305_state {
896 libsodium_sys::crypto_secretstream_xchacha20poly1305_state {
897 k: state.k,
898 nonce: state.nonce,
899 _pad: [0u8; 8],
900 }
901 }
902
903 fn assert_same_state(
904 so: &libsodium_sys::crypto_secretstream_xchacha20poly1305_state,
905 state: &State,
906 ctx: &str,
907 ) {
908 assert_eq!((so.k, so.nonce), (state.k, state.nonce), "{ctx}: state");
909 }
910
911 fn so_push(
913 so: &mut libsodium_sys::crypto_secretstream_xchacha20poly1305_state,
914 message: &[u8],
915 ad: &[u8],
916 tag: u8,
917 ) -> Vec<u8> {
918 crate::native_test_util::init();
919 let mut ciphertext =
920 vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
921 let mut clen: libc::c_ulonglong = 0;
922 let rc = unsafe {
926 libsodium_sys::crypto_secretstream_xchacha20poly1305_push(
927 so,
928 ciphertext.as_mut_ptr(),
929 &mut clen,
930 message.as_ptr(),
931 message.len() as libc::c_ulonglong,
932 ad.as_ptr(),
933 ad.len() as libc::c_ulonglong,
934 tag,
935 )
936 };
937 assert_eq!((rc, clen as usize), (0, ciphertext.len()));
938 ciphertext
939 }
940
941 fn so_pull(
943 so: &mut libsodium_sys::crypto_secretstream_xchacha20poly1305_state,
944 ciphertext: &[u8],
945 ad: &[u8],
946 ) -> (Vec<u8>, u8) {
947 crate::native_test_util::init();
948 let mut message =
949 vec![0u8; ciphertext.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
950 let mut mlen: libc::c_ulonglong = 0;
951 let mut tag: libc::c_uchar = 0;
952 let rc = unsafe {
954 libsodium_sys::crypto_secretstream_xchacha20poly1305_pull(
955 so,
956 message.as_mut_ptr(),
957 &mut mlen,
958 &mut tag,
959 ciphertext.as_ptr(),
960 ciphertext.len() as libc::c_ulonglong,
961 ad.as_ptr(),
962 ad.len() as libc::c_ulonglong,
963 )
964 };
965 assert_eq!((rc, mlen as usize), (0, message.len()));
966 (message, tag)
967 }
968
969 #[allow(clippy::too_many_arguments)]
974 fn check_push_pull_match_libsodium(
975 push_state: &mut State,
976 so_push_state: &mut libsodium_sys::crypto_secretstream_xchacha20poly1305_state,
977 pull_state: &mut State,
978 so_pull_state: &mut libsodium_sys::crypto_secretstream_xchacha20poly1305_state,
979 message: &[u8],
980 ad: &[u8],
981 tag: u8,
982 ctx: &str,
983 ) {
984 let expected = so_push(so_push_state, message, ad, tag);
985 let mut ciphertext =
986 vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
987 crypto_secretstream_xchacha20poly1305_push(
988 push_state,
989 &mut ciphertext,
990 message,
991 Some(ad),
992 tag,
993 )
994 .expect("push");
995 assert_eq!(ciphertext, expected, "{ctx}: ciphertext");
996 assert_same_state(so_push_state, push_state, &format!("{ctx}: push"));
997
998 let (expected_message, expected_tag) = so_pull(so_pull_state, &ciphertext, ad);
999 let mut output = vec![0u8; message.len()];
1000 let mut pulled_tag = 0xa5;
1001 let mlen = crypto_secretstream_xchacha20poly1305_pull(
1002 pull_state,
1003 &mut output,
1004 &mut pulled_tag,
1005 &ciphertext,
1006 Some(ad),
1007 )
1008 .expect("pull");
1009 assert_eq!(mlen, message.len(), "{ctx}: pulled length");
1010 assert_eq!(
1011 (&output, pulled_tag),
1012 (&expected_message, expected_tag),
1013 "{ctx}: pull"
1014 );
1015 assert_eq!(
1016 (&output, pulled_tag),
1017 (&message.to_vec(), tag),
1018 "{ctx}: round trip"
1019 );
1020 assert_same_state(so_pull_state, pull_state, &format!("{ctx}: pull"));
1021 }
1022
1023 fn boundary_lens() -> Vec<usize> {
1027 let mut lens = vec![0, 1, 63, 64, 65, 127, 128, 129];
1028 for chunk in [8 * 64, 9 * 64, 16 * 64] {
1029 lens.extend([
1030 chunk - 129,
1031 chunk - 128,
1032 chunk - 127,
1033 chunk - 1,
1034 chunk,
1035 chunk + 1,
1036 ]);
1037 }
1038 lens.sort_unstable();
1039 lens.dedup();
1040 lens
1041 }
1042
1043 #[test]
1049 fn test_every_tag_and_length_matches_libsodium() {
1050 let mut rng = crate::utils::test_util::XorShift64::new(0x7f4a_7c15_9e37_79b9);
1051 let key: Key = rng.next_bytes32();
1052 let mut push_state = State::new();
1053 let mut header = Header::default();
1054 crypto_secretstream_xchacha20poly1305_init_push(&mut push_state, &mut header, &key);
1055 let mut pull_state = State::new();
1056 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &header, &key);
1057 let mut so_push_state = so_state(&push_state);
1058 let mut so_pull_state = so_state(&pull_state);
1059
1060 let tags = [
1061 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE,
1062 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH,
1063 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY,
1064 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL,
1065 0x42,
1066 ];
1067 let ads: [&[u8]; 3] = [b"", b"aad", &[0x5a; 16]];
1068 for len in boundary_lens() {
1069 let message: Vec<u8> = (0..len.div_ceil(8))
1070 .flat_map(|_| rng.next_u64().to_le_bytes())
1071 .take(len)
1072 .collect();
1073 for tag in tags {
1074 for ad in ads {
1075 check_push_pull_match_libsodium(
1076 &mut push_state,
1077 &mut so_push_state,
1078 &mut pull_state,
1079 &mut so_pull_state,
1080 &message,
1081 ad,
1082 tag,
1083 &format!("len {len}, tag {tag:#04x}, ad {}", ad.len()),
1084 );
1085 }
1086 }
1087 }
1088 }
1089
1090 #[test]
1095 fn test_counter_wrap_rekeys_like_libsodium() {
1096 let mut rng = crate::utils::test_util::XorShift64::new(0x2545_f491_4f6c_dd1d);
1097 let key: Key = rng.next_bytes32();
1098 for start in [
1099 [0xfd, 0xff, 0xff, 0xff],
1100 [0xfe, 0xff, 0xff, 0xff],
1101 [0xff; 4],
1102 ] {
1103 let mut push_state = State::new();
1104 let mut header = Header::default();
1105 crypto_secretstream_xchacha20poly1305_init_push(&mut push_state, &mut header, &key);
1106 let mut pull_state = State::new();
1107 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &header, &key);
1108 push_state.nonce[..CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES]
1109 .copy_from_slice(&start);
1110 pull_state.nonce[..CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES]
1111 .copy_from_slice(&start);
1112 let mut so_push_state = so_state(&push_state);
1113 let mut so_pull_state = so_state(&pull_state);
1114
1115 let before_wrap = push_state.clone();
1116 for step in 0..5 {
1117 let message: Vec<u8> = (0..37 * step).map(|i| i as u8).collect();
1118 check_push_pull_match_libsodium(
1119 &mut push_state,
1120 &mut so_push_state,
1121 &mut pull_state,
1122 &mut so_pull_state,
1123 &message,
1124 b"counter",
1125 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE,
1126 &format!("start {start:02x?}, step {step}"),
1127 );
1128 let counter = u32::from_le_bytes(
1129 push_state.nonce[..CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES]
1130 .try_into()
1131 .unwrap(),
1132 );
1133 let wrapped = u32::from_le_bytes(start).checked_add(step + 1).is_none();
1134 if wrapped {
1135 assert_ne!(
1137 push_state.k, before_wrap.k,
1138 "start {start:02x?}, step {step}"
1139 );
1140 assert_eq!(
1141 counter,
1142 (step + 1) - (u32::MAX - u32::from_le_bytes(start)),
1143 "start {start:02x?}, step {step}"
1144 );
1145 } else {
1146 assert_eq!(
1147 push_state.k, before_wrap.k,
1148 "start {start:02x?}, step {step}"
1149 );
1150 assert_eq!(
1151 counter,
1152 u32::from_le_bytes(start) + step + 1,
1153 "start {start:02x?}, step {step}"
1154 );
1155 }
1156 }
1157 }
1158 }
1159
1160 #[test]
1161 fn test_secretstream_basic_push() {
1162 use base64::Engine as _;
1163 use base64::engine::general_purpose;
1164 use libsodium_sys::{
1165 crypto_secretstream_xchacha20poly1305_init_pull as so_crypto_secretstream_xchacha20poly1305_init_pull,
1166 crypto_secretstream_xchacha20poly1305_pull as so_crypto_secretstream_xchacha20poly1305_pull,
1167 crypto_secretstream_xchacha20poly1305_push as so_crypto_secretstream_xchacha20poly1305_push,
1168 crypto_secretstream_xchacha20poly1305_state,
1169 };
1170
1171 use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
1172 use crate::dryocstream::Tag;
1173
1174 crate::native_test_util::init();
1175
1176 let mut key = Key::default();
1177 crypto_secretstream_xchacha20poly1305_keygen(&mut key);
1178
1179 let mut push_state = State::new();
1180 let mut push_header = Header::default();
1181 crypto_secretstream_xchacha20poly1305_init_push(
1182 &mut push_state,
1183 &mut push_header,
1184 &key,
1185 );
1186 let push_state_init = push_state.clone();
1187
1188 let message = b"hello";
1189 let mut output =
1190 vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
1191 let aad = b"";
1192 let tag = Tag::Message.bits();
1193 crypto_secretstream_xchacha20poly1305_push(
1194 &mut push_state,
1195 &mut output,
1196 message,
1197 Some(aad),
1198 tag,
1199 )
1200 .expect("push failed");
1201
1202 let mut so_output = output.clone();
1203 unsafe {
1204 use libc::{c_uchar, c_ulonglong};
1205 let mut so_state = crypto_secretstream_xchacha20poly1305_state {
1206 k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
1207 nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
1208 _pad: [0u8; 8],
1209 };
1210 so_state.k.copy_from_slice(&push_state_init.k);
1211 so_state.nonce.copy_from_slice(&push_state_init.nonce);
1212 let mut clen_p: c_ulonglong = 0;
1213 let ret = so_crypto_secretstream_xchacha20poly1305_push(
1214 &mut so_state,
1215 so_output.as_mut_ptr(),
1216 &mut clen_p,
1217 message.as_ptr(),
1218 message.len() as u64,
1219 aad.as_ptr(),
1220 aad.len() as u64,
1221 0,
1222 );
1223 assert_eq!(ret, 0);
1224 so_output.resize(clen_p as usize, 0);
1225 assert_eq!(
1226 general_purpose::STANDARD.encode(&so_output),
1227 general_purpose::STANDARD.encode(&output)
1228 );
1229 assert_eq!(
1230 general_purpose::STANDARD.encode(so_state.k),
1231 general_purpose::STANDARD.encode(push_state.k)
1232 );
1233 assert_eq!(
1234 general_purpose::STANDARD.encode(so_state.nonce),
1235 general_purpose::STANDARD.encode(push_state.nonce)
1236 );
1237
1238 let mut so_state = crypto_secretstream_xchacha20poly1305_state {
1239 k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
1240 nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
1241 _pad: [0u8; 8],
1242 };
1243 let mut mlen_p: c_ulonglong = 0;
1244 let mut tag_p: c_uchar = 0;
1245 let ret = so_crypto_secretstream_xchacha20poly1305_init_pull(
1246 &mut so_state,
1247 push_header.as_ptr(),
1248 key.as_ptr(),
1249 );
1250 assert_eq!(ret, 0);
1251 assert_eq!(
1252 general_purpose::STANDARD.encode(so_state.k),
1253 general_purpose::STANDARD.encode(push_state_init.k)
1254 );
1255 assert_eq!(
1256 general_purpose::STANDARD.encode(so_state.nonce),
1257 general_purpose::STANDARD.encode(push_state_init.nonce)
1258 );
1259 assert!(so_output.len() >= CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES);
1260 let ret = so_crypto_secretstream_xchacha20poly1305_pull(
1261 &mut so_state,
1262 so_output.as_mut_ptr(),
1263 &mut mlen_p,
1264 &mut tag_p,
1265 output.as_ptr(),
1266 output.len() as u64,
1267 aad.as_ptr(),
1268 aad.len() as u64,
1269 );
1270 assert_eq!(ret, 0);
1271 so_output.resize(mlen_p as usize, 0);
1272 }
1273 assert_eq!(
1274 general_purpose::STANDARD.encode(message),
1275 general_purpose::STANDARD.encode(&so_output)
1276 );
1277
1278 let mut pull_state = State::default();
1279 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &push_header, &key);
1280
1281 assert_eq!(
1282 general_purpose::STANDARD.encode(pull_state.k),
1283 general_purpose::STANDARD.encode(push_state_init.k)
1284 );
1285 assert_eq!(
1286 general_purpose::STANDARD.encode(pull_state.nonce),
1287 general_purpose::STANDARD.encode(push_state_init.nonce)
1288 );
1289
1290 let mut pull_result_message =
1291 vec![0u8; output.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
1292 let mut pull_result_tag = 0u8;
1293 crypto_secretstream_xchacha20poly1305_pull(
1294 &mut pull_state,
1295 &mut pull_result_message,
1296 &mut pull_result_tag,
1297 &output,
1298 Some(&[]),
1299 )
1300 .expect("pull failed");
1301
1302 assert_eq!(Tag::Message, Tag::try_from(pull_result_tag).expect("tag"));
1303 assert_eq!(
1304 general_purpose::STANDARD.encode(&pull_result_message),
1305 general_purpose::STANDARD.encode(message)
1306 );
1307 }
1308
1309 #[test]
1310 fn test_rekey() {
1311 use base64::Engine as _;
1312 use base64::engine::general_purpose;
1313 use libsodium_sys::{
1314 crypto_secretstream_xchacha20poly1305_rekey as so_crypto_secretstream_xchacha20poly1305_rekey,
1315 crypto_secretstream_xchacha20poly1305_state,
1316 };
1317
1318 use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
1319
1320 crate::native_test_util::init();
1321
1322 let mut key = Key::default();
1323 crypto_secretstream_xchacha20poly1305_keygen(&mut key);
1324
1325 let mut push_state = State::default();
1326 let mut push_header: Header = Header::default();
1327 crypto_secretstream_xchacha20poly1305_init_push(
1328 &mut push_state,
1329 &mut push_header,
1330 &key,
1331 );
1332 let push_state_init = push_state.clone();
1333
1334 crypto_secretstream_xchacha20poly1305_rekey(&mut push_state);
1335
1336 let mut so_state = crypto_secretstream_xchacha20poly1305_state {
1337 k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
1338 nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
1339 _pad: [0u8; 8],
1340 };
1341 so_state.k.copy_from_slice(&push_state_init.k);
1342 so_state.nonce.copy_from_slice(&push_state_init.nonce);
1343 unsafe {
1344 so_crypto_secretstream_xchacha20poly1305_rekey(&mut so_state);
1345 }
1346 assert_eq!(
1347 general_purpose::STANDARD.encode(so_state.k),
1348 general_purpose::STANDARD.encode(push_state.k)
1349 );
1350 assert_eq!(
1351 general_purpose::STANDARD.encode(so_state.nonce),
1352 general_purpose::STANDARD.encode(push_state.nonce)
1353 );
1354 }
1355
1356 #[test]
1357 fn test_secretstream_lots_of_messages_push() {
1358 use base64::Engine as _;
1359 use base64::engine::general_purpose;
1360 use libc::{c_uchar, c_ulonglong};
1361 use libsodium_sys::{
1362 crypto_secretstream_xchacha20poly1305_init_pull as so_crypto_secretstream_xchacha20poly1305_init_pull,
1363 crypto_secretstream_xchacha20poly1305_pull as so_crypto_secretstream_xchacha20poly1305_pull,
1364 crypto_secretstream_xchacha20poly1305_state,
1365 };
1366
1367 use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
1368 use crate::dryocstream::Tag;
1369
1370 crate::native_test_util::init();
1371
1372 let mut key = Key::default();
1373 crypto_secretstream_xchacha20poly1305_keygen(&mut key);
1374
1375 let mut push_state = State::new();
1376 let mut push_header = Header::default();
1377 crypto_secretstream_xchacha20poly1305_init_push(
1378 &mut push_state,
1379 &mut push_header,
1380 &key,
1381 );
1382 let push_state_init = push_state.clone();
1383
1384 let mut pull_state = State::default();
1385 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &push_header, &key);
1386
1387 assert_eq!(
1388 general_purpose::STANDARD.encode(pull_state.k),
1389 general_purpose::STANDARD.encode(push_state_init.k)
1390 );
1391 assert_eq!(
1392 general_purpose::STANDARD.encode(pull_state.nonce),
1393 general_purpose::STANDARD.encode(push_state_init.nonce)
1394 );
1395
1396 let mut so_state = crypto_secretstream_xchacha20poly1305_state {
1397 k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
1398 nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
1399 _pad: [0u8; 8],
1400 };
1401 so_state.k.copy_from_slice(&push_state_init.k);
1402 so_state.nonce.copy_from_slice(&push_state_init.nonce);
1403
1404 let mut so_state = crypto_secretstream_xchacha20poly1305_state {
1405 k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
1406 nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
1407 _pad: [0u8; 8],
1408 };
1409 let mut mlen_p: c_ulonglong = 0;
1410 let mut tag_p: c_uchar = 0;
1411 unsafe {
1412 let ret = so_crypto_secretstream_xchacha20poly1305_init_pull(
1413 &mut so_state,
1414 push_header.as_ptr(),
1415 key.as_ptr(),
1416 );
1417 assert_eq!(ret, 0);
1418 }
1419 assert_eq!(
1420 general_purpose::STANDARD.encode(so_state.k),
1421 general_purpose::STANDARD.encode(push_state_init.k)
1422 );
1423 assert_eq!(
1424 general_purpose::STANDARD.encode(so_state.nonce),
1425 general_purpose::STANDARD.encode(push_state_init.nonce)
1426 );
1427
1428 for i in 0..100 {
1429 let message = format!("hello {}", i);
1430 let aad = format!("aad {}", i);
1431 let tag = if i % 7 == 0 { Tag::Rekey } else { Tag::Message };
1432
1433 let mut output =
1434 vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
1435 crypto_secretstream_xchacha20poly1305_push(
1436 &mut push_state,
1437 &mut output,
1438 message.as_bytes(),
1439 Some(aad.as_bytes()),
1440 tag.bits(),
1441 )
1442 .expect("push failed");
1443
1444 let mut so_output = output.clone();
1445 unsafe {
1446 assert!(so_output.len() >= CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES);
1447 let ret = so_crypto_secretstream_xchacha20poly1305_pull(
1448 &mut so_state,
1449 so_output.as_mut_ptr(),
1450 &mut mlen_p,
1451 &mut tag_p,
1452 output.as_ptr(),
1453 output.len() as u64,
1454 aad.as_ptr(),
1455 aad.len() as u64,
1456 );
1457 assert_eq!(ret, 0);
1458 so_output.resize(mlen_p as usize, 0);
1459 }
1460 assert_eq!(
1461 general_purpose::STANDARD.encode(&message),
1462 general_purpose::STANDARD.encode(&so_output)
1463 );
1464
1465 let mut pull_result_message =
1466 vec![0u8; output.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
1467 let mut pull_result_tag = 0u8;
1468 crypto_secretstream_xchacha20poly1305_pull(
1469 &mut pull_state,
1470 &mut pull_result_message,
1471 &mut pull_result_tag,
1472 &output,
1473 Some(aad.as_bytes()),
1474 )
1475 .expect("pull failed");
1476
1477 assert_eq!(tag, Tag::try_from(pull_result_tag).expect("tag"));
1478 assert_eq!(
1479 general_purpose::STANDARD.encode(&pull_result_message),
1480 general_purpose::STANDARD.encode(&message)
1481 );
1482 }
1483 }
1484
1485 #[test]
1486 fn test_secretstream_basic_pull() {
1487 use base64::Engine as _;
1488 use base64::engine::general_purpose;
1489 use libc::c_ulonglong;
1490 use libsodium_sys::{
1491 crypto_secretstream_xchacha20poly1305_init_push as so_crypto_secretstream_xchacha20poly1305_init_push,
1492 crypto_secretstream_xchacha20poly1305_push as so_crypto_secretstream_xchacha20poly1305_push,
1493 crypto_secretstream_xchacha20poly1305_state,
1494 };
1495
1496 use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
1497
1498 crate::native_test_util::init();
1499
1500 let mut key = Key::default();
1501 crypto_secretstream_xchacha20poly1305_keygen(&mut key);
1502
1503 let mut so_state = crypto_secretstream_xchacha20poly1305_state {
1504 k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
1505 nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
1506 _pad: [0u8; 8],
1507 };
1508 let mut so_header = Header::default();
1509 unsafe {
1510 so_crypto_secretstream_xchacha20poly1305_init_push(
1511 &mut so_state,
1512 so_header.as_mut_ptr(),
1513 key.as_ptr(),
1514 );
1515 }
1516
1517 let mut pull_state = State::new();
1518 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &so_header, &key);
1519
1520 let message = b"hello";
1521 let aad = b"aad";
1522 let mut so_output =
1523 vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
1524 let mut clen_p: c_ulonglong = 0;
1525
1526 unsafe {
1527 let ret = so_crypto_secretstream_xchacha20poly1305_push(
1528 &mut so_state,
1529 so_output.as_mut_ptr(),
1530 &mut clen_p,
1531 message.as_ptr(),
1532 message.len() as u64,
1533 aad.as_ptr(),
1534 aad.len() as u64,
1535 0,
1536 );
1537 assert_eq!(ret, 0);
1538 so_output.resize(clen_p as usize, 0);
1539 }
1540
1541 let mut output = vec![0u8; so_output.len()];
1542 let mut tag = 0u8;
1543 let mlen = crypto_secretstream_xchacha20poly1305_pull(
1544 &mut pull_state,
1545 &mut output,
1546 &mut tag,
1547 &so_output,
1548 Some(aad),
1549 )
1550 .expect("decrypt failed");
1551 output.resize(mlen, 0);
1552
1553 assert_eq!(
1554 general_purpose::STANDARD.encode(&output),
1555 general_purpose::STANDARD.encode(message)
1556 );
1557 assert_eq!(tag, 0);
1558 }
1559
1560 #[test]
1561 fn test_secretstream_lots_of_messages_pull() {
1562 use base64::Engine as _;
1563 use base64::engine::general_purpose;
1564 use libc::c_ulonglong;
1565 use libsodium_sys::{
1566 crypto_secretstream_xchacha20poly1305_init_push as so_crypto_secretstream_xchacha20poly1305_init_push,
1567 crypto_secretstream_xchacha20poly1305_push as so_crypto_secretstream_xchacha20poly1305_push,
1568 crypto_secretstream_xchacha20poly1305_state,
1569 };
1570
1571 use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
1572 use crate::dryocstream::Tag;
1573
1574 crate::native_test_util::init();
1575
1576 let mut key = Key::default();
1577 crypto_secretstream_xchacha20poly1305_keygen(&mut key);
1578
1579 let mut so_state = crypto_secretstream_xchacha20poly1305_state {
1580 k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
1581 nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
1582 _pad: [0u8; 8],
1583 };
1584 let mut so_header = Header::default();
1585 unsafe {
1586 so_crypto_secretstream_xchacha20poly1305_init_push(
1587 &mut so_state,
1588 so_header.as_mut_ptr(),
1589 key.as_ptr(),
1590 );
1591 }
1592
1593 let mut pull_state = State::new();
1594 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &so_header, &key);
1595
1596 for i in 0..100 {
1597 let message = format!("hello {}", i);
1598 let aad = format!("aad {}", i);
1599 let mut so_output =
1600 vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
1601 let mut clen_p: c_ulonglong = 0;
1602
1603 let tag = if i % 7 == 0 { Tag::Rekey } else { Tag::Message };
1604
1605 unsafe {
1606 let ret = so_crypto_secretstream_xchacha20poly1305_push(
1607 &mut so_state,
1608 so_output.as_mut_ptr(),
1609 &mut clen_p,
1610 message.as_ptr(),
1611 message.len() as u64,
1612 aad.as_ptr(),
1613 aad.len() as u64,
1614 tag.bits(),
1615 );
1616 assert_eq!(ret, 0);
1617 so_output.resize(clen_p as usize, 0);
1618 }
1619
1620 let mut output =
1621 vec![0u8; so_output.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
1622 let mut outtag = 0u8;
1623 crypto_secretstream_xchacha20poly1305_pull(
1624 &mut pull_state,
1625 &mut output,
1626 &mut outtag,
1627 &so_output,
1628 Some(aad.as_bytes()),
1629 )
1630 .expect("decrypt failed");
1631
1632 assert_eq!(
1633 general_purpose::STANDARD.encode(so_state.k),
1634 general_purpose::STANDARD.encode(pull_state.k)
1635 );
1636 assert_eq!(
1637 general_purpose::STANDARD.encode(so_state.nonce),
1638 general_purpose::STANDARD.encode(pull_state.nonce)
1639 );
1640
1641 assert_eq!(
1642 general_purpose::STANDARD.encode(&output),
1643 general_purpose::STANDARD.encode(&message)
1644 );
1645 assert_eq!(outtag, tag.bits());
1646 }
1647 }
1648 }
1649}