Coverage Report

Created: 2026-02-14 06:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/aws-lc-rs-1.12.4/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
    /// PKCS#7 Padding. ([See RFC 5652](https://datatracker.ietf.org/doc/html/rfc5652#section-6.3))
17
    PKCS7,
18
}
19
20
impl PaddingStrategy {
21
0
    fn add_padding<InOut>(self, block_len: usize, in_out: &mut InOut) -> Result<(), Unspecified>
22
0
    where
23
0
        InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
24
    {
25
0
        match self {
26
            PaddingStrategy::PKCS7 => {
27
0
                let mut padding_buffer = [0u8; MAX_CIPHER_BLOCK_LEN];
28
29
0
                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
0
                let remainder = in_out_len % block_len;
32
0
                let padding_size = block_len - remainder;
33
0
                let v: u8 = padding_size.try_into().map_err(|_| Unspecified)?;
34
0
                padding_buffer.fill(v);
35
                // Possible heap allocation here :(
36
0
                in_out.extend(padding_buffer[0..padding_size].iter());
37
            }
38
        }
39
0
        Ok(())
40
0
    }
Unexecuted instantiation: <aws_lc_rs::cipher::padded::PaddingStrategy>::add_padding::<alloc::vec::Vec<u8>>
Unexecuted instantiation: <aws_lc_rs::cipher::padded::PaddingStrategy>::add_padding::<_>
41
42
0
    fn remove_padding(self, block_len: usize, in_out: &mut [u8]) -> Result<&mut [u8], Unspecified> {
43
0
        match self {
44
            PaddingStrategy::PKCS7 => {
45
0
                let block_size: u8 = block_len.try_into().map_err(|_| Unspecified)?;
46
47
0
                if in_out.is_empty() || in_out.len() < block_len {
48
0
                    return Err(Unspecified);
49
0
                }
50
51
0
                let padding: u8 = in_out[in_out.len() - 1];
52
0
                if padding == 0 || padding > block_size {
53
0
                    return Err(Unspecified);
54
0
                }
55
56
0
                for item in in_out.iter().skip(in_out.len() - padding as usize) {
57
0
                    if *item != padding {
58
0
                        return Err(Unspecified);
59
0
                    }
60
                }
61
62
0
                let final_len = in_out.len() - padding as usize;
63
0
                Ok(&mut in_out[0..final_len])
64
            }
65
        }
66
0
    }
67
}
68
69
/// A cipher encryption key that performs block padding.
70
pub struct PaddedBlockEncryptingKey {
71
    algorithm: &'static Algorithm,
72
    key: SymmetricCipherKey,
73
    mode: OperatingMode,
74
    padding: PaddingStrategy,
75
}
76
77
impl 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
0
    pub fn cbc_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
89
0
        Self::new(key, OperatingMode::CBC, PaddingStrategy::PKCS7)
90
0
    }
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
0
    pub fn ecb_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
102
0
        Self::new(key, OperatingMode::ECB, PaddingStrategy::PKCS7)
103
0
    }
104
105
    #[allow(clippy::unnecessary_wraps)]
106
0
    fn new(
107
0
        key: UnboundCipherKey,
108
0
        mode: OperatingMode,
109
0
        padding: PaddingStrategy,
110
0
    ) -> Result<PaddedBlockEncryptingKey, Unspecified> {
111
0
        let algorithm = key.algorithm();
112
0
        let key = key.try_into()?;
113
0
        Ok(Self {
114
0
            algorithm,
115
0
            key,
116
0
            mode,
117
0
            padding,
118
0
        })
119
0
    }
120
121
    /// Returns the cipher algorithm.
122
    #[must_use]
123
0
    pub fn algorithm(&self) -> &Algorithm {
124
0
        self.algorithm
125
0
    }
126
127
    /// Returns the cipher operating mode.
128
    #[must_use]
129
0
    pub fn mode(&self) -> OperatingMode {
130
0
        self.mode
131
0
    }
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
0
    pub fn encrypt<InOut>(&self, in_out: &mut InOut) -> Result<DecryptionContext, Unspecified>
139
0
    where
140
0
        InOut: AsMut<[u8]> + for<'a> Extend<&'a u8>,
141
    {
142
0
        let context = self.algorithm.new_encryption_context(self.mode)?;
143
0
        self.less_safe_encrypt(in_out, context)
144
0
    }
Unexecuted instantiation: <aws_lc_rs::cipher::padded::PaddedBlockEncryptingKey>::encrypt::<alloc::vec::Vec<u8>>
Unexecuted instantiation: <aws_lc_rs::cipher::padded::PaddedBlockEncryptingKey>::encrypt::<_>
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
0
    pub fn less_safe_encrypt<InOut>(
152
0
        &self,
153
0
        in_out: &mut InOut,
154
0
        context: EncryptionContext,
155
0
    ) -> Result<DecryptionContext, Unspecified>
156
0
    where
157
0
        InOut: AsMut<[u8]> + for<'a> Extend<&'a u8>,
158
    {
159
0
        if !self
160
0
            .algorithm()
161
0
            .is_valid_encryption_context(self.mode, &context)
162
        {
163
0
            return Err(Unspecified);
164
0
        }
165
166
0
        self.padding
167
0
            .add_padding(self.algorithm().block_len(), in_out)?;
168
0
        cipher::encrypt(
169
0
            self.algorithm(),
170
0
            &self.key,
171
0
            self.mode,
172
0
            in_out.as_mut(),
173
0
            context,
174
        )
175
0
    }
Unexecuted instantiation: <aws_lc_rs::cipher::padded::PaddedBlockEncryptingKey>::less_safe_encrypt::<alloc::vec::Vec<u8>>
Unexecuted instantiation: <aws_lc_rs::cipher::padded::PaddedBlockEncryptingKey>::less_safe_encrypt::<_>
176
}
177
178
impl Debug for PaddedBlockEncryptingKey {
179
0
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
180
0
        f.debug_struct("PaddedBlockEncryptingKey")
181
0
            .field("algorithm", &self.algorithm)
182
0
            .field("mode", &self.mode)
183
0
            .field("padding", &self.padding)
184
0
            .finish_non_exhaustive()
185
0
    }
186
}
187
188
/// A cipher decryption key that performs block padding.
189
pub struct PaddedBlockDecryptingKey {
190
    algorithm: &'static Algorithm,
191
    key: SymmetricCipherKey,
192
    mode: OperatingMode,
193
    padding: PaddingStrategy,
194
}
195
196
impl 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
0
    pub fn cbc_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
208
0
        Self::new(key, OperatingMode::CBC, PaddingStrategy::PKCS7)
209
0
    }
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
0
    pub fn ecb_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
226
0
        Self::new(key, OperatingMode::ECB, PaddingStrategy::PKCS7)
227
0
    }
228
229
    #[allow(clippy::unnecessary_wraps)]
230
0
    fn new(
231
0
        key: UnboundCipherKey,
232
0
        mode: OperatingMode,
233
0
        padding: PaddingStrategy,
234
0
    ) -> Result<PaddedBlockDecryptingKey, Unspecified> {
235
0
        let algorithm = key.algorithm();
236
0
        let key = key.try_into()?;
237
0
        Ok(PaddedBlockDecryptingKey {
238
0
            algorithm,
239
0
            key,
240
0
            mode,
241
0
            padding,
242
0
        })
243
0
    }
244
245
    /// Returns the cipher algorithm.
246
    #[must_use]
247
0
    pub fn algorithm(&self) -> &Algorithm {
248
0
        self.algorithm
249
0
    }
250
251
    /// Returns the cipher operating mode.
252
    #[must_use]
253
0
    pub fn mode(&self) -> OperatingMode {
254
0
        self.mode
255
0
    }
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
0
    pub fn decrypt<'in_out>(
263
0
        &self,
264
0
        in_out: &'in_out mut [u8],
265
0
        context: DecryptionContext,
266
0
    ) -> Result<&'in_out mut [u8], Unspecified> {
267
0
        if !self
268
0
            .algorithm()
269
0
            .is_valid_decryption_context(self.mode, &context)
270
        {
271
0
            return Err(Unspecified);
272
0
        }
273
274
0
        let block_len = self.algorithm().block_len();
275
0
        let padding = self.padding;
276
0
        let mut in_out = cipher::decrypt(self.algorithm, &self.key, self.mode, in_out, context)?;
277
0
        in_out = padding.remove_padding(block_len, in_out)?;
278
0
        Ok(in_out)
279
0
    }
280
}
281
282
impl Debug for PaddedBlockDecryptingKey {
283
0
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
284
0
        f.debug_struct("PaddedBlockDecryptingKey")
285
0
            .field("algorithm", &self.algorithm)
286
0
            .field("mode", &self.mode)
287
0
            .field("padding", &self.padding)
288
0
            .finish_non_exhaustive()
289
0
    }
290
}
291
292
#[cfg(test)]
293
mod 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
}