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//! You should use a [`DryocStream`] when you want to:
8//!
9//! * read and write messages from/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//! # Rustaceous API example
22//!
23//! ```
24//! use dryoc::dryocstream::*;
25//! let message1 = b"Arbitrary data to encrypt";
26//! let message2 = b"split into";
27//! let message3 = b"three messages";
28//!
29//! // Generate a random secret key for this stream
30//! let key = Key::generate();
31//!
32//! // Initialize the push side, type annotations required on return type
33//! let (mut push_stream, header): (_, Header) = DryocStream::init_push(&key);
34//!
35//! // Encrypt a series of messages
36//! let c1 = push_stream
37//!     .push_to_vec(message1, None, Tag::MESSAGE)
38//!     .expect("Encrypt failed");
39//! let c2 = push_stream
40//!     .push_to_vec(message2, None, Tag::MESSAGE)
41//!     .expect("Encrypt failed");
42//! let c3 = push_stream
43//!     .push_to_vec(message3, None, Tag::FINAL)
44//!     .expect("Encrypt failed");
45//!
46//! // Initialize the pull side using header generated by the push side
47//! let mut pull_stream = DryocStream::init_pull(&key, &header);
48//!
49//! // Decrypt the encrypted messages, type annotations required
50//! let (m1, tag1) = pull_stream.pull_to_vec(&c1, None).expect("Decrypt failed");
51//! let (m2, tag2) = pull_stream.pull_to_vec(&c2, None).expect("Decrypt failed");
52//! let (m3, tag3) = pull_stream.pull_to_vec(&c3, None).expect("Decrypt failed");
53//!
54//! assert_eq!(message1, m1.as_slice());
55//! assert_eq!(message2, m2.as_slice());
56//! assert_eq!(message3, m3.as_slice());
57//!
58//! assert_eq!(tag1, Tag::MESSAGE);
59//! assert_eq!(tag2, Tag::MESSAGE);
60//! assert_eq!(tag3, Tag::FINAL);
61//! ```
62//!
63//! ## Additional resources
64//!
65//! * See <https://libsodium.gitbook.io/doc/secret-key_cryptography/secretstream>
66//!   for additional details on secret streams
67//! * For public-key based encryption, see [`DryocBox`](crate::dryocbox)
68//! * For secret-key based encryption, see
69//!   [`DryocSecretBox`](crate::dryocsecretbox)
70//! * See the [protected] mod for an example using the protected memory features
71//!   with [`DryocStream`]
72
73use bitflags::bitflags;
74use zeroize::Zeroize;
75
76use crate::classic::crypto_secretstream_xchacha20poly1305::{
77    State, crypto_secretstream_xchacha20poly1305_init_pull,
78    crypto_secretstream_xchacha20poly1305_init_push, crypto_secretstream_xchacha20poly1305_pull,
79    crypto_secretstream_xchacha20poly1305_push, crypto_secretstream_xchacha20poly1305_rekey,
80};
81use crate::constants::{
82    CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES,
83    CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES,
84    CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE,
85    CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH,
86    CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY, CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES,
87};
88use crate::error::Error;
89pub use crate::types::*;
90
91/// Stream mode marker trait
92pub trait Mode {}
93/// Indicates a push stream
94pub struct Push;
95/// Indicates a pull stream
96pub struct Pull;
97
98impl Mode for Push {}
99impl Mode for Pull {}
100
101/// Stack-allocated secret for authenticated secret streams.
102pub type Key = StackByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES>;
103/// Stack-allocated nonce for authenticated secret streams.
104pub type Nonce = StackByteArray<CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES>;
105/// Stack-allocated header data for authenticated secret streams.
106pub type Header = StackByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES>;
107
108#[cfg(any(feature = "nightly", all(doc, not(doctest))))]
109#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "nightly")))]
110pub mod protected {
111    //! #  Protected memory type aliases for [`DryocStream`]
112    //!
113    //! This mod provides re-exports of type aliases for protected memory usage
114    //! with [`DryocStream`]. These type aliases are provided for convenience.
115    //!
116    //! ## Example
117    //! ```
118    //! use dryoc::dryocstream::protected::*;
119    //! use dryoc::dryocstream::{DryocStream, Tag};
120    //!
121    //! // Load some message into locked readonly memory.
122    //! let message1 = HeapBytes::from_slice_into_readonly_locked(b"Arbitrary data to encrypt")
123    //!     .expect("from slice failed");
124    //! let message2 =
125    //!     HeapBytes::from_slice_into_readonly_locked(b"split into").expect("from slice failed");
126    //! let message3 =
127    //!     HeapBytes::from_slice_into_readonly_locked(b"three messages").expect("from slice failed");
128    //!
129    //! // Generate a random key into locked readonly memory.
130    //! let key = Key::gen_readonly_locked().expect("key failed");
131    //!
132    //! // Initialize the push stream, place the header into locked memory
133    //! let (mut push_stream, header): (_, Locked<Header>) = DryocStream::init_push(&key);
134    //!
135    //! // Encrypt the set of messages, placing everything into locked memory.
136    //! let c1: LockedBytes = push_stream
137    //!     .push(&message1, None, Tag::MESSAGE)
138    //!     .expect("Encrypt failed");
139    //! let c2: LockedBytes = push_stream
140    //!     .push(&message2, None, Tag::MESSAGE)
141    //!     .expect("Encrypt failed");
142    //! let c3: LockedBytes = push_stream
143    //!     .push(&message3, None, Tag::FINAL)
144    //!     .expect("Encrypt failed");
145    //!
146    //! // Initialized the pull stream
147    //! let mut pull_stream = DryocStream::init_pull(&key, &header);
148    //!
149    //! // Decrypt the set of messages, putting everything into locked memory
150    //! let (m1, tag1): (LockedBytes, Tag) = pull_stream.pull(&c1, None).expect("Decrypt failed");
151    //! let (m2, tag2): (LockedBytes, Tag) = pull_stream.pull(&c2, None).expect("Decrypt failed");
152    //! let (m3, tag3): (LockedBytes, Tag) = pull_stream.pull(&c3, None).expect("Decrypt failed");
153    //!
154    //! assert_eq!(message1.as_slice(), m1.as_slice());
155    //! assert_eq!(message2.as_slice(), m2.as_slice());
156    //! assert_eq!(message3.as_slice(), m3.as_slice());
157    //!
158    //! assert_eq!(tag1, Tag::MESSAGE);
159    //! assert_eq!(tag2, Tag::MESSAGE);
160    //! assert_eq!(tag3, Tag::FINAL);
161    //! ```
162    use super::*;
163    pub use crate::protected::*;
164
165    /// Heap-allocated, page-aligned secret key for authenticated secret
166    /// streams, for use with protected memory.
167    pub type Key = HeapByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES>;
168    /// Heap-allocated, page-aligned nonce for authenticated secret
169    /// streams, for use with protected memory.
170    pub type Nonce = HeapByteArray<CRYPTO_STREAM_CHACHA20_IETF_NONCEBYTES>;
171    /// Heap-allocated, page-aligned header for authenticated secret
172    /// streams, for use with protected memory.
173    pub type Header = HeapByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES>;
174}
175
176bitflags! {
177    /// Message tag definitions
178    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
179    pub struct Tag: u8 {
180        /// Describes a normal message in a stream.
181        const MESSAGE = CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE;
182        /// Indicates the message marks the end of a series of messages in a
183        /// stream, but not the end of the stream.
184        const PUSH = CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH;
185        /// Derives a new key for the stream.
186        const REKEY = CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY;
187        /// Indicates the end of the stream.
188        const FINAL = Self::PUSH.bits() | Self::REKEY.bits();
189    }
190}
191
192impl From<u8> for Tag {
193    fn from(other: u8) -> Self {
194        Self::from_bits(other).expect("Unable to parse tag")
195    }
196}
197
198/// Secret-key authenticated encrypted streams
199#[derive(PartialEq, Eq, Clone, Zeroize)]
200pub struct DryocStream<Mode> {
201    state: State,
202    phantom: std::marker::PhantomData<Mode>,
203}
204
205impl<Mode> Drop for DryocStream<Mode> {
206    fn drop(&mut self) {
207        self.state.zeroize()
208    }
209}
210
211impl<M> DryocStream<M> {
212    /// Manually rekeys the stream. Both the push and pull sides of the stream
213    /// need to manually rekey if you use this function (i.e., it's not handled
214    /// by the library).
215    ///
216    /// Automatic rekeying will occur normally, and you generally shouldn't need
217    /// to manually rekey.
218    ///
219    /// Refer to the [libsodium
220    /// docs](https://libsodium.gitbook.io/doc/secret-key_cryptography/secretstream#rekeying)
221    /// for details.
222    pub fn rekey(&mut self) {
223        crypto_secretstream_xchacha20poly1305_rekey(&mut self.state)
224    }
225}
226
227impl DryocStream<Push> {
228    /// Returns a new push stream, initialized from `key`.
229    pub fn init_push<
230        Key: ByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES>,
231        Header: NewByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES>,
232    >(
233        key: &Key,
234    ) -> (Self, Header) {
235        let mut state = State::new();
236        let mut header = Header::new_byte_array();
237        crypto_secretstream_xchacha20poly1305_init_push(
238            &mut state,
239            header.as_mut_array(),
240            key.as_array(),
241        );
242        (
243            Self {
244                state,
245                phantom: std::marker::PhantomData,
246            },
247            header,
248        )
249    }
250
251    /// Encrypts `message` for this stream with `associated_data` and `tag`,
252    /// returning the ciphertext.
253    pub fn push<Input: Bytes, Output: NewBytes + ResizableBytes>(
254        &mut self,
255        message: &Input,
256        associated_data: Option<&Input>,
257        tag: Tag,
258    ) -> Result<Output, Error> {
259        use crate::constants::CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES;
260        let mut ciphertext = Output::new_bytes();
261        ciphertext.resize(
262            message.as_slice().len() + CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES,
263            0,
264        );
265        crypto_secretstream_xchacha20poly1305_push(
266            &mut self.state,
267            ciphertext.as_mut_slice(),
268            message.as_slice(),
269            associated_data.map(|aad| aad.as_slice()),
270            tag.bits(),
271        )?;
272        Ok(ciphertext)
273    }
274
275    /// Encrypts `message` for this stream with `associated_data` and `tag`,
276    /// returning the ciphertext.
277    pub fn push_to_vec<Input: Bytes>(
278        &mut self,
279        message: &Input,
280        associated_data: Option<&Input>,
281        tag: Tag,
282    ) -> Result<Vec<u8>, Error> {
283        self.push(message, associated_data, tag)
284    }
285}
286
287impl DryocStream<Pull> {
288    /// Returns a new pull stream, initialized from `key` and `header`.
289    pub fn init_pull<
290        Key: ByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES>,
291        Header: ByteArray<CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES>,
292    >(
293        key: &Key,
294        header: &Header,
295    ) -> Self {
296        let mut state = State::new();
297        crypto_secretstream_xchacha20poly1305_init_pull(
298            &mut state,
299            header.as_array(),
300            key.as_array(),
301        );
302        Self {
303            state,
304            phantom: std::marker::PhantomData,
305        }
306    }
307
308    /// Decrypts `ciphertext` for this stream with `associated_data`, returning
309    /// the decrypted message and tag.
310    pub fn pull<Input: Bytes, Output: MutBytes + Default + ResizableBytes>(
311        &mut self,
312        ciphertext: &Input,
313        associated_data: Option<&Input>,
314    ) -> Result<(Output, Tag), Error> {
315        use crate::constants::CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES;
316        if ciphertext.as_slice().len() < CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES {
317            return Err(dryoc_error!(format!(
318                "Ciphertext length was {}, should be at least {}",
319                ciphertext.as_slice().len(),
320                CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
321            )));
322        }
323
324        let mut message = Output::default();
325        message.resize(
326            ciphertext.as_slice().len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES,
327            0,
328        );
329        let mut tag = 0u8;
330        crypto_secretstream_xchacha20poly1305_pull(
331            &mut self.state,
332            message.as_mut_slice(),
333            &mut tag,
334            ciphertext.as_slice(),
335            associated_data.map(|aad| aad.as_slice()),
336        )?;
337
338        Ok((message, Tag::from_bits(tag).expect("invalid tag")))
339    }
340
341    /// Decrypts `ciphertext` for this stream with `associated_data`, returning
342    /// the decrypted message and tag into a [`Vec`].
343    pub fn pull_to_vec<Input: Bytes>(
344        &mut self,
345        ciphertext: &Input,
346        associated_data: Option<&Input>,
347    ) -> Result<(Vec<u8>, Tag), Error> {
348        self.pull(ciphertext, associated_data)
349    }
350}
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355
356    #[test]
357    fn test_stream_push() {
358        use sodiumoxide::crypto::secretstream::{
359            Header as SOHeader, Key as SOKey, Stream as SOStream, Tag as SOTag,
360        };
361
362        let message1 = b"Arbitrary data to encrypt";
363        let message2 = b"split into";
364        let message3 = b"three messages";
365
366        // Generate a random secret key for this stream
367        let key = Key::generate();
368
369        // Initialize the push side, type annotations required on return type
370        let (mut push_stream, header): (_, Header) = DryocStream::init_push(&key);
371        // Encrypt a series of messages
372        let c1: Vec<u8> = push_stream
373            .push(message1, None, Tag::MESSAGE)
374            .expect("Encrypt failed");
375        let c2: Vec<u8> = push_stream
376            .push(message2, None, Tag::MESSAGE)
377            .expect("Encrypt failed");
378        let c3: Vec<u8> = push_stream
379            .push(message3, None, Tag::FINAL)
380            .expect("Encrypt failed");
381
382        // Initialize the pull side using header generated by the push side
383        let mut so_stream_pull = SOStream::init_pull(
384            &SOHeader::from_slice(header.as_slice()).expect("header failed"),
385            &SOKey::from_slice(key.as_slice()).expect("key failed"),
386        )
387        .expect("pull init failed");
388
389        let (m1, tag1) = so_stream_pull.pull(&c1, None).expect("decrypt failed");
390        let (m2, tag2) = so_stream_pull.pull(&c2, None).expect("decrypt failed");
391        let (m3, tag3) = so_stream_pull.pull(&c3, None).expect("decrypt failed");
392
393        assert_eq!(message1, m1.as_slice());
394        assert_eq!(message2, m2.as_slice());
395        assert_eq!(message3, m3.as_slice());
396
397        assert_eq!(tag1, SOTag::Message);
398        assert_eq!(tag2, SOTag::Message);
399        assert_eq!(tag3, SOTag::Final);
400    }
401
402    #[test]
403    fn test_stream_pull() {
404        use std::convert::TryFrom;
405
406        use sodiumoxide::crypto::secretstream::{Key as SOKey, Stream as SOStream, Tag as SOTag};
407
408        let message1 = b"Arbitrary data to encrypt";
409        let message2 = b"split into";
410        let message3 = b"three messages";
411
412        // Generate a random secret key for this stream
413        let key = Key::generate();
414
415        // Initialize the push side, type annotations required on return type
416        let (mut so_push_stream, so_header) =
417            SOStream::init_push(&SOKey::from_slice(key.as_slice()).expect("key failed"))
418                .expect("init push failed");
419        // Encrypt a series of messages
420        let c1: Vec<u8> = so_push_stream
421            .push(message1, None, SOTag::Message)
422            .expect("Encrypt failed");
423        let c2: Vec<u8> = so_push_stream
424            .push(message2, None, SOTag::Message)
425            .expect("Encrypt failed");
426        let c3: Vec<u8> = so_push_stream
427            .push(message3, None, SOTag::Final)
428            .expect("Encrypt failed");
429
430        // Initialize the pull side using header generated by the push side
431        let mut pull_stream =
432            DryocStream::init_pull(&key, &Header::try_from(so_header.as_ref()).expect("header"));
433
434        // Decrypt the encrypted messages, type annotations required
435        let (m1, tag1): (Vec<u8>, Tag) = pull_stream.pull(&c1, None).expect("Decrypt failed");
436        let (m2, tag2): (Vec<u8>, Tag) = pull_stream.pull(&c2, None).expect("Decrypt failed");
437        let (m3, tag3): (Vec<u8>, Tag) = pull_stream.pull(&c3, None).expect("Decrypt failed");
438
439        assert_eq!(message1, m1.as_slice());
440        assert_eq!(message2, m2.as_slice());
441        assert_eq!(message3, m3.as_slice());
442
443        assert_eq!(tag1, Tag::MESSAGE);
444        assert_eq!(tag2, Tag::MESSAGE);
445        assert_eq!(tag3, Tag::FINAL);
446    }
447
448    #[cfg(feature = "nightly")]
449    #[test]
450    fn test_protected_memory() {
451        use crate::protected::*;
452
453        let message1 = b"Arbitrary data to encrypt";
454        let message2 = b"split into";
455        let message3 = b"three messages";
456
457        // Generate a random secret key for this stream
458        let key = protected::Key::gen_locked().expect("gen locked");
459
460        // Initialize the push side, type annotations required on return type
461        let (mut push_stream, header): (_, Header) = DryocStream::init_push(&key);
462
463        // Set secret key memory to no-access, but it must be unlocked first
464        let key = key
465            .munlock()
466            .expect("munlock")
467            .mprotect_noaccess()
468            .expect("mprotect");
469
470        // Encrypt a series of messages
471        let c1: Locked<HeapBytes> = push_stream
472            .push(message1, None, Tag::MESSAGE)
473            .expect("Encrypt failed");
474        let c2: Vec<u8> = push_stream
475            .push(message2, None, Tag::MESSAGE)
476            .expect("Encrypt failed");
477        let c3: Vec<u8> = push_stream
478            .push(message3, None, Tag::FINAL)
479            .expect("Encrypt failed");
480
481        // allow access again
482        let key = key.mprotect_readonly().expect("mprotect");
483
484        // Initialize the pull side using header generated by the push side
485        let mut pull_stream = DryocStream::init_pull(&key, &header);
486
487        // Set secret key memory to no-access
488        let _key = key.mprotect_noaccess().expect("mprotect");
489
490        // Decrypt the encrypted messages, type annotations required
491        let (m1, tag1): (Locked<HeapBytes>, Tag) =
492            pull_stream.pull(&c1, None).expect("Decrypt failed");
493        let (m2, tag2): (Locked<HeapBytes>, Tag) =
494            pull_stream.pull(&c2, None).expect("Decrypt failed");
495        let (m3, tag3): (Locked<HeapBytes>, Tag) =
496            pull_stream.pull(&c3, None).expect("Decrypt failed");
497
498        assert_eq!(message1, m1.as_slice());
499        assert_eq!(message2, m2.as_slice());
500        assert_eq!(message3, m3.as_slice());
501
502        assert_eq!(tag1, Tag::MESSAGE);
503        assert_eq!(tag2, Tag::MESSAGE);
504        assert_eq!(tag3, Tag::FINAL);
505    }
506}