/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 | | } |