bitwarden_crypto/stream/mod.rs
1/// Large blob encryption and decryption needs to be streamed to keep performance reasonable.
2/// This module implements streamed encryption, that can be plugged into other IO streaming
3/// interfaces, to create a streamed encryption pipeline. Further, it defines the cryptography
4/// behind attachments, with the legacy format for compatibility, and a new format that
5/// supports random-access decryption, and has enhanced security properties.
6use std::ops::Range;
7
8pub(crate) mod aes256_cbc_hmac_legacy_stream;
9mod large_memory_buffer;
10mod streaming_attachment_cipher;
11
12pub use streaming_attachment_cipher::{StreamingAttachmentDecryptor, StreamingAttachmentEncryptor};
13use thiserror::Error;
14
15/// Error returned by streaming-cipher constructors when the supplied key cannot be used with
16/// that streaming cipher.
17pub(crate) enum StreamCreationError {
18 /// The supplied [`crate::SymmetricCryptoKey`] is not the variant this cipher expects.
19 WrongKeyType,
20}
21
22/// Opaque error returned when a streaming decryptor fails. The reason (HMAC mismatch, invalid
23/// padding, truncation) is intentionally not distinguished, to avoid leaking which check failed.
24#[derive(Debug, Error)]
25#[error("streaming decryption failed")]
26pub(crate) struct StreamDecryptionError;
27
28/// Opaque error returned when a streaming encryptor fails.
29#[derive(Debug, Error)]
30#[error("streaming encryption failed")]
31pub(crate) struct StreamEncryptionError;
32
33/// Outcome of feeding one chunk of plaintext to a [`StreamingEncryptor::update`].
34pub(crate) enum ChunkEncryptionResult {
35 /// The encryptor needs more input bytes before it can produce ciphertext.
36 NeedMoreData,
37 /// A chunk of the ciphertext is emitted, but the stream is not yet complete
38 EncryptedChunk(Vec<u8>),
39 /// The last chunk is emitted and the stream is completed. No more calls to `update` should be
40 /// made after this.
41 FinalEncryptedChunk(Vec<u8>),
42 /// Encryption failed. Discard the entire stream.
43 Error(StreamEncryptionError),
44}
45
46/// Outcome of feeding one chunk of wire bytes to a [`StreamingDecryptor::update`].
47pub(crate) enum ChunkDecryptionResult {
48 /// The decryptor needs more input bytes before it can produce plaintext.
49 NeedMoreData,
50 /// A chunk of decrypted plaintext. Whether this is already authenticated is
51 /// implementation-defined: chunked-AEAD STREAM authenticates per chunk and these bytes
52 /// can be trusted; AES-CBC + HMAC authenticates only at finalize, and these bytes must be
53 /// treated as untrusted until the terminal [`Self::FinalDecryptedChunk`] is observed.
54 DecryptedChunk(Vec<u8>),
55 /// The final chunk of decrypted, authenticated plaintext.
56 FinalDecryptedChunk(Vec<u8>),
57 /// Decryption failed. Discard the entire stream.
58 Error(StreamDecryptionError),
59}
60
61/// A symmetric streaming encryptor.
62pub(crate) trait StreamingEncryptor: Sized {
63 /// Pass in a chunk of any size of the stream. The encryptor buffers and emits as much
64 /// ciphertext as it can. The caller signals the end of the stream by calling `update` with
65 /// `last_block = true`.
66 ///
67 /// The encryptor *MAY* buffer the entire plaintext before emitting any ciphertext depending
68 /// on the underlying algorithm implementation. In this case, the caller must
69 /// keep calling update with an empty chunk until the final encrypted chunk is emitted.
70 fn update(&mut self, plaintext_chunk: &[u8], last_block: bool) -> ChunkEncryptionResult;
71}
72
73/// A symmetric streaming decryptor.
74pub(crate) trait StreamingDecryptor: Sized {
75 /// Pass in a chunk of any size of the stream. The decryptor buffers and emits as much plaintext
76 /// as it can. The caller signals the end of the stream by calling `update` with `last_block =
77 /// true`. The decryptor emits the final chunk of plaintext and performs any final
78 /// authentication checks if required. The caller MUST verify the presence of the final
79 /// decrypted chunk before using the plaintext.
80 fn update(&mut self, ciphertext_chunk: &[u8], last_block: bool) -> ChunkDecryptionResult;
81}
82
83/// A symmetric decryptor that supports random-access reads: given the complete encrypted
84/// wire stream and a plaintext byte range, return only the plaintext covering that range.
85///
86/// Random access is only possible for wire formats whose framing lets the decryptor seek
87/// into the ciphertext without reading the whole stream and whose chunks are individually
88/// authenticated. Chunked-AEAD STREAM qualifies (fixed-size nonce prefix + fixed-size
89/// chunks, each with its own AEAD tag); AES-256-CBC + HMAC-SHA256 does not, because its
90/// trailing MAC covers the entire ciphertext and the stream must be read in full before
91/// any bytes can be trusted.
92#[allow(unused)]
93pub(crate) trait RandomAccessDecryptor<R> {
94 /// Error returned when authentication fails on any read chunk, the wire bytes are
95 /// shorter than the framing requires, or `range` exceeds the plaintext length.
96 type Error;
97
98 /// Decrypt the plaintext bytes covering `range` from the encrypted `ciphertext` byte stream.
99 async fn decrypt_range(
100 &self,
101 data_source: R,
102 range: Range<usize>,
103 ) -> Result<Vec<u8>, Self::Error>;
104}
105
106/// A data source that supports random-access reads
107#[allow(unused)]
108pub(crate) trait RandomAccessDataSource {
109 /// Error returned when authentication fails on any read chunk, the wire bytes are
110 /// shorter than the framing requires, or `range` exceeds the plaintext length.
111 type Error;
112
113 /// Decrypt the plaintext bytes covering `range` from the encrypted `ciphertext` byte stream.
114 async fn read_range(&self, range: Range<usize>) -> Result<Vec<u8>, Self::Error>;
115}