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::gen();
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 let mut message = Output::default();
317 message.resize(
318 ciphertext.as_slice().len() - CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES,
319 0,
320 );
321 let mut tag = 0u8;
322 crypto_secretstream_xchacha20poly1305_pull(
323 &mut self.state,
324 message.as_mut_slice(),
325 &mut tag,
326 ciphertext.as_slice(),
327 associated_data.map(|aad| aad.as_slice()),
328 )?;
329
330 Ok((message, Tag::from_bits(tag).expect("invalid tag")))
331 }
332
333 /// Decrypts `ciphertext` for this stream with `associated_data`, returning
334 /// the decrypted message and tag into a [`Vec`].
335 pub fn pull_to_vec<Input: Bytes>(
336 &mut self,
337 ciphertext: &Input,
338 associated_data: Option<&Input>,
339 ) -> Result<(Vec<u8>, Tag), Error> {
340 self.pull(ciphertext, associated_data)
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347
348 #[test]
349 fn test_stream_push() {
350 use sodiumoxide::crypto::secretstream::{
351 Header as SOHeader, Key as SOKey, Stream as SOStream, Tag as SOTag,
352 };
353
354 let message1 = b"Arbitrary data to encrypt";
355 let message2 = b"split into";
356 let message3 = b"three messages";
357
358 // Generate a random secret key for this stream
359 let key = Key::gen();
360
361 // Initialize the push side, type annotations required on return type
362 let (mut push_stream, header): (_, Header) = DryocStream::init_push(&key);
363 // Encrypt a series of messages
364 let c1: Vec<u8> = push_stream
365 .push(message1, None, Tag::MESSAGE)
366 .expect("Encrypt failed");
367 let c2: Vec<u8> = push_stream
368 .push(message2, None, Tag::MESSAGE)
369 .expect("Encrypt failed");
370 let c3: Vec<u8> = push_stream
371 .push(message3, None, Tag::FINAL)
372 .expect("Encrypt failed");
373
374 // Initialize the pull side using header generated by the push side
375 let mut so_stream_pull = SOStream::init_pull(
376 &SOHeader::from_slice(header.as_slice()).expect("header failed"),
377 &SOKey::from_slice(key.as_slice()).expect("key failed"),
378 )
379 .expect("pull init failed");
380
381 let (m1, tag1) = so_stream_pull.pull(&c1, None).expect("decrypt failed");
382 let (m2, tag2) = so_stream_pull.pull(&c2, None).expect("decrypt failed");
383 let (m3, tag3) = so_stream_pull.pull(&c3, None).expect("decrypt failed");
384
385 assert_eq!(message1, m1.as_slice());
386 assert_eq!(message2, m2.as_slice());
387 assert_eq!(message3, m3.as_slice());
388
389 assert_eq!(tag1, SOTag::Message);
390 assert_eq!(tag2, SOTag::Message);
391 assert_eq!(tag3, SOTag::Final);
392 }
393
394 #[test]
395 fn test_stream_pull() {
396 use std::convert::TryFrom;
397
398 use sodiumoxide::crypto::secretstream::{Key as SOKey, Stream as SOStream, Tag as SOTag};
399
400 let message1 = b"Arbitrary data to encrypt";
401 let message2 = b"split into";
402 let message3 = b"three messages";
403
404 // Generate a random secret key for this stream
405 let key = Key::gen();
406
407 // Initialize the push side, type annotations required on return type
408 let (mut so_push_stream, so_header) =
409 SOStream::init_push(&SOKey::from_slice(key.as_slice()).expect("key failed"))
410 .expect("init push failed");
411 // Encrypt a series of messages
412 let c1: Vec<u8> = so_push_stream
413 .push(message1, None, SOTag::Message)
414 .expect("Encrypt failed");
415 let c2: Vec<u8> = so_push_stream
416 .push(message2, None, SOTag::Message)
417 .expect("Encrypt failed");
418 let c3: Vec<u8> = so_push_stream
419 .push(message3, None, SOTag::Final)
420 .expect("Encrypt failed");
421
422 // Initialize the pull side using header generated by the push side
423 let mut pull_stream =
424 DryocStream::init_pull(&key, &Header::try_from(so_header.as_ref()).expect("header"));
425
426 // Decrypt the encrypted messages, type annotations required
427 let (m1, tag1): (Vec<u8>, Tag) = pull_stream.pull(&c1, None).expect("Decrypt failed");
428 let (m2, tag2): (Vec<u8>, Tag) = pull_stream.pull(&c2, None).expect("Decrypt failed");
429 let (m3, tag3): (Vec<u8>, Tag) = pull_stream.pull(&c3, None).expect("Decrypt failed");
430
431 assert_eq!(message1, m1.as_slice());
432 assert_eq!(message2, m2.as_slice());
433 assert_eq!(message3, m3.as_slice());
434
435 assert_eq!(tag1, Tag::MESSAGE);
436 assert_eq!(tag2, Tag::MESSAGE);
437 assert_eq!(tag3, Tag::FINAL);
438 }
439
440 #[cfg(feature = "nightly")]
441 #[test]
442 fn test_protected_memory() {
443 use crate::protected::*;
444
445 let message1 = b"Arbitrary data to encrypt";
446 let message2 = b"split into";
447 let message3 = b"three messages";
448
449 // Generate a random secret key for this stream
450 let key = protected::Key::gen_locked().expect("gen locked");
451
452 // Initialize the push side, type annotations required on return type
453 let (mut push_stream, header): (_, Header) = DryocStream::init_push(&key);
454
455 // Set secret key memory to no-access, but it must be unlocked first
456 let key = key
457 .munlock()
458 .expect("munlock")
459 .mprotect_noaccess()
460 .expect("mprotect");
461
462 // Encrypt a series of messages
463 let c1: Locked<HeapBytes> = push_stream
464 .push(message1, None, Tag::MESSAGE)
465 .expect("Encrypt failed");
466 let c2: Vec<u8> = push_stream
467 .push(message2, None, Tag::MESSAGE)
468 .expect("Encrypt failed");
469 let c3: Vec<u8> = push_stream
470 .push(message3, None, Tag::FINAL)
471 .expect("Encrypt failed");
472
473 // allow access again
474 let key = key.mprotect_readonly().expect("mprotect");
475
476 // Initialize the pull side using header generated by the push side
477 let mut pull_stream = DryocStream::init_pull(&key, &header);
478
479 // Set secret key memory to no-access
480 let _key = key.mprotect_noaccess().expect("mprotect");
481
482 // Decrypt the encrypted messages, type annotations required
483 let (m1, tag1): (Locked<HeapBytes>, Tag) =
484 pull_stream.pull(&c1, None).expect("Decrypt failed");
485 let (m2, tag2): (Locked<HeapBytes>, Tag) =
486 pull_stream.pull(&c2, None).expect("Decrypt failed");
487 let (m3, tag3): (Locked<HeapBytes>, Tag) =
488 pull_stream.pull(&c3, None).expect("Decrypt failed");
489
490 assert_eq!(message1, m1.as_slice());
491 assert_eq!(message2, m2.as_slice());
492 assert_eq!(message3, m3.as_slice());
493
494 assert_eq!(tag1, Tag::MESSAGE);
495 assert_eq!(tag2, Tag::MESSAGE);
496 assert_eq!(tag3, Tag::FINAL);
497 }
498}