aws_lc_rs/cipher/
padded.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0 OR ISC
3use crate::cipher;
4use crate::cipher::key::SymmetricCipherKey;
5use crate::cipher::{
6    Algorithm, DecryptionContext, EncryptionContext, OperatingMode, UnboundCipherKey,
7    MAX_CIPHER_BLOCK_LEN,
8};
9use crate::error::Unspecified;
10use core::fmt::Debug;
11
12/// The cipher block padding strategy.
13#[non_exhaustive]
14#[derive(Debug, PartialEq, Eq, Clone, Copy)]
15pub(crate) enum PaddingStrategy {
16    /// PKCS#7 Padding. ([See RFC 5652](https://datatracker.ietf.org/doc/html/rfc5652#section-6.3))
17    PKCS7,
18}
19
20impl PaddingStrategy {
21    fn add_padding<InOut>(self, block_len: usize, in_out: &mut InOut) -> Result<(), Unspecified>
22    where
23        InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
24    {
25        match self {
26            PaddingStrategy::PKCS7 => {
27                let mut padding_buffer = [0u8; MAX_CIPHER_BLOCK_LEN];
28
29                let in_out_len = in_out.as_mut().len();
30                // This implements PKCS#7 padding scheme, used by aws-lc if we were using EVP_CIPHER API's
31                let remainder = in_out_len % block_len;
32                let padding_size = block_len - remainder;
33                let v: u8 = padding_size.try_into().map_err(|_| Unspecified)?;
34                padding_buffer.fill(v);
35                // Possible heap allocation here :(
36                in_out.extend(padding_buffer[0..padding_size].iter());
37            }
38        }
39        Ok(())
40    }
41
42    fn remove_padding(self, block_len: usize, in_out: &mut [u8]) -> Result<&mut [u8], Unspecified> {
43        match self {
44            PaddingStrategy::PKCS7 => {
45                let block_size: u8 = block_len.try_into().map_err(|_| Unspecified)?;
46
47                if in_out.is_empty() || in_out.len() < block_len {
48                    return Err(Unspecified);
49                }
50
51                let padding: u8 = in_out[in_out.len() - 1];
52                if padding == 0 || padding > block_size {
53                    return Err(Unspecified);
54                }
55
56                for item in in_out.iter().skip(in_out.len() - padding as usize) {
57                    if *item != padding {
58                        return Err(Unspecified);
59                    }
60                }
61
62                let final_len = in_out.len() - padding as usize;
63                Ok(&mut in_out[0..final_len])
64            }
65        }
66    }
67}
68
69/// A cipher encryption key that performs block padding.
70pub struct PaddedBlockEncryptingKey {
71    algorithm: &'static Algorithm,
72    key: SymmetricCipherKey,
73    mode: OperatingMode,
74    padding: PaddingStrategy,
75}
76
77impl PaddedBlockEncryptingKey {
78    /// Constructs a new `PaddedBlockEncryptingKey` cipher with chaining block cipher (CBC) mode.
79    /// Plaintext data is padded following the PKCS#7 scheme.
80    ///
81    // # FIPS
82    // Use this function with an `UnboundCipherKey` constructed with one of the following algorithms:
83    // * `AES_128`
84    // * `AES_256`
85    //
86    /// # Errors
87    /// * [`Unspecified`]: Returned if there is an error constructing a `PaddedBlockEncryptingKey`.
88    pub fn cbc_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
89        Self::new(key, OperatingMode::CBC, PaddingStrategy::PKCS7)
90    }
91
92    /// Constructs a new `PaddedBlockEncryptingKey` cipher with electronic code book (ECB) mode.
93    /// Plaintext data is padded following the PKCS#7 scheme.
94    ///
95    /// # ☠️ ️️️DANGER ☠️
96    /// Offered for computability purposes only. This is an extremely dangerous mode, and
97    /// very likely not what you want to use.
98    ///
99    /// # Errors
100    /// * [`Unspecified`]: Returned if there is an error constructing a `PaddedBlockEncryptingKey`.
101    pub fn ecb_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
102        Self::new(key, OperatingMode::ECB, PaddingStrategy::PKCS7)
103    }
104
105    #[allow(clippy::unnecessary_wraps)]
106    fn new(
107        key: UnboundCipherKey,
108        mode: OperatingMode,
109        padding: PaddingStrategy,
110    ) -> Result<PaddedBlockEncryptingKey, Unspecified> {
111        let algorithm = key.algorithm();
112        let key = key.try_into()?;
113        Ok(Self {
114            algorithm,
115            key,
116            mode,
117            padding,
118        })
119    }
120
121    /// Returns the cipher algorithm.
122    #[must_use]
123    pub fn algorithm(&self) -> &Algorithm {
124        self.algorithm
125    }
126
127    /// Returns the cipher operating mode.
128    #[must_use]
129    pub fn mode(&self) -> OperatingMode {
130        self.mode
131    }
132
133    /// Pads and encrypts data provided in `in_out` in-place.
134    /// Returns a references to the encrypted data.
135    ///
136    /// # Errors
137    /// * [`Unspecified`]: Returned if encryption fails.
138    pub fn encrypt<InOut>(&self, in_out: &mut InOut) -> Result<DecryptionContext, Unspecified>
139    where
140        InOut: AsMut<[u8]> + for<'a> Extend<&'a u8>,
141    {
142        let context = self.algorithm.new_encryption_context(self.mode)?;
143        self.less_safe_encrypt(in_out, context)
144    }
145
146    /// Pads and encrypts data provided in `in_out` in-place.
147    /// Returns a references to the encryted data.
148    ///
149    /// # Errors
150    /// * [`Unspecified`]: Returned if encryption fails.
151    pub fn less_safe_encrypt<InOut>(
152        &self,
153        in_out: &mut InOut,
154        context: EncryptionContext,
155    ) -> Result<DecryptionContext, Unspecified>
156    where
157        InOut: AsMut<[u8]> + for<'a> Extend<&'a u8>,
158    {
159        if !self
160            .algorithm()
161            .is_valid_encryption_context(self.mode, &context)
162        {
163            return Err(Unspecified);
164        }
165
166        self.padding
167            .add_padding(self.algorithm().block_len(), in_out)?;
168        cipher::encrypt(
169            self.algorithm(),
170            &self.key,
171            self.mode,
172            in_out.as_mut(),
173            context,
174        )
175    }
176}
177
178impl Debug for PaddedBlockEncryptingKey {
179    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
180        f.debug_struct("PaddedBlockEncryptingKey")
181            .field("algorithm", &self.algorithm)
182            .field("mode", &self.mode)
183            .field("padding", &self.padding)
184            .finish_non_exhaustive()
185    }
186}
187
188/// A cipher decryption key that performs block padding.
189pub struct PaddedBlockDecryptingKey {
190    algorithm: &'static Algorithm,
191    key: SymmetricCipherKey,
192    mode: OperatingMode,
193    padding: PaddingStrategy,
194}
195
196impl PaddedBlockDecryptingKey {
197    /// Constructs a new `PaddedBlockDecryptingKey` cipher with chaining block cipher (CBC) mode.
198    /// Decrypted data is unpadded following the PKCS#7 scheme.
199    ///
200    // # FIPS
201    // Use this function with an `UnboundCipherKey` constructed with one of the following algorithms:
202    // * `AES_128`
203    // * `AES_256`
204    //
205    /// # Errors
206    /// * [`Unspecified`]: Returned if there is an error constructing the `PaddedBlockDecryptingKey`.
207    pub fn cbc_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
208        Self::new(key, OperatingMode::CBC, PaddingStrategy::PKCS7)
209    }
210
211    /// Constructs a new `PaddedBlockDecryptingKey` cipher with electronic code book (ECB) mode.
212    /// Decrypted data is unpadded following the PKCS#7 scheme.
213    ///
214    /// # ☠️ ️️️DANGER ☠️
215    /// Offered for computability purposes only. This is an extremely dangerous mode, and
216    /// very likely not what you want to use.
217    ///
218    // # FIPS
219    // Use this function with an `UnboundCipherKey` constructed with one of the following algorithms:
220    // * `AES_128`
221    // * `AES_256`
222    //
223    /// # Errors
224    /// * [`Unspecified`]: Returned if there is an error constructing the `PaddedBlockDecryptingKey`.
225    pub fn ecb_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
226        Self::new(key, OperatingMode::ECB, PaddingStrategy::PKCS7)
227    }
228
229    #[allow(clippy::unnecessary_wraps)]
230    fn new(
231        key: UnboundCipherKey,
232        mode: OperatingMode,
233        padding: PaddingStrategy,
234    ) -> Result<PaddedBlockDecryptingKey, Unspecified> {
235        let algorithm = key.algorithm();
236        let key = key.try_into()?;
237        Ok(PaddedBlockDecryptingKey {
238            algorithm,
239            key,
240            mode,
241            padding,
242        })
243    }
244
245    /// Returns the cipher algorithm.
246    #[must_use]
247    pub fn algorithm(&self) -> &Algorithm {
248        self.algorithm
249    }
250
251    /// Returns the cipher operating mode.
252    #[must_use]
253    pub fn mode(&self) -> OperatingMode {
254        self.mode
255    }
256
257    /// Decrypts and unpads data provided in `in_out` in-place.
258    /// Returns a references to the decrypted data.
259    ///
260    /// # Errors
261    /// * [`Unspecified`]: Returned if decryption fails.
262    pub fn decrypt<'in_out>(
263        &self,
264        in_out: &'in_out mut [u8],
265        context: DecryptionContext,
266    ) -> Result<&'in_out mut [u8], Unspecified> {
267        if !self
268            .algorithm()
269            .is_valid_decryption_context(self.mode, &context)
270        {
271            return Err(Unspecified);
272        }
273
274        let block_len = self.algorithm().block_len();
275        let padding = self.padding;
276        let mut in_out = cipher::decrypt(self.algorithm, &self.key, self.mode, in_out, context)?;
277        in_out = padding.remove_padding(block_len, in_out)?;
278        Ok(in_out)
279    }
280}
281
282impl Debug for PaddedBlockDecryptingKey {
283    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
284        f.debug_struct("PaddedBlockDecryptingKey")
285            .field("algorithm", &self.algorithm)
286            .field("mode", &self.mode)
287            .field("padding", &self.padding)
288            .finish_non_exhaustive()
289    }
290}
291
292#[cfg(test)]
293mod tests {
294    use crate::cipher::padded::PaddingStrategy;
295    use crate::cipher::{
296        Algorithm, EncryptionContext, OperatingMode, PaddedBlockDecryptingKey,
297        PaddedBlockEncryptingKey, UnboundCipherKey, AES_128, AES_256,
298    };
299    use crate::iv::FixedLength;
300    use crate::test::from_hex;
301
302    fn helper_test_padded_cipher_n_bytes(
303        key: &[u8],
304        alg: &'static Algorithm,
305        mode: OperatingMode,
306        padding: PaddingStrategy,
307        n: usize,
308    ) {
309        let mut input: Vec<u8> = Vec::with_capacity(n);
310        for i in 0..n {
311            let byte: u8 = i.try_into().unwrap();
312            input.push(byte);
313        }
314
315        let cipher_key = UnboundCipherKey::new(alg, key).unwrap();
316        let encrypting_key = PaddedBlockEncryptingKey::new(cipher_key, mode, padding).unwrap();
317
318        let mut in_out = input.clone();
319        let decrypt_iv = encrypting_key.encrypt(&mut in_out).unwrap();
320
321        if n > 5 {
322            // There's no more than a 1 in 2^48 chance that this will fail randomly
323            assert_ne!(input.as_slice(), in_out);
324        }
325
326        let cipher_key2 = UnboundCipherKey::new(alg, key).unwrap();
327        let decrypting_key = PaddedBlockDecryptingKey::new(cipher_key2, mode, padding).unwrap();
328
329        let plaintext = decrypting_key.decrypt(&mut in_out, decrypt_iv).unwrap();
330        assert_eq!(input.as_slice(), plaintext);
331    }
332
333    #[test]
334    fn test_aes_128_cbc() {
335        let key = from_hex("000102030405060708090a0b0c0d0e0f").unwrap();
336        for i in 0..=50 {
337            helper_test_padded_cipher_n_bytes(
338                key.as_slice(),
339                &AES_128,
340                OperatingMode::CBC,
341                PaddingStrategy::PKCS7,
342                i,
343            );
344        }
345    }
346
347    #[test]
348    fn test_aes_256_cbc() {
349        let key =
350            from_hex("000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f").unwrap();
351        for i in 0..=50 {
352            helper_test_padded_cipher_n_bytes(
353                key.as_slice(),
354                &AES_256,
355                OperatingMode::CBC,
356                PaddingStrategy::PKCS7,
357                i,
358            );
359        }
360    }
361
362    macro_rules! padded_cipher_kat {
363        ($name:ident, $alg:expr, $mode:expr, $padding:expr, $key:literal, $iv: literal, $plaintext:literal, $ciphertext:literal) => {
364            #[test]
365            fn $name() {
366                let key = from_hex($key).unwrap();
367                let input = from_hex($plaintext).unwrap();
368                let expected_ciphertext = from_hex($ciphertext).unwrap();
369                let mut iv = from_hex($iv).unwrap();
370                let iv = {
371                    let slice = iv.as_mut_slice();
372                    let mut iv = [0u8; $iv.len() / 2];
373                    {
374                        let x = iv.as_mut_slice();
375                        x.copy_from_slice(slice);
376                    }
377                    iv
378                };
379
380                let ec = EncryptionContext::Iv128(FixedLength::from(iv));
381
382                let alg = $alg;
383
384                let unbound_key = UnboundCipherKey::new(alg, &key).unwrap();
385
386                let encrypting_key =
387                    PaddedBlockEncryptingKey::new(unbound_key, $mode, $padding).unwrap();
388
389                let mut in_out = input.clone();
390
391                let context = encrypting_key.less_safe_encrypt(&mut in_out, ec).unwrap();
392
393                assert_eq!(expected_ciphertext, in_out);
394
395                let unbound_key2 = UnboundCipherKey::new(alg, &key).unwrap();
396                let decrypting_key =
397                    PaddedBlockDecryptingKey::new(unbound_key2, $mode, $padding).unwrap();
398
399                let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap();
400                assert_eq!(input.as_slice(), plaintext);
401            }
402        };
403    }
404
405    padded_cipher_kat!(
406        test_iv_aes_128_cbc_16_bytes,
407        &AES_128,
408        OperatingMode::CBC,
409        PaddingStrategy::PKCS7,
410        "000102030405060708090a0b0c0d0e0f",
411        "00000000000000000000000000000000",
412        "00112233445566778899aabbccddeeff",
413        "69c4e0d86a7b0430d8cdb78070b4c55a9e978e6d16b086570ef794ef97984232"
414    );
415
416    padded_cipher_kat!(
417        test_iv_aes_256_cbc_15_bytes,
418        &AES_256,
419        OperatingMode::CBC,
420        PaddingStrategy::PKCS7,
421        "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
422        "00000000000000000000000000000000",
423        "00112233445566778899aabbccddee",
424        "2ddfb635a651a43f582997966840ca0c"
425    );
426
427    padded_cipher_kat!(
428        test_openssl_aes_128_cbc_15_bytes,
429        &AES_128,
430        OperatingMode::CBC,
431        PaddingStrategy::PKCS7,
432        "053304bb3899e1d99db9d29343ea782d",
433        "b5313560244a4822c46c2a0c9d0cf7fd",
434        "a3e4c990356c01f320043c3d8d6f43",
435        "ad96993f248bd6a29760ec7ccda95ee1"
436    );
437
438    padded_cipher_kat!(
439        test_openssl_aes_128_cbc_16_bytes,
440        &AES_128,
441        OperatingMode::CBC,
442        PaddingStrategy::PKCS7,
443        "95af71f1c63e4a1d0b0b1a27fb978283",
444        "89e40797dca70197ff87d3dbb0ef2802",
445        "aece7b5e3c3df1ffc9802d2dfe296dc7",
446        "301b5dab49fb11e919d0d39970d06739301919743304f23f3cbc67d28564b25b"
447    );
448
449    padded_cipher_kat!(
450        test_openssl_aes_256_cbc_15_bytes,
451        &AES_256,
452        OperatingMode::CBC,
453        PaddingStrategy::PKCS7,
454        "d369e03e9752784917cc7bac1db7399598d9555e691861d9dd7b3292a693ef57",
455        "1399bb66b2f6ad99a7f064140eaaa885",
456        "7385f5784b85bf0a97768ddd896d6d",
457        "4351082bac9b4593ae8848cc9dfb5a01"
458    );
459
460    padded_cipher_kat!(
461        test_openssl_aes_256_cbc_16_bytes,
462        &AES_256,
463        OperatingMode::CBC,
464        PaddingStrategy::PKCS7,
465        "d4a8206dcae01242f9db79a4ecfe277d0f7bb8ccbafd8f9809adb39f35aa9b41",
466        "24f6076548fb9d93c8f7ed9f6e661ef9",
467        "a39c1fdf77ea3e1f18178c0ec237c70a",
468        "f1af484830a149ee0387b854d65fe87ca0e62efc1c8e6909d4b9ab8666470453"
469    );
470}