Coverage Report

Created: 2025-11-16 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/aws-lc-rs-1.14.1/src/cipher/padded.rs
Line
Count
Source
1
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
// SPDX-License-Identifier: Apache-2.0 OR ISC
3
use crate::cipher;
4
use crate::cipher::key::SymmetricCipherKey;
5
use crate::cipher::{
6
    Algorithm, DecryptionContext, EncryptionContext, OperatingMode, UnboundCipherKey,
7
    MAX_CIPHER_BLOCK_LEN,
8
};
9
use crate::error::Unspecified;
10
use core::fmt::Debug;
11
12
/// The cipher block padding strategy.
13
#[non_exhaustive]
14
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
15
pub(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
22
impl PaddingStrategy {
23
0
    fn add_padding<InOut>(self, block_len: usize, in_out: &mut InOut) -> Result<(), Unspecified>
24
0
    where
25
0
        InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
26
    {
27
0
        match self {
28
            // PKCS7 padding can be unpadded as ISO 10126 padding
29
            PaddingStrategy::ISO10126 | PaddingStrategy::PKCS7 => {
30
0
                let mut padding_buffer = [0u8; MAX_CIPHER_BLOCK_LEN];
31
32
0
                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
0
                let remainder = in_out_len % block_len;
35
0
                let padding_size = block_len - remainder;
36
0
                let v: u8 = padding_size.try_into().map_err(|_| Unspecified)?;
37
0
                padding_buffer.fill(v);
38
                // Possible heap allocation here :(
39
0
                in_out.extend(padding_buffer[0..padding_size].iter());
40
            }
41
        }
42
0
        Ok(())
43
0
    }
44
45
0
    fn remove_padding(self, block_len: usize, in_out: &mut [u8]) -> Result<&mut [u8], Unspecified> {
46
0
        if in_out.is_empty() || in_out.len() < block_len {
47
0
            return Err(Unspecified);
48
0
        }
49
0
        match self {
50
            PaddingStrategy::ISO10126 => {
51
0
                let padding: u8 = in_out[in_out.len() - 1];
52
0
                if padding == 0 || padding as usize > block_len {
53
0
                    return Err(Unspecified);
54
0
                }
55
56
                // ISO 10126 padding is a random padding scheme, so we cannot verify the padding bytes
57
0
                let final_len = in_out.len() - padding as usize;
58
0
                Ok(&mut in_out[0..final_len])
59
            }
60
            PaddingStrategy::PKCS7 => {
61
0
                let block_size: u8 = block_len.try_into().map_err(|_| Unspecified)?;
62
63
0
                let padding: u8 = in_out[in_out.len() - 1];
64
0
                if padding == 0 || padding > block_size {
65
0
                    return Err(Unspecified);
66
0
                }
67
68
0
                for item in in_out.iter().skip(in_out.len() - padding as usize) {
69
0
                    if *item != padding {
70
0
                        return Err(Unspecified);
71
0
                    }
72
                }
73
74
0
                let final_len = in_out.len() - padding as usize;
75
0
                Ok(&mut in_out[0..final_len])
76
            }
77
        }
78
0
    }
79
}
80
81
/// A cipher encryption key that performs block padding.
82
pub struct PaddedBlockEncryptingKey {
83
    algorithm: &'static Algorithm,
84
    key: SymmetricCipherKey,
85
    mode: OperatingMode,
86
    padding: PaddingStrategy,
87
}
88
89
impl 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
0
    pub fn cbc_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
101
0
        Self::new(key, OperatingMode::CBC, PaddingStrategy::PKCS7)
102
0
    }
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
0
    pub fn ecb_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
114
0
        Self::new(key, OperatingMode::ECB, PaddingStrategy::PKCS7)
115
0
    }
116
117
    #[allow(clippy::unnecessary_wraps)]
118
0
    fn new(
119
0
        key: UnboundCipherKey,
120
0
        mode: OperatingMode,
121
0
        padding: PaddingStrategy,
122
0
    ) -> Result<PaddedBlockEncryptingKey, Unspecified> {
123
0
        let algorithm = key.algorithm();
124
0
        let key = key.try_into()?;
125
0
        Ok(Self {
126
0
            algorithm,
127
0
            key,
128
0
            mode,
129
0
            padding,
130
0
        })
131
0
    }
132
133
    /// Returns the cipher algorithm.
134
    #[must_use]
135
0
    pub fn algorithm(&self) -> &Algorithm {
136
0
        self.algorithm
137
0
    }
138
139
    /// Returns the cipher operating mode.
140
    #[must_use]
141
0
    pub fn mode(&self) -> OperatingMode {
142
0
        self.mode
143
0
    }
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
0
    pub fn encrypt<InOut>(&self, in_out: &mut InOut) -> Result<DecryptionContext, Unspecified>
151
0
    where
152
0
        InOut: AsMut<[u8]> + for<'a> Extend<&'a u8>,
153
    {
154
0
        let context = self.algorithm.new_encryption_context(self.mode)?;
155
0
        self.less_safe_encrypt(in_out, context)
156
0
    }
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
0
    pub fn less_safe_encrypt<InOut>(
164
0
        &self,
165
0
        in_out: &mut InOut,
166
0
        context: EncryptionContext,
167
0
    ) -> Result<DecryptionContext, Unspecified>
168
0
    where
169
0
        InOut: AsMut<[u8]> + for<'a> Extend<&'a u8>,
170
    {
171
0
        if !self
172
0
            .algorithm()
173
0
            .is_valid_encryption_context(self.mode, &context)
174
        {
175
0
            return Err(Unspecified);
176
0
        }
177
178
0
        self.padding
179
0
            .add_padding(self.algorithm().block_len(), in_out)?;
180
0
        cipher::encrypt(
181
0
            self.algorithm(),
182
0
            &self.key,
183
0
            self.mode,
184
0
            in_out.as_mut(),
185
0
            context,
186
        )
187
0
    }
188
}
189
190
impl Debug for PaddedBlockEncryptingKey {
191
0
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
192
0
        f.debug_struct("PaddedBlockEncryptingKey")
193
0
            .field("algorithm", &self.algorithm)
194
0
            .field("mode", &self.mode)
195
0
            .field("padding", &self.padding)
196
0
            .finish_non_exhaustive()
197
0
    }
198
}
199
200
/// A cipher decryption key that performs block padding.
201
pub struct PaddedBlockDecryptingKey {
202
    algorithm: &'static Algorithm,
203
    key: SymmetricCipherKey,
204
    mode: OperatingMode,
205
    padding: PaddingStrategy,
206
}
207
208
impl 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
0
    pub fn cbc_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
220
0
        Self::new(key, OperatingMode::CBC, PaddingStrategy::PKCS7)
221
0
    }
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
0
    pub fn cbc_iso10126(key: UnboundCipherKey) -> Result<Self, Unspecified> {
237
0
        Self::new(key, OperatingMode::CBC, PaddingStrategy::ISO10126)
238
0
    }
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
0
    pub fn ecb_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
255
0
        Self::new(key, OperatingMode::ECB, PaddingStrategy::PKCS7)
256
0
    }
257
258
    #[allow(clippy::unnecessary_wraps)]
259
0
    fn new(
260
0
        key: UnboundCipherKey,
261
0
        mode: OperatingMode,
262
0
        padding: PaddingStrategy,
263
0
    ) -> Result<PaddedBlockDecryptingKey, Unspecified> {
264
0
        let algorithm = key.algorithm();
265
0
        let key = key.try_into()?;
266
0
        Ok(PaddedBlockDecryptingKey {
267
0
            algorithm,
268
0
            key,
269
0
            mode,
270
0
            padding,
271
0
        })
272
0
    }
273
274
    /// Returns the cipher algorithm.
275
    #[must_use]
276
0
    pub fn algorithm(&self) -> &Algorithm {
277
0
        self.algorithm
278
0
    }
279
280
    /// Returns the cipher operating mode.
281
    #[must_use]
282
0
    pub fn mode(&self) -> OperatingMode {
283
0
        self.mode
284
0
    }
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
0
    pub fn decrypt<'in_out>(
292
0
        &self,
293
0
        in_out: &'in_out mut [u8],
294
0
        context: DecryptionContext,
295
0
    ) -> Result<&'in_out mut [u8], Unspecified> {
296
0
        if !self
297
0
            .algorithm()
298
0
            .is_valid_decryption_context(self.mode, &context)
299
        {
300
0
            return Err(Unspecified);
301
0
        }
302
303
0
        let block_len = self.algorithm().block_len();
304
0
        let padding = self.padding;
305
0
        let mut in_out = cipher::decrypt(self.algorithm, &self.key, self.mode, in_out, context)?;
306
0
        in_out = padding.remove_padding(block_len, in_out)?;
307
0
        Ok(in_out)
308
0
    }
309
}
310
311
impl Debug for PaddedBlockDecryptingKey {
312
0
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
313
0
        f.debug_struct("PaddedBlockDecryptingKey")
314
0
            .field("algorithm", &self.algorithm)
315
0
            .field("mode", &self.mode)
316
0
            .field("padding", &self.padding)
317
0
            .finish_non_exhaustive()
318
0
    }
319
}
320
321
#[cfg(test)]
322
mod 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
}