1use subtle::ConstantTimeEq;
90use zeroize::{Zeroize, ZeroizeOnDrop};
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 _crypto_secretstream_xchacha20poly1305_counter_reset(state: &mut State) {
145 let counter = state_counter(&mut state.nonce);
146 counter.fill(0);
147 counter[0] = 1;
148}
149
150pub fn crypto_secretstream_xchacha20poly1305_init_push(
159 state: &mut State,
160 header: &mut Header,
161 key: &Key,
162) {
163 copy_randombytes(header);
164
165 let mut k = HChaCha20Key::default();
166 crypto_core_hchacha20(
167 k.as_mut_array(),
168 ByteArray::as_array(&header[..16]),
169 key,
170 None,
171 );
172 state.k.copy_from_slice(&k);
174 _crypto_secretstream_xchacha20poly1305_counter_reset(state);
175
176 let inonce = state_inonce(&mut state.nonce);
177 inonce.copy_from_slice(
178 &header[CRYPTO_CORE_HCHACHA20_INPUTBYTES
179 ..(CRYPTO_CORE_HCHACHA20_INPUTBYTES
180 + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES)],
181 );
182}
183
184pub fn crypto_secretstream_xchacha20poly1305_init_pull(
193 state: &mut State,
194 header: &Header,
195 key: &Key,
196) {
197 let mut k = HChaCha20Key::default();
198 crypto_core_hchacha20(
199 k.as_mut_array(),
200 ByteArray::as_array(&header[0..16]),
201 key,
202 None,
203 );
204 state.k.copy_from_slice(&k);
205
206 _crypto_secretstream_xchacha20poly1305_counter_reset(state);
207
208 let inonce = state_inonce(&mut state.nonce);
209 inonce.copy_from_slice(
210 &header[CRYPTO_CORE_HCHACHA20_INPUTBYTES
211 ..(CRYPTO_CORE_HCHACHA20_INPUTBYTES
212 + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES)],
213 );
214}
215
216pub fn crypto_secretstream_xchacha20poly1305_rekey(state: &mut State) {
221 use chacha20::cipher::{KeyIvInit, StreamCipher};
222 use chacha20::{ChaCha20, Key, Nonce};
223
224 let mut new_state = [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES
225 + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES];
226
227 new_state[..CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES].copy_from_slice(&state.k);
228 new_state[CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES..]
229 .copy_from_slice(state_inonce(&mut state.nonce));
230
231 let key = Key::from_slice(&state.k);
232 let nonce = Nonce::from_slice(&state.nonce);
233 let mut cipher = ChaCha20::new(key, nonce);
234 cipher.apply_keystream(&mut new_state);
235
236 state
237 .k
238 .copy_from_slice(&new_state[0..CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES]);
239 state_inonce(&mut state.nonce)
240 .copy_from_slice(&new_state[CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES..]);
241
242 _crypto_secretstream_xchacha20poly1305_counter_reset(state);
243}
244
245pub fn crypto_secretstream_xchacha20poly1305_push(
255 state: &mut State,
256 ciphertext: &mut [u8],
257 message: &[u8],
258 associated_data: Option<&[u8]>,
259 tag: u8,
260) -> Result<(), Error> {
261 use chacha20::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
262 use chacha20::{ChaCha20, Key, Nonce};
263
264 use crate::poly1305::Poly1305;
265
266 let _pad0 = [0u8; 16];
267
268 if ciphertext.len() != message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES {
269 return Err(dryoc_error!(format!(
270 "Ciphertext length was {}, should be {}",
271 ciphertext.len(),
272 message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
273 )));
274 }
275
276 if message.len() > CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX {
277 return Err(dryoc_error!(format!(
278 "Message length {} exceeds max length {}",
279 message.len(),
280 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX
281 )));
282 }
283
284 let associated_data = associated_data.unwrap_or(&[]);
285
286 let mut mac_key = crate::poly1305::Key::new();
287 let _pad0 = [0u8; 16];
288
289 let key = Key::from_slice(&state.k);
290 let nonce = Nonce::from_slice(&state.nonce);
291 let mut cipher = ChaCha20::new(key, nonce);
292
293 cipher.apply_keystream(&mut mac_key);
294 let mut mac = Poly1305::new(&mac_key);
295 mac_key.zeroize();
296
297 mac.update(associated_data);
298 mac.update(&_pad0[..pad16(associated_data.len())]);
299
300 let mut block = [0u8; 64];
301 block[0] = tag;
302 cipher.seek(64);
303 cipher.apply_keystream(&mut block);
304 mac.update(&block);
305
306 let mlen = message.len();
307 ciphertext[0] = block[0];
308 ciphertext[1..(1 + mlen)].copy_from_slice(message);
309
310 cipher.seek(128);
311 cipher.apply_keystream(&mut ciphertext[1..(1 + mlen)]);
312
313 let mut size_data = [0u8; 16];
314 size_data[..8].copy_from_slice(&associated_data.len().to_le_bytes());
315 size_data[8..16].copy_from_slice(&(block.len() + mlen).to_le_bytes());
316
317 mac.update(&ciphertext[1..(1 + mlen)]);
318 let buffer_mac_pad = ((0x10 - block.len() as i64 + mlen as i64) & 0xf) as usize;
322 mac.update(&_pad0[0..buffer_mac_pad]);
323 mac.update(&size_data);
324
325 mac.finalize(&mut ciphertext[1 + mlen..]);
326
327 let inonce = state_inonce(&mut state.nonce);
328 xor_buf(inonce, &ciphertext[1 + mlen..]);
329
330 let counter = state_counter(&mut state.nonce);
331 increment_bytes(counter);
332
333 if tag & CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY
334 == CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY
335 || state_counter(&mut state.nonce)
336 .ct_eq(&[0u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES])
337 .unwrap_u8()
338 == 1
339 {
340 crypto_secretstream_xchacha20poly1305_rekey(state);
341 }
342
343 Ok(())
344}
345
346pub fn crypto_secretstream_xchacha20poly1305_pull(
360 state: &mut State,
361 message: &mut [u8],
362 tag: &mut u8,
363 ciphertext: &[u8],
364 associated_data: Option<&[u8]>,
365) -> Result<usize, Error> {
366 use chacha20::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
367 use chacha20::{ChaCha20, Key, Nonce};
368
369 use crate::poly1305::Poly1305;
370
371 let _pad0 = [0u8; 16];
372
373 if message.len() < ciphertext.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES {
374 return Err(dryoc_error!(format!(
375 "Message length was {}, should be at least {}",
376 message.len(),
377 ciphertext.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
378 )));
379 }
380
381 if ciphertext.len() > CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX {
382 return Err(dryoc_error!(format!(
383 "Message length {} exceeds max length {}",
384 ciphertext.len(),
385 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX
386 )));
387 }
388
389 let associated_data = associated_data.unwrap_or(&[]);
390
391 let mut mac_key = crate::poly1305::Key::new();
392
393 let key = Key::from_slice(&state.k);
394 let nonce = Nonce::from_slice(&state.nonce);
395 let mut cipher = ChaCha20::new(key, nonce);
396
397 cipher.apply_keystream(&mut mac_key);
398 let mut mac = Poly1305::new(&mac_key);
399 mac_key.zeroize();
400
401 mac.update(associated_data);
402 mac.update(&_pad0[..pad16(associated_data.len())]);
403
404 let mut block = [0u8; 64];
405 block[0] = ciphertext[0];
406
407 cipher.seek(64);
408 cipher.apply_keystream(&mut block);
409
410 *tag = block[0];
411 block[0] = ciphertext[0];
412
413 mac.update(&block);
414
415 let mlen = ciphertext.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES;
416 message[..mlen].copy_from_slice(&ciphertext[1..1 + mlen]);
417
418 let buffer_mac_pad = ((0x10 - block.len() as i64 + mlen as i64) & 0xf) as usize;
422 mac.update(&message[..mlen]);
423 mac.update(&_pad0[..buffer_mac_pad]);
424
425 let mut size_data = [0u8; 16];
426 size_data[..8].copy_from_slice(&associated_data.len().to_le_bytes());
427 size_data[8..16].copy_from_slice(&(block.len() + mlen).to_le_bytes());
428 mac.update(&size_data);
429 let mac = mac.finalize_to_array();
430
431 cipher.seek(128);
432 cipher.apply_keystream(&mut message[..mlen]);
433
434 if ciphertext[1 + mlen..].ct_eq(&mac).unwrap_u8() == 0 {
435 return Err(dryoc_error!("Message authentication mismatch"));
436 }
437
438 let inonce = state_inonce(&mut state.nonce);
439 xor_buf(inonce, &mac);
440
441 let counter = state_counter(&mut state.nonce);
442 increment_bytes(counter);
443
444 if *tag & CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY
445 == CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY
446 || state_counter(&mut state.nonce)
447 .ct_eq(&[0u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES])
448 .unwrap_u8()
449 == 1
450 {
451 crypto_secretstream_xchacha20poly1305_rekey(state);
452 }
453
454 Ok(mlen)
455}
456
457#[cfg(test)]
458mod tests {
459 use super::*;
460 use crate::dryocstream::Tag;
461
462 #[test]
463 fn test_sizes() {
464 use static_assertions::*;
465
466 use crate::constants::*;
467
468 const_assert!(
469 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES
470 == CRYPTO_CORE_HCHACHA20_INPUTBYTES
471 + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES
472 );
473
474 const_assert!(
475 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES
476 == CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES
477 );
478
479 const_assert!(
480 CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES
481 == CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES
482 + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_COUNTERBYTES
483 );
484
485 const_assert!(
486 CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX
487 <= CRYPTO_AEAD_CHACHA20POLY1305_IETF_MESSAGEBYTES_MAX
488 );
489
490 const_assert!(
491 CRYPTO_ONETIMEAUTH_POLY1305_BYTES >= CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_INONCEBYTES
492 );
493 }
494
495 #[test]
496 fn test_secretstream_basic_push() {
497 use base64::Engine as _;
498 use base64::engine::general_purpose;
499 use libsodium_sys::{
500 crypto_secretstream_xchacha20poly1305_init_pull as so_crypto_secretstream_xchacha20poly1305_init_pull,
501 crypto_secretstream_xchacha20poly1305_pull as so_crypto_secretstream_xchacha20poly1305_pull,
502 crypto_secretstream_xchacha20poly1305_push as so_crypto_secretstream_xchacha20poly1305_push,
503 crypto_secretstream_xchacha20poly1305_state,
504 };
505
506 use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
507 use crate::dryocstream::Tag;
508
509 let mut key = Key::default();
510 crypto_secretstream_xchacha20poly1305_keygen(&mut key);
511
512 let mut push_state = State::new();
513 let mut push_header = Header::default();
514 crypto_secretstream_xchacha20poly1305_init_push(&mut push_state, &mut push_header, &key);
515 let push_state_init = push_state.clone();
516
517 let message = b"hello";
518 let mut output = vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
519 let aad = b"";
520 let tag = Tag::MESSAGE.bits();
521 crypto_secretstream_xchacha20poly1305_push(
522 &mut push_state,
523 &mut output,
524 message,
525 Some(aad),
526 tag,
527 )
528 .expect("push failed");
529
530 let mut so_output = output.clone();
531 unsafe {
532 use libc::{c_uchar, c_ulonglong};
533 let mut so_state = crypto_secretstream_xchacha20poly1305_state {
534 k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
535 nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
536 _pad: [0u8; 8],
537 };
538 so_state.k.copy_from_slice(&push_state_init.k);
539 so_state.nonce.copy_from_slice(&push_state_init.nonce);
540 let mut clen_p: c_ulonglong = 0;
541 let ret = so_crypto_secretstream_xchacha20poly1305_push(
542 &mut so_state,
543 so_output.as_mut_ptr(),
544 &mut clen_p,
545 message.as_ptr(),
546 message.len() as u64,
547 aad.as_ptr(),
548 aad.len() as u64,
549 0,
550 );
551 assert_eq!(ret, 0);
552 so_output.resize(clen_p as usize, 0);
553 assert_eq!(
554 general_purpose::STANDARD.encode(&so_output),
555 general_purpose::STANDARD.encode(&output)
556 );
557 assert_eq!(
558 general_purpose::STANDARD.encode(so_state.k),
559 general_purpose::STANDARD.encode(push_state.k)
560 );
561 assert_eq!(
562 general_purpose::STANDARD.encode(so_state.nonce),
563 general_purpose::STANDARD.encode(push_state.nonce)
564 );
565
566 let mut so_state = crypto_secretstream_xchacha20poly1305_state {
567 k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
568 nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
569 _pad: [0u8; 8],
570 };
571 let mut mlen_p: c_ulonglong = 0;
572 let mut tag_p: c_uchar = 0;
573 let ret = so_crypto_secretstream_xchacha20poly1305_init_pull(
574 &mut so_state,
575 push_header.as_ptr(),
576 key.as_ptr(),
577 );
578 assert_eq!(ret, 0);
579 assert_eq!(
580 general_purpose::STANDARD.encode(so_state.k),
581 general_purpose::STANDARD.encode(push_state_init.k)
582 );
583 assert_eq!(
584 general_purpose::STANDARD.encode(so_state.nonce),
585 general_purpose::STANDARD.encode(push_state_init.nonce)
586 );
587 assert!(so_output.len() >= CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES);
588 let ret = so_crypto_secretstream_xchacha20poly1305_pull(
589 &mut so_state,
590 so_output.as_mut_ptr(),
591 &mut mlen_p,
592 &mut tag_p,
593 output.as_ptr(),
594 output.len() as u64,
595 aad.as_ptr(),
596 aad.len() as u64,
597 );
598 assert_eq!(ret, 0);
599 so_output.resize(mlen_p as usize, 0);
600 }
601 assert_eq!(
602 general_purpose::STANDARD.encode(message),
603 general_purpose::STANDARD.encode(&so_output)
604 );
605
606 let mut pull_state = State::default();
607 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &push_header, &key);
608
609 assert_eq!(
610 general_purpose::STANDARD.encode(pull_state.k),
611 general_purpose::STANDARD.encode(push_state_init.k)
612 );
613 assert_eq!(
614 general_purpose::STANDARD.encode(pull_state.nonce),
615 general_purpose::STANDARD.encode(push_state_init.nonce)
616 );
617
618 let mut pull_result_message =
619 vec![0u8; output.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
620 let mut pull_result_tag = 0u8;
621 crypto_secretstream_xchacha20poly1305_pull(
622 &mut pull_state,
623 &mut pull_result_message,
624 &mut pull_result_tag,
625 &output,
626 Some(&[]),
627 )
628 .expect("pull failed");
629
630 assert_eq!(Tag::MESSAGE, Tag::from_bits(tag).expect("tag"));
631 assert_eq!(
632 general_purpose::STANDARD.encode(&pull_result_message),
633 general_purpose::STANDARD.encode(message)
634 );
635 }
636
637 #[test]
638 fn test_rekey() {
639 use base64::Engine as _;
640 use base64::engine::general_purpose;
641 use libsodium_sys::{
642 crypto_secretstream_xchacha20poly1305_rekey as so_crypto_secretstream_xchacha20poly1305_rekey,
643 crypto_secretstream_xchacha20poly1305_state,
644 };
645
646 use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
647
648 let mut key = Key::default();
649 crypto_secretstream_xchacha20poly1305_keygen(&mut key);
650
651 let mut push_state = State::default();
652 let mut push_header: Header = Header::default();
653 crypto_secretstream_xchacha20poly1305_init_push(&mut push_state, &mut push_header, &key);
654 let push_state_init = push_state.clone();
655
656 crypto_secretstream_xchacha20poly1305_rekey(&mut push_state);
657
658 let mut so_state = crypto_secretstream_xchacha20poly1305_state {
659 k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
660 nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
661 _pad: [0u8; 8],
662 };
663 so_state.k.copy_from_slice(&push_state_init.k);
664 so_state.nonce.copy_from_slice(&push_state_init.nonce);
665 unsafe {
666 so_crypto_secretstream_xchacha20poly1305_rekey(&mut so_state);
667 }
668 assert_eq!(
669 general_purpose::STANDARD.encode(so_state.k),
670 general_purpose::STANDARD.encode(push_state.k)
671 );
672 assert_eq!(
673 general_purpose::STANDARD.encode(so_state.nonce),
674 general_purpose::STANDARD.encode(push_state.nonce)
675 );
676 }
677
678 #[test]
679 fn test_secretstream_lots_of_messages_push() {
680 use base64::Engine as _;
681 use base64::engine::general_purpose;
682 use libc::{c_uchar, c_ulonglong};
683 use libsodium_sys::{
684 crypto_secretstream_xchacha20poly1305_init_pull as so_crypto_secretstream_xchacha20poly1305_init_pull,
685 crypto_secretstream_xchacha20poly1305_pull as so_crypto_secretstream_xchacha20poly1305_pull,
686 crypto_secretstream_xchacha20poly1305_state,
687 };
688
689 use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
690 use crate::dryocstream::Tag;
691
692 let mut key = Key::default();
693 crypto_secretstream_xchacha20poly1305_keygen(&mut key);
694
695 let mut push_state = State::new();
696 let mut push_header = Header::default();
697 crypto_secretstream_xchacha20poly1305_init_push(&mut push_state, &mut push_header, &key);
698 let push_state_init = push_state.clone();
699
700 let mut pull_state = State::default();
701 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &push_header, &key);
702
703 assert_eq!(
704 general_purpose::STANDARD.encode(pull_state.k),
705 general_purpose::STANDARD.encode(push_state_init.k)
706 );
707 assert_eq!(
708 general_purpose::STANDARD.encode(pull_state.nonce),
709 general_purpose::STANDARD.encode(push_state_init.nonce)
710 );
711
712 let mut so_state = crypto_secretstream_xchacha20poly1305_state {
713 k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
714 nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
715 _pad: [0u8; 8],
716 };
717 so_state.k.copy_from_slice(&push_state_init.k);
718 so_state.nonce.copy_from_slice(&push_state_init.nonce);
719
720 let mut so_state = crypto_secretstream_xchacha20poly1305_state {
721 k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
722 nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
723 _pad: [0u8; 8],
724 };
725 let mut mlen_p: c_ulonglong = 0;
726 let mut tag_p: c_uchar = 0;
727 unsafe {
728 let ret = so_crypto_secretstream_xchacha20poly1305_init_pull(
729 &mut so_state,
730 push_header.as_ptr(),
731 key.as_ptr(),
732 );
733 assert_eq!(ret, 0);
734 }
735 assert_eq!(
736 general_purpose::STANDARD.encode(so_state.k),
737 general_purpose::STANDARD.encode(push_state_init.k)
738 );
739 assert_eq!(
740 general_purpose::STANDARD.encode(so_state.nonce),
741 general_purpose::STANDARD.encode(push_state_init.nonce)
742 );
743
744 for i in 0..100 {
745 let message = format!("hello {}", i);
746 let aad = format!("aad {}", i);
747 let tag = if i % 7 == 0 { Tag::REKEY } else { Tag::MESSAGE };
748
749 let mut output =
750 vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
751 crypto_secretstream_xchacha20poly1305_push(
752 &mut push_state,
753 &mut output,
754 message.as_bytes(),
755 Some(aad.as_bytes()),
756 tag.bits(),
757 )
758 .expect("push failed");
759
760 let mut so_output = output.clone();
761 unsafe {
762 assert!(so_output.len() >= CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES);
763 let ret = so_crypto_secretstream_xchacha20poly1305_pull(
764 &mut so_state,
765 so_output.as_mut_ptr(),
766 &mut mlen_p,
767 &mut tag_p,
768 output.as_ptr(),
769 output.len() as u64,
770 aad.as_ptr(),
771 aad.len() as u64,
772 );
773 assert_eq!(ret, 0);
774 so_output.resize(mlen_p as usize, 0);
775 }
776 assert_eq!(
777 general_purpose::STANDARD.encode(&message),
778 general_purpose::STANDARD.encode(&so_output)
779 );
780
781 let mut pull_result_message =
782 vec![0u8; output.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
783 let mut pull_result_tag = 0u8;
784 crypto_secretstream_xchacha20poly1305_pull(
785 &mut pull_state,
786 &mut pull_result_message,
787 &mut pull_result_tag,
788 &output,
789 Some(aad.as_bytes()),
790 )
791 .expect("pull failed");
792
793 assert_eq!(tag, Tag::from_bits(pull_result_tag).expect("tag"));
794 assert_eq!(
795 general_purpose::STANDARD.encode(&pull_result_message),
796 general_purpose::STANDARD.encode(&message)
797 );
798 }
799 }
800
801 #[test]
802 fn test_secretstream_basic_pull() {
803 use base64::Engine as _;
804 use base64::engine::general_purpose;
805 use libc::c_ulonglong;
806 use libsodium_sys::{
807 crypto_secretstream_xchacha20poly1305_init_push as so_crypto_secretstream_xchacha20poly1305_init_push,
808 crypto_secretstream_xchacha20poly1305_push as so_crypto_secretstream_xchacha20poly1305_push,
809 crypto_secretstream_xchacha20poly1305_state,
810 };
811
812 use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
813
814 let mut key = Key::default();
815 crypto_secretstream_xchacha20poly1305_keygen(&mut key);
816
817 let mut so_state = crypto_secretstream_xchacha20poly1305_state {
818 k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
819 nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
820 _pad: [0u8; 8],
821 };
822 let mut so_header = Header::default();
823 unsafe {
824 so_crypto_secretstream_xchacha20poly1305_init_push(
825 &mut so_state,
826 so_header.as_mut_ptr(),
827 key.as_ptr(),
828 );
829 }
830
831 let mut pull_state = State::new();
832 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &so_header, &key);
833
834 let message = b"hello";
835 let aad = b"aad";
836 let mut so_output = vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
837 let mut clen_p: c_ulonglong = 0;
838
839 unsafe {
840 let ret = so_crypto_secretstream_xchacha20poly1305_push(
841 &mut so_state,
842 so_output.as_mut_ptr(),
843 &mut clen_p,
844 message.as_ptr(),
845 message.len() as u64,
846 aad.as_ptr(),
847 aad.len() as u64,
848 0,
849 );
850 assert_eq!(ret, 0);
851 so_output.resize(clen_p as usize, 0);
852 }
853
854 let mut output = vec![0u8; so_output.len()];
855 let mut tag = 0u8;
856 let mlen = crypto_secretstream_xchacha20poly1305_pull(
857 &mut pull_state,
858 &mut output,
859 &mut tag,
860 &so_output,
861 Some(aad),
862 )
863 .expect("decrypt failed");
864 output.resize(mlen, 0);
865
866 assert_eq!(
867 general_purpose::STANDARD.encode(&output),
868 general_purpose::STANDARD.encode(message)
869 );
870 assert_eq!(tag, 0);
871 }
872
873 #[test]
874 fn test_secretstream_lots_of_messages_pull() {
875 use base64::Engine as _;
876 use base64::engine::general_purpose;
877 use libc::c_ulonglong;
878 use libsodium_sys::{
879 crypto_secretstream_xchacha20poly1305_init_push as so_crypto_secretstream_xchacha20poly1305_init_push,
880 crypto_secretstream_xchacha20poly1305_push as so_crypto_secretstream_xchacha20poly1305_push,
881 crypto_secretstream_xchacha20poly1305_state,
882 };
883
884 use crate::constants::CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES;
885 use crate::dryocstream::Tag;
886
887 let mut key = Key::default();
888 crypto_secretstream_xchacha20poly1305_keygen(&mut key);
889
890 let mut so_state = crypto_secretstream_xchacha20poly1305_state {
891 k: [0u8; CRYPTO_STREAM_CHACHA20_IETF_KEYBYTES],
892 nonce: [0u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES],
893 _pad: [0u8; 8],
894 };
895 let mut so_header = Header::default();
896 unsafe {
897 so_crypto_secretstream_xchacha20poly1305_init_push(
898 &mut so_state,
899 so_header.as_mut_ptr(),
900 key.as_ptr(),
901 );
902 }
903
904 let mut pull_state = State::new();
905 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &so_header, &key);
906
907 for i in 0..100 {
908 let message = format!("hello {}", i);
909 let aad = format!("aad {}", i);
910 let mut so_output =
911 vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
912 let mut clen_p: c_ulonglong = 0;
913
914 let tag = if i % 7 == 0 { Tag::REKEY } else { Tag::MESSAGE };
915
916 unsafe {
917 let ret = so_crypto_secretstream_xchacha20poly1305_push(
918 &mut so_state,
919 so_output.as_mut_ptr(),
920 &mut clen_p,
921 message.as_ptr(),
922 message.len() as u64,
923 aad.as_ptr(),
924 aad.len() as u64,
925 tag.bits(),
926 );
927 assert_eq!(ret, 0);
928 so_output.resize(clen_p as usize, 0);
929 }
930
931 let mut output =
932 vec![0u8; so_output.len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
933 let mut outtag = 0u8;
934 crypto_secretstream_xchacha20poly1305_pull(
935 &mut pull_state,
936 &mut output,
937 &mut outtag,
938 &so_output,
939 Some(aad.as_bytes()),
940 )
941 .expect("decrypt failed");
942
943 assert_eq!(
944 general_purpose::STANDARD.encode(so_state.k),
945 general_purpose::STANDARD.encode(pull_state.k)
946 );
947 assert_eq!(
948 general_purpose::STANDARD.encode(so_state.nonce),
949 general_purpose::STANDARD.encode(pull_state.nonce)
950 );
951
952 assert_eq!(
953 general_purpose::STANDARD.encode(&output),
954 general_purpose::STANDARD.encode(&message)
955 );
956 assert_eq!(outtag, tag.bits());
957 }
958 }
959
960 #[test]
961 fn test_secretstream_large_aad() {
962 let mut key = Key::default();
963 crypto_secretstream_xchacha20poly1305_keygen(&mut key);
964
965 let mut push_state = State::new();
966 let mut push_header = Header::default();
967 crypto_secretstream_xchacha20poly1305_init_push(&mut push_state, &mut push_header, &key);
968
969 let message = b"hello world";
970 let large_aad = vec![0x42u8; 328]; let mut ciphertext =
973 vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
974 crypto_secretstream_xchacha20poly1305_push(
975 &mut push_state,
976 &mut ciphertext,
977 message,
978 Some(&large_aad),
979 Tag::MESSAGE.bits(),
980 )
981 .expect("push failed");
982
983 let mut pull_state = State::new();
984 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &push_header, &key);
985
986 let mut decrypted = vec![0u8; message.len()];
987 let mut tag = 0u8;
988
989 crypto_secretstream_xchacha20poly1305_pull(
990 &mut pull_state,
991 &mut decrypted,
992 &mut tag,
993 &ciphertext,
994 Some(&large_aad),
995 )
996 .expect("pull failed");
997
998 assert_eq!(message.as_slice(), decrypted.as_slice());
999 assert_eq!(tag, Tag::MESSAGE.bits());
1000
1001 let mut wrong_aad = large_aad.clone();
1003 wrong_aad[100] = 0x43; let mut decrypted = vec![0u8; message.len()];
1006 let mut tag = 0u8;
1007
1008 assert!(
1009 crypto_secretstream_xchacha20poly1305_pull(
1010 &mut pull_state,
1011 &mut decrypted,
1012 &mut tag,
1013 &ciphertext,
1014 Some(&wrong_aad),
1015 )
1016 .is_err()
1017 );
1018 }
1019
1020 #[test]
1021 fn test_secretstream_small_aad() {
1022 let mut key = Key::default();
1023 crypto_secretstream_xchacha20poly1305_keygen(&mut key);
1024
1025 let mut push_state = State::new();
1026 let mut push_header = Header::default();
1027 crypto_secretstream_xchacha20poly1305_init_push(&mut push_state, &mut push_header, &key);
1028
1029 let message = b"hello world";
1030 let small_aad = b"abc"; let mut ciphertext =
1033 vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
1034 crypto_secretstream_xchacha20poly1305_push(
1035 &mut push_state,
1036 &mut ciphertext,
1037 message,
1038 Some(small_aad),
1039 Tag::MESSAGE.bits(),
1040 )
1041 .expect("push failed");
1042
1043 let mut pull_state = State::new();
1044 crypto_secretstream_xchacha20poly1305_init_pull(&mut pull_state, &push_header, &key);
1045
1046 let mut decrypted = vec![0u8; message.len()];
1047 let mut tag = 0u8;
1048
1049 crypto_secretstream_xchacha20poly1305_pull(
1050 &mut pull_state,
1051 &mut decrypted,
1052 &mut tag,
1053 &ciphertext,
1054 Some(small_aad),
1055 )
1056 .expect("pull failed");
1057
1058 assert_eq!(message.as_slice(), decrypted.as_slice());
1059 assert_eq!(tag, Tag::MESSAGE.bits());
1060
1061 let wrong_aad = b"xyz"; let mut decrypted = vec![0u8; message.len()];
1065 let mut tag = 0u8;
1066
1067 assert!(
1068 crypto_secretstream_xchacha20poly1305_pull(
1069 &mut pull_state,
1070 &mut decrypted,
1071 &mut tag,
1072 &ciphertext,
1073 Some(wrong_aad),
1074 )
1075 .is_err()
1076 );
1077 }
1078}