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