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 CBC cipher mode
423    /// with pkcs7 padding.
424    /// The resulting ciphertext will be longer than the plaintext; padding is added
425    /// to fill the next block of ciphertext.
426    ///
427    /// This is considered less safe because the caller could potentially construct
428    /// an `EncryptionContext` from a previously used initialization vector (IV).
429    ///
430    /// # Errors
431    /// Returns an error on an internal failure. With `legacy-des` enabled, also
432    /// returned if `key` was constructed with `DES_FOR_LEGACY_USE_ONLY`,
433    /// `DES_EDE_FOR_LEGACY_USE_ONLY` or `DES_EDE3_FOR_LEGACY_USE_ONLY` and the
434    /// provided key material contains weak or semi-weak DES subkeys, or (for
435    /// Triple DES) a degenerate subkey configuration (e.g. `K1 == K2` for 2TDEA,
436    /// or any pairwise equality for 3TDEA).
437    pub fn less_safe_cbc_pkcs7(
438        key: UnboundCipherKey,
439        context: EncryptionContext,
440    ) -> Result<Self, Unspecified> {
441        Self::new(key, OperatingMode::CBC, context)
442    }
443}
444
445/// A key for streaming decryption operations.
446pub struct StreamingDecryptingKey {
447    algorithm: &'static Algorithm,
448    mode: OperatingMode,
449    cipher_ctx: LcPtr<EVP_CIPHER_CTX>,
450    output_generated: usize,
451}
452
453unsafe impl Send for StreamingDecryptingKey {}
454
455impl StreamingDecryptingKey {
456    #[allow(clippy::needless_pass_by_value)]
457    fn new(
458        key: UnboundCipherKey,
459        mode: OperatingMode,
460        context: DecryptionContext,
461    ) -> Result<Self, Unspecified> {
462        let algorithm = key.algorithm();
463        if !algorithm.supports_mode(mode) {
464            return Err(Unspecified);
465        }
466        if !algorithm.is_valid_decryption_context(mode, &context) {
467            return Err(Unspecified);
468        }
469        // See comment in `StreamingEncryptingKey::new`.
470        key.validate_key_material()?;
471        let mut cipher_ctx = LcPtr::new(unsafe { EVP_CIPHER_CTX_new() })?;
472        let cipher = mode.evp_cipher(key.algorithm);
473        let key_bytes = key.key_bytes.as_ref();
474        if key_bytes.len()
475            != <usize>::try_from(unsafe { EVP_CIPHER_key_length(cipher.as_const_ptr()) }).unwrap()
476        {
477            return Err(Unspecified);
478        }
479
480        match &context {
481            ctx @ DecryptionContext::Iv128(..) => {
482                let iv = <&[u8]>::try_from(ctx)?;
483                debug_assert_eq!(
484                    iv.len(),
485                    <usize>::try_from(unsafe { EVP_CIPHER_iv_length(cipher.as_const_ptr()) })
486                        .unwrap()
487                );
488                evp_decrypt_init(&mut cipher_ctx, &cipher, key_bytes, Some(iv))?;
489            }
490            #[cfg(feature = "legacy-des")]
491            ctx @ DecryptionContext::Iv64(..) => {
492                let iv = <&[u8]>::try_from(ctx)?;
493                debug_assert_eq!(
494                    iv.len(),
495                    <usize>::try_from(unsafe { EVP_CIPHER_iv_length(cipher.as_const_ptr()) })
496                        .unwrap()
497                );
498                evp_decrypt_init(&mut cipher_ctx, &cipher, key_bytes, Some(iv))?;
499            }
500            DecryptionContext::None => {
501                evp_decrypt_init(&mut cipher_ctx, &cipher, key_bytes, None)?;
502            }
503        }
504
505        Ok(Self {
506            algorithm,
507            mode,
508            cipher_ctx,
509            output_generated: 0,
510        })
511    }
512
513    fn update_internal<'a>(
514        &mut self,
515        input: &[u8],
516        output: &'a mut [u8],
517        min_outsize: usize,
518    ) -> Result<BufferUpdate<'a>, Unspecified> {
519        if output.len() < min_outsize {
520            return Err(Unspecified);
521        }
522        let mut outlen: i32 = 0;
523        let inlen: i32 = input.len().try_into()?;
524
525        if 1 != unsafe {
526            EVP_DecryptUpdate(
527                self.cipher_ctx.as_mut_ptr(),
528                output.as_mut_ptr(),
529                &mut outlen,
530                input.as_ptr(),
531                inlen,
532            )
533        } {
534            return Err(Unspecified);
535        }
536        let outlen: usize = outlen.try_into()?;
537        debug_assert!(outlen <= min_outsize);
538        self.output_generated += outlen;
539        // Reported length, not bytes written -- so this is not a bounds check on
540        // the write. The canary tests in this module cover that.
541        assert!(outlen <= output.len());
542
543        Ok(BufferUpdate::new(output, outlen))
544    }
545
546    /// Updates the internal state of the key with the provided ciphertext `input`,
547    /// potentially also writing bytes of plaintext to `output`.
548    /// The number of bytes written to `output` can be up to `input.len()`
549    /// plus the block length of the cipher algorithm (e.g., [`Algorithm::block_len`]) minus one.
550    ///
551    /// # Errors
552    /// * Returns an error if the `output` buffer is smaller than the length of
553    ///   the `input` plus the algorithm's block length minus one.
554    /// * May return an error if the length of `input` plus the algorithm's block length is larger
555    ///   than `i32::MAX`.
556    pub fn update<'a>(
557        &mut self,
558        input: &[u8],
559        output: &'a mut [u8],
560    ) -> Result<BufferUpdate<'a>, Unspecified> {
561        let min_outsize = input
562            .len()
563            .checked_add(self.algorithm().block_len())
564            .ok_or(Unspecified)?
565            .checked_sub(1)
566            .ok_or(Unspecified)?;
567        self.update_internal(input, output, min_outsize)
568    }
569
570    /// Updates the internal state of the key with the provided ciphertext `input`,
571    /// potentially writing bytes of plaintext to `output`.
572    ///
573    /// This function has looser output buffer size requirements than [`Self::update`],
574    /// calculating the minimum required size based on the total bytes of output generated
575    /// and the cipher's block length. This is considered "less safe" because it's
576    /// based on assumptions about the state of the underlying operations.
577    ///
578    /// The minimum output buffer size is calculated based on how many bytes are needed to
579    /// reach the next block boundary after processing the input. If `next_total` is the sum
580    /// of bytes already generated plus `input.len()`, then the minimum size is:
581    /// `input.len() + ((block_len - (next_total % block_len)) % block_len)`
582    ///
583    /// # Errors
584    /// Returns an error if the `output` buffer is smaller than the calculated minimum size,
585    /// if the total output length overflows, or if the length of `input` is larger than
586    /// `i32::MAX`.
587    ///
588    /// # Panics
589    /// Panics if the number of bytes written by the cipher operation exceeds the output
590    /// buffer length.
591    pub fn less_safe_update<'a>(
592        &mut self,
593        input: &[u8],
594        output: &'a mut [u8],
595    ) -> Result<BufferUpdate<'a>, Unspecified> {
596        let next_total = self
597            .output_generated
598            .checked_add(input.len())
599            .ok_or(Unspecified)?;
600        let extra_buffer_size = (self.algorithm().block_len
601            - next_total.rem_euclid(self.algorithm().block_len))
602        .rem_euclid(self.algorithm().block_len);
603        let min_outsize = input
604            .len()
605            .checked_add(extra_buffer_size)
606            .ok_or(Unspecified)?;
607        self.update_internal(input, output, min_outsize)
608    }
609
610    /// Finishes the decryption operation, writing the remaining plaintext to
611    /// `output`.
612    /// The number of bytes written to `output` can be up to the block length of
613    /// the cipher algorithm (e.g., [`Algorithm::block_len`]).
614    ///
615    /// # Errors
616    /// * Returns an error if the `output` buffer is smaller than the algorithm's
617    ///   block length.
618    pub fn finish(mut self, output: &mut [u8]) -> Result<BufferUpdate<'_>, Unspecified> {
619        if output.len() < self.algorithm().block_len() {
620            return Err(Unspecified);
621        }
622        let mut outlen: i32 = 0;
623
624        if 1 != indicator_check!(unsafe {
625            EVP_DecryptFinal_ex(
626                self.cipher_ctx.as_mut_ptr(),
627                output.as_mut_ptr(),
628                &mut outlen,
629            )
630        }) {
631            return Err(Unspecified);
632        }
633        let outlen: usize = outlen.try_into()?;
634        debug_assert!(outlen <= self.algorithm().block_len());
635        Ok(BufferUpdate::new(output, outlen))
636    }
637
638    /// Returns the cipher operating mode.
639    #[must_use]
640    pub fn mode(&self) -> OperatingMode {
641        self.mode
642    }
643
644    /// Returns the cipher algorithm
645    #[must_use]
646    pub fn algorithm(&self) -> &'static Algorithm {
647        self.algorithm
648    }
649
650    /// Constructs a `StreamingDecryptingKey` for decrypting using the CTR cipher mode.
651    /// The resulting plaintext will be the same length as the ciphertext.
652    ///
653    /// # Errors
654    /// Returns an error on an internal failure. With `legacy-des` enabled, also
655    /// returned if `key`'s algorithm does not support CTR mode (e.g.
656    /// `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY`,
657    /// `DES_EDE3_FOR_LEGACY_USE_ONLY`).
658    pub fn ctr(key: UnboundCipherKey, context: DecryptionContext) -> Result<Self, Unspecified> {
659        Self::new(key, OperatingMode::CTR, context)
660    }
661
662    /// Constructs a `StreamingDecryptingKey` for decrypting using the CBC cipher mode.
663    /// The resulting plaintext will be shorter than the ciphertext.
664    ///
665    /// # Errors
666    /// Returns an error on an internal failure. With `legacy-des` enabled, also
667    /// returned if `key` was constructed with `DES_FOR_LEGACY_USE_ONLY`,
668    /// `DES_EDE_FOR_LEGACY_USE_ONLY` or `DES_EDE3_FOR_LEGACY_USE_ONLY` and the
669    /// provided key material contains weak or semi-weak DES subkeys, or (for
670    /// Triple DES) a degenerate subkey configuration (e.g. `K1 == K2` for 2TDEA,
671    /// or any pairwise equality for 3TDEA).
672    pub fn cbc_pkcs7(
673        key: UnboundCipherKey,
674        context: DecryptionContext,
675    ) -> Result<Self, Unspecified> {
676        Self::new(key, OperatingMode::CBC, context)
677    }
678
679    // Constructs a `StreamingDecryptingKey` for decrypting using the CFB128 cipher mode.
680    /// The resulting plaintext will be the same length as the ciphertext.
681    ///
682    /// # Errors
683    /// Returns an error on an internal failure. With `legacy-des` enabled, also
684    /// returned if `key`'s algorithm does not support CFB128 mode (e.g.
685    /// `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY`,
686    /// `DES_EDE3_FOR_LEGACY_USE_ONLY`).
687    pub fn cfb128(key: UnboundCipherKey, context: DecryptionContext) -> Result<Self, Unspecified> {
688        Self::new(key, OperatingMode::CFB128, context)
689    }
690
691    /// Constructs a `StreamingDecryptingKey` for decrypting using the ECB cipher mode.
692    /// The resulting plaintext will be the same length as the ciphertext.
693    ///
694    /// # ☠️ ️️️DANGER ☠️
695    /// Offered for computability purposes only. This is an extremely dangerous mode, and
696    /// very likely not what you want to use.
697    ///
698    /// # Errors
699    /// Returns an error on an internal failure. With `legacy-des` enabled, also
700    /// returned if `key` was constructed with `DES_FOR_LEGACY_USE_ONLY`,
701    /// `DES_EDE_FOR_LEGACY_USE_ONLY` or `DES_EDE3_FOR_LEGACY_USE_ONLY` and the
702    /// provided key material contains weak or semi-weak DES subkeys, or (for
703    /// Triple DES) a degenerate subkey configuration (e.g. `K1 == K2` for 2TDEA,
704    /// or any pairwise equality for 3TDEA).
705    pub fn ecb_pkcs7(
706        key: UnboundCipherKey,
707        context: DecryptionContext,
708    ) -> Result<Self, Unspecified> {
709        Self::new(key, OperatingMode::ECB, context)
710    }
711}
712
713#[cfg(test)]
714mod tests {
715    #[cfg(feature = "legacy-des")]
716    #[allow(deprecated)]
717    use crate::cipher::DES_FOR_LEGACY_USE_ONLY;
718    use crate::cipher::{
719        DecryptionContext, EncryptionContext, OperatingMode, StreamingDecryptingKey,
720        StreamingEncryptingKey, UnboundCipherKey, AES_128, AES_128_KEY_LEN, AES_256,
721        AES_256_KEY_LEN,
722    };
723    use crate::iv::{FixedLength, IV_LEN_128_BIT};
724    use crate::rand::{SecureRandom, SystemRandom};
725    use crate::test::from_hex;
726    use paste::*;
727
728    // Complementary fills so a stray write matching one canary is caught by
729    // the other. Refilled immediately before each update/finish so a prior
730    // call's (legal) write into a larger slice cannot be mistaken for an
731    // overrun of a later, shorter one.
732    const OUTPUT_CANARIES: [u8; 2] = [0xAA, 0x55];
733
734    fn assert_no_write_past(output: &[u8], slice_start: usize, slice_end: usize, canary: u8) {
735        let slice_len = slice_end - slice_start;
736        let tail = &output[slice_end..output.len().min(slice_end + 16)];
737        assert!(
738            output[slice_end..].iter().all(|&b| b == canary),
739            "wrote past the provided {slice_len}-byte output slice at buffer offsets \
740             {slice_start}..{slice_end} \
741             (canary {canary:#04x}, bytes at {slice_end}..: {tail:02x?})"
742        );
743    }
744
745    fn apply_update_with_canary(
746        output: &mut [u8],
747        out_idx: usize,
748        out_end: usize,
749        canary: u8,
750        op: impl FnOnce(&mut [u8]) -> usize,
751    ) -> usize {
752        output[out_end..].fill(canary);
753        let written = op(&mut output[out_idx..out_end]);
754        assert_no_write_past(output, out_idx, out_end, canary);
755        written
756    }
757
758    /// Generic helper for step encryption that accepts a closure for the update operation.
759    /// The closure receives: (key, input, output_buffer, out_idx, block_len, step)
760    /// and returns the number of bytes written.
761    fn step_encrypt_with_updater<F>(
762        mut encrypting_key: StreamingEncryptingKey,
763        plaintext: &[u8],
764        step: usize,
765        canary: u8,
766        mut updater: F,
767    ) -> (Box<[u8]>, DecryptionContext)
768    where
769        F: FnMut(&mut StreamingEncryptingKey, &[u8], &mut [u8], usize, usize, usize) -> usize,
770    {
771        let alg = encrypting_key.algorithm();
772        let mode = encrypting_key.mode();
773        let block_len = alg.block_len();
774        let n = plaintext.len();
775        // Extra block so there is always canary past the documented-min slice
776        // (`in_len + block_len - 1`) and past `finish`'s block-sized tail.
777        let mut ciphertext = vec![canary; n + 2 * block_len];
778
779        let mut in_idx: usize = 0;
780        let mut out_idx: usize = 0;
781        loop {
782            let mut in_end = in_idx + step;
783            if in_end > n {
784                in_end = n;
785            }
786            let written = updater(
787                &mut encrypting_key,
788                &plaintext[in_idx..in_end],
789                &mut ciphertext,
790                out_idx,
791                block_len,
792                step,
793            );
794            in_idx += step;
795            out_idx += written;
796            if in_idx >= n {
797                break;
798            }
799        }
800        let out_end = out_idx + block_len;
801        ciphertext[out_end..].fill(canary);
802        let (decrypt_iv, written_len) = {
803            let (decrypt_iv, output) = encrypting_key
804                .finish(&mut ciphertext[out_idx..out_end])
805                .unwrap();
806            (decrypt_iv, output.written().len())
807        };
808        assert_no_write_past(&ciphertext, out_idx, out_end, canary);
809        ciphertext.truncate(out_idx + written_len);
810        match mode {
811            OperatingMode::CBC | OperatingMode::ECB => {
812                assert!(ciphertext.len() > plaintext.len());
813                assert!(ciphertext.len() <= plaintext.len() + block_len);
814            }
815            _ => {
816                assert_eq!(ciphertext.len(), plaintext.len());
817            }
818        }
819
820        (ciphertext.into_boxed_slice(), decrypt_iv)
821    }
822
823    /// Generic helper for step decryption that accepts a closure for the update operation.
824    /// The closure receives: (key, input, output_buffer, out_idx, block_len, step)
825    /// and returns the number of bytes written.
826    fn step_decrypt_with_updater<F>(
827        mut decrypting_key: StreamingDecryptingKey,
828        ciphertext: &[u8],
829        step: usize,
830        canary: u8,
831        mut updater: F,
832    ) -> Box<[u8]>
833    where
834        F: FnMut(&mut StreamingDecryptingKey, &[u8], &mut [u8], usize, usize, usize) -> usize,
835    {
836        let alg = decrypting_key.algorithm();
837        let mode = decrypting_key.mode();
838        let block_len = alg.block_len();
839        let n = ciphertext.len();
840        let mut plaintext = vec![canary; n + 2 * block_len];
841
842        let mut in_idx: usize = 0;
843        let mut out_idx: usize = 0;
844        loop {
845            let mut in_end = in_idx + step;
846            if in_end > n {
847                in_end = n;
848            }
849            let written = updater(
850                &mut decrypting_key,
851                &ciphertext[in_idx..in_end],
852                &mut plaintext,
853                out_idx,
854                block_len,
855                step,
856            );
857            in_idx += step;
858            out_idx += written;
859            if in_idx >= n {
860                break;
861            }
862        }
863        let out_end = out_idx + block_len;
864        plaintext[out_end..].fill(canary);
865        let written_len = {
866            let output = decrypting_key
867                .finish(&mut plaintext[out_idx..out_end])
868                .unwrap();
869            output.written().len()
870        };
871        assert_no_write_past(&plaintext, out_idx, out_end, canary);
872        plaintext.truncate(out_idx + written_len);
873        match mode {
874            OperatingMode::CBC | OperatingMode::ECB => {
875                assert!(ciphertext.len() > plaintext.len());
876                assert!(ciphertext.len() <= plaintext.len() + block_len);
877            }
878            _ => {
879                assert_eq!(ciphertext.len(), plaintext.len());
880            }
881        }
882        plaintext.into_boxed_slice()
883    }
884
885    fn step_encrypt(
886        encrypting_key: StreamingEncryptingKey,
887        plaintext: &[u8],
888        step: usize,
889        canary: u8,
890    ) -> (Box<[u8]>, DecryptionContext) {
891        step_encrypt_with_updater(
892            encrypting_key,
893            plaintext,
894            step,
895            canary,
896            |key, input, output, out_idx, block_len, _step| {
897                let out_end = out_idx + input.len() + block_len - 1;
898                apply_update_with_canary(output, out_idx, out_end, canary, |out| {
899                    key.update(input, out).unwrap().written().len()
900                })
901            },
902        )
903    }
904
905    fn step_decrypt(
906        decrypting_key: StreamingDecryptingKey,
907        ciphertext: &[u8],
908        step: usize,
909        canary: u8,
910    ) -> Box<[u8]> {
911        step_decrypt_with_updater(
912            decrypting_key,
913            ciphertext,
914            step,
915            canary,
916            |key, input, output, out_idx, block_len, _step| {
917                let out_end = out_idx + input.len() + block_len - 1;
918                apply_update_with_canary(output, out_idx, out_end, canary, |out| {
919                    key.update(input, out).unwrap().written().len()
920                })
921            },
922        )
923    }
924
925    fn less_safe_min_out(block_len: usize, out_idx: usize, input_len: usize) -> usize {
926        let next_total = out_idx + input_len;
927        input_len + ((block_len - (next_total % block_len)) % block_len)
928    }
929
930    fn step_encrypt_less_safe(
931        encrypting_key: StreamingEncryptingKey,
932        plaintext: &[u8],
933        step: usize,
934        canary: u8,
935    ) -> (Box<[u8]>, DecryptionContext) {
936        step_encrypt_with_updater(
937            encrypting_key,
938            plaintext,
939            step,
940            canary,
941            |key, input, output, out_idx, block_len, step| {
942                let min_out_len = less_safe_min_out(block_len, out_idx, input.len());
943                if input.len() % block_len == 0 && step % block_len == 0 {
944                    // When input is provided one block at a time, no additional space should be needed.
945                    assert_eq!(input.len(), min_out_len);
946                }
947                let out_end = out_idx + min_out_len;
948                apply_update_with_canary(output, out_idx, out_end, canary, |out| {
949                    key.less_safe_update(input, out).unwrap().written().len()
950                })
951            },
952        )
953    }
954
955    fn step_decrypt_less_safe(
956        decrypting_key: StreamingDecryptingKey,
957        ciphertext: &[u8],
958        step: usize,
959        canary: u8,
960    ) -> Box<[u8]> {
961        step_decrypt_with_updater(
962            decrypting_key,
963            ciphertext,
964            step,
965            canary,
966            |key, input, output, out_idx, block_len, step| {
967                let min_out_len = less_safe_min_out(block_len, out_idx, input.len());
968                if input.len() % block_len == 0 && step % block_len == 0 {
969                    // When input is provided one block at a time, no additional space should be needed.
970                    assert_eq!(input.len(), min_out_len);
971                }
972                let out_end = out_idx + min_out_len;
973                apply_update_with_canary(output, out_idx, out_end, canary, |out| {
974                    key.less_safe_update(input, out).unwrap().written().len()
975                })
976            },
977        )
978    }
979
980    macro_rules! helper_stream_step_encrypt_test {
981        ($mode:ident) => {
982            paste! {
983                fn [<helper_test_ $mode _stream_encrypt_step_n_bytes>](
984                    encrypting_key_creator: impl Fn() -> StreamingEncryptingKey,
985                    decrypting_key_creator: impl Fn(DecryptionContext) -> StreamingDecryptingKey,
986                    n: usize,
987                    step: usize,
988                    canary: u8,
989                ) {
990                    let mut input = vec![0u8; n];
991                    let random = SystemRandom::new();
992                    random.fill(&mut input).unwrap();
993
994                    let encrypting_key = encrypting_key_creator();
995
996                    let (ciphertext, decrypt_iv) =
997                        step_encrypt(encrypting_key, &input, step, canary);
998
999                    let decrypting_key = decrypting_key_creator(decrypt_iv);
1000
1001                    let plaintext = step_decrypt(decrypting_key, &ciphertext, step, canary);
1002
1003                    assert_eq!(input.as_slice(), &*plaintext);
1004                }
1005            }
1006        };
1007        ($mode:ident, less_safe) => {
1008            paste! {
1009                fn [<helper_test_ $mode _stream_encrypt_step_n_bytes_less_safe>](
1010                    encrypting_key_creator: impl Fn() -> StreamingEncryptingKey,
1011                    decrypting_key_creator: impl Fn(DecryptionContext) -> StreamingDecryptingKey,
1012                    n: usize,
1013                    step: usize,
1014                    canary: u8,
1015                ) {
1016                    let mut input = vec![0u8; n];
1017                    let random = SystemRandom::new();
1018                    random.fill(&mut input).unwrap();
1019
1020                    let encrypting_key = encrypting_key_creator();
1021
1022                    let (ciphertext, decrypt_iv) =
1023                        step_encrypt_less_safe(encrypting_key, &input, step, canary);
1024
1025                    let decrypting_key = decrypting_key_creator(decrypt_iv);
1026
1027                    let plaintext =
1028                        step_decrypt_less_safe(decrypting_key, &ciphertext, step, canary);
1029
1030                    assert_eq!(input.as_slice(), &*plaintext);
1031                }
1032            }
1033        };
1034    }
1035
1036    helper_stream_step_encrypt_test!(cbc_pkcs7);
1037    helper_stream_step_encrypt_test!(ctr);
1038    helper_stream_step_encrypt_test!(cfb128);
1039    helper_stream_step_encrypt_test!(ecb_pkcs7);
1040
1041    helper_stream_step_encrypt_test!(cbc_pkcs7, less_safe);
1042    helper_stream_step_encrypt_test!(ctr, less_safe);
1043    helper_stream_step_encrypt_test!(cfb128, less_safe);
1044    helper_stream_step_encrypt_test!(ecb_pkcs7, less_safe);
1045
1046    fn run_step_matrix(test: impl Fn(usize, usize)) {
1047        for i in 13..=21 {
1048            for j in 124..=131 {
1049                test(j, i);
1050                test(j, j - i);
1051            }
1052        }
1053        for j in 124..=131 {
1054            test(j, j);
1055            test(j, 256);
1056            test(j, 1);
1057        }
1058    }
1059
1060    fn random_aes_key(len: usize) -> Vec<u8> {
1061        let mut key = vec![0u8; len];
1062        SystemRandom::new().fill(&mut key).unwrap();
1063        key
1064    }
1065
1066    macro_rules! step_roundtrip_tests {
1067        ($name:ident, $constructor:ident, $helper:ident) => {
1068            #[test]
1069            fn $name() {
1070                for (alg, key_len) in [(&AES_128, AES_128_KEY_LEN), (&AES_256, AES_256_KEY_LEN)] {
1071                    let key = random_aes_key(key_len);
1072                    let encrypting_key_creator = || {
1073                        let key = UnboundCipherKey::new(alg, &key).unwrap();
1074                        StreamingEncryptingKey::$constructor(key).unwrap()
1075                    };
1076                    let decrypting_key_creator = |decryption_ctx: DecryptionContext| {
1077                        let key = UnboundCipherKey::new(alg, &key).unwrap();
1078                        StreamingDecryptingKey::$constructor(key, decryption_ctx).unwrap()
1079                    };
1080                    // Alternate complementary canaries across the (n, step)
1081                    // grid so both fills are exercised without doubling the
1082                    // already-expensive AES-128 x AES-256 matrix.
1083                    run_step_matrix(|n, step| {
1084                        let canary = OUTPUT_CANARIES[(n + step) % OUTPUT_CANARIES.len()];
1085                        $helper(
1086                            encrypting_key_creator,
1087                            decrypting_key_creator,
1088                            n,
1089                            step,
1090                            canary,
1091                        );
1092                    });
1093                }
1094            }
1095        };
1096    }
1097
1098    step_roundtrip_tests!(
1099        test_step_cbc,
1100        cbc_pkcs7,
1101        helper_test_cbc_pkcs7_stream_encrypt_step_n_bytes
1102    );
1103    step_roundtrip_tests!(
1104        test_step_ctr,
1105        ctr,
1106        helper_test_ctr_stream_encrypt_step_n_bytes
1107    );
1108    step_roundtrip_tests!(
1109        test_step_cfb128,
1110        cfb128,
1111        helper_test_cfb128_stream_encrypt_step_n_bytes
1112    );
1113    step_roundtrip_tests!(
1114        test_step_ecb_pkcs7,
1115        ecb_pkcs7,
1116        helper_test_ecb_pkcs7_stream_encrypt_step_n_bytes
1117    );
1118    step_roundtrip_tests!(
1119        test_step_cbc_less_safe,
1120        cbc_pkcs7,
1121        helper_test_cbc_pkcs7_stream_encrypt_step_n_bytes_less_safe
1122    );
1123    step_roundtrip_tests!(
1124        test_step_ctr_less_safe,
1125        ctr,
1126        helper_test_ctr_stream_encrypt_step_n_bytes_less_safe
1127    );
1128    step_roundtrip_tests!(
1129        test_step_cfb128_less_safe,
1130        cfb128,
1131        helper_test_cfb128_stream_encrypt_step_n_bytes_less_safe
1132    );
1133    step_roundtrip_tests!(
1134        test_step_ecb_pkcs7_less_safe,
1135        ecb_pkcs7,
1136        helper_test_ecb_pkcs7_stream_encrypt_step_n_bytes_less_safe
1137    );
1138
1139    #[derive(Clone, Copy)]
1140    enum UpdateVariant {
1141        /// [`StreamingDecryptingKey::update`]: `in_len + block_len - 1`.
1142        Documented,
1143        /// [`StreamingDecryptingKey::less_safe_update`]: `in_len` when aligned.
1144        LessSafe,
1145    }
1146
1147    /// Feeds block-aligned chunks through `key`, handing each call exactly the
1148    /// minimum output slice its API documents, with canary bytes just past it.
1149    /// `update`-only: the overrun precedes any padding check, so the ciphertext
1150    /// is arbitrary and `finish` is never called.
1151    fn assert_block_aligned_updates_stay_in_slice(
1152        mut key: StreamingDecryptingKey,
1153        variant: UpdateVariant,
1154        chunks: usize,
1155        canary: u8,
1156    ) {
1157        let block_len = key.algorithm().block_len();
1158        let ciphertext = vec![0x42u8; chunks * block_len];
1159        // Widest documented minimum is `2 * block_len - 1`, leaving every slice
1160        // >= `block_len + 1` canary bytes -- enough for a full-block overrun.
1161        let mut output = vec![canary; ciphertext.len() + 3 * block_len];
1162        let mut out_idx = 0usize;
1163
1164        for chunk in ciphertext.chunks(block_len) {
1165            let min_outsize = match variant {
1166                UpdateVariant::Documented => chunk.len() + block_len - 1,
1167                UpdateVariant::LessSafe => less_safe_min_out(block_len, out_idx, chunk.len()),
1168            };
1169            let out_end = out_idx + min_outsize;
1170            out_idx += apply_update_with_canary(&mut output, out_idx, out_end, canary, |out| {
1171                match variant {
1172                    UpdateVariant::Documented => key.update(chunk, out),
1173                    UpdateVariant::LessSafe => key.less_safe_update(chunk, out),
1174                }
1175                .expect("update rejected a documented-minimum output slice")
1176                .written()
1177                .len()
1178            });
1179        }
1180    }
1181
1182    /// The overrun needs a second call: the first leaves the cipher aligned (so
1183    /// AWS-LC buffers a block), the next replays it. Four gives three chances.
1184    const REGRESSION_CHUNKS: usize = 4;
1185
1186    macro_rules! decrypt_output_bounds_tests {
1187        ($name:ident, $alg:expr, $key_len:expr, $constructor:ident, $context:expr) => {
1188            paste! {
1189                #[test]
1190                fn [<test_ $name _update_stays_in_output_slice>]() {
1191                    for canary in OUTPUT_CANARIES {
1192                        let unbound =
1193                            UnboundCipherKey::new($alg, &random_aes_key($key_len)).unwrap();
1194                        let key =
1195                            StreamingDecryptingKey::$constructor(unbound, $context).unwrap();
1196                        assert_block_aligned_updates_stay_in_slice(
1197                            key,
1198                            UpdateVariant::Documented,
1199                            REGRESSION_CHUNKS,
1200                            canary,
1201                        );
1202                    }
1203                }
1204
1205                #[test]
1206                fn [<test_ $name _less_safe_update_stays_in_output_slice>]() {
1207                    for canary in OUTPUT_CANARIES {
1208                        let unbound =
1209                            UnboundCipherKey::new($alg, &random_aes_key($key_len)).unwrap();
1210                        let key =
1211                            StreamingDecryptingKey::$constructor(unbound, $context).unwrap();
1212                        assert_block_aligned_updates_stay_in_slice(
1213                            key,
1214                            UpdateVariant::LessSafe,
1215                            REGRESSION_CHUNKS,
1216                            canary,
1217                        );
1218                    }
1219                }
1220            }
1221        };
1222    }
1223
1224    decrypt_output_bounds_tests!(
1225        aes_128_cbc_pkcs7,
1226        &AES_128,
1227        AES_128_KEY_LEN,
1228        cbc_pkcs7,
1229        DecryptionContext::Iv128(FixedLength::from([0u8; IV_LEN_128_BIT]))
1230    );
1231    decrypt_output_bounds_tests!(
1232        aes_256_cbc_pkcs7,
1233        &AES_256,
1234        AES_256_KEY_LEN,
1235        cbc_pkcs7,
1236        DecryptionContext::Iv128(FixedLength::from([0u8; IV_LEN_128_BIT]))
1237    );
1238    decrypt_output_bounds_tests!(
1239        aes_128_ecb_pkcs7,
1240        &AES_128,
1241        AES_128_KEY_LEN,
1242        ecb_pkcs7,
1243        DecryptionContext::None
1244    );
1245    decrypt_output_bounds_tests!(
1246        aes_256_ecb_pkcs7,
1247        &AES_256,
1248        AES_256_KEY_LEN,
1249        ecb_pkcs7,
1250        DecryptionContext::None
1251    );
1252
1253    macro_rules! streaming_cipher_kat {
1254        ($name:ident, $alg:expr, $mode:expr, $key:literal, $iv: literal, $plaintext:literal, $ciphertext:literal, $from_step:literal, $to_step:literal) => {
1255            #[test]
1256            fn $name() {
1257                let key = from_hex($key).unwrap();
1258                let input = from_hex($plaintext).unwrap();
1259                let expected_ciphertext = from_hex($ciphertext).unwrap();
1260                let iv = from_hex($iv).unwrap();
1261
1262                for step in ($from_step..=$to_step) {
1263                    let ec = EncryptionContext::Iv128(
1264                        FixedLength::<IV_LEN_128_BIT>::try_from(iv.as_slice()).unwrap(),
1265                    );
1266
1267                    let unbound_key = UnboundCipherKey::new($alg, &key).unwrap();
1268
1269                    let encrypting_key =
1270                        StreamingEncryptingKey::new(unbound_key, $mode, ec).unwrap();
1271
1272                    let (ciphertext, decrypt_ctx) =
1273                        step_encrypt(encrypting_key, &input, step, OUTPUT_CANARIES[0]);
1274
1275                    assert_eq!(expected_ciphertext.as_slice(), ciphertext.as_ref());
1276
1277                    let unbound_key2 = UnboundCipherKey::new($alg, &key).unwrap();
1278                    let decrypting_key =
1279                        StreamingDecryptingKey::new(unbound_key2, $mode, decrypt_ctx).unwrap();
1280
1281                    let plaintext =
1282                        step_decrypt(decrypting_key, &ciphertext, step, OUTPUT_CANARIES[1]);
1283                    assert_eq!(input.as_slice(), plaintext.as_ref());
1284                }
1285            }
1286        };
1287        ($name:ident, $alg:expr, $mode:expr, $key:literal, $plaintext:literal, $ciphertext:literal, $from_step:literal, $to_step:literal) => {
1288            #[test]
1289            fn $name() {
1290                let key = from_hex($key).unwrap();
1291                let input = from_hex($plaintext).unwrap();
1292                let expected_ciphertext = from_hex($ciphertext).unwrap();
1293
1294                for step in ($from_step..=$to_step) {
1295                    let unbound_key = UnboundCipherKey::new($alg, &key).unwrap();
1296
1297                    let encrypting_key =
1298                        StreamingEncryptingKey::new(unbound_key, $mode, EncryptionContext::None)
1299                            .unwrap();
1300
1301                    let (ciphertext, decrypt_ctx) =
1302                        step_encrypt(encrypting_key, &input, step, OUTPUT_CANARIES[0]);
1303
1304                    assert_eq!(expected_ciphertext.as_slice(), ciphertext.as_ref());
1305
1306                    let unbound_key2 = UnboundCipherKey::new($alg, &key).unwrap();
1307                    let decrypting_key =
1308                        StreamingDecryptingKey::new(unbound_key2, $mode, decrypt_ctx).unwrap();
1309
1310                    let plaintext =
1311                        step_decrypt(decrypting_key, &ciphertext, step, OUTPUT_CANARIES[1]);
1312                    assert_eq!(input.as_slice(), plaintext.as_ref());
1313                }
1314            }
1315        };
1316    }
1317
1318    streaming_cipher_kat!(
1319        test_iv_aes_128_ctr_16_bytes,
1320        &AES_128,
1321        OperatingMode::CTR,
1322        "000102030405060708090a0b0c0d0e0f",
1323        "00000000000000000000000000000000",
1324        "00112233445566778899aabbccddeeff",
1325        "c6b01904c3da3df5e7d62bd96d153686",
1326        2,
1327        9
1328    );
1329    streaming_cipher_kat!(
1330        test_iv_aes_256_ctr_15_bytes,
1331        &AES_256,
1332        OperatingMode::CTR,
1333        "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
1334        "00000000000000000000000000000000",
1335        "00112233445566778899aabbccddee",
1336        "f28122856e1cf9a7216a30d111f399",
1337        2,
1338        9
1339    );
1340
1341    streaming_cipher_kat!(
1342        test_openssl_aes_128_ctr_15_bytes,
1343        &AES_128,
1344        OperatingMode::CTR,
1345        "244828580821c1652582c76e34d299f5",
1346        "093145d5af233f46072a5eb5adc11aa1",
1347        "3ee38cec171e6cf466bf0df98aa0e1",
1348        "bd7d928f60e3422d96b3f8cd614eb2",
1349        2,
1350        9
1351    );
1352
1353    streaming_cipher_kat!(
1354        test_openssl_aes_256_ctr_15_bytes,
1355        &AES_256,
1356        OperatingMode::CTR,
1357        "0857db8240ea459bdf660b4cced66d1f2d3734ff2de7b81e92740e65e7cc6a1d",
1358        "f028ecb053f801102d11fccc9d303a27",
1359        "eca7285d19f3c20e295378460e8729",
1360        "b5098e5e788de6ac2f2098eb2fc6f8",
1361        2,
1362        9
1363    );
1364
1365    streaming_cipher_kat!(
1366        test_iv_aes_128_cbc_16_bytes,
1367        &AES_128,
1368        OperatingMode::CBC,
1369        "000102030405060708090a0b0c0d0e0f",
1370        "00000000000000000000000000000000",
1371        "00112233445566778899aabbccddeeff",
1372        "69c4e0d86a7b0430d8cdb78070b4c55a9e978e6d16b086570ef794ef97984232",
1373        2,
1374        9
1375    );
1376
1377    streaming_cipher_kat!(
1378        test_iv_aes_256_cbc_15_bytes,
1379        &AES_256,
1380        OperatingMode::CBC,
1381        "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
1382        "00000000000000000000000000000000",
1383        "00112233445566778899aabbccddee",
1384        "2ddfb635a651a43f582997966840ca0c",
1385        2,
1386        9
1387    );
1388
1389    streaming_cipher_kat!(
1390        test_openssl_aes_128_cbc_15_bytes,
1391        &AES_128,
1392        OperatingMode::CBC,
1393        "053304bb3899e1d99db9d29343ea782d",
1394        "b5313560244a4822c46c2a0c9d0cf7fd",
1395        "a3e4c990356c01f320043c3d8d6f43",
1396        "ad96993f248bd6a29760ec7ccda95ee1",
1397        2,
1398        9
1399    );
1400
1401    streaming_cipher_kat!(
1402        test_openssl_aes_128_cbc_16_bytes,
1403        &AES_128,
1404        OperatingMode::CBC,
1405        "95af71f1c63e4a1d0b0b1a27fb978283",
1406        "89e40797dca70197ff87d3dbb0ef2802",
1407        "aece7b5e3c3df1ffc9802d2dfe296dc7",
1408        "301b5dab49fb11e919d0d39970d06739301919743304f23f3cbc67d28564b25b",
1409        2,
1410        9
1411    );
1412
1413    streaming_cipher_kat!(
1414        test_openssl_aes_256_cbc_15_bytes,
1415        &AES_256,
1416        OperatingMode::CBC,
1417        "d369e03e9752784917cc7bac1db7399598d9555e691861d9dd7b3292a693ef57",
1418        "1399bb66b2f6ad99a7f064140eaaa885",
1419        "7385f5784b85bf0a97768ddd896d6d",
1420        "4351082bac9b4593ae8848cc9dfb5a01",
1421        2,
1422        9
1423    );
1424
1425    streaming_cipher_kat!(
1426        test_openssl_aes_256_cbc_16_bytes,
1427        &AES_256,
1428        OperatingMode::CBC,
1429        "d4a8206dcae01242f9db79a4ecfe277d0f7bb8ccbafd8f9809adb39f35aa9b41",
1430        "24f6076548fb9d93c8f7ed9f6e661ef9",
1431        "a39c1fdf77ea3e1f18178c0ec237c70a",
1432        "f1af484830a149ee0387b854d65fe87ca0e62efc1c8e6909d4b9ab8666470453",
1433        2,
1434        9
1435    );
1436
1437    streaming_cipher_kat!(
1438        test_openssl_aes_128_cfb128_16_bytes,
1439        &AES_128,
1440        OperatingMode::CFB128,
1441        "5c353f739429bbd48b7e3f9a76facf4d",
1442        "7b2c7ce17a9b6a59a9e64253b98c8cd1",
1443        "add1bcebeaabe9423d4e916400e877c5",
1444        "8440ec442e4135a613ddb2ce26107e10",
1445        2,
1446        9
1447    );
1448
1449    streaming_cipher_kat!(
1450        test_openssl_aes_128_cfb128_15_bytes,
1451        &AES_128,
1452        OperatingMode::CFB128,
1453        "e1f39d70ad378efc1ac318aa8ac4489f",
1454        "ec78c3d54fff2fe09678c7883024ddce",
1455        "b8c905004b2a92a323769f1b8dc1b2",
1456        "964c3e9bf8bf2a3cca02d8e2e75608",
1457        2,
1458        9
1459    );
1460
1461    streaming_cipher_kat!(
1462        test_openssl_aes_256_cfb128_16_bytes,
1463        &AES_256,
1464        OperatingMode::CFB128,
1465        "0e8117d0984d6acb957a5d6ca526a12fa612ce5de2daadebd42c14d28a0a192e",
1466        "09147a153b230a40cd7bf4197ad0e825",
1467        "13f4540a4e06394148ade31a6f678787",
1468        "250e590e47b7613b7d0a53f684e970d6",
1469        2,
1470        9
1471    );
1472
1473    streaming_cipher_kat!(
1474        test_openssl_aes_256_cfb128_15_bytes,
1475        &AES_256,
1476        OperatingMode::CFB128,
1477        "5cb17d8d5b9dbd81e4f1e0a2c82ebf36cf61156388fb7abf99d4526622858225",
1478        "13c77415ec24f3e2f784f228478a85be",
1479        "3efa583df4405aab61e18155aa7e0d",
1480        "c1f2ffe8aa5064199e8f4f1b388303",
1481        2,
1482        9
1483    );
1484
1485    streaming_cipher_kat!(
1486        test_openssl_aes_128_ecb_pkcs7_16_bytes,
1487        &AES_128,
1488        OperatingMode::ECB,
1489        "a1b7cd124f9824a1532d8440f8136788",
1490        "388118e6848b0cea97401707a754d7a1",
1491        "19b7c7f5d9c2bda3f957e9e7d20847828d5eb5624bcbf221014063a87b38d133",
1492        2,
1493        9
1494    );
1495
1496    streaming_cipher_kat!(
1497        test_openssl_aes_128_ecb_pkcs7_15_bytes,
1498        &AES_128,
1499        OperatingMode::ECB,
1500        "d10e12accb837aaffbb284448e53138c",
1501        "b21cfd1c9e6e7e6e912c82c7dd1aa8",
1502        "3d1168e61df34b51c6ab6745c20ee881",
1503        2,
1504        9
1505    );
1506
1507    streaming_cipher_kat!(
1508        test_openssl_aes_256_ecb_pkcs7_16_bytes,
1509        &AES_256,
1510        OperatingMode::ECB,
1511        "0600f4ad4eda4bc8e3e99592abdfce7eb08fee0ccc801c5ccee26134bcaafbbd",
1512        "516b45cb1342239a549bd8c1d5998f98",
1513        "854c593555a213e4a862c6f66aa4a79631faca131eba6f163e5cd3940e9c0a57",
1514        2,
1515        9
1516    );
1517
1518    streaming_cipher_kat!(
1519        test_openssl_aes_256_ecb_pkcs7_15_bytes,
1520        &AES_256,
1521        OperatingMode::ECB,
1522        "80f235756c8f70094ae1f99a95a599c27c4452a4b8412fd934e2b253f7098508",
1523        "2235590b90190d7a1dc2464a0205ad",
1524        "8547d8ac8dc6d9cebb2dc77a7034bb67",
1525        2,
1526        9
1527    );
1528
1529    #[test]
1530    fn test_new_rejects_none_context_for_iv_required_modes() {
1531        let key_bytes = [0u8; AES_128_KEY_LEN];
1532        for mode in [
1533            OperatingMode::CBC,
1534            OperatingMode::CTR,
1535            OperatingMode::CFB128,
1536        ] {
1537            let key = UnboundCipherKey::new(&AES_128, &key_bytes).unwrap();
1538            assert!(
1539                StreamingEncryptingKey::new(key, mode, EncryptionContext::None).is_err(),
1540                "AES + {mode:?} + EncryptionContext::None should be rejected"
1541            );
1542
1543            let key = UnboundCipherKey::new(&AES_128, &key_bytes).unwrap();
1544            assert!(
1545                StreamingDecryptingKey::new(key, mode, DecryptionContext::None).is_err(),
1546                "AES + {mode:?} + DecryptionContext::None should be rejected"
1547            );
1548        }
1549    }
1550
1551    #[test]
1552    fn test_new_accepts_none_context_for_ecb() {
1553        let key_bytes = [0u8; AES_128_KEY_LEN];
1554
1555        let key = UnboundCipherKey::new(&AES_128, &key_bytes).unwrap();
1556        assert!(
1557            StreamingEncryptingKey::new(key, OperatingMode::ECB, EncryptionContext::None).is_ok()
1558        );
1559
1560        let key = UnboundCipherKey::new(&AES_128, &key_bytes).unwrap();
1561        assert!(
1562            StreamingDecryptingKey::new(key, OperatingMode::ECB, DecryptionContext::None).is_ok()
1563        );
1564    }
1565
1566    #[cfg(feature = "legacy-des")]
1567    #[test]
1568    fn test_new_rejects_iv64_context_for_aes() {
1569        let key_bytes = [0u8; AES_128_KEY_LEN];
1570        for mode in [
1571            OperatingMode::CBC,
1572            OperatingMode::CTR,
1573            OperatingMode::CFB128,
1574        ] {
1575            let key = UnboundCipherKey::new(&AES_128, &key_bytes).unwrap();
1576            let context = EncryptionContext::Iv64(FixedLength::new().unwrap());
1577            assert!(
1578                StreamingEncryptingKey::new(key, mode, context).is_err(),
1579                "AES + {mode:?} + EncryptionContext::Iv64 should be rejected"
1580            );
1581
1582            let key = UnboundCipherKey::new(&AES_128, &key_bytes).unwrap();
1583            let context = DecryptionContext::Iv64(FixedLength::new().unwrap());
1584            assert!(
1585                StreamingDecryptingKey::new(key, mode, context).is_err(),
1586                "AES + {mode:?} + DecryptionContext::Iv64 should be rejected"
1587            );
1588        }
1589    }
1590
1591    #[cfg(feature = "legacy-des")]
1592    #[test]
1593    #[allow(deprecated)]
1594    fn test_new_rejects_iv128_context_for_des() {
1595        let key_bytes = from_hex("0123456789abcdef").unwrap();
1596
1597        let key = UnboundCipherKey::new(&DES_FOR_LEGACY_USE_ONLY, &key_bytes).unwrap();
1598        let context = EncryptionContext::Iv128(FixedLength::new().unwrap());
1599        assert!(
1600            StreamingEncryptingKey::new(key, OperatingMode::CBC, context).is_err(),
1601            "DES + CBC + EncryptionContext::Iv128 should be rejected"
1602        );
1603
1604        let key = UnboundCipherKey::new(&DES_FOR_LEGACY_USE_ONLY, &key_bytes).unwrap();
1605        let context = DecryptionContext::Iv128(FixedLength::new().unwrap());
1606        assert!(
1607            StreamingDecryptingKey::new(key, OperatingMode::CBC, context).is_err(),
1608            "DES + CBC + DecryptionContext::Iv128 should be rejected"
1609        );
1610    }
1611
1612    #[cfg(feature = "legacy-des")]
1613    #[test]
1614    #[allow(deprecated)]
1615    fn test_new_accepts_valid_des_contexts() {
1616        let key_bytes = from_hex("0123456789abcdef").unwrap();
1617
1618        let key = UnboundCipherKey::new(&DES_FOR_LEGACY_USE_ONLY, &key_bytes).unwrap();
1619        assert!(
1620            StreamingEncryptingKey::new(key, OperatingMode::ECB, EncryptionContext::None).is_ok()
1621        );
1622        let key = UnboundCipherKey::new(&DES_FOR_LEGACY_USE_ONLY, &key_bytes).unwrap();
1623        assert!(
1624            StreamingDecryptingKey::new(key, OperatingMode::ECB, DecryptionContext::None).is_ok()
1625        );
1626
1627        let key = UnboundCipherKey::new(&DES_FOR_LEGACY_USE_ONLY, &key_bytes).unwrap();
1628        let context = EncryptionContext::Iv64(FixedLength::new().unwrap());
1629        assert!(StreamingEncryptingKey::new(key, OperatingMode::CBC, context).is_ok());
1630
1631        let key = UnboundCipherKey::new(&DES_FOR_LEGACY_USE_ONLY, &key_bytes).unwrap();
1632        let context = DecryptionContext::Iv64(FixedLength::new().unwrap());
1633        assert!(StreamingDecryptingKey::new(key, OperatingMode::CBC, context).is_ok());
1634    }
1635}