Skip to main content

aws_lc_rs/cipher/
aes.rs

1// Copyright 2018 Brian Smith.
2// SPDX-License-Identifier: ISC
3// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
4// SPDX-License-Identifier: Apache-2.0 OR ISC
5
6use crate::aws_lc::{
7    AES_cbc_encrypt, AES_cfb128_encrypt, AES_cfb1_encrypt, AES_cfb8_encrypt, AES_ctr128_encrypt,
8    AES_ecb_encrypt, AES_ofb128_encrypt, AES_DECRYPT, AES_ENCRYPT, AES_KEY,
9};
10use crate::cipher::block::Block;
11use crate::error::Unspecified;
12use crate::fips::indicator_check;
13use zeroize::Zeroize;
14
15use super::{DecryptionContext, EncryptionContext, OperatingMode, SymmetricCipherKey};
16
17/// Length of an AES-128 key in bytes.
18pub const AES_128_KEY_LEN: usize = 16;
19
20/// Length of an AES-192 key in bytes.
21pub const AES_192_KEY_LEN: usize = 24;
22
23/// Length of an AES-256 key in bytes.
24pub const AES_256_KEY_LEN: usize = 32;
25
26/// The number of bytes for an AES-CBC initialization vector (IV)
27pub const AES_CBC_IV_LEN: usize = 16;
28
29/// The number of bytes for an AES-CTR initialization vector (IV)
30pub const AES_CTR_IV_LEN: usize = 16;
31
32/// The number of bytes for an AES-CFB initialization vector (IV)
33pub const AES_CFB_IV_LEN: usize = 16;
34
35/// The number of bytes for an AES-OFB initialization vector (IV)
36pub const AES_OFB_IV_LEN: usize = 16;
37
38pub const AES_BLOCK_LEN: usize = 16;
39
40#[inline]
41pub(crate) fn encrypt_block(aes_key: &AES_KEY, mut block: Block) -> Block {
42    {
43        let block_ref = block.as_mut();
44        debug_assert_eq!(block_ref.len(), AES_BLOCK_LEN);
45        aes_ecb_encrypt(aes_key, block_ref);
46    }
47    block
48}
49
50pub(super) fn encrypt_ctr_mode(
51    key: &SymmetricCipherKey,
52    context: EncryptionContext,
53    in_out: &mut [u8],
54) -> Result<DecryptionContext, Unspecified> {
55    let (SymmetricCipherKey::Aes128 { enc_key, .. }
56    | SymmetricCipherKey::Aes192 { enc_key, .. }
57    | SymmetricCipherKey::Aes256 { enc_key, .. }) = &key
58    else {
59        unreachable!()
60    };
61
62    let mut iv = {
63        let mut iv = [0u8; AES_CTR_IV_LEN];
64        iv.copy_from_slice((&context).try_into()?);
65        iv
66    };
67
68    let mut buffer = [0u8; AES_BLOCK_LEN];
69
70    aes_ctr128_encrypt(enc_key, &mut iv, &mut buffer, in_out);
71    iv.zeroize();
72
73    Ok(context.into())
74}
75
76pub(super) fn decrypt_ctr_mode<'in_out>(
77    key: &SymmetricCipherKey,
78    context: DecryptionContext,
79    in_out: &'in_out mut [u8],
80) -> Result<&'in_out mut [u8], Unspecified> {
81    // it's the same in CTR, just providing a nice named wrapper to match
82    encrypt_ctr_mode(key, context.into(), in_out).map(|_| in_out)
83}
84
85pub(super) fn encrypt_cbc_mode(
86    key: &SymmetricCipherKey,
87    context: EncryptionContext,
88    in_out: &mut [u8],
89) -> Result<DecryptionContext, Unspecified> {
90    let (SymmetricCipherKey::Aes128 { enc_key, .. }
91    | SymmetricCipherKey::Aes192 { enc_key, .. }
92    | SymmetricCipherKey::Aes256 { enc_key, .. }) = &key
93    else {
94        unreachable!()
95    };
96
97    let mut iv = {
98        let mut iv = [0u8; AES_CBC_IV_LEN];
99        iv.copy_from_slice((&context).try_into()?);
100        iv
101    };
102
103    aes_cbc_encrypt(enc_key, &mut iv, in_out);
104    iv.zeroize();
105
106    Ok(context.into())
107}
108
109#[allow(clippy::needless_pass_by_value)]
110pub(super) fn decrypt_cbc_mode<'in_out>(
111    key: &SymmetricCipherKey,
112    context: DecryptionContext,
113    in_out: &'in_out mut [u8],
114) -> Result<&'in_out mut [u8], Unspecified> {
115    let (SymmetricCipherKey::Aes128 { dec_key, .. }
116    | SymmetricCipherKey::Aes192 { dec_key, .. }
117    | SymmetricCipherKey::Aes256 { dec_key, .. }) = &key
118    else {
119        unreachable!()
120    };
121
122    let mut iv = {
123        let mut iv = [0u8; AES_CBC_IV_LEN];
124        iv.copy_from_slice((&context).try_into()?);
125        iv
126    };
127
128    aes_cbc_decrypt(dec_key, &mut iv, in_out);
129    iv.zeroize();
130
131    Ok(in_out)
132}
133
134pub(super) fn encrypt_cfb_mode(
135    key: &SymmetricCipherKey,
136    mode: OperatingMode,
137    context: EncryptionContext,
138    in_out: &mut [u8],
139) -> Result<DecryptionContext, Unspecified> {
140    let (SymmetricCipherKey::Aes128 { enc_key, .. }
141    | SymmetricCipherKey::Aes192 { enc_key, .. }
142    | SymmetricCipherKey::Aes256 { enc_key, .. }) = &key
143    else {
144        unreachable!()
145    };
146
147    let mut iv = {
148        let mut iv = [0u8; AES_CFB_IV_LEN];
149        iv.copy_from_slice((&context).try_into()?);
150        iv
151    };
152
153    let cfb_encrypt: fn(&AES_KEY, &mut [u8], &mut [u8]) = match mode {
154        OperatingMode::CFB128 => aes_cfb128_encrypt,
155        OperatingMode::CFB8 => aes_cfb8_encrypt,
156        OperatingMode::CFB1 => aes_cfb1_encrypt,
157        _ => unreachable!(),
158    };
159
160    cfb_encrypt(enc_key, &mut iv, in_out);
161    iv.zeroize();
162
163    Ok(context.into())
164}
165
166#[allow(clippy::needless_pass_by_value)]
167pub(super) fn decrypt_cfb_mode<'in_out>(
168    key: &SymmetricCipherKey,
169    mode: OperatingMode,
170    context: DecryptionContext,
171    in_out: &'in_out mut [u8],
172) -> Result<&'in_out mut [u8], Unspecified> {
173    let (SymmetricCipherKey::Aes128 { enc_key, .. }
174    | SymmetricCipherKey::Aes192 { enc_key, .. }
175    | SymmetricCipherKey::Aes256 { enc_key, .. }) = &key
176    else {
177        unreachable!()
178    };
179
180    let mut iv = {
181        let mut iv = [0u8; AES_CFB_IV_LEN];
182        iv.copy_from_slice((&context).try_into()?);
183        iv
184    };
185
186    let cfb_decrypt: fn(&AES_KEY, &mut [u8], &mut [u8]) = match mode {
187        OperatingMode::CFB128 => aes_cfb128_decrypt,
188        OperatingMode::CFB8 => aes_cfb8_decrypt,
189        OperatingMode::CFB1 => aes_cfb1_decrypt,
190        _ => unreachable!(),
191    };
192
193    cfb_decrypt(enc_key, &mut iv, in_out);
194
195    iv.zeroize();
196
197    Ok(in_out)
198}
199
200pub(super) fn encrypt_ecb_mode(
201    key: &SymmetricCipherKey,
202    context: EncryptionContext,
203    in_out: &mut [u8],
204) -> Result<DecryptionContext, Unspecified> {
205    if !matches!(context, EncryptionContext::None) {
206        return Err(Unspecified);
207    }
208
209    let (SymmetricCipherKey::Aes128 { enc_key, .. }
210    | SymmetricCipherKey::Aes192 { enc_key, .. }
211    | SymmetricCipherKey::Aes256 { enc_key, .. }) = &key
212    else {
213        unreachable!()
214    };
215
216    let mut in_out_iter = in_out.chunks_exact_mut(AES_BLOCK_LEN);
217
218    for block in in_out_iter.by_ref() {
219        aes_ecb_encrypt(enc_key, block);
220    }
221
222    // This is a sanity check that should not happen. We validate in `encrypt` that in_out.len() % block_len == 0
223    // for this mode.
224    debug_assert_eq!(in_out_iter.into_remainder(), []);
225
226    Ok(context.into())
227}
228
229#[allow(clippy::needless_pass_by_value)]
230pub(super) fn decrypt_ecb_mode<'in_out>(
231    key: &SymmetricCipherKey,
232    context: DecryptionContext,
233    in_out: &'in_out mut [u8],
234) -> Result<&'in_out mut [u8], Unspecified> {
235    if !matches!(context, DecryptionContext::None) {
236        return Err(Unspecified);
237    }
238
239    let (SymmetricCipherKey::Aes128 { dec_key, .. }
240    | SymmetricCipherKey::Aes192 { dec_key, .. }
241    | SymmetricCipherKey::Aes256 { dec_key, .. }) = &key
242    else {
243        unreachable!()
244    };
245
246    {
247        let mut in_out_iter = in_out.chunks_exact_mut(AES_BLOCK_LEN);
248
249        for block in in_out_iter.by_ref() {
250            aes_ecb_decrypt(dec_key, block);
251        }
252
253        // This is a sanity check that should not fail. We validate in `decrypt` that in_out.len() % block_len == 0 for
254        // this mode.
255        debug_assert_eq!(in_out_iter.into_remainder(), []);
256    }
257
258    Ok(in_out)
259}
260
261fn aes_ecb_encrypt(key: &AES_KEY, in_out: &mut [u8]) {
262    indicator_check!(unsafe {
263        AES_ecb_encrypt(in_out.as_ptr(), in_out.as_mut_ptr(), key, AES_ENCRYPT);
264    });
265}
266
267fn aes_ecb_decrypt(key: &AES_KEY, in_out: &mut [u8]) {
268    indicator_check!(unsafe {
269        AES_ecb_encrypt(in_out.as_ptr(), in_out.as_mut_ptr(), key, AES_DECRYPT);
270    });
271}
272
273fn aes_ctr128_encrypt(key: &AES_KEY, iv: &mut [u8], block_buffer: &mut [u8], in_out: &mut [u8]) {
274    let mut num: u32 = 0;
275
276    indicator_check!(unsafe {
277        AES_ctr128_encrypt(
278            in_out.as_ptr(),
279            in_out.as_mut_ptr(),
280            in_out.len(),
281            key,
282            iv.as_mut_ptr(),
283            block_buffer.as_mut_ptr(),
284            &mut num,
285        );
286    });
287
288    Zeroize::zeroize(block_buffer);
289}
290
291fn aes_cbc_encrypt(key: &AES_KEY, iv: &mut [u8], in_out: &mut [u8]) {
292    indicator_check!(unsafe {
293        AES_cbc_encrypt(
294            in_out.as_ptr(),
295            in_out.as_mut_ptr(),
296            in_out.len(),
297            key,
298            iv.as_mut_ptr(),
299            AES_ENCRYPT,
300        );
301    });
302}
303
304fn aes_cbc_decrypt(key: &AES_KEY, iv: &mut [u8], in_out: &mut [u8]) {
305    indicator_check!(unsafe {
306        AES_cbc_encrypt(
307            in_out.as_ptr(),
308            in_out.as_mut_ptr(),
309            in_out.len(),
310            key,
311            iv.as_mut_ptr(),
312            AES_DECRYPT,
313        );
314    });
315}
316
317fn aes_cfb128_encrypt(key: &AES_KEY, iv: &mut [u8], in_out: &mut [u8]) {
318    let mut num: i32 = 0;
319    indicator_check!(unsafe {
320        AES_cfb128_encrypt(
321            in_out.as_ptr(),
322            in_out.as_mut_ptr(),
323            in_out.len(),
324            key,
325            iv.as_mut_ptr(),
326            &mut num,
327            AES_ENCRYPT,
328        );
329    });
330}
331
332fn aes_cfb128_decrypt(key: &AES_KEY, iv: &mut [u8], in_out: &mut [u8]) {
333    let mut num: i32 = 0;
334    indicator_check!(unsafe {
335        AES_cfb128_encrypt(
336            in_out.as_ptr(),
337            in_out.as_mut_ptr(),
338            in_out.len(),
339            key,
340            iv.as_mut_ptr(),
341            &mut num,
342            AES_DECRYPT,
343        );
344    });
345}
346
347// AWS-LC does not update the FIPS service indicator for these AES modes, so they report as non-approved.
348fn aes_cfb8_encrypt(key: &AES_KEY, iv: &mut [u8], in_out: &mut [u8]) {
349    let mut num: i32 = 0;
350    indicator_check!(unsafe {
351        AES_cfb8_encrypt(
352            in_out.as_ptr(),
353            in_out.as_mut_ptr(),
354            in_out.len(),
355            key,
356            iv.as_mut_ptr(),
357            &mut num,
358            AES_ENCRYPT,
359        );
360    });
361}
362
363fn aes_cfb8_decrypt(key: &AES_KEY, iv: &mut [u8], in_out: &mut [u8]) {
364    let mut num: i32 = 0;
365    indicator_check!(unsafe {
366        AES_cfb8_encrypt(
367            in_out.as_ptr(),
368            in_out.as_mut_ptr(),
369            in_out.len(),
370            key,
371            iv.as_mut_ptr(),
372            &mut num,
373            AES_DECRYPT,
374        );
375    });
376}
377
378/// Bounds one `AES_cfb1_encrypt` call so the byte-to-bit conversion cannot overflow,
379/// which it would above `usize::MAX / 8` = 512 MiB (on a 32-bit target). Mirrors
380/// `MAXBITCHUNK` in AWS-LC's `crypto/decrepit/cfb/cfb.c`.
381const MAX_CFB1_CHUNK_LEN: usize = 1 << (usize::BITS - 4);
382
383/// `iv` is the feedback register and carries the state across chunks, so splitting the
384/// input does not change the result. `AES_cfb1_encrypt` serves both directions.
385fn cfb1_cipher(key: &AES_KEY, iv: &mut [u8], in_out: &mut [u8], enc: i32) {
386    cfb1_cipher_in_chunks(key, iv, in_out, enc, MAX_CFB1_CHUNK_LEN);
387}
388
389/// Chunk length is a parameter so tests can reach the multi-chunk path.
390fn cfb1_cipher_in_chunks(
391    key: &AES_KEY,
392    iv: &mut [u8],
393    in_out: &mut [u8],
394    enc: i32,
395    chunk_len: usize,
396) {
397    debug_assert!(chunk_len > 0 && chunk_len <= MAX_CFB1_CHUNK_LEN);
398    let mut num: i32 = 0;
399    indicator_check!({
400        for chunk in in_out.chunks_mut(chunk_len) {
401            unsafe {
402                AES_cfb1_encrypt(
403                    chunk.as_ptr(),
404                    chunk.as_mut_ptr(),
405                    chunk.len() * 8,
406                    key,
407                    iv.as_mut_ptr(),
408                    &mut num,
409                    enc,
410                );
411            }
412        }
413    });
414}
415
416fn aes_cfb1_encrypt(key: &AES_KEY, iv: &mut [u8], in_out: &mut [u8]) {
417    cfb1_cipher(key, iv, in_out, AES_ENCRYPT);
418}
419
420fn aes_cfb1_decrypt(key: &AES_KEY, iv: &mut [u8], in_out: &mut [u8]) {
421    cfb1_cipher(key, iv, in_out, AES_DECRYPT);
422}
423
424/// `AES_ofb128_encrypt` takes no direction argument: the keystream does not
425/// depend on the data, so OFB encryption and decryption are the same operation.
426fn aes_ofb128_encrypt(key: &AES_KEY, iv: &mut [u8], in_out: &mut [u8]) {
427    let mut num: i32 = 0;
428    indicator_check!(unsafe {
429        AES_ofb128_encrypt(
430            in_out.as_ptr(),
431            in_out.as_mut_ptr(),
432            in_out.len(),
433            key,
434            iv.as_mut_ptr(),
435            &mut num,
436        );
437    });
438}
439
440#[allow(clippy::needless_pass_by_value)]
441pub(super) fn encrypt_ofb_mode(
442    key: &SymmetricCipherKey,
443    context: EncryptionContext,
444    in_out: &mut [u8],
445) -> Result<DecryptionContext, Unspecified> {
446    let (SymmetricCipherKey::Aes128 { enc_key, .. }
447    | SymmetricCipherKey::Aes192 { enc_key, .. }
448    | SymmetricCipherKey::Aes256 { enc_key, .. }) = &key
449    else {
450        unreachable!()
451    };
452
453    let mut iv = {
454        let mut iv = [0u8; AES_OFB_IV_LEN];
455        iv.copy_from_slice((&context).try_into()?);
456        iv
457    };
458
459    aes_ofb128_encrypt(enc_key, &mut iv, in_out);
460    iv.zeroize();
461
462    Ok(context.into())
463}
464
465pub(super) fn decrypt_ofb_mode<'in_out>(
466    key: &SymmetricCipherKey,
467    context: DecryptionContext,
468    in_out: &'in_out mut [u8],
469) -> Result<&'in_out mut [u8], Unspecified> {
470    // it's the same in OFB, just providing a nice named wrapper to match
471    encrypt_ofb_mode(key, context.into(), in_out).map(|_| in_out)
472}
473
474#[cfg(test)]
475mod tests {
476    use super::{cfb1_cipher_in_chunks, MAX_CFB1_CHUNK_LEN};
477    use crate::aws_lc::{AES_DECRYPT, AES_ENCRYPT};
478    use crate::cipher::key::SymmetricCipherKey;
479
480    /// `MAX_CFB1_CHUNK_LEN` is 2^60 on a 64-bit target, so a small bound is used to
481    /// reach the loop at all.
482    #[test]
483    fn cfb1_chunking_does_not_change_the_result() {
484        let key_bytes = [
485            0x2bu8, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf,
486            0x4f, 0x3c,
487        ];
488        let key = SymmetricCipherKey::aes128(&key_bytes).unwrap();
489        let SymmetricCipherKey::Aes128 { enc_key, .. } = &key else {
490            unreachable!()
491        };
492        let plaintext: Vec<u8> = (0..64u8).collect();
493
494        let run = |input: &[u8], enc: i32, chunk_len: usize| {
495            let mut iv = [0u8; 16];
496            let mut buf = input.to_vec();
497            cfb1_cipher_in_chunks(enc_key, &mut iv, &mut buf, enc, chunk_len);
498            (buf, iv)
499        };
500
501        let (ciphertext, final_iv) = run(&plaintext, AES_ENCRYPT, MAX_CFB1_CHUNK_LEN);
502
503        for chunk_len in [1usize, 2, 3, 7, 16, 17, 63, 64] {
504            assert_eq!(
505                run(&plaintext, AES_ENCRYPT, chunk_len),
506                (ciphertext.clone(), final_iv),
507                "chunked encryption at {chunk_len} bytes changed the ciphertext or final IV"
508            );
509            assert_eq!(
510                run(&ciphertext, AES_DECRYPT, chunk_len),
511                (plaintext.clone(), final_iv),
512                "chunked decryption at {chunk_len} bytes changed the plaintext or final IV"
513            );
514        }
515    }
516}