Skip to main content

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}