Skip to main content

dryoc/
dryocstream.rs

1//! # Encrypted streams
2//!
3//! [`DryocStream`] implements libsodium's secret-key authenticated stream
4//! encryption, also known as a _secretstream_. This implementation uses the
5//! XChaCha20 stream cipher, and Poly1305 for message authentication.
6//!
7//! Use [`DryocStream`] to:
8//!
9//! * encrypt a sequence of messages written to a file or network socket
10//! * exchange messages between two parties
11//! * send messages in a particular sequence, and authenticate the order of
12//!   messages
13//! * provide a way to determine the start and end of a sequence of messages
14//! * use a shared secret, which could be pre-shared, or derived using one or
15//!   more of:
16//!   * [`Kdf`](crate::kdf)
17//!   * [`Kx`](crate::kx)
18//!   * a passphrase with a strong password hashing function, such as
19//!     [`crypto_pwhash`](crate::classic::crypto_pwhash)
20//!
21//! [`DryocStream::init_push`] generates a public header for each stream. Send
22//! that header to the pull side and do not reuse the same key/header pair for a
23//! separate stream, because doing so repeats the stream's initial state.
24//!
25//! # Rustaceous API example
26//!
27//! ```
28//! use dryoc::dryocstream::*;
29//! let message1 = b"Arbitrary data to encrypt";
30//! let message2 = b"split into";
31//! let message3 = b"three messages";
32//!
33//! // Generate a random secret key for this stream
34//! let key = Key::generate();
35//!
36//! // Initialize the push side, type annotations required on return type
37//! let (mut push_stream, header): (_, Header) = DryocStream::init_push(&key);
38//!
39//! // Encrypt a series of messages
40//! let c1 = push_stream
41//!     .push_to_vec(message1, None, Tag::MESSAGE)
42//!     .expect("Encrypt failed");
43//! let c2 = push_stream
44//!     .push_to_vec(message2, None, Tag::MESSAGE)
45//!     .expect("Encrypt failed");
46//! let c3 = push_stream
47//!     .push_to_vec(message3, None, Tag::FINAL)
48//!     .expect("Encrypt failed");
49//!
50//! // Initialize the pull side using header generated by the push side
51//! let mut pull_stream = DryocStream::init_pull(&key, &header);
52//!
53//! // Decrypt the encrypted messages, type annotations required
54//! let (m1, tag1) = pull_stream.pull_to_vec(&c1, None).expect("Decrypt failed");
55//! let (m2, tag2) = pull_stream.pull_to_vec(&c2, None).expect("Decrypt failed");
56//! let (m3, tag3) = pull_stream.pull_to_vec(&c3, None).expect("Decrypt failed");
57//!
58//! assert_eq!(message1, m1.as_slice());
59//! assert_eq!(message2, m2.as_slice());
60//! assert_eq!(message3, m3.as_slice());
61//!
62//! assert_eq!(tag1, Tag::MESSAGE);
63//! assert_eq!(tag2, Tag::MESSAGE);
64//! assert_eq!(tag3, Tag::FINAL);
65//! ```
66//!
67//! ## Additional resources
68//!
69//! * See <https://libsodium.gitbook.io/doc/secret-key_cryptography/secretstream>
70//!   for additional details on secret streams
71//! * For public-key based encryption, see [`DryocBox`](crate::dryocbox)
72//! * For secret-key based encryption, see
73//!   [`DryocSecretBox`](crate::dryocsecretbox)
74//! * See the [protected] mod for an example using the protected memory features
75//!   with [`DryocStream`]
76
77use zeroize::Zeroize;
78
79use crate::classic::crypto_secretstream_xchacha20poly1305::{
80    State, crypto_secretstream_xchacha20poly1305_init_pull,
81    crypto_secretstream_xchacha20poly1305_init_push, crypto_secretstream_xchacha20poly1305_pull,
82    crypto_secretstream_xchacha20poly1305_push, crypto_secretstream_xchacha20poly1305_rekey,
83};
84use crate::constants::{
85    CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES,
86    CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES, CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES,
87};
88use crate::error::Error;
89pub use crate::types::*;
90
91mod tag;
92pub use tag::{Tag, TagIter, TagIterNames};
93
94/// Stream mode marker trait
95pub trait Mode {}
96/// Indicates a push stream
97pub struct Push;
98/// Indicates a pull stream
99pub struct Pull;
100
101impl Mode for Push {}
102impl Mode for Pull {}
103
104/// Stack-allocated secret for authenticated secret streams.
105pub type Key = StackByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES>;
106/// Stack-allocated nonce for authenticated secret streams.
107pub type Nonce = StackByteArray<CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES>;
108/// Stack-allocated header data for authenticated secret streams.
109pub type Header = StackByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES>;
110
111#[cfg(any(all(feature = "protected", any(unix, windows)), all(doc, not(doctest))))]
112#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "protected")))]
113pub mod protected {
114    //! # Protected memory type aliases for [`DryocStream`]
115    //!
116    //! Type aliases for using [`DryocStream`] with protected memory.
117    //!
118    //! ## Example
119    //! ```
120    //! use dryoc::dryocstream::protected::*;
121    //! use dryoc::dryocstream::{DryocStream, Tag};
122    //!
123    //! // Load some message into locked readonly memory.
124    //! let message1 = HeapBytes::from_slice_into_readonly_locked(b"Arbitrary data to encrypt")
125    //!     .expect("from slice failed");
126    //! let message2 =
127    //!     HeapBytes::from_slice_into_readonly_locked(b"split into").expect("from slice failed");
128    //! let message3 =
129    //!     HeapBytes::from_slice_into_readonly_locked(b"three messages").expect("from slice failed");
130    //!
131    //! // Generate a random key into locked readonly memory.
132    //! let key = Key::generate_readonly_locked().expect("key failed");
133    //!
134    //! // Initialize the push stream, place the header into locked memory
135    //! let (mut push_stream, header): (_, Locked<Header>) = DryocStream::init_push(&key);
136    //!
137    //! // Encrypt the set of messages, placing everything into locked memory.
138    //! let c1: LockedBytes = push_stream
139    //!     .push(&message1, None, Tag::MESSAGE)
140    //!     .expect("Encrypt failed");
141    //! let c2: LockedBytes = push_stream
142    //!     .push(&message2, None, Tag::MESSAGE)
143    //!     .expect("Encrypt failed");
144    //! let c3: LockedBytes = push_stream
145    //!     .push(&message3, None, Tag::FINAL)
146    //!     .expect("Encrypt failed");
147    //!
148    //! // Initialize the pull stream
149    //! let mut pull_stream = DryocStream::init_pull(&key, &header);
150    //!
151    //! // Decrypt the set of messages, putting everything into locked memory
152    //! let (m1, tag1): (LockedBytes, Tag) = pull_stream.pull(&c1, None).expect("Decrypt failed");
153    //! let (m2, tag2): (LockedBytes, Tag) = pull_stream.pull(&c2, None).expect("Decrypt failed");
154    //! let (m3, tag3): (LockedBytes, Tag) = pull_stream.pull(&c3, None).expect("Decrypt failed");
155    //!
156    //! assert_eq!(message1.as_slice(), m1.as_slice());
157    //! assert_eq!(message2.as_slice(), m2.as_slice());
158    //! assert_eq!(message3.as_slice(), m3.as_slice());
159    //!
160    //! assert_eq!(tag1, Tag::MESSAGE);
161    //! assert_eq!(tag2, Tag::MESSAGE);
162    //! assert_eq!(tag3, Tag::FINAL);
163    //! ```
164    use super::*;
165    pub use crate::protected::*;
166
167    /// Heap-allocated, page-aligned secret key for authenticated secret
168    /// streams, for use with protected memory.
169    pub type Key = HeapByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES>;
170    /// Heap-allocated, page-aligned nonce for authenticated secret
171    /// streams, for use with protected memory.
172    pub type Nonce = HeapByteArray<CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES>;
173    /// Heap-allocated, page-aligned header for authenticated secret
174    /// streams, for use with protected memory.
175    pub type Header = HeapByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES>;
176}
177
178/// Secret-key authenticated encrypted streams
179#[derive(PartialEq, Eq, Clone, Zeroize)]
180pub struct DryocStream<Mode> {
181    state: State,
182    phantom: std::marker::PhantomData<Mode>,
183}
184
185impl<Mode> Drop for DryocStream<Mode> {
186    fn drop(&mut self) {
187        self.state.zeroize()
188    }
189}
190
191impl<M> DryocStream<M> {
192    /// Manually rekeys the stream. Both the push and pull sides of the stream
193    /// must rekey at the same position.
194    ///
195    /// Automatic rekeying normally makes manual rekeying unnecessary.
196    ///
197    /// Refer to the [libsodium
198    /// docs](https://libsodium.gitbook.io/doc/secret-key_cryptography/secretstream#rekeying)
199    /// for details.
200    pub fn rekey(&mut self) {
201        crypto_secretstream_xchacha20poly1305_rekey(&mut self.state)
202    }
203}
204
205impl DryocStream<Push> {
206    /// Returns a new push stream, initialized from `key`.
207    pub fn init_push<
208        Key: ByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES>,
209        Header: NewByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES>,
210    >(
211        key: &Key,
212    ) -> (Self, Header) {
213        let mut state = State::new();
214        let mut header = Header::new_byte_array();
215        crypto_secretstream_xchacha20poly1305_init_push(
216            &mut state,
217            header.as_mut_array(),
218            key.as_array(),
219        );
220        (
221            Self {
222                state,
223                phantom: std::marker::PhantomData,
224            },
225            header,
226        )
227    }
228
229    /// Encrypts `message` for this stream with `associated_data` and `tag`,
230    /// returning the ciphertext.
231    ///
232    /// # Errors
233    ///
234    /// Returns an error if `tag` contains unknown bits, the message exceeds the
235    /// stream's maximum message length, or the output storage does not resize
236    /// to exactly the required ciphertext length.
237    pub fn push<Input: Bytes, Output: NewBytes + ResizableBytes>(
238        &mut self,
239        message: &Input,
240        associated_data: Option<&Input>,
241        tag: Tag,
242    ) -> Result<Output, Error> {
243        use crate::constants::{
244            CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES,
245            CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX,
246        };
247        Tag::try_from(tag.bits())?;
248
249        let message_len = message.as_slice().len();
250        if message_len > CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX {
251            return Err(length_error!(
252                crate::ErrorContext::Message,
253                message_len,
254                max CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX
255            ));
256        }
257
258        let mut ciphertext = Output::new_bytes();
259        ciphertext.resize(
260            message_len + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES,
261            0,
262        );
263        crypto_secretstream_xchacha20poly1305_push(
264            &mut self.state,
265            ciphertext.as_mut_slice(),
266            message.as_slice(),
267            associated_data.map(|aad| aad.as_slice()),
268            tag.bits(),
269        )?;
270        Ok(ciphertext)
271    }
272
273    /// Encrypts `message` for this stream with `associated_data` and `tag`,
274    /// returning the ciphertext.
275    ///
276    /// # Errors
277    ///
278    /// Returns an error if `tag` contains unknown bits or the message exceeds
279    /// the stream's maximum message length.
280    pub fn push_to_vec<Input: Bytes>(
281        &mut self,
282        message: &Input,
283        associated_data: Option<&Input>,
284        tag: Tag,
285    ) -> Result<Vec<u8>, Error> {
286        self.push(message, associated_data, tag)
287    }
288}
289
290impl DryocStream<Pull> {
291    /// Returns a new pull stream, initialized from `key` and `header`.
292    pub fn init_pull<
293        Key: ByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES>,
294        Header: ByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES>,
295    >(
296        key: &Key,
297        header: &Header,
298    ) -> Self {
299        let mut state = State::new();
300        crypto_secretstream_xchacha20poly1305_init_pull(
301            &mut state,
302            header.as_array(),
303            key.as_array(),
304        );
305        Self {
306            state,
307            phantom: std::marker::PhantomData,
308        }
309    }
310
311    /// Decrypts `ciphertext` for this stream with `associated_data`, returning
312    /// the decrypted message and tag.
313    ///
314    /// # Errors
315    ///
316    /// Returns an error if the ciphertext is too short or too long, the output
317    /// storage cannot hold the plaintext, or authentication fails.
318    /// Authentication fails for a wrong key or header, mismatched associated
319    /// data, modified ciphertext, or messages processed out of order.
320    /// Authenticated tag values containing unknown bits are also rejected
321    /// without advancing the stream.
322    pub fn pull<Input: Bytes, Output: MutBytes + Default + ResizableBytes>(
323        &mut self,
324        ciphertext: &Input,
325        associated_data: Option<&Input>,
326    ) -> Result<(Output, Tag), Error> {
327        use crate::constants::{
328            CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES,
329            CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX,
330        };
331        if ciphertext.as_slice().len() < CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES {
332            return Err(length_error!(
333                crate::ErrorContext::Ciphertext,
334                ciphertext.as_slice().len(),
335                min CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
336            ));
337        }
338
339        let message_len =
340            ciphertext.as_slice().len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES;
341        if message_len > CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX {
342            return Err(length_error!(
343                crate::ErrorContext::Ciphertext,
344                ciphertext.as_slice().len(),
345                max CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX
346                    + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
347            ));
348        }
349
350        let mut message = Output::default();
351        message.resize(message_len, 0);
352        let mut tag = 0u8;
353        let mut next_state = self.state.clone();
354        crypto_secretstream_xchacha20poly1305_pull(
355            &mut next_state,
356            message.as_mut_slice(),
357            &mut tag,
358            ciphertext.as_slice(),
359            associated_data.map(|aad| aad.as_slice()),
360        )?;
361
362        let tag = match Tag::try_from(tag) {
363            Ok(tag) => tag,
364            Err(error) => {
365                message.as_mut_slice().zeroize();
366                return Err(error);
367            }
368        };
369        self.state = next_state;
370
371        Ok((message, tag))
372    }
373
374    /// Decrypts `ciphertext` for this stream with `associated_data`, returning
375    /// the decrypted message and tag into a [`Vec`].
376    ///
377    /// # Errors
378    ///
379    /// Returns an error if the ciphertext is too short or too long, or
380    /// authentication fails because the key, header, associated data, stream
381    /// position, or ciphertext does not match. Authenticated tag values
382    /// containing unknown bits are also rejected without advancing the stream.
383    pub fn pull_to_vec<Input: Bytes>(
384        &mut self,
385        ciphertext: &Input,
386        associated_data: Option<&Input>,
387    ) -> Result<(Vec<u8>, Tag), Error> {
388        self.pull(ciphertext, associated_data)
389    }
390}
391
392#[cfg(test)]
393mod validation_tests {
394    use super::*;
395    use crate::constants::CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES;
396
397    #[test]
398    fn rustaceous_push_rejects_unknown_tag_without_advancing_state() {
399        let key = Key::generate();
400        let (mut push_stream, _header): (_, Header) = DryocStream::init_push(&key);
401        let original_state = push_stream.state.clone();
402        let invalid_tag = Tag::from_bits_retain(0x80);
403
404        let result: Result<Vec<u8>, Error> = push_stream.push_to_vec(b"message", None, invalid_tag);
405        assert!(matches!(
406            result,
407            Err(Error::InvalidValue {
408                context: crate::ErrorContext::Tag,
409                ..
410            })
411        ));
412        assert!(push_stream.state == original_state);
413    }
414
415    #[test]
416    fn rustaceous_pull_rejects_unknown_tag_without_advancing_state() {
417        let key = Key::generate();
418        let mut invalid_push_state = State::new();
419        let mut raw_header =
420            [0u8; crate::constants::CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES];
421        crypto_secretstream_xchacha20poly1305_init_push(
422            &mut invalid_push_state,
423            &mut raw_header,
424            key.as_array(),
425        );
426        let mut valid_push_state = invalid_push_state.clone();
427        let header = Header::try_from(raw_header.as_slice()).expect("header conversion failed");
428        let mut pull_stream = DryocStream::init_pull(&key, &header);
429        let original_pull_state = pull_stream.state.clone();
430        let message = b"authenticated unknown tag";
431
432        let mut invalid_ciphertext =
433            vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
434        crypto_secretstream_xchacha20poly1305_push(
435            &mut invalid_push_state,
436            &mut invalid_ciphertext,
437            message,
438            None,
439            0x80,
440        )
441        .expect("classic push failed");
442
443        let error = pull_stream
444            .pull_to_vec(&invalid_ciphertext, None)
445            .expect_err("unknown tag must be rejected");
446        assert!(matches!(
447            error,
448            Error::InvalidValue {
449                context: crate::ErrorContext::Tag,
450                ..
451            }
452        ));
453        assert!(pull_stream.state == original_pull_state);
454
455        let mut valid_ciphertext =
456            vec![0u8; message.len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES];
457        crypto_secretstream_xchacha20poly1305_push(
458            &mut valid_push_state,
459            &mut valid_ciphertext,
460            message,
461            None,
462            Tag::MESSAGE.bits(),
463        )
464        .expect("classic push failed");
465        let (decrypted, tag) = pull_stream
466            .pull_to_vec(&valid_ciphertext, None)
467            .expect("state must remain usable after rejection");
468        assert_eq!(decrypted, message);
469        assert_eq!(tag, Tag::MESSAGE);
470    }
471}
472
473#[cfg(all(test, dryoc_native_tests))]
474mod tests {
475    use super::*;
476
477    #[test]
478    fn test_stream_push() {
479        use sodiumoxide::crypto::secretstream::{
480            Header as SOHeader, Key as SOKey, Stream as SOStream, Tag as SOTag,
481        };
482
483        let message1 = b"Arbitrary data to encrypt";
484        let message2 = b"split into";
485        let message3 = b"three messages";
486
487        // Generate a random secret key for this stream
488        let key = Key::generate();
489
490        // Initialize the push side, type annotations required on return type
491        let (mut push_stream, header): (_, Header) = DryocStream::init_push(&key);
492        // Encrypt a series of messages
493        let c1: Vec<u8> = push_stream
494            .push(message1, None, Tag::MESSAGE)
495            .expect("Encrypt failed");
496        let c2: Vec<u8> = push_stream
497            .push(message2, None, Tag::MESSAGE)
498            .expect("Encrypt failed");
499        let c3: Vec<u8> = push_stream
500            .push(message3, None, Tag::FINAL)
501            .expect("Encrypt failed");
502
503        // Initialize the pull side using header generated by the push side
504        let mut so_stream_pull = SOStream::init_pull(
505            &SOHeader::from_slice(header.as_slice()).expect("header failed"),
506            &SOKey::from_slice(key.as_slice()).expect("key failed"),
507        )
508        .expect("pull init failed");
509
510        let (m1, tag1) = so_stream_pull.pull(&c1, None).expect("decrypt failed");
511        let (m2, tag2) = so_stream_pull.pull(&c2, None).expect("decrypt failed");
512        let (m3, tag3) = so_stream_pull.pull(&c3, None).expect("decrypt failed");
513
514        assert_eq!(message1, m1.as_slice());
515        assert_eq!(message2, m2.as_slice());
516        assert_eq!(message3, m3.as_slice());
517
518        assert_eq!(tag1, SOTag::Message);
519        assert_eq!(tag2, SOTag::Message);
520        assert_eq!(tag3, SOTag::Final);
521    }
522
523    #[test]
524    fn test_stream_pull() {
525        use std::convert::TryFrom;
526
527        use sodiumoxide::crypto::secretstream::{Key as SOKey, Stream as SOStream, Tag as SOTag};
528
529        let message1 = b"Arbitrary data to encrypt";
530        let message2 = b"split into";
531        let message3 = b"three messages";
532
533        // Generate a random secret key for this stream
534        let key = Key::generate();
535
536        // Initialize the push side, type annotations required on return type
537        let (mut so_push_stream, so_header) =
538            SOStream::init_push(&SOKey::from_slice(key.as_slice()).expect("key failed"))
539                .expect("init push failed");
540        // Encrypt a series of messages
541        let c1: Vec<u8> = so_push_stream
542            .push(message1, None, SOTag::Message)
543            .expect("Encrypt failed");
544        let c2: Vec<u8> = so_push_stream
545            .push(message2, None, SOTag::Message)
546            .expect("Encrypt failed");
547        let c3: Vec<u8> = so_push_stream
548            .push(message3, None, SOTag::Final)
549            .expect("Encrypt failed");
550
551        // Initialize the pull side using header generated by the push side
552        let mut pull_stream =
553            DryocStream::init_pull(&key, &Header::try_from(so_header.as_ref()).expect("header"));
554
555        // Decrypt the encrypted messages, type annotations required
556        let (m1, tag1): (Vec<u8>, Tag) = pull_stream.pull(&c1, None).expect("Decrypt failed");
557        let (m2, tag2): (Vec<u8>, Tag) = pull_stream.pull(&c2, None).expect("Decrypt failed");
558        let (m3, tag3): (Vec<u8>, Tag) = pull_stream.pull(&c3, None).expect("Decrypt failed");
559
560        assert_eq!(message1, m1.as_slice());
561        assert_eq!(message2, m2.as_slice());
562        assert_eq!(message3, m3.as_slice());
563
564        assert_eq!(tag1, Tag::MESSAGE);
565        assert_eq!(tag2, Tag::MESSAGE);
566        assert_eq!(tag3, Tag::FINAL);
567    }
568
569    #[cfg(all(feature = "protected", any(unix, windows)))]
570    #[test]
571    fn test_protected_memory() {
572        use crate::protected::*;
573
574        let message1 = b"Arbitrary data to encrypt";
575        let message2 = b"split into";
576        let message3 = b"three messages";
577
578        // Generate a random secret key for this stream
579        let key = protected::Key::generate_locked().expect("generate locked");
580
581        // Initialize the push side, type annotations required on return type
582        let (mut push_stream, header): (_, Header) = DryocStream::init_push(&key);
583
584        // Set secret key memory to no-access, but it must be unlocked first
585        let key = key
586            .munlock()
587            .expect("munlock")
588            .mprotect_noaccess()
589            .expect("mprotect");
590
591        // Encrypt a series of messages
592        let c1: Locked<HeapBytes> = push_stream
593            .push(message1, None, Tag::MESSAGE)
594            .expect("Encrypt failed");
595        let c2: Vec<u8> = push_stream
596            .push(message2, None, Tag::MESSAGE)
597            .expect("Encrypt failed");
598        let c3: Vec<u8> = push_stream
599            .push(message3, None, Tag::FINAL)
600            .expect("Encrypt failed");
601
602        // allow access again
603        let key = key.mprotect_readonly().expect("mprotect");
604
605        // Initialize the pull side using header generated by the push side
606        let mut pull_stream = DryocStream::init_pull(&key, &header);
607
608        // Set secret key memory to no-access
609        let _key = key.mprotect_noaccess().expect("mprotect");
610
611        // Decrypt the encrypted messages, type annotations required
612        let (m1, tag1): (Locked<HeapBytes>, Tag) =
613            pull_stream.pull(&c1, None).expect("Decrypt failed");
614        let (m2, tag2): (Locked<HeapBytes>, Tag) =
615            pull_stream.pull(&c2, None).expect("Decrypt failed");
616        let (m3, tag3): (Locked<HeapBytes>, Tag) =
617            pull_stream.pull(&c3, None).expect("Decrypt failed");
618
619        assert_eq!(message1, m1.as_slice());
620        assert_eq!(message2, m2.as_slice());
621        assert_eq!(message3, m3.as_slice());
622
623        assert_eq!(tag1, Tag::MESSAGE);
624        assert_eq!(tag2, Tag::MESSAGE);
625        assert_eq!(tag3, Tag::FINAL);
626    }
627}