dryoc/classic/
crypto_secretstream_xchacha20poly1305.rs

1//! # Secret stream functions
2//!
3//! Implements authenticated encrypted streams as per
4//! <https://libsodium.gitbook.io/doc/secret-key_cryptography/secretstream>.
5//!
6//! This API is compatible with libsodium's implementation.
7//!
8//! # Classic API example
9//!
10//! ```
11//! use dryoc::classic::crypto_secretstream_xchacha20poly1305::*;
12//! use dryoc::constants::{
13//!     CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES,
14//!     CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL,
15//!     CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE,
16//! };
17//! let message1 = b"Arbitrary data to encrypt";
18//! let message2 = b"split into";
19//! let message3 = b"three messages";
20//!
21//! // Generate a key
22//! let mut key = Key::default();
23//! crypto_secretstream_xchacha20poly1305_keygen(&mut key);
24//!
25//! // Create stream push state
26//! let mut state = State::new();
27//! let mut header = Header::default();
28//! crypto_secretstream_xchacha20poly1305_init_push(&mut state, &mut header, &key);
29//!
30//! let (mut c1, mut c2, mut c3) = (
31//!     vec![0u8; message1.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES],
32//!     vec![0u8; message2.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES],
33//!     vec![0u8; message3.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES],
34//! );
35//! // Encrypt a series of messages
36//! crypto_secretstream_xchacha20poly1305_push(
37//!     &mut state,
38//!     &mut c1,
39//!     message1,
40//!     None,
41//!     CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE,
42//! )
43//! .expect("Encrypt failed");
44//! crypto_secretstream_xchacha20poly1305_push(
45//!     &mut state,
46//!     &mut c2,
47//!     message2,
48//!     None,
49//!     CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE,
50//! )
51//! .expect("Encrypt failed");
52//! crypto_secretstream_xchacha20poly1305_push(
53//!     &mut state,
54//!     &mut c3,
55//!     message3,
56//!     None,
57//!     CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL,
58//! )
59//! .expect("Encrypt failed");
60//!
61//! // Create stream pull state, using the same key as above with a new state.
62//! let mut state = State::new();
63//! crypto_secretstream_xchacha20poly1305_init_pull(&mut state, &header, &key);
64//!
65//! let (mut m1, mut m2, mut m3) = (
66//!     vec![0u8; message1.len()],
67//!     vec![0u8; message2.len()],
68//!     vec![0u8; message3.len()],
69//! );
70//! let (mut tag1, mut tag2, mut tag3) = (0u8, 0u8, 0u8);
71//!
72//! // Decrypt the stream of messages
73//! crypto_secretstream_xchacha20poly1305_pull(&mut state, &mut m1, &mut tag1, &c1, None)
74//!     .expect("Decrypt failed");
75//! crypto_secretstream_xchacha20poly1305_pull(&mut state, &mut m2, &mut tag2, &c2, None)
76//!     .expect("Decrypt failed");
77//! crypto_secretstream_xchacha20poly1305_pull(&mut state, &mut m3, &mut tag3, &c3, None)
78//!     .expect("Decrypt failed");
79//!
80//! assert_eq!(message1, m1.as_slice());
81//! assert_eq!(message2, m2.as_slice());
82//! assert_eq!(message3, m3.as_slice());
83//!
84//! assert_eq!(tag1, CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE);
85//! assert_eq!(tag2, CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE);
86//! assert_eq!(tag3, CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL);
87//! ```
88
89use 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
108/// A secret for authenticated secret streams.
109pub type Key = [u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES];
110/// A nonce for authenticated secret streams.
111pub type Nonce = [u8; CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES];
112/// Container for stream header data
113pub type Header = [u8; CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES];
114
115/// Stream state data
116#[derive(PartialEq, Eq, Clone, Default, Zeroize, ZeroizeOnDrop)]
117pub struct State {
118    k: Key,
119    nonce: Nonce,
120}
121
122impl State {
123    /// Returns a new stream state with an empty key and nonce.
124    pub fn new() -> Self {
125        Self::default()
126    }
127}
128
129/// Generates a random stream key using [crate::rng::copy_randombytes].
130pub 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
150/// Initializes a push stream for streaming encryption.
151///
152/// Initializes a push stream into `state` using `key` and returns a stream
153/// header. The stream header can be used to initialize a pull stream using the
154/// same key (i.e., using [crypto_secretstream_xchacha20poly1305_init_pull]).
155///
156/// Compatible with libsodium's
157/// `crypto_secretstream_xchacha20poly1305_init_push`.
158pub 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    // Copy key into state
173    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
184/// Initializes a pull stream for streaming decryption.
185///
186/// Initializes a pull stream from `header` into `state` using `key` and returns
187/// a stream header. The stream header can be generated using
188/// [crypto_secretstream_xchacha20poly1305_init_push].
189///
190/// Compatible with libsodium's
191/// `crypto_secretstream_xchacha20poly1305_init_pull`.
192pub 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
216/// Manually rekeys a stream.
217///
218/// Compatible with libsodium's
219/// `crypto_secretstream_xchacha20poly1305_init_push`.
220pub 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
245/// Encrypts `message` from the stream for `state`, with `tag` and optional
246/// `associated_data`, placing the result into `ciphertext`.
247///
248/// Compatible with libsodium's `crypto_secretstream_xchacha20poly1305_push`.
249///
250/// NOTE: The libsodium version of this function contains an alignment bug which
251/// was left in place, and is reflected in this implementation for compatibility
252/// purposes. Refer to [commit
253/// 290197ba3ee72245fdab5e971c8de43a82b19874](https://github.com/jedisct1/libsodium/commit/290197ba3ee72245fdab5e971c8de43a82b19874#diff-dbd9b6026ac3fd057df0ddf00e4d671af16e5df99b4cc7d08b73b61f193d10f5)
254pub 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    // this is to workaround an unfortunate padding bug in libsodium, there's a
319    // note in commit 290197ba3ee72245fdab5e971c8de43a82b19874. There's no
320    // safety issue, so we can just pretend it's not a bug.
321    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
346/// Decrypts `ciphertext` from the stream for `state` with optional
347/// `additional_data`, placing the result into `message` (which must be manually
348/// resized) and `tag`. Returns the length of the message.
349///
350/// Due to a quirk in libsodium's implementation, you need to manually resize
351/// `message` to the message length after decrypting when using this function.
352///
353/// Compatible with libsodium's `crypto_secretstream_xchacha20poly1305_pull`.
354///
355/// NOTE: The libsodium version of this function contains an alignment bug which
356/// was left in place, and is reflected in this implementation for compatibility
357/// purposes. Refer to [commit
358/// 290197ba3ee72245fdab5e971c8de43a82b19874](https://github.com/jedisct1/libsodium/commit/290197ba3ee72245fdab5e971c8de43a82b19874#diff-dbd9b6026ac3fd057df0ddf00e4d671af16e5df99b4cc7d08b73b61f193d10f5)
359pub 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    // this is to workaround an unfortunate padding bug in libsodium, there's a
419    // note in commit 290197ba3ee72245fdab5e971c8de43a82b19874. There's no
420    // safety issue, so we can just pretend it's not a bug.
421    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]; // 328 bytes of 0x42
971
972        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        // Test with wrong AAD should fail
1002        let mut wrong_aad = large_aad.clone();
1003        wrong_aad[100] = 0x43; // Change one byte
1004
1005        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"; // 3 bytes of AAD
1031
1032        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        // Test with wrong AAD should fail
1062        let wrong_aad = b"xyz"; // Different 3 byte AAD
1063
1064        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}