Skip to main content

aws_lc_rs/cipher/
streaming.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0 OR ISC
3
4use crate::aws_lc::{
5    EVP_CIPHER_CTX_new, EVP_CIPHER_iv_length, EVP_CIPHER_key_length, EVP_DecryptFinal_ex,
6    EVP_DecryptInit_ex, EVP_DecryptUpdate, EVP_EncryptFinal_ex, EVP_EncryptInit_ex,
7    EVP_EncryptUpdate, EVP_CIPHER, EVP_CIPHER_CTX,
8};
9use crate::cipher::{
10    Algorithm, DecryptionContext, EncryptionContext, OperatingMode, UnboundCipherKey,
11};
12use crate::error::Unspecified;
13use crate::fips::indicator_check;
14use crate::ptr::LcPtr;
15use std::ptr::{null, null_mut};
16
17use super::ConstPointer;
18
19/// A key for streaming encryption operations.
20pub struct StreamingEncryptingKey {
21    algorithm: &'static Algorithm,
22    mode: OperatingMode,
23    cipher_ctx: LcPtr<EVP_CIPHER_CTX>,
24    context: EncryptionContext,
25    output_generated: usize,
26}
27
28unsafe impl Send for StreamingEncryptingKey {}
29
30/// A struct indicating the portion of a buffer written to, and/or not written to, during an
31/// encryption/decryption operation.
32pub struct BufferUpdate<'a> {
33    written: &'a [u8],
34    remainder: &'a mut [u8],
35}
36
37impl<'a> BufferUpdate<'a> {
38    fn new(out_buffer: &'a mut [u8], written_len: usize) -> Self {
39        let (written, remainder) = out_buffer.split_at_mut(written_len);
40        Self { written, remainder }
41    }
42}
43
44impl BufferUpdate<'_> {
45    /// Returns the slice from the buffer that was modified by the operation.
46    #[must_use]
47    pub fn written(&self) -> &[u8] {
48        self.written
49    }
50
51    /// Returns the slice of the buffer that was not modified by the operation.
52    #[must_use]
53    pub fn remainder(&self) -> &[u8] {
54        self.remainder
55    }
56
57    /// Returns a mutable slice of the buffer that was not modified by the operation.
58    #[must_use]
59    pub fn remainder_mut(&mut self) -> &mut [u8] {
60        self.remainder
61    }
62}
63
64fn evp_encrypt_init(
65    cipher_ctx: &mut LcPtr<EVP_CIPHER_CTX>,
66    cipher: &ConstPointer<EVP_CIPHER>,
67    key: &[u8],
68    iv: Option<&[u8]>,
69) -> Result<(), Unspecified> {
70    let iv_ptr: *const u8 = if let Some(iv) = iv {
71        iv.as_ptr()
72    } else {
73        null()
74    };
75
76    // AWS-LC copies the key and iv values into the EVP_CIPHER_CTX, and thus can be dropped after this.
77    if 1 != unsafe {
78        EVP_EncryptInit_ex(
79            cipher_ctx.as_mut_ptr(),
80            cipher.as_const_ptr(),
81            null_mut(),
82            key.as_ptr(),
83            iv_ptr,
84        )
85    } {
86        return Err(Unspecified);
87    }
88
89    Ok(())
90}
91
92fn evp_decrypt_init(
93    cipher_ctx: &mut LcPtr<EVP_CIPHER_CTX>,
94    cipher: &ConstPointer<EVP_CIPHER>,
95    key: &[u8],
96    iv: Option<&[u8]>,
97) -> Result<(), Unspecified> {
98    let iv_ptr: *const u8 = if let Some(iv) = iv {
99        iv.as_ptr()
100    } else {
101        null()
102    };
103
104    // AWS-LC copies the key and iv values into the EVP_CIPHER_CTX, and thus can be dropped after this.
105    if 1 != unsafe {
106        EVP_DecryptInit_ex(
107            cipher_ctx.as_mut_ptr(),
108            cipher.as_const_ptr(),
109            null_mut(),
110            key.as_ptr(),
111            iv_ptr,
112        )
113    } {
114        return Err(Unspecified);
115    }
116
117    Ok(())
118}
119
120impl StreamingEncryptingKey {
121    #[allow(clippy::needless_pass_by_value)]
122    fn new(
123        key: UnboundCipherKey,
124        mode: OperatingMode,
125        context: EncryptionContext,
126    ) -> Result<Self, Unspecified> {
127        let algorithm = key.algorithm();
128        if !algorithm.supports_mode(mode) {
129            return Err(Unspecified);
130        }
131        // EVP initialization reads the algorithm's IV length without receiving the slice length.
132        if !algorithm.is_valid_encryption_context(mode, &context) {
133            return Err(Unspecified);
134        }
135        // The streaming path passes raw key bytes to the EVP API rather than
136        // going through `SymmetricCipherKey` construction.  Validate
137        // algorithm-specific key constraints (e.g. DES weak-key / K1!=K2
138        // checks) that would otherwise be missed.
139        key.validate_key_material()?;
140        let mut cipher_ctx = LcPtr::new(unsafe { EVP_CIPHER_CTX_new() })?;
141        let cipher = mode.evp_cipher(key.algorithm);
142        let key_bytes = key.key_bytes.as_ref();
143        if key_bytes.len()
144            != <usize>::try_from(unsafe { EVP_CIPHER_key_length(cipher.as_const_ptr()) }).unwrap()
145        {
146            return Err(Unspecified);
147        }
148
149        match &context {
150            ctx @ EncryptionContext::Iv128(..) => {
151                let iv = <&[u8]>::try_from(ctx)?;
152                debug_assert_eq!(
153                    iv.len(),
154                    <usize>::try_from(unsafe { EVP_CIPHER_iv_length(cipher.as_const_ptr()) })
155                        .unwrap()
156                );
157                evp_encrypt_init(&mut cipher_ctx, &cipher, key_bytes, Some(iv))?;
158            }
159            #[cfg(feature = "legacy-des")]
160            ctx @ EncryptionContext::Iv64(..) => {
161                let iv = <&[u8]>::try_from(ctx)?;
162                debug_assert_eq!(
163                    iv.len(),
164                    <usize>::try_from(unsafe { EVP_CIPHER_iv_length(cipher.as_const_ptr()) })
165                        .unwrap()
166                );
167                evp_encrypt_init(&mut cipher_ctx, &cipher, key_bytes, Some(iv))?;
168            }
169            EncryptionContext::None => {
170                evp_encrypt_init(&mut cipher_ctx, &cipher, key_bytes, None)?;
171            }
172        }
173
174        Ok(Self {
175            algorithm,
176            mode,
177            cipher_ctx,
178            context,
179            output_generated: 0,
180        })
181    }
182
183    fn update_internal<'a>(
184        &mut self,
185        input: &[u8],
186        output: &'a mut [u8],
187        min_outsize: usize,
188    ) -> Result<BufferUpdate<'a>, Unspecified> {
189        if output.len() < min_outsize {
190            return Err(Unspecified);
191        }
192        let mut outlen: i32 = 0;
193        let inlen: i32 = input.len().try_into()?;
194
195        if 1 != unsafe {
196            EVP_EncryptUpdate(
197                self.cipher_ctx.as_mut_ptr(),
198                output.as_mut_ptr(),
199                &mut outlen,
200                input.as_ptr(),
201                inlen,
202            )
203        } {
204            return Err(Unspecified);
205        }
206        let outlen: usize = outlen.try_into()?;
207        debug_assert!(outlen <= min_outsize);
208        self.output_generated += outlen;
209        assert!(outlen <= output.len());
210
211        Ok(BufferUpdate::new(output, outlen))
212    }
213
214    /// Updates the internal state of the key with the provided plaintext `input`,
215    /// potentially writing bytes of ciphertext to `output`.
216    ///
217    /// The number of bytes written to `output` can be up to `input.len()`
218    /// plus the block length of the algorithm (e.g., [`Algorithm::block_len`]) minus one.
219    ///
220    /// # Errors
221    /// * Returns an error if the `output` buffer is smaller than the length of
222    ///   the `input` plus the algorithm's block length (e.g. [`Algorithm::block_len`]) minus one.
223    /// * May return an error if the length of `input` plus the algorithm's block length is larger than `i32::MAX`.
224    pub fn update<'a>(
225        &mut self,
226        input: &[u8],
227        output: &'a mut [u8],
228    ) -> Result<BufferUpdate<'a>, Unspecified> {
229        let min_outsize = input
230            .len()
231            .checked_add(self.algorithm().block_len())
232            .ok_or(Unspecified)?
233            .checked_sub(1)
234            .ok_or(Unspecified)?;
235        self.update_internal(input, output, min_outsize)
236    }
237
238    /// Updates the internal state of the key with the provided plaintext `input`,
239    /// potentially writing bytes of ciphertext to `output`.
240    ///
241    /// This function has looser output buffer size requirements than [`Self::update`],
242    /// calculating the minimum required size based on the total bytes of output generated
243    /// and the cipher's block length. This is considered "less safe" because it's
244    /// based on assumptions about the state of the underlying operations.
245    ///
246    /// The minimum output buffer size is calculated based on how many bytes are needed to
247    /// reach the next block boundary after processing the input. If `next_total` is the sum
248    /// of bytes already generated plus `input.len()`, then the minimum size is:
249    /// `input.len() + ((block_len - (next_total % block_len)) % block_len)`
250    ///
251    /// # Errors
252    /// Returns an error if the `output` buffer is smaller than the calculated minimum size,
253    /// if the total output length overflows, or if the length of `input` is larger than
254    /// `i32::MAX`.
255    ///
256    /// # Panics
257    /// Panics if the number of bytes written by the cipher operation exceeds the output
258    /// buffer length.
259    pub fn less_safe_update<'a>(
260        &mut self,
261        input: &[u8],
262        output: &'a mut [u8],
263    ) -> Result<BufferUpdate<'a>, Unspecified> {
264        let next_total = self
265            .output_generated
266            .checked_add(input.len())
267            .ok_or(Unspecified)?;
268        let extra_buffer_size = (self.algorithm().block_len
269            - next_total.rem_euclid(self.algorithm().block_len))
270        .rem_euclid(self.algorithm().block_len);
271        let min_outsize = input
272            .len()
273            .checked_add(extra_buffer_size)
274            .ok_or(Unspecified)?;
275        self.update_internal(input, output, min_outsize)
276    }
277
278    /// Finishes the encryption operation, writing any remaining ciphertext to
279    /// `output`.
280    ///
281    /// The number of bytes written to `output` can be up to the block length of
282    /// [`Algorithm::block_len`].
283    ///
284    /// # Errors
285    /// * Returns an error if the `output` buffer is smaller than the algorithm's
286    ///   block length.
287    pub fn finish(
288        mut self,
289        output: &mut [u8],
290    ) -> Result<(DecryptionContext, BufferUpdate<'_>), Unspecified> {
291        if output.len() < self.algorithm().block_len() {
292            return Err(Unspecified);
293        }
294        let mut outlen: i32 = 0;
295
296        if 1 != indicator_check!(unsafe {
297            EVP_EncryptFinal_ex(
298                self.cipher_ctx.as_mut_ptr(),
299                output.as_mut_ptr(),
300                &mut outlen,
301            )
302        }) {
303            return Err(Unspecified);
304        }
305        let outlen: usize = outlen.try_into()?;
306        debug_assert!(outlen <= self.algorithm().block_len());
307        Ok((self.context.into(), BufferUpdate::new(output, outlen)))
308    }
309
310    /// Returns the cipher operating mode.
311    #[must_use]
312    pub fn mode(&self) -> OperatingMode {
313        self.mode
314    }
315
316    /// Returns the cipher algorithm.
317    #[must_use]
318    pub fn algorithm(&self) -> &'static Algorithm {
319        self.algorithm
320    }
321
322    /// Constructs a `StreamingEncryptingKey` for encrypting data using the CTR cipher mode.
323    /// The resulting ciphertext will be the same length as the plaintext.
324    ///
325    /// # Errors
326    /// Returns an error on an internal failure. With `legacy-des` enabled, also
327    /// returned if `key`'s algorithm does not support CTR mode (e.g.
328    /// `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY`,
329    /// `DES_EDE3_FOR_LEGACY_USE_ONLY`).
330    pub fn ctr(key: UnboundCipherKey) -> Result<Self, Unspecified> {
331        let context = key.algorithm().new_encryption_context(OperatingMode::CTR)?;
332        Self::less_safe_ctr(key, context)
333    }
334
335    /// Constructs a `StreamingEncryptingKey` for encrypting data using the CTR cipher mode.
336    /// The resulting ciphertext will be the same length as the plaintext.
337    ///
338    /// This is considered less safe because the caller could potentially construct
339    /// an `EncryptionContext` from a previously used initialization vector (IV).
340    ///
341    /// # Errors
342    /// Returns an error on an internal failure. With `legacy-des` enabled, also
343    /// returned if `key`'s algorithm does not support CTR mode (e.g.
344    /// `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY`,
345    /// `DES_EDE3_FOR_LEGACY_USE_ONLY`).
346    pub fn less_safe_ctr(
347        key: UnboundCipherKey,
348        context: EncryptionContext,
349    ) -> Result<Self, Unspecified> {
350        Self::new(key, OperatingMode::CTR, context)
351    }
352
353    /// Constructs a `StreamingEncryptingKey` for encrypting data using the CBC cipher mode
354    /// with pkcs7 padding.
355    /// The resulting ciphertext will be longer than the plaintext; padding is added
356    /// to fill the next block of ciphertext.
357    ///
358    /// # Errors
359    /// Returns an error on an internal failure. With `legacy-des` enabled, also
360    /// returned if `key` was constructed with `DES_FOR_LEGACY_USE_ONLY`,
361    /// `DES_EDE_FOR_LEGACY_USE_ONLY` or `DES_EDE3_FOR_LEGACY_USE_ONLY` and the
362    /// provided key material contains weak or semi-weak DES subkeys, or (for
363    /// Triple DES) a degenerate subkey configuration (e.g. `K1 == K2` for 2TDEA,
364    /// or any pairwise equality for 3TDEA).
365    pub fn cbc_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
366        let context = key.algorithm().new_encryption_context(OperatingMode::CBC)?;
367        Self::less_safe_cbc_pkcs7(key, context)
368    }
369
370    /// Constructs a `StreamingEncryptingKey` for encrypting data using the CFB128 cipher mode.
371    /// The resulting ciphertext will be the same length as the plaintext.
372    ///
373    /// # Errors
374    /// Returns an error on an internal failure. With `legacy-des` enabled, also
375    /// returned if `key`'s algorithm does not support CFB128 mode (e.g.
376    /// `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY`,
377    /// `DES_EDE3_FOR_LEGACY_USE_ONLY`).
378    pub fn cfb128(key: UnboundCipherKey) -> Result<Self, Unspecified> {
379        let context = key
380            .algorithm()
381            .new_encryption_context(OperatingMode::CFB128)?;
382        Self::less_safe_cfb128(key, context)
383    }
384
385    /// Constructs a `StreamingEncryptingKey` for encrypting using ECB cipher mode with PKCS7 padding.
386    /// The resulting plaintext will be the same length as the ciphertext.
387    ///
388    /// # ☠️ ️️️DANGER ☠️
389    /// Offered for computability purposes only. This is an extremely dangerous mode, and
390    /// very likely not what you want to use.
391    ///
392    /// # Errors
393    /// Returns an error on an internal failure. With `legacy-des` enabled, also
394    /// returned if `key` was constructed with `DES_FOR_LEGACY_USE_ONLY`,
395    /// `DES_EDE_FOR_LEGACY_USE_ONLY` or `DES_EDE3_FOR_LEGACY_USE_ONLY` and the
396    /// provided key material contains weak or semi-weak DES subkeys, or (for
397    /// Triple DES) a degenerate subkey configuration (e.g. `K1 == K2` for 2TDEA,
398    /// or any pairwise equality for 3TDEA).
399    pub fn ecb_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
400        let context = key.algorithm().new_encryption_context(OperatingMode::ECB)?;
401        Self::new(key, OperatingMode::ECB, context)
402    }
403
404    /// Constructs a `StreamingEncryptingKey` for encrypting data using the CFB128 cipher mode.
405    /// The resulting ciphertext will be the same length as the plaintext.
406    ///
407    /// This is considered less safe because the caller could potentially construct
408    /// an `EncryptionContext` from a previously used initialization vector (IV).
409    ///
410    /// # Errors
411    /// Returns an error on an internal failure. With `legacy-des` enabled, also
412    /// returned if `key`'s algorithm does not support CFB128 mode (e.g.
413    /// `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY`,
414    /// `DES_EDE3_FOR_LEGACY_USE_ONLY`).
415    pub fn less_safe_cfb128(
416        key: UnboundCipherKey,
417        context: EncryptionContext,
418    ) -> Result<Self, Unspecified> {
419        Self::new(key, OperatingMode::CFB128, context)
420    }
421
422    /// Constructs a `StreamingEncryptingKey` for encrypting data using the CFB8 cipher mode.
423    /// The resulting ciphertext will be the same length as the plaintext.
424    ///
425    /// # Errors
426    /// Returns an error on an internal failure. With `legacy-des` enabled, also
427    /// returned if `key`'s algorithm does not support CFB8 mode (e.g.
428    /// `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY`,
429    /// `DES_EDE3_FOR_LEGACY_USE_ONLY`).
430    pub fn cfb8(key: UnboundCipherKey) -> Result<Self, Unspecified> {
431        let context = key
432            .algorithm()
433            .new_encryption_context(OperatingMode::CFB8)?;
434        Self::less_safe_cfb8(key, context)
435    }
436
437    /// Constructs a `StreamingEncryptingKey` for encrypting data using the CFB8 cipher mode
438    /// with the provided `context`.
439    /// The resulting ciphertext will be the same length as the plaintext.
440    ///
441    /// This is considered less safe because the caller could potentially construct
442    /// an `EncryptionContext` from a previously used initialization vector (IV).
443    ///
444    /// `context` must be an `EncryptionContext::Iv128`.
445    /// The IV must be unpredictable and must not be reused with the same key.
446    ///
447    /// # Errors
448    /// Returns an error on an internal failure. With `legacy-des` enabled, also
449    /// returned if `key`'s algorithm does not support CFB8 mode (e.g.
450    /// `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY`,
451    /// `DES_EDE3_FOR_LEGACY_USE_ONLY`).
452    pub fn less_safe_cfb8(
453        key: UnboundCipherKey,
454        context: EncryptionContext,
455    ) -> Result<Self, Unspecified> {
456        Self::new(key, OperatingMode::CFB8, context)
457    }
458
459    /// Constructs a `StreamingEncryptingKey` for encrypting data using the CFB1 cipher mode.
460    /// The resulting ciphertext will be the same length as the plaintext.
461    ///
462    /// This API is byte-oriented: lengths are counted in bytes, and each byte
463    /// is processed as eight successive 1-bit segments.
464    ///
465    /// # Errors
466    /// Returns an error on an internal failure. With `legacy-des` enabled, also
467    /// returned if `key`'s algorithm does not support CFB1 mode (e.g.
468    /// `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY`,
469    /// `DES_EDE3_FOR_LEGACY_USE_ONLY`).
470    pub fn cfb1(key: UnboundCipherKey) -> Result<Self, Unspecified> {
471        let context = key
472            .algorithm()
473            .new_encryption_context(OperatingMode::CFB1)?;
474        Self::less_safe_cfb1(key, context)
475    }
476
477    /// Constructs a `StreamingEncryptingKey` for encrypting data using the CFB1 cipher mode
478    /// with the provided `context`.
479    /// The resulting ciphertext will be the same length as the plaintext.
480    ///
481    /// This is considered less safe because the caller could potentially construct
482    /// an `EncryptionContext` from a previously used initialization vector (IV).
483    ///
484    /// This API is byte-oriented: lengths are counted in bytes, and each byte
485    /// is processed as eight successive 1-bit segments.
486    ///
487    /// `context` must be an `EncryptionContext::Iv128`.
488    /// The IV must be unpredictable and must not be reused with the same key.
489    ///
490    /// # Errors
491    /// Returns an error on an internal failure. With `legacy-des` enabled, also
492    /// returned if `key`'s algorithm does not support CFB1 mode (e.g.
493    /// `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY`,
494    /// `DES_EDE3_FOR_LEGACY_USE_ONLY`).
495    pub fn less_safe_cfb1(
496        key: UnboundCipherKey,
497        context: EncryptionContext,
498    ) -> Result<Self, Unspecified> {
499        Self::new(key, OperatingMode::CFB1, context)
500    }
501
502    /// Constructs a `StreamingEncryptingKey` for encrypting data using the OFB cipher mode.
503    /// The resulting ciphertext will be the same length as the plaintext.
504    ///
505    /// # Errors
506    /// Returns an error on an internal failure. With `legacy-des` enabled, also
507    /// returned if `key`'s algorithm does not support OFB mode (e.g.
508    /// `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY`,
509    /// `DES_EDE3_FOR_LEGACY_USE_ONLY`).
510    pub fn ofb(key: UnboundCipherKey) -> Result<Self, Unspecified> {
511        let context = key.algorithm().new_encryption_context(OperatingMode::OFB)?;
512        Self::less_safe_ofb(key, context)
513    }
514
515    /// Constructs a `StreamingEncryptingKey` for encrypting data using the OFB cipher mode
516    /// with the provided `context`.
517    /// The resulting ciphertext will be the same length as the plaintext.
518    ///
519    /// This is considered less safe because the caller could potentially construct
520    /// an `EncryptionContext` from a previously used initialization vector (IV).
521    ///
522    /// `context` must be an `EncryptionContext::Iv128`.
523    /// The IV must not be reused with the same key.
524    ///
525    /// # Errors
526    /// Returns an error on an internal failure. With `legacy-des` enabled, also
527    /// returned if `key`'s algorithm does not support OFB mode (e.g.
528    /// `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY`,
529    /// `DES_EDE3_FOR_LEGACY_USE_ONLY`).
530    pub fn less_safe_ofb(
531        key: UnboundCipherKey,
532        context: EncryptionContext,
533    ) -> Result<Self, Unspecified> {
534        Self::new(key, OperatingMode::OFB, context)
535    }
536
537    /// Constructs a `StreamingEncryptingKey` for encrypting data using the CBC cipher mode
538    /// with pkcs7 padding.
539    /// The resulting ciphertext will be longer than the plaintext; padding is added
540    /// to fill the next block of ciphertext.
541    ///
542    /// This is considered less safe because the caller could potentially construct
543    /// an `EncryptionContext` from a previously used initialization vector (IV).
544    ///
545    /// # Errors
546    /// Returns an error on an internal failure. With `legacy-des` enabled, also
547    /// returned if `key` was constructed with `DES_FOR_LEGACY_USE_ONLY`,
548    /// `DES_EDE_FOR_LEGACY_USE_ONLY` or `DES_EDE3_FOR_LEGACY_USE_ONLY` and the
549    /// provided key material contains weak or semi-weak DES subkeys, or (for
550    /// Triple DES) a degenerate subkey configuration (e.g. `K1 == K2` for 2TDEA,
551    /// or any pairwise equality for 3TDEA).
552    pub fn less_safe_cbc_pkcs7(
553        key: UnboundCipherKey,
554        context: EncryptionContext,
555    ) -> Result<Self, Unspecified> {
556        Self::new(key, OperatingMode::CBC, context)
557    }
558}
559
560/// A key for streaming decryption operations.
561pub struct StreamingDecryptingKey {
562    algorithm: &'static Algorithm,
563    mode: OperatingMode,
564    cipher_ctx: LcPtr<EVP_CIPHER_CTX>,
565    output_generated: usize,
566}
567
568unsafe impl Send for StreamingDecryptingKey {}
569
570impl StreamingDecryptingKey {
571    #[allow(clippy::needless_pass_by_value)]
572    fn new(
573        key: UnboundCipherKey,
574        mode: OperatingMode,
575        context: DecryptionContext,
576    ) -> Result<Self, Unspecified> {
577        let algorithm = key.algorithm();
578        if !algorithm.supports_mode(mode) {
579            return Err(Unspecified);
580        }
581        if !algorithm.is_valid_decryption_context(mode, &context) {
582            return Err(Unspecified);
583        }
584        // See comment in `StreamingEncryptingKey::new`.
585        key.validate_key_material()?;
586        let mut cipher_ctx = LcPtr::new(unsafe { EVP_CIPHER_CTX_new() })?;
587        let cipher = mode.evp_cipher(key.algorithm);
588        let key_bytes = key.key_bytes.as_ref();
589        if key_bytes.len()
590            != <usize>::try_from(unsafe { EVP_CIPHER_key_length(cipher.as_const_ptr()) }).unwrap()
591        {
592            return Err(Unspecified);
593        }
594
595        match &context {
596            ctx @ DecryptionContext::Iv128(..) => {
597                let iv = <&[u8]>::try_from(ctx)?;
598                debug_assert_eq!(
599                    iv.len(),
600                    <usize>::try_from(unsafe { EVP_CIPHER_iv_length(cipher.as_const_ptr()) })
601                        .unwrap()
602                );
603                evp_decrypt_init(&mut cipher_ctx, &cipher, key_bytes, Some(iv))?;
604            }
605            #[cfg(feature = "legacy-des")]
606            ctx @ DecryptionContext::Iv64(..) => {
607                let iv = <&[u8]>::try_from(ctx)?;
608                debug_assert_eq!(
609                    iv.len(),
610                    <usize>::try_from(unsafe { EVP_CIPHER_iv_length(cipher.as_const_ptr()) })
611                        .unwrap()
612                );
613                evp_decrypt_init(&mut cipher_ctx, &cipher, key_bytes, Some(iv))?;
614            }
615            DecryptionContext::None => {
616                evp_decrypt_init(&mut cipher_ctx, &cipher, key_bytes, None)?;
617            }
618        }
619
620        Ok(Self {
621            algorithm,
622            mode,
623            cipher_ctx,
624            output_generated: 0,
625        })
626    }
627
628    fn update_internal<'a>(
629        &mut self,
630        input: &[u8],
631        output: &'a mut [u8],
632        min_outsize: usize,
633    ) -> Result<BufferUpdate<'a>, Unspecified> {
634        if output.len() < min_outsize {
635            return Err(Unspecified);
636        }
637        let mut outlen: i32 = 0;
638        let inlen: i32 = input.len().try_into()?;
639
640        if 1 != unsafe {
641            EVP_DecryptUpdate(
642                self.cipher_ctx.as_mut_ptr(),
643                output.as_mut_ptr(),
644                &mut outlen,
645                input.as_ptr(),
646                inlen,
647            )
648        } {
649            return Err(Unspecified);
650        }
651        let outlen: usize = outlen.try_into()?;
652        debug_assert!(outlen <= min_outsize);
653        self.output_generated += outlen;
654        // Reported length, not bytes written -- so this is not a bounds check on
655        // the write. The canary tests in this module cover that.
656        assert!(outlen <= output.len());
657
658        Ok(BufferUpdate::new(output, outlen))
659    }
660
661    /// Updates the internal state of the key with the provided ciphertext `input`,
662    /// potentially also writing bytes of plaintext to `output`.
663    /// The number of bytes written to `output` can be up to `input.len()`
664    /// plus the block length of the cipher algorithm (e.g., [`Algorithm::block_len`]) minus one.
665    ///
666    /// # Errors
667    /// * Returns an error if the `output` buffer is smaller than the length of
668    ///   the `input` plus the algorithm's block length minus one.
669    /// * May return an error if the length of `input` plus the algorithm's block length is larger
670    ///   than `i32::MAX`.
671    pub fn update<'a>(
672        &mut self,
673        input: &[u8],
674        output: &'a mut [u8],
675    ) -> Result<BufferUpdate<'a>, Unspecified> {
676        let min_outsize = input
677            .len()
678            .checked_add(self.algorithm().block_len())
679            .ok_or(Unspecified)?
680            .checked_sub(1)
681            .ok_or(Unspecified)?;
682        self.update_internal(input, output, min_outsize)
683    }
684
685    /// Updates the internal state of the key with the provided ciphertext `input`,
686    /// potentially writing bytes of plaintext to `output`.
687    ///
688    /// This function has looser output buffer size requirements than [`Self::update`],
689    /// calculating the minimum required size based on the total bytes of output generated
690    /// and the cipher's block length. This is considered "less safe" because it's
691    /// based on assumptions about the state of the underlying operations.
692    ///
693    /// The minimum output buffer size is calculated based on how many bytes are needed to
694    /// reach the next block boundary after processing the input. If `next_total` is the sum
695    /// of bytes already generated plus `input.len()`, then the minimum size is:
696    /// `input.len() + ((block_len - (next_total % block_len)) % block_len)`
697    ///
698    /// # Errors
699    /// Returns an error if the `output` buffer is smaller than the calculated minimum size,
700    /// if the total output length overflows, or if the length of `input` is larger than
701    /// `i32::MAX`.
702    ///
703    /// # Panics
704    /// Panics if the number of bytes written by the cipher operation exceeds the output
705    /// buffer length.
706    pub fn less_safe_update<'a>(
707        &mut self,
708        input: &[u8],
709        output: &'a mut [u8],
710    ) -> Result<BufferUpdate<'a>, Unspecified> {
711        let next_total = self
712            .output_generated
713            .checked_add(input.len())
714            .ok_or(Unspecified)?;
715        let extra_buffer_size = (self.algorithm().block_len
716            - next_total.rem_euclid(self.algorithm().block_len))
717        .rem_euclid(self.algorithm().block_len);
718        let min_outsize = input
719            .len()
720            .checked_add(extra_buffer_size)
721            .ok_or(Unspecified)?;
722        self.update_internal(input, output, min_outsize)
723    }
724
725    /// Finishes the decryption operation, writing the remaining plaintext to
726    /// `output`.
727    /// The number of bytes written to `output` can be up to the block length of
728    /// the cipher algorithm (e.g., [`Algorithm::block_len`]).
729    ///
730    /// # Errors
731    /// * Returns an error if the `output` buffer is smaller than the algorithm's
732    ///   block length.
733    pub fn finish(mut self, output: &mut [u8]) -> Result<BufferUpdate<'_>, Unspecified> {
734        if output.len() < self.algorithm().block_len() {
735            return Err(Unspecified);
736        }
737        let mut outlen: i32 = 0;
738
739        if 1 != indicator_check!(unsafe {
740            EVP_DecryptFinal_ex(
741                self.cipher_ctx.as_mut_ptr(),
742                output.as_mut_ptr(),
743                &mut outlen,
744            )
745        }) {
746            return Err(Unspecified);
747        }
748        let outlen: usize = outlen.try_into()?;
749        debug_assert!(outlen <= self.algorithm().block_len());
750        Ok(BufferUpdate::new(output, outlen))
751    }
752
753    /// Returns the cipher operating mode.
754    #[must_use]
755    pub fn mode(&self) -> OperatingMode {
756        self.mode
757    }
758
759    /// Returns the cipher algorithm
760    #[must_use]
761    pub fn algorithm(&self) -> &'static Algorithm {
762        self.algorithm
763    }
764
765    /// Constructs a `StreamingDecryptingKey` for decrypting using the CTR cipher mode.
766    /// The resulting plaintext will be the same length as the ciphertext.
767    ///
768    /// # Errors
769    /// Returns an error on an internal failure. With `legacy-des` enabled, also
770    /// returned if `key`'s algorithm does not support CTR mode (e.g.
771    /// `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY`,
772    /// `DES_EDE3_FOR_LEGACY_USE_ONLY`).
773    pub fn ctr(key: UnboundCipherKey, context: DecryptionContext) -> Result<Self, Unspecified> {
774        Self::new(key, OperatingMode::CTR, context)
775    }
776
777    /// Constructs a `StreamingDecryptingKey` for decrypting using the CBC cipher mode.
778    /// The resulting plaintext will be shorter than the ciphertext.
779    ///
780    /// # Errors
781    /// Returns an error on an internal failure. With `legacy-des` enabled, also
782    /// returned if `key` was constructed with `DES_FOR_LEGACY_USE_ONLY`,
783    /// `DES_EDE_FOR_LEGACY_USE_ONLY` or `DES_EDE3_FOR_LEGACY_USE_ONLY` and the
784    /// provided key material contains weak or semi-weak DES subkeys, or (for
785    /// Triple DES) a degenerate subkey configuration (e.g. `K1 == K2` for 2TDEA,
786    /// or any pairwise equality for 3TDEA).
787    pub fn cbc_pkcs7(
788        key: UnboundCipherKey,
789        context: DecryptionContext,
790    ) -> Result<Self, Unspecified> {
791        Self::new(key, OperatingMode::CBC, context)
792    }
793
794    // Constructs a `StreamingDecryptingKey` for decrypting using the CFB128 cipher mode.
795    /// The resulting plaintext will be the same length as the ciphertext.
796    ///
797    /// # Errors
798    /// Returns an error on an internal failure. With `legacy-des` enabled, also
799    /// returned if `key`'s algorithm does not support CFB128 mode (e.g.
800    /// `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY`,
801    /// `DES_EDE3_FOR_LEGACY_USE_ONLY`).
802    pub fn cfb128(key: UnboundCipherKey, context: DecryptionContext) -> Result<Self, Unspecified> {
803        Self::new(key, OperatingMode::CFB128, context)
804    }
805
806    /// Constructs a `StreamingDecryptingKey` for decrypting using the CFB8 cipher mode.
807    /// The resulting plaintext will be the same length as the ciphertext.
808    ///
809    /// # Errors
810    /// Returns an error on an internal failure. With `legacy-des` enabled, also
811    /// returned if `key`'s algorithm does not support CFB8 mode (e.g.
812    /// `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY`,
813    /// `DES_EDE3_FOR_LEGACY_USE_ONLY`).
814    pub fn cfb8(key: UnboundCipherKey, context: DecryptionContext) -> Result<Self, Unspecified> {
815        Self::new(key, OperatingMode::CFB8, context)
816    }
817
818    /// Constructs a `StreamingDecryptingKey` for decrypting using the CFB1 cipher mode.
819    /// The resulting plaintext will be the same length as the ciphertext.
820    ///
821    /// This API is byte-oriented: lengths are counted in bytes, and each byte
822    /// is processed as eight successive 1-bit segments.
823    ///
824    /// # Errors
825    /// Returns an error on an internal failure. With `legacy-des` enabled, also
826    /// returned if `key`'s algorithm does not support CFB1 mode (e.g.
827    /// `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY`,
828    /// `DES_EDE3_FOR_LEGACY_USE_ONLY`).
829    pub fn cfb1(key: UnboundCipherKey, context: DecryptionContext) -> Result<Self, Unspecified> {
830        Self::new(key, OperatingMode::CFB1, context)
831    }
832
833    /// Constructs a `StreamingDecryptingKey` for decrypting using the OFB cipher mode.
834    /// The resulting plaintext will be the same length as the ciphertext.
835    ///
836    /// # Errors
837    /// Returns an error on an internal failure. With `legacy-des` enabled, also
838    /// returned if `key`'s algorithm does not support OFB mode (e.g.
839    /// `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY`,
840    /// `DES_EDE3_FOR_LEGACY_USE_ONLY`).
841    pub fn ofb(key: UnboundCipherKey, context: DecryptionContext) -> Result<Self, Unspecified> {
842        Self::new(key, OperatingMode::OFB, context)
843    }
844
845    /// Constructs a `StreamingDecryptingKey` for decrypting using the ECB cipher mode.
846    /// The resulting plaintext will be the same length as the ciphertext.
847    ///
848    /// # ☠️ ️️️DANGER ☠️
849    /// Offered for computability purposes only. This is an extremely dangerous mode, and
850    /// very likely not what you want to use.
851    ///
852    /// # Errors
853    /// Returns an error on an internal failure. With `legacy-des` enabled, also
854    /// returned if `key` was constructed with `DES_FOR_LEGACY_USE_ONLY`,
855    /// `DES_EDE_FOR_LEGACY_USE_ONLY` or `DES_EDE3_FOR_LEGACY_USE_ONLY` and the
856    /// provided key material contains weak or semi-weak DES subkeys, or (for
857    /// Triple DES) a degenerate subkey configuration (e.g. `K1 == K2` for 2TDEA,
858    /// or any pairwise equality for 3TDEA).
859    pub fn ecb_pkcs7(
860        key: UnboundCipherKey,
861        context: DecryptionContext,
862    ) -> Result<Self, Unspecified> {
863        Self::new(key, OperatingMode::ECB, context)
864    }
865}
866
867#[cfg(test)]
868mod tests {
869    #[cfg(feature = "legacy-des")]
870    #[allow(deprecated)]
871    use crate::cipher::DES_FOR_LEGACY_USE_ONLY;
872    use crate::cipher::{
873        DecryptionContext, EncryptionContext, OperatingMode, StreamingDecryptingKey,
874        StreamingEncryptingKey, UnboundCipherKey, AES_128, AES_128_KEY_LEN, AES_256,
875        AES_256_KEY_LEN,
876    };
877    use crate::iv::{FixedLength, IV_LEN_128_BIT};
878    use crate::rand::{SecureRandom, SystemRandom};
879    use crate::test::from_hex;
880    use paste::*;
881
882    // Complementary fills so a stray write matching one canary is caught by
883    // the other. Refilled immediately before each update/finish so a prior
884    // call's (legal) write into a larger slice cannot be mistaken for an
885    // overrun of a later, shorter one.
886    const OUTPUT_CANARIES: [u8; 2] = [0xAA, 0x55];
887
888    fn assert_no_write_past(output: &[u8], slice_start: usize, slice_end: usize, canary: u8) {
889        let slice_len = slice_end - slice_start;
890        let tail = &output[slice_end..output.len().min(slice_end + 16)];
891        assert!(
892            output[slice_end..].iter().all(|&b| b == canary),
893            "wrote past the provided {slice_len}-byte output slice at buffer offsets \
894             {slice_start}..{slice_end} \
895             (canary {canary:#04x}, bytes at {slice_end}..: {tail:02x?})"
896        );
897    }
898
899    fn apply_update_with_canary(
900        output: &mut [u8],
901        out_idx: usize,
902        out_end: usize,
903        canary: u8,
904        op: impl FnOnce(&mut [u8]) -> usize,
905    ) -> usize {
906        output[out_end..].fill(canary);
907        let written = op(&mut output[out_idx..out_end]);
908        assert_no_write_past(output, out_idx, out_end, canary);
909        written
910    }
911
912    /// Generic helper for step encryption that accepts a closure for the update operation.
913    /// The closure receives: (key, input, output_buffer, out_idx, block_len, step)
914    /// and returns the number of bytes written.
915    fn step_encrypt_with_updater<F>(
916        mut encrypting_key: StreamingEncryptingKey,
917        plaintext: &[u8],
918        step: usize,
919        canary: u8,
920        mut updater: F,
921    ) -> (Box<[u8]>, DecryptionContext)
922    where
923        F: FnMut(&mut StreamingEncryptingKey, &[u8], &mut [u8], usize, usize, usize) -> usize,
924    {
925        let alg = encrypting_key.algorithm();
926        let mode = encrypting_key.mode();
927        let block_len = alg.block_len();
928        let n = plaintext.len();
929        // Extra block so there is always canary past the documented-min slice
930        // (`in_len + block_len - 1`) and past `finish`'s block-sized tail.
931        let mut ciphertext = vec![canary; n + 2 * block_len];
932
933        let mut in_idx: usize = 0;
934        let mut out_idx: usize = 0;
935        loop {
936            let mut in_end = in_idx + step;
937            if in_end > n {
938                in_end = n;
939            }
940            let written = updater(
941                &mut encrypting_key,
942                &plaintext[in_idx..in_end],
943                &mut ciphertext,
944                out_idx,
945                block_len,
946                step,
947            );
948            in_idx += step;
949            out_idx += written;
950            if in_idx >= n {
951                break;
952            }
953        }
954        let out_end = out_idx + block_len;
955        ciphertext[out_end..].fill(canary);
956        let (decrypt_iv, written_len) = {
957            let (decrypt_iv, output) = encrypting_key
958                .finish(&mut ciphertext[out_idx..out_end])
959                .unwrap();
960            (decrypt_iv, output.written().len())
961        };
962        assert_no_write_past(&ciphertext, out_idx, out_end, canary);
963        ciphertext.truncate(out_idx + written_len);
964        match mode {
965            OperatingMode::CBC | OperatingMode::ECB => {
966                assert!(ciphertext.len() > plaintext.len());
967                assert!(ciphertext.len() <= plaintext.len() + block_len);
968            }
969            _ => {
970                assert_eq!(ciphertext.len(), plaintext.len());
971            }
972        }
973
974        (ciphertext.into_boxed_slice(), decrypt_iv)
975    }
976
977    /// Generic helper for step decryption that accepts a closure for the update operation.
978    /// The closure receives: (key, input, output_buffer, out_idx, block_len, step)
979    /// and returns the number of bytes written.
980    fn step_decrypt_with_updater<F>(
981        mut decrypting_key: StreamingDecryptingKey,
982        ciphertext: &[u8],
983        step: usize,
984        canary: u8,
985        mut updater: F,
986    ) -> Box<[u8]>
987    where
988        F: FnMut(&mut StreamingDecryptingKey, &[u8], &mut [u8], usize, usize, usize) -> usize,
989    {
990        let alg = decrypting_key.algorithm();
991        let mode = decrypting_key.mode();
992        let block_len = alg.block_len();
993        let n = ciphertext.len();
994        let mut plaintext = vec![canary; n + 2 * block_len];
995
996        let mut in_idx: usize = 0;
997        let mut out_idx: usize = 0;
998        loop {
999            let mut in_end = in_idx + step;
1000            if in_end > n {
1001                in_end = n;
1002            }
1003            let written = updater(
1004                &mut decrypting_key,
1005                &ciphertext[in_idx..in_end],
1006                &mut plaintext,
1007                out_idx,
1008                block_len,
1009                step,
1010            );
1011            in_idx += step;
1012            out_idx += written;
1013            if in_idx >= n {
1014                break;
1015            }
1016        }
1017        let out_end = out_idx + block_len;
1018        plaintext[out_end..].fill(canary);
1019        let written_len = {
1020            let output = decrypting_key
1021                .finish(&mut plaintext[out_idx..out_end])
1022                .unwrap();
1023            output.written().len()
1024        };
1025        assert_no_write_past(&plaintext, out_idx, out_end, canary);
1026        plaintext.truncate(out_idx + written_len);
1027        match mode {
1028            OperatingMode::CBC | OperatingMode::ECB => {
1029                assert!(ciphertext.len() > plaintext.len());
1030                assert!(ciphertext.len() <= plaintext.len() + block_len);
1031            }
1032            _ => {
1033                assert_eq!(ciphertext.len(), plaintext.len());
1034            }
1035        }
1036        plaintext.into_boxed_slice()
1037    }
1038
1039    fn step_encrypt(
1040        encrypting_key: StreamingEncryptingKey,
1041        plaintext: &[u8],
1042        step: usize,
1043        canary: u8,
1044    ) -> (Box<[u8]>, DecryptionContext) {
1045        step_encrypt_with_updater(
1046            encrypting_key,
1047            plaintext,
1048            step,
1049            canary,
1050            |key, input, output, out_idx, block_len, _step| {
1051                let out_end = out_idx + input.len() + block_len - 1;
1052                apply_update_with_canary(output, out_idx, out_end, canary, |out| {
1053                    key.update(input, out).unwrap().written().len()
1054                })
1055            },
1056        )
1057    }
1058
1059    fn step_decrypt(
1060        decrypting_key: StreamingDecryptingKey,
1061        ciphertext: &[u8],
1062        step: usize,
1063        canary: u8,
1064    ) -> Box<[u8]> {
1065        step_decrypt_with_updater(
1066            decrypting_key,
1067            ciphertext,
1068            step,
1069            canary,
1070            |key, input, output, out_idx, block_len, _step| {
1071                let out_end = out_idx + input.len() + block_len - 1;
1072                apply_update_with_canary(output, out_idx, out_end, canary, |out| {
1073                    key.update(input, out).unwrap().written().len()
1074                })
1075            },
1076        )
1077    }
1078
1079    fn less_safe_min_out(block_len: usize, out_idx: usize, input_len: usize) -> usize {
1080        let next_total = out_idx + input_len;
1081        input_len + ((block_len - (next_total % block_len)) % block_len)
1082    }
1083
1084    fn step_encrypt_less_safe(
1085        encrypting_key: StreamingEncryptingKey,
1086        plaintext: &[u8],
1087        step: usize,
1088        canary: u8,
1089    ) -> (Box<[u8]>, DecryptionContext) {
1090        step_encrypt_with_updater(
1091            encrypting_key,
1092            plaintext,
1093            step,
1094            canary,
1095            |key, input, output, out_idx, block_len, step| {
1096                let min_out_len = less_safe_min_out(block_len, out_idx, input.len());
1097                if input.len() % block_len == 0 && step % block_len == 0 {
1098                    // When input is provided one block at a time, no additional space should be needed.
1099                    assert_eq!(input.len(), min_out_len);
1100                }
1101                let out_end = out_idx + min_out_len;
1102                apply_update_with_canary(output, out_idx, out_end, canary, |out| {
1103                    key.less_safe_update(input, out).unwrap().written().len()
1104                })
1105            },
1106        )
1107    }
1108
1109    fn step_decrypt_less_safe(
1110        decrypting_key: StreamingDecryptingKey,
1111        ciphertext: &[u8],
1112        step: usize,
1113        canary: u8,
1114    ) -> Box<[u8]> {
1115        step_decrypt_with_updater(
1116            decrypting_key,
1117            ciphertext,
1118            step,
1119            canary,
1120            |key, input, output, out_idx, block_len, step| {
1121                let min_out_len = less_safe_min_out(block_len, out_idx, input.len());
1122                if input.len() % block_len == 0 && step % block_len == 0 {
1123                    // When input is provided one block at a time, no additional space should be needed.
1124                    assert_eq!(input.len(), min_out_len);
1125                }
1126                let out_end = out_idx + min_out_len;
1127                apply_update_with_canary(output, out_idx, out_end, canary, |out| {
1128                    key.less_safe_update(input, out).unwrap().written().len()
1129                })
1130            },
1131        )
1132    }
1133
1134    macro_rules! helper_stream_step_encrypt_test {
1135        ($mode:ident) => {
1136            paste! {
1137                fn [<helper_test_ $mode _stream_encrypt_step_n_bytes>](
1138                    encrypting_key_creator: impl Fn() -> StreamingEncryptingKey,
1139                    decrypting_key_creator: impl Fn(DecryptionContext) -> StreamingDecryptingKey,
1140                    n: usize,
1141                    step: usize,
1142                    canary: u8,
1143                ) {
1144                    let mut input = vec![0u8; n];
1145                    let random = SystemRandom::new();
1146                    random.fill(&mut input).unwrap();
1147
1148                    let encrypting_key = encrypting_key_creator();
1149
1150                    let (ciphertext, decrypt_iv) =
1151                        step_encrypt(encrypting_key, &input, step, canary);
1152
1153                    let decrypting_key = decrypting_key_creator(decrypt_iv);
1154
1155                    let plaintext = step_decrypt(decrypting_key, &ciphertext, step, canary);
1156
1157                    assert_eq!(input.as_slice(), &*plaintext);
1158                }
1159            }
1160        };
1161        ($mode:ident, less_safe) => {
1162            paste! {
1163                fn [<helper_test_ $mode _stream_encrypt_step_n_bytes_less_safe>](
1164                    encrypting_key_creator: impl Fn() -> StreamingEncryptingKey,
1165                    decrypting_key_creator: impl Fn(DecryptionContext) -> StreamingDecryptingKey,
1166                    n: usize,
1167                    step: usize,
1168                    canary: u8,
1169                ) {
1170                    let mut input = vec![0u8; n];
1171                    let random = SystemRandom::new();
1172                    random.fill(&mut input).unwrap();
1173
1174                    let encrypting_key = encrypting_key_creator();
1175
1176                    let (ciphertext, decrypt_iv) =
1177                        step_encrypt_less_safe(encrypting_key, &input, step, canary);
1178
1179                    let decrypting_key = decrypting_key_creator(decrypt_iv);
1180
1181                    let plaintext =
1182                        step_decrypt_less_safe(decrypting_key, &ciphertext, step, canary);
1183
1184                    assert_eq!(input.as_slice(), &*plaintext);
1185                }
1186            }
1187        };
1188    }
1189
1190    helper_stream_step_encrypt_test!(cbc_pkcs7);
1191    helper_stream_step_encrypt_test!(ctr);
1192    helper_stream_step_encrypt_test!(cfb128);
1193    helper_stream_step_encrypt_test!(ecb_pkcs7);
1194    helper_stream_step_encrypt_test!(cfb1);
1195
1196    helper_stream_step_encrypt_test!(cbc_pkcs7, less_safe);
1197    helper_stream_step_encrypt_test!(ctr, less_safe);
1198    helper_stream_step_encrypt_test!(cfb128, less_safe);
1199    helper_stream_step_encrypt_test!(ecb_pkcs7, less_safe);
1200    helper_stream_step_encrypt_test!(cfb1, less_safe);
1201
1202    fn run_step_matrix(test: impl Fn(usize, usize)) {
1203        for i in 13..=21 {
1204            for j in 124..=131 {
1205                test(j, i);
1206                test(j, j - i);
1207            }
1208        }
1209        for j in 124..=131 {
1210            test(j, j);
1211            test(j, 256);
1212            test(j, 1);
1213        }
1214    }
1215
1216    fn random_aes_key(len: usize) -> Vec<u8> {
1217        let mut key = vec![0u8; len];
1218        SystemRandom::new().fill(&mut key).unwrap();
1219        key
1220    }
1221
1222    macro_rules! step_roundtrip_tests {
1223        ($name:ident, $constructor:ident, $helper:ident) => {
1224            #[test]
1225            fn $name() {
1226                for (alg, key_len) in [(&AES_128, AES_128_KEY_LEN), (&AES_256, AES_256_KEY_LEN)] {
1227                    let key = random_aes_key(key_len);
1228                    let encrypting_key_creator = || {
1229                        let key = UnboundCipherKey::new(alg, &key).unwrap();
1230                        StreamingEncryptingKey::$constructor(key).unwrap()
1231                    };
1232                    let decrypting_key_creator = |decryption_ctx: DecryptionContext| {
1233                        let key = UnboundCipherKey::new(alg, &key).unwrap();
1234                        StreamingDecryptingKey::$constructor(key, decryption_ctx).unwrap()
1235                    };
1236                    // Alternate complementary canaries across the (n, step)
1237                    // grid so both fills are exercised without doubling the
1238                    // already-expensive AES-128 x AES-256 matrix.
1239                    run_step_matrix(|n, step| {
1240                        let canary = OUTPUT_CANARIES[(n + step) % OUTPUT_CANARIES.len()];
1241                        $helper(
1242                            encrypting_key_creator,
1243                            decrypting_key_creator,
1244                            n,
1245                            step,
1246                            canary,
1247                        );
1248                    });
1249                }
1250            }
1251        };
1252    }
1253
1254    step_roundtrip_tests!(
1255        test_step_cbc,
1256        cbc_pkcs7,
1257        helper_test_cbc_pkcs7_stream_encrypt_step_n_bytes
1258    );
1259    step_roundtrip_tests!(
1260        test_step_ctr,
1261        ctr,
1262        helper_test_ctr_stream_encrypt_step_n_bytes
1263    );
1264    step_roundtrip_tests!(
1265        test_step_cfb128,
1266        cfb128,
1267        helper_test_cfb128_stream_encrypt_step_n_bytes
1268    );
1269    step_roundtrip_tests!(
1270        test_step_ecb_pkcs7,
1271        ecb_pkcs7,
1272        helper_test_ecb_pkcs7_stream_encrypt_step_n_bytes
1273    );
1274    step_roundtrip_tests!(
1275        test_step_cfb1,
1276        cfb1,
1277        helper_test_cfb1_stream_encrypt_step_n_bytes
1278    );
1279    step_roundtrip_tests!(
1280        test_step_cbc_less_safe,
1281        cbc_pkcs7,
1282        helper_test_cbc_pkcs7_stream_encrypt_step_n_bytes_less_safe
1283    );
1284    step_roundtrip_tests!(
1285        test_step_ctr_less_safe,
1286        ctr,
1287        helper_test_ctr_stream_encrypt_step_n_bytes_less_safe
1288    );
1289    step_roundtrip_tests!(
1290        test_step_cfb128_less_safe,
1291        cfb128,
1292        helper_test_cfb128_stream_encrypt_step_n_bytes_less_safe
1293    );
1294    step_roundtrip_tests!(
1295        test_step_ecb_pkcs7_less_safe,
1296        ecb_pkcs7,
1297        helper_test_ecb_pkcs7_stream_encrypt_step_n_bytes_less_safe
1298    );
1299    step_roundtrip_tests!(
1300        test_step_cfb1_less_safe,
1301        cfb1,
1302        helper_test_cfb1_stream_encrypt_step_n_bytes_less_safe
1303    );
1304
1305    #[derive(Clone, Copy)]
1306    enum UpdateVariant {
1307        /// [`StreamingDecryptingKey::update`]: `in_len + block_len - 1`.
1308        Documented,
1309        /// [`StreamingDecryptingKey::less_safe_update`]: `in_len` when aligned.
1310        LessSafe,
1311    }
1312
1313    /// Feeds block-aligned chunks through `key`, handing each call exactly the
1314    /// minimum output slice its API documents, with canary bytes just past it.
1315    /// `update`-only: the overrun precedes any padding check, so the ciphertext
1316    /// is arbitrary and `finish` is never called.
1317    fn assert_block_aligned_updates_stay_in_slice(
1318        mut key: StreamingDecryptingKey,
1319        variant: UpdateVariant,
1320        chunks: usize,
1321        canary: u8,
1322    ) {
1323        let block_len = key.algorithm().block_len();
1324        let ciphertext = vec![0x42u8; chunks * block_len];
1325        // Widest documented minimum is `2 * block_len - 1`, leaving every slice
1326        // >= `block_len + 1` canary bytes -- enough for a full-block overrun.
1327        let mut output = vec![canary; ciphertext.len() + 3 * block_len];
1328        let mut out_idx = 0usize;
1329
1330        for chunk in ciphertext.chunks(block_len) {
1331            let min_outsize = match variant {
1332                UpdateVariant::Documented => chunk.len() + block_len - 1,
1333                UpdateVariant::LessSafe => less_safe_min_out(block_len, out_idx, chunk.len()),
1334            };
1335            let out_end = out_idx + min_outsize;
1336            out_idx += apply_update_with_canary(&mut output, out_idx, out_end, canary, |out| {
1337                match variant {
1338                    UpdateVariant::Documented => key.update(chunk, out),
1339                    UpdateVariant::LessSafe => key.less_safe_update(chunk, out),
1340                }
1341                .expect("update rejected a documented-minimum output slice")
1342                .written()
1343                .len()
1344            });
1345        }
1346    }
1347
1348    /// The overrun needs a second call: the first leaves the cipher aligned (so
1349    /// AWS-LC buffers a block), the next replays it. Four gives three chances.
1350    const REGRESSION_CHUNKS: usize = 4;
1351
1352    macro_rules! decrypt_output_bounds_tests {
1353        ($name:ident, $alg:expr, $key_len:expr, $constructor:ident, $context:expr) => {
1354            paste! {
1355                #[test]
1356                fn [<test_ $name _update_stays_in_output_slice>]() {
1357                    for canary in OUTPUT_CANARIES {
1358                        let unbound =
1359                            UnboundCipherKey::new($alg, &random_aes_key($key_len)).unwrap();
1360                        let key =
1361                            StreamingDecryptingKey::$constructor(unbound, $context).unwrap();
1362                        assert_block_aligned_updates_stay_in_slice(
1363                            key,
1364                            UpdateVariant::Documented,
1365                            REGRESSION_CHUNKS,
1366                            canary,
1367                        );
1368                    }
1369                }
1370
1371                #[test]
1372                fn [<test_ $name _less_safe_update_stays_in_output_slice>]() {
1373                    for canary in OUTPUT_CANARIES {
1374                        let unbound =
1375                            UnboundCipherKey::new($alg, &random_aes_key($key_len)).unwrap();
1376                        let key =
1377                            StreamingDecryptingKey::$constructor(unbound, $context).unwrap();
1378                        assert_block_aligned_updates_stay_in_slice(
1379                            key,
1380                            UpdateVariant::LessSafe,
1381                            REGRESSION_CHUNKS,
1382                            canary,
1383                        );
1384                    }
1385                }
1386            }
1387        };
1388    }
1389
1390    decrypt_output_bounds_tests!(
1391        aes_128_cbc_pkcs7,
1392        &AES_128,
1393        AES_128_KEY_LEN,
1394        cbc_pkcs7,
1395        DecryptionContext::Iv128(FixedLength::from([0u8; IV_LEN_128_BIT]))
1396    );
1397    decrypt_output_bounds_tests!(
1398        aes_256_cbc_pkcs7,
1399        &AES_256,
1400        AES_256_KEY_LEN,
1401        cbc_pkcs7,
1402        DecryptionContext::Iv128(FixedLength::from([0u8; IV_LEN_128_BIT]))
1403    );
1404    decrypt_output_bounds_tests!(
1405        aes_128_ecb_pkcs7,
1406        &AES_128,
1407        AES_128_KEY_LEN,
1408        ecb_pkcs7,
1409        DecryptionContext::None
1410    );
1411    decrypt_output_bounds_tests!(
1412        aes_256_ecb_pkcs7,
1413        &AES_256,
1414        AES_256_KEY_LEN,
1415        ecb_pkcs7,
1416        DecryptionContext::None
1417    );
1418
1419    macro_rules! streaming_cipher_kat {
1420        ($name:ident, $alg:expr, $mode:expr, $key:literal, $iv: literal, $plaintext:literal, $ciphertext:literal, $from_step:literal, $to_step:literal) => {
1421            #[test]
1422            fn $name() {
1423                let key = from_hex($key).unwrap();
1424                let input = from_hex($plaintext).unwrap();
1425                let expected_ciphertext = from_hex($ciphertext).unwrap();
1426                let iv = from_hex($iv).unwrap();
1427
1428                for step in ($from_step..=$to_step) {
1429                    let ec = EncryptionContext::Iv128(
1430                        FixedLength::<IV_LEN_128_BIT>::try_from(iv.as_slice()).unwrap(),
1431                    );
1432
1433                    let unbound_key = UnboundCipherKey::new($alg, &key).unwrap();
1434
1435                    let encrypting_key =
1436                        StreamingEncryptingKey::new(unbound_key, $mode, ec).unwrap();
1437
1438                    let (ciphertext, decrypt_ctx) =
1439                        step_encrypt(encrypting_key, &input, step, OUTPUT_CANARIES[0]);
1440
1441                    assert_eq!(expected_ciphertext.as_slice(), ciphertext.as_ref());
1442
1443                    let unbound_key2 = UnboundCipherKey::new($alg, &key).unwrap();
1444                    let decrypting_key =
1445                        StreamingDecryptingKey::new(unbound_key2, $mode, decrypt_ctx).unwrap();
1446
1447                    let plaintext =
1448                        step_decrypt(decrypting_key, &ciphertext, step, OUTPUT_CANARIES[1]);
1449                    assert_eq!(input.as_slice(), plaintext.as_ref());
1450                }
1451            }
1452        };
1453        ($name:ident, $alg:expr, $mode:expr, $key:literal, $plaintext:literal, $ciphertext:literal, $from_step:literal, $to_step:literal) => {
1454            #[test]
1455            fn $name() {
1456                let key = from_hex($key).unwrap();
1457                let input = from_hex($plaintext).unwrap();
1458                let expected_ciphertext = from_hex($ciphertext).unwrap();
1459
1460                for step in ($from_step..=$to_step) {
1461                    let unbound_key = UnboundCipherKey::new($alg, &key).unwrap();
1462
1463                    let encrypting_key =
1464                        StreamingEncryptingKey::new(unbound_key, $mode, EncryptionContext::None)
1465                            .unwrap();
1466
1467                    let (ciphertext, decrypt_ctx) =
1468                        step_encrypt(encrypting_key, &input, step, OUTPUT_CANARIES[0]);
1469
1470                    assert_eq!(expected_ciphertext.as_slice(), ciphertext.as_ref());
1471
1472                    let unbound_key2 = UnboundCipherKey::new($alg, &key).unwrap();
1473                    let decrypting_key =
1474                        StreamingDecryptingKey::new(unbound_key2, $mode, decrypt_ctx).unwrap();
1475
1476                    let plaintext =
1477                        step_decrypt(decrypting_key, &ciphertext, step, OUTPUT_CANARIES[1]);
1478                    assert_eq!(input.as_slice(), plaintext.as_ref());
1479                }
1480            }
1481        };
1482    }
1483
1484    streaming_cipher_kat!(
1485        test_iv_aes_128_ctr_16_bytes,
1486        &AES_128,
1487        OperatingMode::CTR,
1488        "000102030405060708090a0b0c0d0e0f",
1489        "00000000000000000000000000000000",
1490        "00112233445566778899aabbccddeeff",
1491        "c6b01904c3da3df5e7d62bd96d153686",
1492        2,
1493        9
1494    );
1495    streaming_cipher_kat!(
1496        test_iv_aes_256_ctr_15_bytes,
1497        &AES_256,
1498        OperatingMode::CTR,
1499        "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
1500        "00000000000000000000000000000000",
1501        "00112233445566778899aabbccddee",
1502        "f28122856e1cf9a7216a30d111f399",
1503        2,
1504        9
1505    );
1506
1507    streaming_cipher_kat!(
1508        test_openssl_aes_128_ctr_15_bytes,
1509        &AES_128,
1510        OperatingMode::CTR,
1511        "244828580821c1652582c76e34d299f5",
1512        "093145d5af233f46072a5eb5adc11aa1",
1513        "3ee38cec171e6cf466bf0df98aa0e1",
1514        "bd7d928f60e3422d96b3f8cd614eb2",
1515        2,
1516        9
1517    );
1518
1519    streaming_cipher_kat!(
1520        test_openssl_aes_256_ctr_15_bytes,
1521        &AES_256,
1522        OperatingMode::CTR,
1523        "0857db8240ea459bdf660b4cced66d1f2d3734ff2de7b81e92740e65e7cc6a1d",
1524        "f028ecb053f801102d11fccc9d303a27",
1525        "eca7285d19f3c20e295378460e8729",
1526        "b5098e5e788de6ac2f2098eb2fc6f8",
1527        2,
1528        9
1529    );
1530
1531    streaming_cipher_kat!(
1532        test_iv_aes_128_cbc_16_bytes,
1533        &AES_128,
1534        OperatingMode::CBC,
1535        "000102030405060708090a0b0c0d0e0f",
1536        "00000000000000000000000000000000",
1537        "00112233445566778899aabbccddeeff",
1538        "69c4e0d86a7b0430d8cdb78070b4c55a9e978e6d16b086570ef794ef97984232",
1539        2,
1540        9
1541    );
1542
1543    streaming_cipher_kat!(
1544        test_iv_aes_256_cbc_15_bytes,
1545        &AES_256,
1546        OperatingMode::CBC,
1547        "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
1548        "00000000000000000000000000000000",
1549        "00112233445566778899aabbccddee",
1550        "2ddfb635a651a43f582997966840ca0c",
1551        2,
1552        9
1553    );
1554
1555    streaming_cipher_kat!(
1556        test_openssl_aes_128_cbc_15_bytes,
1557        &AES_128,
1558        OperatingMode::CBC,
1559        "053304bb3899e1d99db9d29343ea782d",
1560        "b5313560244a4822c46c2a0c9d0cf7fd",
1561        "a3e4c990356c01f320043c3d8d6f43",
1562        "ad96993f248bd6a29760ec7ccda95ee1",
1563        2,
1564        9
1565    );
1566
1567    streaming_cipher_kat!(
1568        test_openssl_aes_128_cbc_16_bytes,
1569        &AES_128,
1570        OperatingMode::CBC,
1571        "95af71f1c63e4a1d0b0b1a27fb978283",
1572        "89e40797dca70197ff87d3dbb0ef2802",
1573        "aece7b5e3c3df1ffc9802d2dfe296dc7",
1574        "301b5dab49fb11e919d0d39970d06739301919743304f23f3cbc67d28564b25b",
1575        2,
1576        9
1577    );
1578
1579    streaming_cipher_kat!(
1580        test_openssl_aes_256_cbc_15_bytes,
1581        &AES_256,
1582        OperatingMode::CBC,
1583        "d369e03e9752784917cc7bac1db7399598d9555e691861d9dd7b3292a693ef57",
1584        "1399bb66b2f6ad99a7f064140eaaa885",
1585        "7385f5784b85bf0a97768ddd896d6d",
1586        "4351082bac9b4593ae8848cc9dfb5a01",
1587        2,
1588        9
1589    );
1590
1591    streaming_cipher_kat!(
1592        test_openssl_aes_256_cbc_16_bytes,
1593        &AES_256,
1594        OperatingMode::CBC,
1595        "d4a8206dcae01242f9db79a4ecfe277d0f7bb8ccbafd8f9809adb39f35aa9b41",
1596        "24f6076548fb9d93c8f7ed9f6e661ef9",
1597        "a39c1fdf77ea3e1f18178c0ec237c70a",
1598        "f1af484830a149ee0387b854d65fe87ca0e62efc1c8e6909d4b9ab8666470453",
1599        2,
1600        9
1601    );
1602
1603    streaming_cipher_kat!(
1604        test_openssl_aes_128_cfb128_16_bytes,
1605        &AES_128,
1606        OperatingMode::CFB128,
1607        "5c353f739429bbd48b7e3f9a76facf4d",
1608        "7b2c7ce17a9b6a59a9e64253b98c8cd1",
1609        "add1bcebeaabe9423d4e916400e877c5",
1610        "8440ec442e4135a613ddb2ce26107e10",
1611        2,
1612        9
1613    );
1614
1615    streaming_cipher_kat!(
1616        test_openssl_aes_128_cfb128_15_bytes,
1617        &AES_128,
1618        OperatingMode::CFB128,
1619        "e1f39d70ad378efc1ac318aa8ac4489f",
1620        "ec78c3d54fff2fe09678c7883024ddce",
1621        "b8c905004b2a92a323769f1b8dc1b2",
1622        "964c3e9bf8bf2a3cca02d8e2e75608",
1623        2,
1624        9
1625    );
1626
1627    streaming_cipher_kat!(
1628        test_openssl_aes_256_cfb128_16_bytes,
1629        &AES_256,
1630        OperatingMode::CFB128,
1631        "0e8117d0984d6acb957a5d6ca526a12fa612ce5de2daadebd42c14d28a0a192e",
1632        "09147a153b230a40cd7bf4197ad0e825",
1633        "13f4540a4e06394148ade31a6f678787",
1634        "250e590e47b7613b7d0a53f684e970d6",
1635        2,
1636        9
1637    );
1638
1639    streaming_cipher_kat!(
1640        test_openssl_aes_256_cfb128_15_bytes,
1641        &AES_256,
1642        OperatingMode::CFB128,
1643        "5cb17d8d5b9dbd81e4f1e0a2c82ebf36cf61156388fb7abf99d4526622858225",
1644        "13c77415ec24f3e2f784f228478a85be",
1645        "3efa583df4405aab61e18155aa7e0d",
1646        "c1f2ffe8aa5064199e8f4f1b388303",
1647        2,
1648        9
1649    );
1650
1651    streaming_cipher_kat!(
1652        test_openssl_aes_128_ecb_pkcs7_16_bytes,
1653        &AES_128,
1654        OperatingMode::ECB,
1655        "a1b7cd124f9824a1532d8440f8136788",
1656        "388118e6848b0cea97401707a754d7a1",
1657        "19b7c7f5d9c2bda3f957e9e7d20847828d5eb5624bcbf221014063a87b38d133",
1658        2,
1659        9
1660    );
1661
1662    streaming_cipher_kat!(
1663        test_openssl_aes_128_ecb_pkcs7_15_bytes,
1664        &AES_128,
1665        OperatingMode::ECB,
1666        "d10e12accb837aaffbb284448e53138c",
1667        "b21cfd1c9e6e7e6e912c82c7dd1aa8",
1668        "3d1168e61df34b51c6ab6745c20ee881",
1669        2,
1670        9
1671    );
1672
1673    streaming_cipher_kat!(
1674        test_openssl_aes_256_ecb_pkcs7_16_bytes,
1675        &AES_256,
1676        OperatingMode::ECB,
1677        "0600f4ad4eda4bc8e3e99592abdfce7eb08fee0ccc801c5ccee26134bcaafbbd",
1678        "516b45cb1342239a549bd8c1d5998f98",
1679        "854c593555a213e4a862c6f66aa4a79631faca131eba6f163e5cd3940e9c0a57",
1680        2,
1681        9
1682    );
1683
1684    streaming_cipher_kat!(
1685        test_openssl_aes_256_ecb_pkcs7_15_bytes,
1686        &AES_256,
1687        OperatingMode::ECB,
1688        "80f235756c8f70094ae1f99a95a599c27c4452a4b8412fd934e2b253f7098508",
1689        "2235590b90190d7a1dc2464a0205ad",
1690        "8547d8ac8dc6d9cebb2dc77a7034bb67",
1691        2,
1692        9
1693    );
1694
1695    #[test]
1696    fn test_new_rejects_none_context_for_iv_required_modes() {
1697        let key_bytes = [0u8; AES_128_KEY_LEN];
1698        for mode in [
1699            OperatingMode::CBC,
1700            OperatingMode::CTR,
1701            OperatingMode::CFB128,
1702            OperatingMode::CFB8,
1703            OperatingMode::CFB1,
1704            OperatingMode::OFB,
1705        ] {
1706            let key = UnboundCipherKey::new(&AES_128, &key_bytes).unwrap();
1707            assert!(
1708                StreamingEncryptingKey::new(key, mode, EncryptionContext::None).is_err(),
1709                "AES + {mode:?} + EncryptionContext::None should be rejected"
1710            );
1711
1712            let key = UnboundCipherKey::new(&AES_128, &key_bytes).unwrap();
1713            assert!(
1714                StreamingDecryptingKey::new(key, mode, DecryptionContext::None).is_err(),
1715                "AES + {mode:?} + DecryptionContext::None should be rejected"
1716            );
1717        }
1718    }
1719
1720    #[test]
1721    fn test_new_accepts_none_context_for_ecb() {
1722        let key_bytes = [0u8; AES_128_KEY_LEN];
1723
1724        let key = UnboundCipherKey::new(&AES_128, &key_bytes).unwrap();
1725        assert!(
1726            StreamingEncryptingKey::new(key, OperatingMode::ECB, EncryptionContext::None).is_ok()
1727        );
1728
1729        let key = UnboundCipherKey::new(&AES_128, &key_bytes).unwrap();
1730        assert!(
1731            StreamingDecryptingKey::new(key, OperatingMode::ECB, DecryptionContext::None).is_ok()
1732        );
1733    }
1734
1735    #[cfg(feature = "legacy-des")]
1736    #[test]
1737    fn test_new_rejects_iv64_context_for_aes() {
1738        let key_bytes = [0u8; AES_128_KEY_LEN];
1739        for mode in [
1740            OperatingMode::CBC,
1741            OperatingMode::CTR,
1742            OperatingMode::CFB128,
1743            OperatingMode::CFB8,
1744            OperatingMode::CFB1,
1745            OperatingMode::OFB,
1746        ] {
1747            let key = UnboundCipherKey::new(&AES_128, &key_bytes).unwrap();
1748            let context = EncryptionContext::Iv64(FixedLength::new().unwrap());
1749            assert!(
1750                StreamingEncryptingKey::new(key, mode, context).is_err(),
1751                "AES + {mode:?} + EncryptionContext::Iv64 should be rejected"
1752            );
1753
1754            let key = UnboundCipherKey::new(&AES_128, &key_bytes).unwrap();
1755            let context = DecryptionContext::Iv64(FixedLength::new().unwrap());
1756            assert!(
1757                StreamingDecryptingKey::new(key, mode, context).is_err(),
1758                "AES + {mode:?} + DecryptionContext::Iv64 should be rejected"
1759            );
1760        }
1761    }
1762
1763    #[cfg(feature = "legacy-des")]
1764    #[test]
1765    #[allow(deprecated)]
1766    fn test_new_rejects_iv128_context_for_des() {
1767        let key_bytes = from_hex("0123456789abcdef").unwrap();
1768
1769        let key = UnboundCipherKey::new(&DES_FOR_LEGACY_USE_ONLY, &key_bytes).unwrap();
1770        let context = EncryptionContext::Iv128(FixedLength::new().unwrap());
1771        assert!(
1772            StreamingEncryptingKey::new(key, OperatingMode::CBC, context).is_err(),
1773            "DES + CBC + EncryptionContext::Iv128 should be rejected"
1774        );
1775
1776        let key = UnboundCipherKey::new(&DES_FOR_LEGACY_USE_ONLY, &key_bytes).unwrap();
1777        let context = DecryptionContext::Iv128(FixedLength::new().unwrap());
1778        assert!(
1779            StreamingDecryptingKey::new(key, OperatingMode::CBC, context).is_err(),
1780            "DES + CBC + DecryptionContext::Iv128 should be rejected"
1781        );
1782    }
1783
1784    #[cfg(feature = "legacy-des")]
1785    #[test]
1786    #[allow(deprecated)]
1787    fn test_new_accepts_valid_des_contexts() {
1788        let key_bytes = from_hex("0123456789abcdef").unwrap();
1789
1790        let key = UnboundCipherKey::new(&DES_FOR_LEGACY_USE_ONLY, &key_bytes).unwrap();
1791        assert!(
1792            StreamingEncryptingKey::new(key, OperatingMode::ECB, EncryptionContext::None).is_ok()
1793        );
1794        let key = UnboundCipherKey::new(&DES_FOR_LEGACY_USE_ONLY, &key_bytes).unwrap();
1795        assert!(
1796            StreamingDecryptingKey::new(key, OperatingMode::ECB, DecryptionContext::None).is_ok()
1797        );
1798
1799        let key = UnboundCipherKey::new(&DES_FOR_LEGACY_USE_ONLY, &key_bytes).unwrap();
1800        let context = EncryptionContext::Iv64(FixedLength::new().unwrap());
1801        assert!(StreamingEncryptingKey::new(key, OperatingMode::CBC, context).is_ok());
1802
1803        let key = UnboundCipherKey::new(&DES_FOR_LEGACY_USE_ONLY, &key_bytes).unwrap();
1804        let context = DecryptionContext::Iv64(FixedLength::new().unwrap());
1805        assert!(StreamingDecryptingKey::new(key, OperatingMode::CBC, context).is_ok());
1806    }
1807}