diff --git a/aws-lc-rs-testing/benches/kem_benchmark.rs b/aws-lc-rs-testing/benches/kem_benchmark.rs index e906b3d2723..afe708156a2 100644 --- a/aws-lc-rs-testing/benches/kem_benchmark.rs +++ b/aws-lc-rs-testing/benches/kem_benchmark.rs @@ -7,6 +7,7 @@ use aws_lc_rs::{ }; use criterion::{criterion_group, criterion_main, Criterion}; +#[allow(deprecated)] const UNSTABLE_ALGORITHMS: &[Option<&aws_lc_rs::kem::Algorithm>] = &[ get_algorithm(AlgorithmId::Kyber512_R3), get_algorithm(AlgorithmId::Kyber768_R3), diff --git a/aws-lc-rs/src/cipher.rs b/aws-lc-rs/src/cipher.rs index 31d85865b27..e5c8608dc42 100644 --- a/aws-lc-rs/src/cipher.rs +++ b/aws-lc-rs/src/cipher.rs @@ -11,10 +11,10 @@ //! The modes provided here only provide confidentiality, but **do not** //! provide integrity or authentication verification of ciphertext. //! -//! These algorithms are provided solely for applications requring them -//! in order to maintain backwards compatability in legacy applications. +//! These algorithms are provided solely for applications requiring them +//! in order to maintain backwards compatibility in legacy applications. //! -//! If you are developing new applications requring data encryption see +//! If you are developing new applications requiring data encryption see //! the algorithms provided in [`aead`](crate::aead). //! //! # Examples @@ -135,6 +135,35 @@ //! # } //! ``` //! +//! ### AES-128 CFB 128-bit mode +//! +//! ```rust +//! # use std::error::Error; +//! # +//! # fn main() -> Result<(), Box> { +//! use aws_lc_rs::cipher::{DecryptingKey, EncryptingKey, UnboundCipherKey, AES_128}; +//! +//! let original_message = "This is a secret message!".as_bytes(); +//! let mut in_out_buffer = Vec::from(original_message); +//! +//! let key_bytes: &[u8] = &[ +//! 0xff, 0x0b, 0xe5, 0x84, 0x64, 0x0b, 0x00, 0xc8, 0x90, 0x7a, 0x4b, 0xbf, 0x82, 0x7c, 0xb6, +//! 0xd1, +//! ]; +//! +//! let key = UnboundCipherKey::new(&AES_128, key_bytes)?; +//! let mut encrypting_key = EncryptingKey::cfb128(key)?; +//! let context = encrypting_key.encrypt(&mut in_out_buffer)?; +//! +//! let key = UnboundCipherKey::new(&AES_128, key_bytes)?; +//! let mut decrypting_key = DecryptingKey::cfb128(key)?; +//! let plaintext = decrypting_key.decrypt(&mut in_out_buffer, context)?; +//! assert_eq!(original_message, plaintext); +//! # +//! # Ok(()) +//! # } +//! ``` +//! //! ## Constructing a `DecryptionContext` for decryption. //! //! ```rust @@ -207,11 +236,11 @@ use crate::hkdf::KeyType; use crate::iv::{FixedLength, IV_LEN_128_BIT}; use crate::ptr::ConstPointer; use aws_lc::{ - AES_cbc_encrypt, AES_ctr128_encrypt, EVP_aes_128_cbc, EVP_aes_128_ctr, EVP_aes_256_cbc, - EVP_aes_256_ctr, AES_DECRYPT, AES_ENCRYPT, AES_KEY, EVP_CIPHER, + AES_cbc_encrypt, AES_cfb128_encrypt, AES_ctr128_encrypt, EVP_aes_128_cbc, EVP_aes_128_cfb128, + EVP_aes_128_ctr, EVP_aes_256_cbc, EVP_aes_256_cfb128, EVP_aes_256_ctr, AES_DECRYPT, + AES_ENCRYPT, AES_KEY, EVP_CIPHER, }; use core::fmt::Debug; -use core::mem::MaybeUninit; use key::SymmetricCipherKey; use zeroize::Zeroize; @@ -228,6 +257,10 @@ pub const AES_CBC_IV_LEN: usize = 16; /// The number of bytes for an AES-CTR initialization vector (IV) pub const AES_CTR_IV_LEN: usize = 16; + +/// The number of bytes for an AES-CFB initialization vector (IV) +pub const AES_CFB_IV_LEN: usize = 16; + const AES_BLOCK_LEN: usize = 16; const MAX_CIPHER_BLOCK_LEN: usize = AES_BLOCK_LEN; @@ -241,6 +274,9 @@ pub enum OperatingMode { /// Counter (CTR) mode. CTR, + + /// CFB 128-bit mode. + CFB128, } impl OperatingMode { @@ -249,8 +285,10 @@ impl OperatingMode { ConstPointer::new(match (self, algorithm.id) { (OperatingMode::CBC, AlgorithmId::Aes128) => unsafe { EVP_aes_128_cbc() }, (OperatingMode::CTR, AlgorithmId::Aes128) => unsafe { EVP_aes_128_ctr() }, + (OperatingMode::CFB128, AlgorithmId::Aes128) => unsafe { EVP_aes_128_cfb128() }, (OperatingMode::CBC, AlgorithmId::Aes256) => unsafe { EVP_aes_256_cbc() }, (OperatingMode::CTR, AlgorithmId::Aes256) => unsafe { EVP_aes_256_ctr() }, + (OperatingMode::CFB128, AlgorithmId::Aes256) => unsafe { EVP_aes_256_cfb128() }, }) .unwrap() } @@ -345,8 +383,9 @@ impl Algorithm { mode: OperatingMode, ) -> Result { match self.id { + // TODO: Hopefully support CFB1, and CFB8 AlgorithmId::Aes128 | AlgorithmId::Aes256 => match mode { - OperatingMode::CBC | OperatingMode::CTR => { + OperatingMode::CBC | OperatingMode::CTR | OperatingMode::CFB128 => { Ok(EncryptionContext::Iv128(FixedLength::new()?)) } }, @@ -355,8 +394,9 @@ impl Algorithm { fn is_valid_encryption_context(&self, mode: OperatingMode, input: &EncryptionContext) -> bool { match self.id { + // TODO: Hopefully support CFB1, and CFB8 AlgorithmId::Aes128 | AlgorithmId::Aes256 => match mode { - OperatingMode::CBC | OperatingMode::CTR => { + OperatingMode::CBC | OperatingMode::CTR | OperatingMode::CFB128 => { matches!(input, EncryptionContext::Iv128(_)) } }, @@ -364,9 +404,10 @@ impl Algorithm { } fn is_valid_decryption_context(&self, mode: OperatingMode, input: &DecryptionContext) -> bool { + // TODO: Hopefully support CFB1, and CFB8 match self.id { AlgorithmId::Aes128 | AlgorithmId::Aes256 => match mode { - OperatingMode::CBC | OperatingMode::CTR => { + OperatingMode::CBC | OperatingMode::CTR | OperatingMode::CFB128 => { matches!(input, DecryptionContext::Iv128(_)) } }, @@ -459,6 +500,19 @@ impl EncryptingKey { EncryptingKey::new(key, OperatingMode::CTR) } + /// Constructs an `EncryptingKey` operating in cipher feedback 128-bit mode (CFB128) using the provided key. + /// + // # FIPS + // Use this function with an `UnboundCipherKey` constructed with one of the following algorithms: + // * `AES_128` + // * `AES_256` + // + /// # Errors + /// * [`Unspecified`]: Returned if there is an error constructing the `EncryptingKey`. + pub fn cfb128(key: UnboundCipherKey) -> Result { + EncryptingKey::new(key, OperatingMode::CFB128) + } + #[allow(clippy::unnecessary_wraps)] fn new(key: UnboundCipherKey, mode: OperatingMode) -> Result { let algorithm = key.algorithm(); @@ -547,6 +601,19 @@ impl DecryptingKey { DecryptingKey::new(key, OperatingMode::CTR) } + /// Constructs a cipher decrypting key operating in cipher feedback 128-bit mode (CFB128) using the provided key and context. + /// + // # FIPS + // Use this function with an `UnboundCipherKey` constructed with one of the following algorithms: + // * `AES_128` + // * `AES_256` + // + /// # Errors + /// * [`Unspecified`]: Returned if there is an error during decryption. + pub fn cfb128(key: UnboundCipherKey) -> Result { + DecryptingKey::new(key, OperatingMode::CFB128) + } + #[allow(clippy::unnecessary_wraps)] fn new(key: UnboundCipherKey, mode: OperatingMode) -> Result { let algorithm = key.algorithm(); @@ -603,13 +670,8 @@ fn encrypt( ) -> Result { let block_len = algorithm.block_len(); - match mode { - OperatingMode::CTR => {} - _ => { - if (in_out.len() % block_len) != 0 { - return Err(Unspecified); - } - } + if mode == OperatingMode::CBC && (in_out.len() % block_len) != 0 { + return Err(Unspecified); } match mode { @@ -619,6 +681,12 @@ fn encrypt( OperatingMode::CTR => match algorithm.id() { AlgorithmId::Aes128 | AlgorithmId::Aes256 => encrypt_aes_ctr_mode(key, context, in_out), }, + // TODO: Hopefully support CFB1, and CFB8 + OperatingMode::CFB128 => match algorithm.id() { + AlgorithmId::Aes128 | AlgorithmId::Aes256 => { + encrypt_aes_cfb_mode(key, mode, context, in_out) + } + }, } } @@ -631,13 +699,8 @@ fn decrypt<'in_out>( ) -> Result<&'in_out mut [u8], Unspecified> { let block_len = algorithm.block_len(); - match mode { - OperatingMode::CTR => {} - _ => { - if (in_out.len() % block_len) != 0 { - return Err(Unspecified); - } - } + if mode == OperatingMode::CBC && (in_out.len() % block_len) != 0 { + return Err(Unspecified); } match mode { @@ -647,6 +710,12 @@ fn decrypt<'in_out>( OperatingMode::CTR => match algorithm.id() { AlgorithmId::Aes128 | AlgorithmId::Aes256 => decrypt_aes_ctr_mode(key, context, in_out), }, + // TODO: Hopefully support CFB1, and CFB8 + OperatingMode::CFB128 => match algorithm.id() { + AlgorithmId::Aes128 | AlgorithmId::Aes256 => { + decrypt_aes_cfb_mode(key, mode, context, in_out) + } + }, } } @@ -660,7 +729,7 @@ fn encrypt_aes_ctr_mode( SymmetricCipherKey::Aes128 { enc_key, .. } | SymmetricCipherKey::Aes256 { enc_key, .. } => { enc_key } - _ => return Err(Unspecified), + _ => unreachable!(), }; let mut iv = { @@ -696,7 +765,7 @@ fn encrypt_aes_cbc_mode( SymmetricCipherKey::Aes128 { enc_key, .. } | SymmetricCipherKey::Aes256 { enc_key, .. } => { enc_key } - _ => return Err(Unspecified), + _ => unreachable!(), }; let mut iv = { @@ -722,7 +791,7 @@ fn decrypt_aes_cbc_mode<'in_out>( SymmetricCipherKey::Aes128 { dec_key, .. } | SymmetricCipherKey::Aes256 { dec_key, .. } => { dec_key } - _ => return Err(Unspecified), + _ => unreachable!(), }; let mut iv = { @@ -737,8 +806,75 @@ fn decrypt_aes_cbc_mode<'in_out>( Ok(in_out) } +#[allow(clippy::needless_pass_by_value)] +fn encrypt_aes_cfb_mode( + key: &SymmetricCipherKey, + mode: OperatingMode, + context: EncryptionContext, + in_out: &mut [u8], +) -> Result { + #[allow(clippy::match_wildcard_for_single_variants)] + let key = match &key { + SymmetricCipherKey::Aes128 { enc_key, .. } | SymmetricCipherKey::Aes256 { enc_key, .. } => { + enc_key + } + _ => unreachable!(), + }; + + let mut iv = { + let mut iv = [0u8; AES_CFB_IV_LEN]; + iv.copy_from_slice((&context).try_into()?); + iv + }; + + let cfb_encrypt: fn(&AES_KEY, &mut [u8], &mut [u8]) = match mode { + // TODO: Hopefully support CFB1, and CFB8 + OperatingMode::CFB128 => aes_cfb128_encrypt, + _ => unreachable!(), + }; + + cfb_encrypt(key, &mut iv, in_out); + iv.zeroize(); + + Ok(context.into()) +} + +#[allow(clippy::needless_pass_by_value)] +fn decrypt_aes_cfb_mode<'in_out>( + key: &SymmetricCipherKey, + mode: OperatingMode, + context: DecryptionContext, + in_out: &'in_out mut [u8], +) -> Result<&'in_out mut [u8], Unspecified> { + #[allow(clippy::match_wildcard_for_single_variants)] + let key = match &key { + SymmetricCipherKey::Aes128 { enc_key, .. } | SymmetricCipherKey::Aes256 { enc_key, .. } => { + enc_key + } + _ => unreachable!(), + }; + + let mut iv = { + let mut iv = [0u8; AES_CFB_IV_LEN]; + iv.copy_from_slice((&context).try_into()?); + iv + }; + + let cfb_decrypt: fn(&AES_KEY, &mut [u8], &mut [u8]) = match mode { + // TODO: Hopefully support CFB1, and CFB8 + OperatingMode::CFB128 => aes_cfb128_decrypt, + _ => unreachable!(), + }; + + cfb_decrypt(key, &mut iv, in_out); + + iv.zeroize(); + + Ok(in_out) +} + fn aes_ctr128_encrypt(key: &AES_KEY, iv: &mut [u8], block_buffer: &mut [u8], in_out: &mut [u8]) { - let mut num = MaybeUninit::::new(0); + let mut num: u32 = 0; indicator_check!(unsafe { AES_ctr128_encrypt( @@ -748,7 +884,7 @@ fn aes_ctr128_encrypt(key: &AES_KEY, iv: &mut [u8], block_buffer: &mut [u8], in_ key, iv.as_mut_ptr(), block_buffer.as_mut_ptr(), - num.as_mut_ptr(), + &mut num, ); }); @@ -781,6 +917,36 @@ fn aes_cbc_decrypt(key: &AES_KEY, iv: &mut [u8], in_out: &mut [u8]) { }); } +fn aes_cfb128_encrypt(key: &AES_KEY, iv: &mut [u8], in_out: &mut [u8]) { + let mut num: i32 = 0; + indicator_check!(unsafe { + AES_cfb128_encrypt( + in_out.as_ptr(), + in_out.as_mut_ptr(), + in_out.len(), + key, + iv.as_mut_ptr(), + &mut num, + AES_ENCRYPT, + ); + }); +} + +fn aes_cfb128_decrypt(key: &AES_KEY, iv: &mut [u8], in_out: &mut [u8]) { + let mut num: i32 = 0; + indicator_check!(unsafe { + AES_cfb128_encrypt( + in_out.as_ptr(), + in_out.as_mut_ptr(), + in_out.len(), + key, + iv.as_mut_ptr(), + &mut num, + AES_DECRYPT, + ); + }); +} + #[cfg(test)] mod tests { use super::*; @@ -963,4 +1129,24 @@ mod tests { "eca7285d19f3c20e295378460e8729", "b5098e5e788de6ac2f2098eb2fc6f8" ); + + cipher_kat!( + test_sp800_38a_cfb128_aes128, + &AES_128, + OperatingMode::CFB128, + "2b7e151628aed2a6abf7158809cf4f3c", + "000102030405060708090a0b0c0d0e0f", + "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + "3b3fd92eb72dad20333449f8e83cfb4ac8a64537a0b3a93fcde3cdad9f1ce58b26751f67a3cbb140b1808cf187a4f4dfc04b05357c5d1c0eeac4c66f9ff7f2e6" + ); + + cipher_kat!( + test_sp800_38a_cfb128_aes256, + &AES_256, + OperatingMode::CFB128, + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "000102030405060708090a0b0c0d0e0f", + "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + "dc7e84bfda79164b7ecd8486985d386039ffed143b28b1c832113c6331e5407bdf10132415e54b92a13ed0a8267ae2f975a385741ab9cef82031623d55b1e471" + ); } diff --git a/aws-lc-rs/src/cipher/streaming.rs b/aws-lc-rs/src/cipher/streaming.rs index b12029abeff..e990678bc07 100644 --- a/aws-lc-rs/src/cipher/streaming.rs +++ b/aws-lc-rs/src/cipher/streaming.rs @@ -217,6 +217,33 @@ impl StreamingEncryptingKey { Self::less_safe_cbc_pkcs7(key, context) } + /// Constructs a `StreamingEncryptingKey` for encrypting data using the CFB128 cipher mode. + /// The resulting ciphertext will be the same length as the plaintext. + /// + /// # Errors + /// Returns and error on an internal failure. + pub fn cfb128(key: UnboundCipherKey) -> Result { + let context = key + .algorithm() + .new_encryption_context(OperatingMode::CFB128)?; + Self::less_safe_cfb128(key, context) + } + + /// Constructs a `StreamingEncryptingKey` for encrypting data using the CFB128 cipher mode. + /// The resulting ciphertext will be the same length as the plaintext. + /// + /// This is considered less safe because the caller could potentially construct + /// an `EncryptionContext` from a previously used initialization vector (IV). + /// + /// # Errors + /// Returns an error on an internal failure. + pub fn less_safe_cfb128( + key: UnboundCipherKey, + context: EncryptionContext, + ) -> Result { + Self::new(key, OperatingMode::CFB128, context) + } + /// Constructs a `StreamingEncryptingKey` for encrypting data using the CBC cipher mode /// with pkcs7 padding. /// The resulting ciphertext will be longer than the plaintext; padding is added @@ -380,6 +407,15 @@ impl StreamingDecryptingKey { ) -> Result { Self::new(key, OperatingMode::CBC, context) } + + // Constructs a `StreamingDecryptingKey` for decrypting using the CFB128 cipher mode. + /// The resulting plaintext will be the same length as the ciphertext. + /// + /// # Errors + /// Returns an error on an internal failure. + pub fn cfb128(key: UnboundCipherKey, context: DecryptionContext) -> Result { + Self::new(key, OperatingMode::CFB128, context) + } } #[cfg(test)] @@ -434,7 +470,7 @@ mod tests { assert!(ciphertext.len() > plaintext.len()); assert!(ciphertext.len() <= plaintext.len() + alg.block_len()); } - OperatingMode::CTR => { + _ => { assert_eq!(ciphertext.len(), plaintext.len()); } } @@ -483,7 +519,7 @@ mod tests { assert!(ciphertext.len() > plaintext.len()); assert!(ciphertext.len() <= plaintext.len() + alg.block_len()); } - OperatingMode::CTR => { + _ => { assert_eq!(ciphertext.len(), plaintext.len()); } } @@ -519,6 +555,7 @@ mod tests { helper_stream_step_encrypt_test!(cbc_pkcs7); helper_stream_step_encrypt_test!(ctr); + helper_stream_step_encrypt_test!(cfb128); #[test] fn test_step_cbc() { @@ -631,6 +668,61 @@ mod tests { } } + #[test] + fn test_step_cfb128() { + let random = SystemRandom::new(); + let mut key = [0u8; AES_256_KEY_LEN]; + random.fill(&mut key).unwrap(); + + let encrypting_key_creator = || { + let key = UnboundCipherKey::new(&AES_256, &key.clone()).unwrap(); + StreamingEncryptingKey::cfb128(key).unwrap() + }; + let decrypting_key_creator = |decryption_ctx: DecryptionContext| { + let key = UnboundCipherKey::new(&AES_256, &key.clone()).unwrap(); + StreamingDecryptingKey::cfb128(key, decryption_ctx).unwrap() + }; + + for i in 13..=21 { + for j in 124..=131 { + helper_test_cfb128_stream_encrypt_step_n_bytes( + encrypting_key_creator, + decrypting_key_creator, + j, + i, + ); + } + for j in 124..=131 { + helper_test_cfb128_stream_encrypt_step_n_bytes( + encrypting_key_creator, + decrypting_key_creator, + j, + j - i, + ); + } + } + for j in 124..=131 { + helper_test_cfb128_stream_encrypt_step_n_bytes( + encrypting_key_creator, + decrypting_key_creator, + j, + j, + ); + helper_test_cfb128_stream_encrypt_step_n_bytes( + encrypting_key_creator, + decrypting_key_creator, + j, + 256, + ); + helper_test_cfb128_stream_encrypt_step_n_bytes( + encrypting_key_creator, + decrypting_key_creator, + j, + 1, + ); + } + } + macro_rules! streaming_cipher_kat { ($name:ident, $alg:expr, $mode:expr, $key:literal, $iv: literal, $plaintext:literal, $ciphertext:literal, $from_step:literal, $to_step:literal) => { #[test] @@ -783,4 +875,52 @@ mod tests { 2, 9 ); + + streaming_cipher_kat!( + test_openssl_aes_128_cfb128_16_bytes, + &AES_128, + OperatingMode::CFB128, + "5c353f739429bbd48b7e3f9a76facf4d", + "7b2c7ce17a9b6a59a9e64253b98c8cd1", + "add1bcebeaabe9423d4e916400e877c5", + "8440ec442e4135a613ddb2ce26107e10", + 2, + 9 + ); + + streaming_cipher_kat!( + test_openssl_aes_128_cfb128_15_bytes, + &AES_128, + OperatingMode::CFB128, + "e1f39d70ad378efc1ac318aa8ac4489f", + "ec78c3d54fff2fe09678c7883024ddce", + "b8c905004b2a92a323769f1b8dc1b2", + "964c3e9bf8bf2a3cca02d8e2e75608", + 2, + 9 + ); + + streaming_cipher_kat!( + test_openssl_aes_256_cfb128_16_bytes, + &AES_256, + OperatingMode::CFB128, + "0e8117d0984d6acb957a5d6ca526a12fa612ce5de2daadebd42c14d28a0a192e", + "09147a153b230a40cd7bf4197ad0e825", + "13f4540a4e06394148ade31a6f678787", + "250e590e47b7613b7d0a53f684e970d6", + 2, + 9 + ); + + streaming_cipher_kat!( + test_openssl_aes_256_cfb128_15_bytes, + &AES_256, + OperatingMode::CFB128, + "5cb17d8d5b9dbd81e4f1e0a2c82ebf36cf61156388fb7abf99d4526622858225", + "13c77415ec24f3e2f784f228478a85be", + "3efa583df4405aab61e18155aa7e0d", + "c1f2ffe8aa5064199e8f4f1b388303", + 2, + 9 + ); } diff --git a/aws-lc-rs/src/test.rs b/aws-lc-rs/src/test.rs index a88716252fb..ccc2f6675fd 100644 --- a/aws-lc-rs/src/test.rs +++ b/aws-lc-rs/src/test.rs @@ -202,7 +202,7 @@ impl TestCase { let result = if s.starts_with('\"') { // The value is a quoted UTF-8 string. - let mut bytes = Vec::with_capacity(s.as_bytes().len()); + let mut bytes = Vec::with_capacity(s.len()); let mut s = s.as_bytes().iter().skip(1); loop { let b = match s.next() {