1use subtle::ConstantTimeEq;
90use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
91
92use crate::classic::crypto_core::{HChaCha20Key, crypto_core_hchacha20};
93use crate::constants::{
94 CRYPTO_CORE_HCHACHA20_INPUTBYTES, CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES,
95 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES,
96 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES,
97 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES,
98 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES,
99 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX,
100 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY, CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES,
101 CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES,
102};
103use crate::error::*;
104use crate::rng::copy_randombytes;
105use crate::types::*;
106use crate::utils::{increment_bytes, pad16, xor_buf};
107
108pub type Key = [u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES];
110pub type Nonce = [u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES];
112pub type Header = [u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES];
114
115#[derive(PartialEq, Eq, Clone, Default, Zeroize, ZeroizeOnDrop)]
117pub struct State {
118 k: Key,
119 nonce: Nonce,
120}
121
122impl State {
123 pub fn new() -> Self {
125 Self::default()
126 }
127}
128
129pub fn crypto_secretstream_xchacha20poly1305_keygen(key: &mut Key) {
131 copy_randombytes(key);
132}
133
134fn state_counter(nonce: &mut Nonce) -> &mut [u8] {
135 &mut nonce[..CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES]
136}
137
138fn state_inonce(nonce: &mut Nonce) -> &mut [u8] {
139 &mut nonce[CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES
140 ..CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES
141 + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES]
142}
143
144fn secretstream_length_block(associated_data_len: usize, message_len: usize) -> [u8; 16] {
145 let mut lengths = [0u8; 16];
146 lengths[..8].copy_from_slice(&(associated_data_len as u64).to_le_bytes());
147 lengths[8..].copy_from_slice(&(64u64 + message_len as u64).to_le_bytes());
148 lengths
149}
150
151fn _crypto_secretstream_xchacha20poly1305_counter_reset(state: &mut State) {
152 let counter = state_counter(&mut state.nonce);
153 counter.fill(0);
154 counter[0] = 1;
155}
156
157pub fn crypto_secretstream_xchacha20poly1305_init_push(
166 state: &mut State,
167 header: &mut Header,
168 key: &Key,
169) {
170 copy_randombytes(header);
171
172 let mut k = Zeroizing::new(HChaCha20Key::default());
173 crypto_core_hchacha20(
174 k.as_mut_array(),
175 ByteArray::as_array(&header[..16]),
176 key,
177 None,
178 );
179 state.k.copy_from_slice(&*k);
181 _crypto_secretstream_xchacha20poly1305_counter_reset(state);
182
183 let inonce = state_inonce(&mut state.nonce);
184 inonce.copy_from_slice(
185 &header[CRYPTO_CORE_HCHACHA20_INPUTBYTES
186 ..(CRYPTO_CORE_HCHACHA20_INPUTBYTES
187 + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES)],
188 );
189}
190
191pub fn crypto_secretstream_xchacha20poly1305_init_pull(
199 state: &mut State,
200 header: &Header,
201 key: &Key,
202) {
203 let mut k = Zeroizing::new(HChaCha20Key::default());
204 crypto_core_hchacha20(
205 k.as_mut_array(),
206 ByteArray::as_array(&header[0..16]),
207 key,
208 None,
209 );
210 state.k.copy_from_slice(&*k);
211
212 _crypto_secretstream_xchacha20poly1305_counter_reset(state);
213
214 let inonce = state_inonce(&mut state.nonce);
215 inonce.copy_from_slice(
216 &header[CRYPTO_CORE_HCHACHA20_INPUTBYTES
217 ..(CRYPTO_CORE_HCHACHA20_INPUTBYTES
218 + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES)],
219 );
220}
221
222pub fn crypto_secretstream_xchacha20poly1305_rekey(state: &mut State) {
227 use chacha20::ChaCha20;
228 use chacha20::cipher::{KeyIvInit, StreamCipher};
229
230 let mut new_state = Zeroizing::new(
231 [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES
232 + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES],
233 );
234
235 new_state[..CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES].copy_from_slice(&state.k);
236 new_state[CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES..]
237 .copy_from_slice(state_inonce(&mut state.nonce));
238
239 let mut cipher = ChaCha20::new((&state.k).into(), (&state.nonce).into());
240 cipher.apply_keystream(&mut *new_state);
241
242 state
243 .k
244 .copy_from_slice(&new_state[0..CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES]);
245 state_inonce(&mut state.nonce)
246 .copy_from_slice(&new_state[CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES..]);
247
248 _crypto_secretstream_xchacha20poly1305_counter_reset(state);
249}
250
251pub fn crypto_secretstream_xchacha20poly1305_push(
266 state: &mut State,
267 ciphertext: &mut [u8],
268 message: &[u8],
269 associated_data: Option<&[u8]>,
270 tag: u8,
271) -> Result<(), Error> {
272 use chacha20::ChaCha20;
273 use chacha20::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
274
275 use crate::poly1305::Poly1305;
276
277 if message.len() > CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX {
278 return Err(length_error!(
279 crate::ErrorContext::Message,
280 message.len(),
281 max CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX
282 ));
283 }
284
285 let expected_ciphertext_len = message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES;
286 if ciphertext.len() != expected_ciphertext_len {
287 return Err(length_error!(
288 crate::ErrorContext::Ciphertext,
289 ciphertext.len(),
290 exact expected_ciphertext_len
291 ));
292 }
293
294 let associated_data = associated_data.unwrap_or(&[]);
295
296 let mut mac_key = crate::poly1305::Key::new();
297 let _pad0 = [0u8; 16];
298
299 let mut cipher = ChaCha20::new((&state.k).into(), (&state.nonce).into());
300
301 cipher.apply_keystream(&mut mac_key);
302 let mut mac = Zeroizing::new(Poly1305::new(&mac_key));
303 mac_key.zeroize();
304
305 mac.update(associated_data);
306 mac.update(&_pad0[..pad16(associated_data.len())]);
307
308 let mut block = Zeroizing::new([0u8; 64]);
309 block[0] = tag;
310 cipher.seek(64);
311 cipher.apply_keystream(&mut *block);
312 mac.update(&*block);
313
314 let mlen = message.len();
315 ciphertext[0] = block[0];
316 ciphertext[1..(1 + mlen)].copy_from_slice(message);
317
318 cipher.seek(128);
319 cipher.apply_keystream(&mut ciphertext[1..(1 + mlen)]);
320
321 let size_data = secretstream_length_block(associated_data.len(), mlen);
322
323 mac.update(&ciphertext[1..(1 + mlen)]);
324 let buffer_mac_pad = ((0x10 - block.len() as i64 + mlen as i64) & 0xf) as usize;
328 mac.update(&_pad0[0..buffer_mac_pad]);
329 mac.update(&size_data);
330
331 mac.finalize(&mut ciphertext[1 + mlen..]);
332
333 let inonce = state_inonce(&mut state.nonce);
334 xor_buf(inonce, &ciphertext[1 + mlen..]);
335
336 let counter = state_counter(&mut state.nonce);
337 increment_bytes(counter);
338
339 if tag & CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY
340 == CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY
341 || state_counter(&mut state.nonce)
342 .ct_eq(&[0u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES])
343 .unwrap_u8()
344 == 1
345 {
346 crypto_secretstream_xchacha20poly1305_rekey(state);
347 }
348
349 Ok(())
350}
351
352pub fn crypto_secretstream_xchacha20poly1305_pull(
371 state: &mut State,
372 message: &mut [u8],
373 tag: &mut u8,
374 ciphertext: &[u8],
375 associated_data: Option<&[u8]>,
376) -> Result<usize, Error> {
377 use chacha20::ChaCha20;
378 use chacha20::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
379
380 use crate::poly1305::Poly1305;
381
382 let _pad0 = [0u8; 16];
383
384 if ciphertext.len() < CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES {
385 return Err(length_error!(
386 crate::ErrorContext::Ciphertext,
387 ciphertext.len(),
388 min CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
389 ));
390 }
391
392 let mlen = ciphertext.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES;
393
394 if mlen > CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX {
395 return Err(length_error!(
396 crate::ErrorContext::Ciphertext,
397 ciphertext.len(),
398 max CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX
399 + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
400 ));
401 }
402
403 if message.len() < mlen {
404 return Err(length_error!(
405 crate::ErrorContext::Message,
406 message.len(),
407 min mlen
408 ));
409 }
410
411 let associated_data = associated_data.unwrap_or(&[]);
412
413 let mut mac_key = crate::poly1305::Key::new();
414
415 let mut cipher = ChaCha20::new((&state.k).into(), (&state.nonce).into());
416
417 cipher.apply_keystream(&mut mac_key);
418 let mut mac = Zeroizing::new(Poly1305::new(&mac_key));
419 mac_key.zeroize();
420
421 mac.update(associated_data);
422 mac.update(&_pad0[..pad16(associated_data.len())]);
423
424 let mut block = Zeroizing::new([0u8; 64]);
425 block[0] = ciphertext[0];
426
427 cipher.seek(64);
428 cipher.apply_keystream(&mut *block);
429
430 let decrypted_tag = block[0];
431 block[0] = ciphertext[0];
432
433 mac.update(&*block);
434
435 let buffer_mac_pad = ((0x10 - block.len() as i64 + mlen as i64) & 0xf) as usize;
439 mac.update(&ciphertext[1..1 + mlen]);
440 mac.update(&_pad0[..buffer_mac_pad]);
441
442 let size_data = secretstream_length_block(associated_data.len(), mlen);
443 mac.update(&size_data);
444 let mac = Zeroizing::new(mac.finalize_to_array());
445
446 if ciphertext[1 + mlen..].ct_eq(mac.as_slice()).unwrap_u8() == 0 {
447 return Err(Error::AuthenticationFailed);
448 }
449
450 message[..mlen].copy_from_slice(&ciphertext[1..1 + mlen]);
451 cipher.seek(128);
452 cipher.apply_keystream(&mut message[..mlen]);
453 *tag = decrypted_tag;
454
455 let inonce = state_inonce(&mut state.nonce);
456 xor_buf(inonce, &*mac);
457
458 let counter = state_counter(&mut state.nonce);
459 increment_bytes(counter);
460
461 if decrypted_tag & CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY
462 == CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY
463 || state_counter(&mut state.nonce)
464 .ct_eq(&[0u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES])
465 .unwrap_u8()
466 == 1
467 {
468 crypto_secretstream_xchacha20poly1305_rekey(state);
469 }
470
471 Ok(mlen)
472}
473
474#[cfg(test)]
475mod tests {
476 use super::*;
477 use crate::dryocstream::Tag;
478
479 #[test]
480 fn push_and_pull_reject_invalid_buffer_lengths() {
481 let mut state = State::new();
482 let mut tag = 0;
483
484 let error = crypto_secretstream_xchacha20poly1305_push(
485 &mut state,
486 &mut [],
487 b"message",
488 None,
489 Tag::MESSAGE.bits(),
490 )
491 .expect_err("ciphertext must include secretstream overhead");
492 assert!(matches!(
493 error,
494 Error::InvalidLength {
495 context: crate::ErrorContext::Ciphertext,
496 ..
497 }
498 ));
499
500 let short_ciphertext = [0u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES - 1];
501 let error = crypto_secretstream_xchacha20poly1305_pull(
502 &mut state,
503 &mut [],
504 &mut tag,
505 &short_ciphertext,
506 None,
507 )
508 .expect_err("ciphertext must include secretstream overhead");
509 assert!(matches!(
510 error,
511 Error::InvalidLength {
512 context: crate::ErrorContext::Ciphertext,
513 ..
514 }
515 ));
516
517 let ciphertext = [0u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES + 1];
518 let error = crypto_secretstream_xchacha20poly1305_pull(
519 &mut state,
520 &mut [],
521 &mut tag,
522 &ciphertext,
523 None,
524 )
525 .expect_err("the message buffer must hold the plaintext");
526 assert!(matches!(
527 error,
528 Error::InvalidLength {
529 context: crate::ErrorContext::Message,
530 ..
531 }
532 ));
533 }
534
535 #[test]
536 fn pull_authenticates_before_mutating_outputs_or_state() {
537 let key = Key::default();
538 let mut push_state = State::new();
539 let mut header = Header::default();
540 crypto_secretstream_xchacha20poly1305_init_push(&mut push_state, &mut header, &key);
541
542 let plaintext = b"do not publish unauthenticated plaintext";
543 let mut ciphertext =
544 vec![0u8; plaintext.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
545 crypto_secretstream_xchacha20poly1305_push(
546 &mut push_state,
547 &mut ciphertext,
548 plaintext,
549 Some(b"associated data"),
550 Tag::FINAL.bits(),
551 )
552 .expect("push failed");
553
554 let mut pull_state = State::new();
555 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &header, &key);
556 let original_state = pull_state.clone();
557 let mut tampered = ciphertext.clone();
558 *tampered.last_mut().expect("authentication tag") ^= 1;
559 let mut output = vec![0xa5; plaintext.len()];
560 let original_output = output.clone();
561 let mut tag = 0x5a;
562
563 assert!(matches!(
564 crypto_secretstream_xchacha20poly1305_pull(
565 &mut pull_state,
566 &mut output,
567 &mut tag,
568 &tampered,
569 Some(b"associated data"),
570 ),
571 Err(Error::AuthenticationFailed)
572 ));
573 assert_eq!(output, original_output);
574 assert_eq!(tag, 0x5a);
575 assert!(pull_state == original_state);
576
577 crypto_secretstream_xchacha20poly1305_pull(
578 &mut pull_state,
579 &mut output,
580 &mut tag,
581 &ciphertext,
582 Some(b"associated data"),
583 )
584 .expect("state must remain usable after authentication failure");
585 assert_eq!(output, plaintext);
586 assert_eq!(tag, Tag::FINAL.bits());
587 }
588
589 #[test]
590 fn length_block_uses_fixed_width_little_endian_values() {
591 let lengths = secretstream_length_block(0x0102_0304, 0x0506_0708);
592
593 assert_eq!(&lengths[..8], &0x0102_0304u64.to_le_bytes());
594 assert_eq!(&lengths[8..], &(64u64 + 0x0506_0708).to_le_bytes());
595 }
596
597 #[test]
598 fn test_sizes() {
599 use crate::constants::*;
600
601 const _: () = assert!(
602 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES
603 == CRYPTO_CORE_HCHACHA20_INPUTBYTES
604 + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES
605 );
606
607 const _: () = assert!(
608 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES
609 == CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES
610 );
611
612 const _: () = assert!(
613 CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES
614 == CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES
615 + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES
616 );
617
618 const _: () = assert!(
619 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX
620 <= CRYPTO_AEAD_CHACHA20POLY1305_IETF_MESSAGEBYTES_MAX
621 );
622
623 const _: () = assert!(
624 CRYPTO_ONETIMEAUTH_POLY1305_BYTES >= CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES
625 );
626
627 #[cfg(target_pointer_width = "32")]
628 {
629 assert_eq!(
630 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX,
631 usize::MAX - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
632 );
633 assert_eq!(
634 CRYPTO_AEAD_CHACHA20POLY1305_IETF_MESSAGEBYTES_MAX,
635 usize::MAX - CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES
636 );
637 assert_eq!(
638 CRYPTO_SECRETBOX_MESSAGEBYTES_MAX,
639 usize::MAX - CRYPTO_SECRETBOX_MACBYTES
640 );
641 }
642 }
643
644 #[test]
645 fn test_secretstream_large_aad() {
646 let mut key = Key::default();
647 crypto_secretstream_xchacha20poly1305_keygen(&mut key);
648
649 let mut push_state = State::new();
650 let mut push_header = Header::default();
651 crypto_secretstream_xchacha20poly1305_init_push(&mut push_state, &mut push_header, &key);
652
653 let message = b"hello world";
654 let large_aad = vec![0x42u8; 328]; let mut ciphertext =
657 vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
658 crypto_secretstream_xchacha20poly1305_push(
659 &mut push_state,
660 &mut ciphertext,
661 message,
662 Some(&large_aad),
663 Tag::MESSAGE.bits(),
664 )
665 .expect("push failed");
666
667 let mut pull_state = State::new();
668 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &push_header, &key);
669
670 let mut decrypted = vec![0u8; message.len()];
671 let mut tag = 0u8;
672
673 crypto_secretstream_xchacha20poly1305_pull(
674 &mut pull_state,
675 &mut decrypted,
676 &mut tag,
677 &ciphertext,
678 Some(&large_aad),
679 )
680 .expect("pull failed");
681
682 assert_eq!(message.as_slice(), decrypted.as_slice());
683 assert_eq!(tag, Tag::MESSAGE.bits());
684
685 let mut wrong_aad = large_aad.clone();
687 wrong_aad[100] = 0x43; let mut wrong_aad_pull_state = State::new();
690 crypto_secretstream_xchacha20poly1305_init_pull(
691 &mut wrong_aad_pull_state,
692 &push_header,
693 &key,
694 );
695
696 let mut decrypted = vec![0u8; message.len()];
697 let mut tag = 0u8;
698
699 assert!(
700 crypto_secretstream_xchacha20poly1305_pull(
701 &mut wrong_aad_pull_state,
702 &mut decrypted,
703 &mut tag,
704 &ciphertext,
705 Some(&wrong_aad),
706 )
707 .is_err()
708 );
709 }
710
711 #[test]
712 fn test_secretstream_small_aad() {
713 let mut key = Key::default();
714 crypto_secretstream_xchacha20poly1305_keygen(&mut key);
715
716 let mut push_state = State::new();
717 let mut push_header = Header::default();
718 crypto_secretstream_xchacha20poly1305_init_push(&mut push_state, &mut push_header, &key);
719
720 let message = b"hello world";
721 let small_aad = b"abc"; let mut ciphertext =
724 vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
725 crypto_secretstream_xchacha20poly1305_push(
726 &mut push_state,
727 &mut ciphertext,
728 message,
729 Some(small_aad),
730 Tag::MESSAGE.bits(),
731 )
732 .expect("push failed");
733
734 let mut pull_state = State::new();
735 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &push_header, &key);
736
737 let mut decrypted = vec![0u8; message.len()];
738 let mut tag = 0u8;
739
740 crypto_secretstream_xchacha20poly1305_pull(
741 &mut pull_state,
742 &mut decrypted,
743 &mut tag,
744 &ciphertext,
745 Some(small_aad),
746 )
747 .expect("pull failed");
748
749 assert_eq!(message.as_slice(), decrypted.as_slice());
750 assert_eq!(tag, Tag::MESSAGE.bits());
751
752 let wrong_aad = b"xyz"; let mut wrong_aad_pull_state = State::new();
756 crypto_secretstream_xchacha20poly1305_init_pull(
757 &mut wrong_aad_pull_state,
758 &push_header,
759 &key,
760 );
761
762 let mut decrypted = vec![0u8; message.len()];
763 let mut tag = 0u8;
764
765 assert!(
766 crypto_secretstream_xchacha20poly1305_pull(
767 &mut wrong_aad_pull_state,
768 &mut decrypted,
769 &mut tag,
770 &ciphertext,
771 Some(wrong_aad),
772 )
773 .is_err()
774 );
775 }
776
777 #[cfg(dryoc_native_tests)]
778 mod native_tests {
779 use super::*;
780
781 #[test]
782 fn test_secretstream_basic_push() {
783 use base64::Engine as _;
784 use base64::engine::general_purpose;
785 use libsodium_sys::{
786 crypto_secretstream_xchacha20poly1305_init_pull as so_crypto_secretstream_xchacha20poly1305_init_pull,
787 crypto_secretstream_xchacha20poly1305_pull as so_crypto_secretstream_xchacha20poly1305_pull,
788 crypto_secretstream_xchacha20poly1305_push as so_crypto_secretstream_xchacha20poly1305_push,
789 crypto_secretstream_xchacha20poly1305_state,
790 };
791
792 use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
793 use crate::dryocstream::Tag;
794
795 let mut key = Key::default();
796 crypto_secretstream_xchacha20poly1305_keygen(&mut key);
797
798 let mut push_state = State::new();
799 let mut push_header = Header::default();
800 crypto_secretstream_xchacha20poly1305_init_push(
801 &mut push_state,
802 &mut push_header,
803 &key,
804 );
805 let push_state_init = push_state.clone();
806
807 let message = b"hello";
808 let mut output =
809 vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
810 let aad = b"";
811 let tag = Tag::MESSAGE.bits();
812 crypto_secretstream_xchacha20poly1305_push(
813 &mut push_state,
814 &mut output,
815 message,
816 Some(aad),
817 tag,
818 )
819 .expect("push failed");
820
821 let mut so_output = output.clone();
822 unsafe {
823 use libc::{c_uchar, c_ulonglong};
824 let mut so_state = crypto_secretstream_xchacha20poly1305_state {
825 k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
826 nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
827 _pad: [0u8; 8],
828 };
829 so_state.k.copy_from_slice(&push_state_init.k);
830 so_state.nonce.copy_from_slice(&push_state_init.nonce);
831 let mut clen_p: c_ulonglong = 0;
832 let ret = so_crypto_secretstream_xchacha20poly1305_push(
833 &mut so_state,
834 so_output.as_mut_ptr(),
835 &mut clen_p,
836 message.as_ptr(),
837 message.len() as u64,
838 aad.as_ptr(),
839 aad.len() as u64,
840 0,
841 );
842 assert_eq!(ret, 0);
843 so_output.resize(clen_p as usize, 0);
844 assert_eq!(
845 general_purpose::STANDARD.encode(&so_output),
846 general_purpose::STANDARD.encode(&output)
847 );
848 assert_eq!(
849 general_purpose::STANDARD.encode(so_state.k),
850 general_purpose::STANDARD.encode(push_state.k)
851 );
852 assert_eq!(
853 general_purpose::STANDARD.encode(so_state.nonce),
854 general_purpose::STANDARD.encode(push_state.nonce)
855 );
856
857 let mut so_state = crypto_secretstream_xchacha20poly1305_state {
858 k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
859 nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
860 _pad: [0u8; 8],
861 };
862 let mut mlen_p: c_ulonglong = 0;
863 let mut tag_p: c_uchar = 0;
864 let ret = so_crypto_secretstream_xchacha20poly1305_init_pull(
865 &mut so_state,
866 push_header.as_ptr(),
867 key.as_ptr(),
868 );
869 assert_eq!(ret, 0);
870 assert_eq!(
871 general_purpose::STANDARD.encode(so_state.k),
872 general_purpose::STANDARD.encode(push_state_init.k)
873 );
874 assert_eq!(
875 general_purpose::STANDARD.encode(so_state.nonce),
876 general_purpose::STANDARD.encode(push_state_init.nonce)
877 );
878 assert!(so_output.len() >= CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES);
879 let ret = so_crypto_secretstream_xchacha20poly1305_pull(
880 &mut so_state,
881 so_output.as_mut_ptr(),
882 &mut mlen_p,
883 &mut tag_p,
884 output.as_ptr(),
885 output.len() as u64,
886 aad.as_ptr(),
887 aad.len() as u64,
888 );
889 assert_eq!(ret, 0);
890 so_output.resize(mlen_p as usize, 0);
891 }
892 assert_eq!(
893 general_purpose::STANDARD.encode(message),
894 general_purpose::STANDARD.encode(&so_output)
895 );
896
897 let mut pull_state = State::default();
898 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &push_header, &key);
899
900 assert_eq!(
901 general_purpose::STANDARD.encode(pull_state.k),
902 general_purpose::STANDARD.encode(push_state_init.k)
903 );
904 assert_eq!(
905 general_purpose::STANDARD.encode(pull_state.nonce),
906 general_purpose::STANDARD.encode(push_state_init.nonce)
907 );
908
909 let mut pull_result_message =
910 vec![0u8; output.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
911 let mut pull_result_tag = 0u8;
912 crypto_secretstream_xchacha20poly1305_pull(
913 &mut pull_state,
914 &mut pull_result_message,
915 &mut pull_result_tag,
916 &output,
917 Some(&[]),
918 )
919 .expect("pull failed");
920
921 assert_eq!(Tag::MESSAGE, Tag::from_bits(tag).expect("tag"));
922 assert_eq!(
923 general_purpose::STANDARD.encode(&pull_result_message),
924 general_purpose::STANDARD.encode(message)
925 );
926 }
927
928 #[test]
929 fn test_rekey() {
930 use base64::Engine as _;
931 use base64::engine::general_purpose;
932 use libsodium_sys::{
933 crypto_secretstream_xchacha20poly1305_rekey as so_crypto_secretstream_xchacha20poly1305_rekey,
934 crypto_secretstream_xchacha20poly1305_state,
935 };
936
937 use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
938
939 let mut key = Key::default();
940 crypto_secretstream_xchacha20poly1305_keygen(&mut key);
941
942 let mut push_state = State::default();
943 let mut push_header: Header = Header::default();
944 crypto_secretstream_xchacha20poly1305_init_push(
945 &mut push_state,
946 &mut push_header,
947 &key,
948 );
949 let push_state_init = push_state.clone();
950
951 crypto_secretstream_xchacha20poly1305_rekey(&mut push_state);
952
953 let mut so_state = crypto_secretstream_xchacha20poly1305_state {
954 k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
955 nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
956 _pad: [0u8; 8],
957 };
958 so_state.k.copy_from_slice(&push_state_init.k);
959 so_state.nonce.copy_from_slice(&push_state_init.nonce);
960 unsafe {
961 so_crypto_secretstream_xchacha20poly1305_rekey(&mut so_state);
962 }
963 assert_eq!(
964 general_purpose::STANDARD.encode(so_state.k),
965 general_purpose::STANDARD.encode(push_state.k)
966 );
967 assert_eq!(
968 general_purpose::STANDARD.encode(so_state.nonce),
969 general_purpose::STANDARD.encode(push_state.nonce)
970 );
971 }
972
973 #[test]
974 fn test_secretstream_lots_of_messages_push() {
975 use base64::Engine as _;
976 use base64::engine::general_purpose;
977 use libc::{c_uchar, c_ulonglong};
978 use libsodium_sys::{
979 crypto_secretstream_xchacha20poly1305_init_pull as so_crypto_secretstream_xchacha20poly1305_init_pull,
980 crypto_secretstream_xchacha20poly1305_pull as so_crypto_secretstream_xchacha20poly1305_pull,
981 crypto_secretstream_xchacha20poly1305_state,
982 };
983
984 use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
985 use crate::dryocstream::Tag;
986
987 let mut key = Key::default();
988 crypto_secretstream_xchacha20poly1305_keygen(&mut key);
989
990 let mut push_state = State::new();
991 let mut push_header = Header::default();
992 crypto_secretstream_xchacha20poly1305_init_push(
993 &mut push_state,
994 &mut push_header,
995 &key,
996 );
997 let push_state_init = push_state.clone();
998
999 let mut pull_state = State::default();
1000 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &push_header, &key);
1001
1002 assert_eq!(
1003 general_purpose::STANDARD.encode(pull_state.k),
1004 general_purpose::STANDARD.encode(push_state_init.k)
1005 );
1006 assert_eq!(
1007 general_purpose::STANDARD.encode(pull_state.nonce),
1008 general_purpose::STANDARD.encode(push_state_init.nonce)
1009 );
1010
1011 let mut so_state = crypto_secretstream_xchacha20poly1305_state {
1012 k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
1013 nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
1014 _pad: [0u8; 8],
1015 };
1016 so_state.k.copy_from_slice(&push_state_init.k);
1017 so_state.nonce.copy_from_slice(&push_state_init.nonce);
1018
1019 let mut so_state = crypto_secretstream_xchacha20poly1305_state {
1020 k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
1021 nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
1022 _pad: [0u8; 8],
1023 };
1024 let mut mlen_p: c_ulonglong = 0;
1025 let mut tag_p: c_uchar = 0;
1026 unsafe {
1027 let ret = so_crypto_secretstream_xchacha20poly1305_init_pull(
1028 &mut so_state,
1029 push_header.as_ptr(),
1030 key.as_ptr(),
1031 );
1032 assert_eq!(ret, 0);
1033 }
1034 assert_eq!(
1035 general_purpose::STANDARD.encode(so_state.k),
1036 general_purpose::STANDARD.encode(push_state_init.k)
1037 );
1038 assert_eq!(
1039 general_purpose::STANDARD.encode(so_state.nonce),
1040 general_purpose::STANDARD.encode(push_state_init.nonce)
1041 );
1042
1043 for i in 0..100 {
1044 let message = format!("hello {}", i);
1045 let aad = format!("aad {}", i);
1046 let tag = if i % 7 == 0 { Tag::REKEY } else { Tag::MESSAGE };
1047
1048 let mut output =
1049 vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
1050 crypto_secretstream_xchacha20poly1305_push(
1051 &mut push_state,
1052 &mut output,
1053 message.as_bytes(),
1054 Some(aad.as_bytes()),
1055 tag.bits(),
1056 )
1057 .expect("push failed");
1058
1059 let mut so_output = output.clone();
1060 unsafe {
1061 assert!(so_output.len() >= CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES);
1062 let ret = so_crypto_secretstream_xchacha20poly1305_pull(
1063 &mut so_state,
1064 so_output.as_mut_ptr(),
1065 &mut mlen_p,
1066 &mut tag_p,
1067 output.as_ptr(),
1068 output.len() as u64,
1069 aad.as_ptr(),
1070 aad.len() as u64,
1071 );
1072 assert_eq!(ret, 0);
1073 so_output.resize(mlen_p as usize, 0);
1074 }
1075 assert_eq!(
1076 general_purpose::STANDARD.encode(&message),
1077 general_purpose::STANDARD.encode(&so_output)
1078 );
1079
1080 let mut pull_result_message =
1081 vec![0u8; output.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
1082 let mut pull_result_tag = 0u8;
1083 crypto_secretstream_xchacha20poly1305_pull(
1084 &mut pull_state,
1085 &mut pull_result_message,
1086 &mut pull_result_tag,
1087 &output,
1088 Some(aad.as_bytes()),
1089 )
1090 .expect("pull failed");
1091
1092 assert_eq!(tag, Tag::from_bits(pull_result_tag).expect("tag"));
1093 assert_eq!(
1094 general_purpose::STANDARD.encode(&pull_result_message),
1095 general_purpose::STANDARD.encode(&message)
1096 );
1097 }
1098 }
1099
1100 #[test]
1101 fn test_secretstream_basic_pull() {
1102 use base64::Engine as _;
1103 use base64::engine::general_purpose;
1104 use libc::c_ulonglong;
1105 use libsodium_sys::{
1106 crypto_secretstream_xchacha20poly1305_init_push as so_crypto_secretstream_xchacha20poly1305_init_push,
1107 crypto_secretstream_xchacha20poly1305_push as so_crypto_secretstream_xchacha20poly1305_push,
1108 crypto_secretstream_xchacha20poly1305_state,
1109 };
1110
1111 use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
1112
1113 let mut key = Key::default();
1114 crypto_secretstream_xchacha20poly1305_keygen(&mut key);
1115
1116 let mut so_state = crypto_secretstream_xchacha20poly1305_state {
1117 k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
1118 nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
1119 _pad: [0u8; 8],
1120 };
1121 let mut so_header = Header::default();
1122 unsafe {
1123 so_crypto_secretstream_xchacha20poly1305_init_push(
1124 &mut so_state,
1125 so_header.as_mut_ptr(),
1126 key.as_ptr(),
1127 );
1128 }
1129
1130 let mut pull_state = State::new();
1131 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &so_header, &key);
1132
1133 let message = b"hello";
1134 let aad = b"aad";
1135 let mut so_output =
1136 vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
1137 let mut clen_p: c_ulonglong = 0;
1138
1139 unsafe {
1140 let ret = so_crypto_secretstream_xchacha20poly1305_push(
1141 &mut so_state,
1142 so_output.as_mut_ptr(),
1143 &mut clen_p,
1144 message.as_ptr(),
1145 message.len() as u64,
1146 aad.as_ptr(),
1147 aad.len() as u64,
1148 0,
1149 );
1150 assert_eq!(ret, 0);
1151 so_output.resize(clen_p as usize, 0);
1152 }
1153
1154 let mut output = vec![0u8; so_output.len()];
1155 let mut tag = 0u8;
1156 let mlen = crypto_secretstream_xchacha20poly1305_pull(
1157 &mut pull_state,
1158 &mut output,
1159 &mut tag,
1160 &so_output,
1161 Some(aad),
1162 )
1163 .expect("decrypt failed");
1164 output.resize(mlen, 0);
1165
1166 assert_eq!(
1167 general_purpose::STANDARD.encode(&output),
1168 general_purpose::STANDARD.encode(message)
1169 );
1170 assert_eq!(tag, 0);
1171 }
1172
1173 #[test]
1174 fn test_secretstream_lots_of_messages_pull() {
1175 use base64::Engine as _;
1176 use base64::engine::general_purpose;
1177 use libc::c_ulonglong;
1178 use libsodium_sys::{
1179 crypto_secretstream_xchacha20poly1305_init_push as so_crypto_secretstream_xchacha20poly1305_init_push,
1180 crypto_secretstream_xchacha20poly1305_push as so_crypto_secretstream_xchacha20poly1305_push,
1181 crypto_secretstream_xchacha20poly1305_state,
1182 };
1183
1184 use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
1185 use crate::dryocstream::Tag;
1186
1187 let mut key = Key::default();
1188 crypto_secretstream_xchacha20poly1305_keygen(&mut key);
1189
1190 let mut so_state = crypto_secretstream_xchacha20poly1305_state {
1191 k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
1192 nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
1193 _pad: [0u8; 8],
1194 };
1195 let mut so_header = Header::default();
1196 unsafe {
1197 so_crypto_secretstream_xchacha20poly1305_init_push(
1198 &mut so_state,
1199 so_header.as_mut_ptr(),
1200 key.as_ptr(),
1201 );
1202 }
1203
1204 let mut pull_state = State::new();
1205 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &so_header, &key);
1206
1207 for i in 0..100 {
1208 let message = format!("hello {}", i);
1209 let aad = format!("aad {}", i);
1210 let mut so_output =
1211 vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
1212 let mut clen_p: c_ulonglong = 0;
1213
1214 let tag = if i % 7 == 0 { Tag::REKEY } else { Tag::MESSAGE };
1215
1216 unsafe {
1217 let ret = so_crypto_secretstream_xchacha20poly1305_push(
1218 &mut so_state,
1219 so_output.as_mut_ptr(),
1220 &mut clen_p,
1221 message.as_ptr(),
1222 message.len() as u64,
1223 aad.as_ptr(),
1224 aad.len() as u64,
1225 tag.bits(),
1226 );
1227 assert_eq!(ret, 0);
1228 so_output.resize(clen_p as usize, 0);
1229 }
1230
1231 let mut output =
1232 vec![0u8; so_output.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
1233 let mut outtag = 0u8;
1234 crypto_secretstream_xchacha20poly1305_pull(
1235 &mut pull_state,
1236 &mut output,
1237 &mut outtag,
1238 &so_output,
1239 Some(aad.as_bytes()),
1240 )
1241 .expect("decrypt failed");
1242
1243 assert_eq!(
1244 general_purpose::STANDARD.encode(so_state.k),
1245 general_purpose::STANDARD.encode(pull_state.k)
1246 );
1247 assert_eq!(
1248 general_purpose::STANDARD.encode(so_state.nonce),
1249 general_purpose::STANDARD.encode(pull_state.nonce)
1250 );
1251
1252 assert_eq!(
1253 general_purpose::STANDARD.encode(&output),
1254 general_purpose::STANDARD.encode(&message)
1255 );
1256 assert_eq!(outtag, tag.bits());
1257 }
1258 }
1259 }
1260}