Skip to main content

bitwarden_crypto/stream/
aes256_cbc_hmac_legacy_stream.rs

1//! # AES-256-CBC-HMAC-Legacy-Stream
2//!
3//! Aes256CBC-HMAC-Legacy-Stream is a format for streaming encryption of attachments. It consists of
4//! a header, an AES-CBC stream, and a hmac over IV + CBC ciphertext. Because there is just one HMAC
5//! over the entire stream, it is not permissible to decrypt a partial portion of this stream, as
6//! the integrity of that portion cannot be guaranteed.
7//!
8//! ## Format
9//! The stream looks as follows:
10//! ```text
11//! (KEY_E, KEY_A) = KEY
12//! IV | HMAC[KEY_A] (over IV + ciphertext) | AES-CBC[KEY_E]() ciphertext
13//! ```
14//!
15//! ## Limitations
16//!
17//! Because the HMAC is written to the start, encryption must be buffered in memory fully
18//! before emitting out as an encryption stream, since the HMAC can only be calculated
19//! after all ciphertext is produced. This is a limitation to the format.
20//!
21//! Further, the HMAC covers the entire stream, not chunks of it, so the entire stream
22//! must be fully read before the output is used. IMPORTANT: YOU MUST READ THE ENTIRE STREAM
23//! BEFORE USING THE DECRYPTED OUTPUT. This contract cannot be enforced by the interface and
24//! requires the correct usage of the caller.
25//!
26//! NOTE: The attachments, stored on the server contain a header idicating which format they are
27//! encrypted with. This header is *NOT* included in the stream processed by this file, and is
28//! expected to be stripped by the caller before passing the ciphertext to this code.
29//!
30//! Random access is not possible with this format, both because of the use of CBC chaining,
31//! and because of the single HMAC over the entire cipher stream.
32
33use aes::cipher::{BlockModeDecrypt, BlockModeEncrypt, KeyIvInit};
34use hkdf::HmacImpl;
35use hybrid_array::Array;
36use rand::Rng;
37use subtle::ConstantTimeEq;
38
39use super::{
40    ChunkDecryptionResult, ChunkEncryptionResult, StreamCreationError, StreamDecryptionError,
41    StreamEncryptionError, StreamingDecryptor, StreamingEncryptor,
42};
43use crate::{
44    Aes256CbcHmacKey, SymmetricCryptoKey,
45    stream::large_memory_buffer::{Buffer, InvalidIndexError},
46    util::PbkdfSha256Hmac,
47};
48
49const AES256_CBC_BLOCK_SIZE: usize = 16;
50const AES256_CBC_IV_SIZE: usize = 16;
51const HMAC_SIZE: usize = 32;
52const HEADER_LENGTH: usize = AES256_CBC_IV_SIZE + HMAC_SIZE;
53const EMISSION_CHUNK_SIZE: usize = 1024 * 1024; // 1 MiB
54
55/// CBC IV
56type Iv = [u8; AES256_CBC_IV_SIZE];
57/// HMAC over IV + Ciphertext
58type Mac = [u8; HMAC_SIZE];
59/// Header is IV || HMAC
60type StreamHeaderBytes = [u8; HEADER_LENGTH];
61type CbcCiphertextBlock = [u8; AES256_CBC_BLOCK_SIZE];
62
63struct CbcDecryptor {
64    decryptor: cbc::Decryptor<aes::Aes256>,
65}
66
67impl CbcDecryptor {
68    fn new(key: &[u8; 32], iv: &Iv) -> Self {
69        Self {
70            decryptor: cbc::Decryptor::<aes::Aes256>::new(key.into(), iv.into()),
71        }
72    }
73
74    fn decrypt_block(&mut self, block: &CbcCiphertextBlock) -> CbcPlaintextBlock {
75        let mut block: Array<u8, _> = (*block).into();
76        self.decryptor.decrypt_block(&mut block);
77        CbcPlaintextBlock(
78            block
79                .as_slice()
80                .try_into()
81                .expect("block size checked by type"),
82        )
83    }
84}
85
86struct CbcEncryptor {
87    encryptor: cbc::Encryptor<aes::Aes256>,
88}
89
90impl CbcEncryptor {
91    fn new(key: &[u8; 32], iv: &Iv) -> Self {
92        Self {
93            encryptor: cbc::Encryptor::<aes::Aes256>::new(key.into(), iv.into()),
94        }
95    }
96
97    fn encrypt_block(&mut self, block: &mut CbcPlaintextBlock) -> CbcCiphertextBlock {
98        let mut block_array: Array<u8, _> = block.0.into();
99        self.encryptor.encrypt_block(&mut block_array);
100        block_array
101            .as_slice()
102            .try_into()
103            .expect("block size checked by type")
104    }
105}
106
107/// A higher level interface over the HMAC validation of the attachment ciphertext
108struct HmacStreamValidator {
109    hmac: PbkdfSha256Hmac,
110}
111
112impl HmacStreamValidator {
113    fn new(mac_key: &[u8; 32], iv: &Iv) -> Self {
114        let mut hmac = PbkdfSha256Hmac::new_from_slice(mac_key);
115        hmac.update(iv);
116        Self { hmac }
117    }
118
119    /// Called on each CBC ciphertext block in order
120    fn read_block(&mut self, data: &CbcCiphertextBlock) {
121        self.hmac.update(data);
122    }
123
124    /// Called after the final block has been ingested by `read_block`, during encryption.
125    fn end_stream(&self) -> Mac {
126        // `HmacImpl::finalize` consumes self; clone so the validator can stay borrowed by the
127        // decryptor state machine and be dropped normally when the state transitions.
128        let mac: Mac = self
129            .hmac
130            .clone()
131            .finalize()
132            .as_slice()
133            .try_into()
134            .expect("hmac output is 32 bytes");
135        mac
136    }
137
138    /// Called after the final block has been ingested by `read_block`, during decryption. Returns
139    /// whether the calculated HMAC matches the expected HMAC from the header.
140    fn validate_stream_end(&self, expected_mac: &Mac) -> bool {
141        // `HmacImpl::finalize` consumes self; clone so the validator can stay borrowed
142        // by the decryptor state machine and be dropped normally when the state transitions.
143        let calculated_mac = self.hmac.clone().finalize();
144        calculated_mac.as_slice().ct_eq(expected_mac).into()
145    }
146}
147
148#[derive(Clone)]
149struct StreamHeader {
150    iv: Iv,
151    mac: Mac,
152}
153
154impl From<&StreamHeader> for StreamHeaderBytes {
155    fn from(header: &StreamHeader) -> Self {
156        let mut out = [0u8; HEADER_LENGTH];
157        out[..AES256_CBC_IV_SIZE].copy_from_slice(&header.iv);
158        out[AES256_CBC_IV_SIZE..].copy_from_slice(&header.mac);
159        out
160    }
161}
162
163impl From<&StreamHeaderBytes> for StreamHeader {
164    fn from(bytes: &StreamHeaderBytes) -> Self {
165        let iv: Iv = bytes[..AES256_CBC_IV_SIZE]
166            .try_into()
167            .expect("slice length checked by type");
168        let mac: Mac = bytes[AES256_CBC_IV_SIZE..]
169            .try_into()
170            .expect("slice length checked by type");
171        StreamHeader { iv, mac }
172    }
173}
174
175enum Pkcs7ValidationResult {
176    // The padding is valid and the containing plaintext is the contained plaintext data in the
177    // last chunk
178    Valid(Vec<u8>),
179    Invalid,
180}
181
182struct CbcPlaintextBlock([u8; AES256_CBC_BLOCK_SIZE]);
183
184impl AsRef<[u8]> for CbcPlaintextBlock {
185    fn as_ref(&self) -> &[u8] {
186        &self.0
187    }
188}
189
190impl CbcPlaintextBlock {
191    /// In PKCS7, the last N bytes of plaintext MUST all have the value N.
192    /// This function validates the padding for the last block and returns the contained plaintext
193    fn is_valid_pkcs7_padding(&self) -> Pkcs7ValidationResult {
194        let data = &self.0;
195        if data.is_empty() {
196            return Pkcs7ValidationResult::Invalid;
197        }
198        let padding_len = *data.last().expect("data is a fixed-size non-empty array") as usize;
199        if padding_len == 0 || padding_len > AES256_CBC_BLOCK_SIZE {
200            return Pkcs7ValidationResult::Invalid;
201        }
202        if data[data.len() - padding_len..]
203            .iter()
204            .all(|&b| b as usize == padding_len)
205        {
206            Pkcs7ValidationResult::Valid(data[..data.len() - padding_len].to_vec())
207        } else {
208            Pkcs7ValidationResult::Invalid
209        }
210    }
211}
212
213enum DecryptorState {
214    Uninitialized {
215        key: Aes256CbcHmacKey,
216    },
217    Streaming {
218        decryptor: Box<CbcDecryptor>,
219        integrity_validator: HmacStreamValidator,
220        expected_mac: Mac,
221    },
222    Done,
223    Error,
224}
225
226enum DecryptorInitializeWithHeaderError {
227    AlreadyInitialized,
228}
229
230impl DecryptorState {
231    /// Initializes the decryptor with the stream header. This
232    /// changes the state from `Uninitialized` to `Streaming`, and must be called before any
233    /// decryption can occur.
234    fn initialize(
235        &mut self,
236        header: StreamHeader,
237    ) -> Result<(), DecryptorInitializeWithHeaderError> {
238        match self {
239            Self::Uninitialized { key } => {
240                *self = Self::Streaming {
241                    decryptor: Box::new(CbcDecryptor::new(&key.enc_key.0, &header.iv)),
242                    integrity_validator: HmacStreamValidator::new(&key.mac_key.0, &header.iv),
243                    expected_mac: header.mac,
244                };
245                Ok(())
246            }
247            _ => Err(DecryptorInitializeWithHeaderError::AlreadyInitialized),
248        }
249    }
250}
251
252/// Reads and removes the first block from the buffer. The size must
253/// be checked by the caller before calling this function, and it may panic otherwise.
254fn read_block_ciphertext(buffer: &mut Vec<u8>) -> CbcCiphertextBlock {
255    buffer
256        .drain(..AES256_CBC_BLOCK_SIZE)
257        .as_slice()
258        .try_into()
259        .expect("slice length checked by external condition")
260}
261
262/// Reads and removes the first block from the buffer. The size must
263/// be checked by the caller before calling this function, and it may panic otherwise.
264fn read_plaintext_block(buffer: &mut Vec<u8>) -> Option<CbcPlaintextBlock> {
265    if buffer.len() < AES256_CBC_BLOCK_SIZE {
266        return None;
267    }
268    Some(CbcPlaintextBlock(
269        buffer
270            .drain(..AES256_CBC_BLOCK_SIZE)
271            .as_slice()
272            .try_into()
273            .expect("slice length checked by condition"),
274    ))
275}
276
277fn read_header(buffer: &mut Vec<u8>) -> Option<StreamHeader> {
278    if buffer.len() >= HEADER_LENGTH {
279        let header_bytes: StreamHeaderBytes = buffer
280            .drain(..HEADER_LENGTH)
281            .as_slice()
282            .try_into()
283            .expect("slice length checked by condition");
284        Some((&header_bytes).into())
285    } else {
286        None
287    }
288}
289
290/// Streaming AES-256-CBC + HMAC-SHA256 decryptor. The HMAC is verified only when
291/// [`StreamingDecryptor::update`] is called with `last_block = true`; bytes returned from
292/// earlier `update` calls as [`ChunkDecryptionResult::DecryptedChunk`] are decrypted but **not
293/// yet authenticated** and must be treated as untrusted until the terminal
294/// [`ChunkDecryptionResult::FinalDecryptedChunk`] is observed.
295pub struct StreamingAes256CbcHmacDecryptor {
296    // Bytes that have been passed in but not yet processed by the crypto implementation.
297    // When passing in external data, they are first concatenated to the buffer, then the
298    // crypto implementation reads bytes
299    buffer: Vec<u8>,
300    decryptor_state: DecryptorState,
301}
302
303impl StreamingAes256CbcHmacDecryptor {
304    pub(crate) fn try_new(key: &SymmetricCryptoKey) -> Result<Self, StreamCreationError> {
305        let key = match key {
306            SymmetricCryptoKey::Aes256CbcHmacKey(key) => key,
307            _ => return Err(StreamCreationError::WrongKeyType),
308        };
309        Ok(Self {
310            buffer: Vec::new(),
311            decryptor_state: DecryptorState::Uninitialized { key: key.clone() },
312        })
313    }
314}
315
316impl StreamingDecryptor for StreamingAes256CbcHmacDecryptor {
317    /// Updates the decryptor with a chunk of ciphertext. If `last_block` is false, the chunk must
318    /// not contain the end of the stream, if it is true, it must contain the end of the stream.
319    fn update(&mut self, ciphertext_chunk: &[u8], last_block: bool) -> ChunkDecryptionResult {
320        self.buffer.extend_from_slice(ciphertext_chunk);
321
322        // If the decryptor is in an error or done state, we should not proceed with decryption and
323        // just return an error.
324        if matches!(
325            self.decryptor_state,
326            DecryptorState::Error | DecryptorState::Done
327        ) {
328            return ChunkDecryptionResult::Error(StreamDecryptionError);
329        }
330
331        // If the decryptor is uninitialized, it must be initialized before proceeding. The
332        // header bytes live at the start of the wire stream, so they are accumulated in
333        // `self.buffer` and drained from there once enough bytes have arrived.
334        if matches!(self.decryptor_state, DecryptorState::Uninitialized { .. }) {
335            if self.buffer.len() < HEADER_LENGTH {
336                if last_block {
337                    self.decryptor_state = DecryptorState::Error;
338                    return ChunkDecryptionResult::Error(StreamDecryptionError);
339                }
340                return ChunkDecryptionResult::NeedMoreData;
341            }
342            let header: StreamHeader =
343                read_header(&mut self.buffer).expect("header length checked by condition above");
344            if self.decryptor_state.initialize(header).is_err() {
345                self.decryptor_state = DecryptorState::Error;
346                return ChunkDecryptionResult::Error(StreamDecryptionError);
347            }
348        }
349
350        // The decryptor can now only be Streaming
351        if let DecryptorState::Streaming {
352            decryptor,
353            integrity_validator,
354            expected_mac,
355        } = &mut self.decryptor_state
356        {
357            // Process as many blocks as possible. On non-last calls, hold back one full block
358            // so PKCS7 stripping can run against it at finalize. (The decryptor must not emit
359            // padding bytes to the caller — but it doesn't know which block is the last until
360            // `last_block = true`.)
361            let blocks_to_process = if last_block {
362                self.buffer.len() / AES256_CBC_BLOCK_SIZE
363            } else {
364                self.buffer.len().saturating_sub(1) / AES256_CBC_BLOCK_SIZE
365            };
366
367            let mut decrypted_data = Vec::new();
368            for _ in 0..blocks_to_process {
369                let ciphertext_block: CbcCiphertextBlock = read_block_ciphertext(&mut self.buffer);
370                integrity_validator.read_block(&ciphertext_block);
371                let plaintext_block = decryptor.decrypt_block(&ciphertext_block);
372                decrypted_data.extend_from_slice(plaintext_block.as_ref());
373            }
374
375            if last_block {
376                // Finalize the HMAC validation. If it fails, we should discard all decrypted data
377                // and return an error.
378                if !integrity_validator.validate_stream_end(expected_mac) {
379                    self.decryptor_state = DecryptorState::Error;
380                    return ChunkDecryptionResult::Error(StreamDecryptionError);
381                }
382
383                // Strip and validate PKCS7 padding from the trailing decrypted block.
384                if decrypted_data.len() < AES256_CBC_BLOCK_SIZE {
385                    self.decryptor_state = DecryptorState::Error;
386                    return ChunkDecryptionResult::Error(StreamDecryptionError);
387                }
388
389                // The end of the stream is guaranteed to be a PKCS7 padding block. This padding
390                // must be validated and stripped.
391                let tail_start = decrypted_data.len() - AES256_CBC_BLOCK_SIZE;
392                let last_block: CbcPlaintextBlock = CbcPlaintextBlock(
393                    decrypted_data
394                        .drain(tail_start..)
395                        .as_slice()
396                        .try_into()
397                        .expect("drained one block"),
398                );
399                match last_block.is_valid_pkcs7_padding() {
400                    Pkcs7ValidationResult::Valid(plaintext) => {
401                        decrypted_data.extend_from_slice(&plaintext);
402                    }
403                    Pkcs7ValidationResult::Invalid => {
404                        self.decryptor_state = DecryptorState::Error;
405                        return ChunkDecryptionResult::Error(StreamDecryptionError);
406                    }
407                }
408
409                self.decryptor_state = DecryptorState::Done;
410                return ChunkDecryptionResult::FinalDecryptedChunk(decrypted_data);
411            } else {
412                return ChunkDecryptionResult::DecryptedChunk(decrypted_data);
413            }
414        }
415
416        // This should be unreachable
417        ChunkDecryptionResult::Error(StreamDecryptionError)
418    }
419}
420
421struct CiphertextBuffer {
422    // Backing store for ciphertext bytes that have been encrypted but not yet emitted. On WASM,
423    // this is a JS Uint8Array to avoid copying between Rust and JS memory; on native, this is a
424    // Vec<u8>.
425    inner: Buffer,
426    // The current size of the buffer. This may be larger than the length of the ciphertext
427    // currently stored in the buffer, since the buffer grows in blocks.
428    size: usize,
429    emitted_bytes: usize,
430}
431
432impl CiphertextBuffer {
433    fn new(capacity: usize) -> Self {
434        Self {
435            inner: Buffer::new(capacity),
436            size: 0,
437            emitted_bytes: 0,
438        }
439    }
440
441    fn append(&mut self, data: &CbcCiphertextBlock) -> Result<(), InvalidIndexError> {
442        self.inner.append(data)?;
443        self.size += data.len();
444        Ok(())
445    }
446
447    fn emit_chunk(&mut self) -> Option<Vec<u8>> {
448        let remaining_bytes = self.size - self.emitted_bytes;
449        if remaining_bytes == 0 {
450            return None;
451        }
452
453        let chunk_size = remaining_bytes.min(EMISSION_CHUNK_SIZE);
454        let chunk = self
455            .inner
456            .index(self.emitted_bytes..self.emitted_bytes + chunk_size)
457            .expect("chunk size is always within bounds of the buffer");
458        self.emitted_bytes += chunk_size;
459        Some(chunk)
460    }
461}
462
463enum EncryptorState {
464    Streaming {
465        encryptor: Box<CbcEncryptor>,
466        integrity_validator: HmacStreamValidator,
467        iv: Iv,
468    },
469    Emitting,
470    Done,
471    Error,
472}
473
474/// Streaming AES-256-CBC + HMAC-SHA256 encryptor. The IV is generated at construction time
475/// and the HMAC is computed over IV || ciphertext, matching the wire format consumed by
476/// [`StreamingAes256CbcHmacDecryptor`]. Because the MAC depends on the entire ciphertext,
477/// the complete wire stream (`IV, ciphertext`) is only emitted once `update` is
478/// called with `last_block = true`, as a single [`ChunkEncryptionResult::FinalEncryptedChunk`].
479pub struct StreamingAes256CbcHmacEncryptor {
480    // Ciphertext bytes that have been encrypted but not yet emitted. The backing store is a
481    // pre-allocated large buffer (released-by-drop on WASM).
482    ciphertext_buffer: CiphertextBuffer,
483    plaintext_buffer: Vec<u8>,
484    encryptor_state: EncryptorState,
485}
486
487impl StreamingAes256CbcHmacEncryptor {
488    /// Creates a new encryptor with a fresh random IV. `plaintext_size` is the total number of
489    /// plaintext bytes that will be fed to the encryptor; it is used to pre-allocate the ciphertext
490    /// buffer to its exact final size so it never has to be resized.
491    pub(crate) fn try_new(
492        key: &SymmetricCryptoKey,
493        plaintext_size: usize,
494    ) -> Result<Self, StreamCreationError> {
495        let key = match key {
496            SymmetricCryptoKey::Aes256CbcHmacKey(key) => key,
497            _ => return Err(StreamCreationError::WrongKeyType),
498        };
499
500        let mut iv: Iv = [0u8; AES256_CBC_IV_SIZE];
501        rand::rng().fill_bytes(&mut iv);
502
503        // The CBC ciphertext is the PKCS7-padded plaintext, which is always rounded up to the next
504        // full block (a full padding block is added when already block-aligned).
505        let ciphertext_capacity =
506            (plaintext_size / AES256_CBC_BLOCK_SIZE + 1) * AES256_CBC_BLOCK_SIZE;
507
508        Ok(Self {
509            ciphertext_buffer: CiphertextBuffer::new(ciphertext_capacity),
510            plaintext_buffer: Vec::new(),
511            encryptor_state: EncryptorState::Streaming {
512                encryptor: Box::new(CbcEncryptor::new(&key.enc_key.0, &iv)),
513                integrity_validator: HmacStreamValidator::new(&key.mac_key.0, &iv),
514                iv,
515            },
516        })
517    }
518}
519
520impl StreamingEncryptor for StreamingAes256CbcHmacEncryptor {
521    fn update(&mut self, plaintext_chunk: &[u8], last_block: bool) -> ChunkEncryptionResult {
522        let (encryptor, stream_validator, iv): (
523            &mut Box<CbcEncryptor>,
524            &mut HmacStreamValidator,
525            &Iv,
526        ) = match &mut self.encryptor_state {
527            EncryptorState::Error | EncryptorState::Done => {
528                return ChunkEncryptionResult::Error(StreamEncryptionError);
529            }
530            EncryptorState::Emitting => {
531                if let Some(chunk) = self.ciphertext_buffer.emit_chunk() {
532                    return ChunkEncryptionResult::EncryptedChunk(chunk);
533                } else {
534                    self.encryptor_state = EncryptorState::Done;
535                    return ChunkEncryptionResult::FinalEncryptedChunk(Vec::new());
536                }
537            }
538            EncryptorState::Streaming {
539                encryptor,
540                integrity_validator,
541                iv,
542            } => (encryptor, integrity_validator, iv),
543        };
544
545        self.plaintext_buffer.extend_from_slice(plaintext_chunk);
546
547        // Encrypt all full blocks currently in the buffer plaintext buffer.
548        while let Some(mut block) = read_plaintext_block(&mut self.plaintext_buffer) {
549            let cipher_block = encryptor.encrypt_block(&mut block);
550            stream_validator.read_block(&cipher_block);
551            if self.ciphertext_buffer.append(&cipher_block).is_err() {
552                self.encryptor_state = EncryptorState::Error;
553                return ChunkEncryptionResult::Error(StreamEncryptionError);
554            }
555        }
556
557        if !last_block {
558            return ChunkEncryptionResult::NeedMoreData;
559        }
560
561        // PKCS7: pad to the next block boundary. If the buffer is already block-aligned (or
562        // empty), this appends a full padding block of value BLOCK_SIZE.
563        let padding_len = AES256_CBC_BLOCK_SIZE - self.plaintext_buffer.len();
564        let mut padding = vec![padding_len as u8; padding_len];
565        self.plaintext_buffer.append(&mut padding);
566
567        let Some(mut block) = read_plaintext_block(&mut self.plaintext_buffer) else {
568            // This should be unreachable, since the padding guarantees exactly one block should be
569            // available.
570            self.encryptor_state = EncryptorState::Error;
571            return ChunkEncryptionResult::Error(StreamEncryptionError);
572        };
573
574        let cipher_block = encryptor.encrypt_block(&mut block);
575        if self.ciphertext_buffer.append(&cipher_block).is_err() {
576            self.encryptor_state = EncryptorState::Error;
577            return ChunkEncryptionResult::Error(StreamEncryptionError);
578        }
579
580        stream_validator.read_block(&cipher_block);
581        let mac = stream_validator.end_stream();
582
583        let header: StreamHeaderBytes = (&StreamHeader { iv: *iv, mac }).into();
584        self.encryptor_state = EncryptorState::Emitting;
585        ChunkEncryptionResult::EncryptedChunk(header.to_vec())
586    }
587}
588
589#[cfg(test)]
590mod tests {
591    use super::*;
592
593    const ENC_KEY: [u8; 32] = [0u8; 32];
594    const MAC_KEY: [u8; 32] = [1u8; 32];
595    const PLAINTEXT: &[u8] = b"This is a test vector text for streaming encryption. It is long enough to require multiple CBC blocks.";
596    const STREAM_TEST_VECTOR: &[u8] = &[
597        202, 35, 233, 113, 188, 138, 45, 92, 122, 28, 38, 85, 31, 242, 192, 113, 213, 46, 222, 105,
598        210, 189, 251, 90, 162, 190, 27, 47, 139, 54, 146, 233, 233, 246, 27, 201, 172, 13, 180,
599        105, 69, 177, 113, 72, 154, 138, 43, 75, 53, 193, 235, 62, 28, 217, 137, 81, 167, 40, 33,
600        95, 241, 1, 154, 92, 69, 252, 151, 218, 106, 46, 189, 208, 154, 123, 192, 207, 253, 155,
601        22, 17, 158, 142, 88, 91, 177, 117, 227, 45, 58, 16, 150, 180, 193, 32, 144, 95, 227, 233,
602        60, 7, 98, 197, 200, 144, 179, 213, 220, 95, 242, 17, 112, 115, 211, 97, 90, 250, 210, 141,
603        200, 157, 156, 71, 133, 165, 246, 161, 110, 127, 232, 225, 121, 190, 235, 121, 228, 4, 31,
604        123, 67, 140, 84, 64, 41, 198, 227, 221, 20, 188, 252, 70, 20, 81, 138, 210, 247, 230, 16,
605        233, 229, 154,
606    ];
607
608    fn test_key() -> SymmetricCryptoKey {
609        SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey {
610            enc_key: Box::pin(ENC_KEY.into()),
611            mac_key: Box::pin(MAC_KEY.into()),
612        })
613    }
614
615    #[test]
616    #[ignore = "used to generate the test vector constant above; not a unit test"]
617    fn generate_test_vectors() {
618        let key = test_key();
619        let out = encrypt_all(&key, PLAINTEXT, 20);
620        println!("const STREAM_TEST_VECTOR: &[u8] = &{:?};", out);
621    }
622
623    fn encrypt_all(key: &SymmetricCryptoKey, plaintext: &[u8], chunk_size: usize) -> Vec<u8> {
624        let mut encryptor = StreamingAes256CbcHmacEncryptor::try_new(key, plaintext.len())
625            .ok()
626            .expect("encryptor construction");
627
628        let mut plaintext_buffer = plaintext.to_vec();
629        let mut ciphertext_buffer = Vec::new();
630
631        loop {
632            let chunk = plaintext_buffer
633                .drain(..chunk_size.min(plaintext_buffer.len()))
634                .collect::<Vec<u8>>();
635            match encryptor.update(&chunk, plaintext_buffer.is_empty()) {
636                ChunkEncryptionResult::NeedMoreData => {}
637                ChunkEncryptionResult::EncryptedChunk(bytes) => {
638                    ciphertext_buffer.extend_from_slice(&bytes);
639                }
640                ChunkEncryptionResult::FinalEncryptedChunk(bytes) => {
641                    ciphertext_buffer.extend_from_slice(&bytes);
642                    break;
643                }
644                ChunkEncryptionResult::Error(_) => panic!("encryption error"),
645            };
646        }
647
648        ciphertext_buffer
649    }
650
651    fn decrypt_all(key: &SymmetricCryptoKey, ciphertext: &[u8], chunk_size: usize) -> Vec<u8> {
652        let mut decryptor = StreamingAes256CbcHmacDecryptor::try_new(key)
653            .ok()
654            .expect("decryptor construction");
655
656        let mut ciphertext_buffer = ciphertext.to_vec();
657        let mut plaintext_buffer = Vec::new();
658
659        loop {
660            let chunk = ciphertext_buffer
661                .drain(..chunk_size.min(ciphertext_buffer.len()))
662                .collect::<Vec<u8>>();
663            match decryptor.update(&chunk, ciphertext_buffer.is_empty()) {
664                ChunkDecryptionResult::NeedMoreData => {}
665                ChunkDecryptionResult::DecryptedChunk(bytes) => {
666                    plaintext_buffer.extend_from_slice(&bytes);
667                }
668                ChunkDecryptionResult::FinalDecryptedChunk(bytes) => {
669                    plaintext_buffer.extend_from_slice(&bytes);
670                    break;
671                }
672                ChunkDecryptionResult::Error(_) => panic!("decryption error"),
673            };
674        }
675
676        plaintext_buffer
677    }
678
679    #[test]
680    fn streaming_encrypt_decrypt_roundtrip() {
681        let key = test_key();
682        let plaintext = PLAINTEXT;
683        let ciphertext = encrypt_all(&key, plaintext, 11);
684        let roundtripped = decrypt_all(&key, &ciphertext, 9);
685
686        assert_eq!(roundtripped, plaintext);
687    }
688
689    #[test]
690    fn streaming_decrypt_with_truncated_ciphertext_fails() {
691        let key = test_key();
692        let ciphertext = STREAM_TEST_VECTOR;
693        let truncated = &ciphertext[..ciphertext.len() - 10];
694        let mut dec = StreamingAes256CbcHmacDecryptor::try_new(&key)
695            .ok()
696            .expect("decryptor construction");
697        let result = dec.update(truncated, true);
698        assert!(matches!(result, ChunkDecryptionResult::Error(_)));
699    }
700
701    #[test]
702    fn streaming_decrypt_with_modified_ciphertext_fails() {
703        let key = test_key();
704        let mut modified = STREAM_TEST_VECTOR.to_vec();
705        // Flip a bit in the middle of the stream
706        modified[50] ^= 0b0000_0001;
707        let mut dec = StreamingAes256CbcHmacDecryptor::try_new(&key)
708            .ok()
709            .expect("decryptor construction");
710        let result = dec.update(&modified, true);
711        assert!(matches!(result, ChunkDecryptionResult::Error(_)));
712    }
713
714    #[test]
715    fn streaming_decrypt_with_modified_header_fails() {
716        let key = test_key();
717        let mut modified = STREAM_TEST_VECTOR.to_vec();
718        // Flip a bit in the header (the IV)
719        modified[5] ^= 0b0000_0001;
720        let mut dec = StreamingAes256CbcHmacDecryptor::try_new(&key)
721            .ok()
722            .expect("decryptor construction");
723        let result = dec.update(&modified, true);
724        assert!(matches!(result, ChunkDecryptionResult::Error(_)));
725    }
726}