diff --git a/aws-lc-rs/src/aead/quic.rs b/aws-lc-rs/src/aead/quic.rs index b5ae045eaa..5e71dee22e 100644 --- a/aws-lc-rs/src/aead/quic.rs +++ b/aws-lc-rs/src/aead/quic.rs @@ -7,7 +7,7 @@ //! //! See draft-ietf-quic-tls. -use crate::cipher::aes::encrypt_block_aes; +use crate::cipher::aes::encrypt_block; use crate::cipher::block; use crate::cipher::chacha::encrypt_block_chacha20; use crate::cipher::key::SymmetricCipherKey; @@ -149,7 +149,7 @@ fn cipher_new_mask( let encrypted_block = match cipher_key { SymmetricCipherKey::Aes128 { enc_key, .. } | SymmetricCipherKey::Aes256 { enc_key, .. } => { - encrypt_block_aes(enc_key, block) + encrypt_block(enc_key, block) } SymmetricCipherKey::ChaCha20 { raw_key } => { let plaintext = block.as_ref(); diff --git a/aws-lc-rs/src/cipher.rs b/aws-lc-rs/src/cipher.rs index e5c8608dc4..01b47e984a 100644 --- a/aws-lc-rs/src/cipher.rs +++ b/aws-lc-rs/src/cipher.rs @@ -230,38 +230,35 @@ pub use streaming::{BufferUpdate, StreamingDecryptingKey, StreamingEncryptingKey use crate::buffer::Buffer; use crate::error::Unspecified; -use crate::fips::indicator_check; use crate::hkdf; use crate::hkdf::KeyType; use crate::iv::{FixedLength, IV_LEN_128_BIT}; use crate::ptr::ConstPointer; use aws_lc::{ - 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, + EVP_aes_128_cbc, EVP_aes_128_cfb128, EVP_aes_128_ctr, EVP_aes_128_ecb, EVP_aes_256_cbc, + EVP_aes_256_cfb128, EVP_aes_256_ctr, EVP_aes_256_ecb, EVP_CIPHER, }; use core::fmt::Debug; use key::SymmetricCipherKey; -use zeroize::Zeroize; /// The number of bytes in an AES 128-bit key -pub const AES_128_KEY_LEN: usize = 16; +pub use crate::cipher::aes::AES_128_KEY_LEN; /// The number of bytes in an AES 256-bit key -pub const AES_256_KEY_LEN: usize = 32; +pub use crate::cipher::aes::AES_256_KEY_LEN; const MAX_CIPHER_KEY_LEN: usize = AES_256_KEY_LEN; /// The number of bytes for an AES-CBC initialization vector (IV) -pub const AES_CBC_IV_LEN: usize = 16; +pub use crate::cipher::aes::AES_CBC_IV_LEN; /// The number of bytes for an AES-CTR initialization vector (IV) -pub const AES_CTR_IV_LEN: usize = 16; +pub use crate::cipher::aes::AES_CTR_IV_LEN; /// The number of bytes for an AES-CFB initialization vector (IV) -pub const AES_CFB_IV_LEN: usize = 16; +pub use crate::cipher::aes::AES_CFB_IV_LEN; -const AES_BLOCK_LEN: usize = 16; +use crate::cipher::aes::AES_BLOCK_LEN; const MAX_CIPHER_BLOCK_LEN: usize = AES_BLOCK_LEN; @@ -277,6 +274,9 @@ pub enum OperatingMode { /// CFB 128-bit mode. CFB128, + + /// Electronic Code Book (ECB) mode. + ECB, } impl OperatingMode { @@ -286,9 +286,11 @@ impl OperatingMode { (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::ECB, AlgorithmId::Aes128) => unsafe { EVP_aes_128_ecb() }, (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() }, + (OperatingMode::ECB, AlgorithmId::Aes256) => unsafe { EVP_aes_256_ecb() }, }) .unwrap() } @@ -301,6 +303,9 @@ macro_rules! define_cipher_context { pub enum $name { /// A 128-bit Initialization Vector. Iv128(FixedLength), + + /// No Cipher Context + None, } impl<'a> TryFrom<&'a $name> for &'a [u8] { @@ -309,6 +314,7 @@ macro_rules! define_cipher_context { fn try_from(value: &'a $name) -> Result { match value { $name::Iv128(iv) => Ok(iv.as_ref()), + _ => Err(Unspecified), } } } @@ -317,6 +323,7 @@ macro_rules! define_cipher_context { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::Iv128(_) => write!(f, "Iv128"), + Self::None => write!(f, "None"), } } } @@ -325,6 +332,7 @@ macro_rules! define_cipher_context { fn from(value: $other) -> Self { match value { $other::Iv128(iv) => $name::Iv128(iv), + $other::None => $name::None, } } } @@ -388,6 +396,7 @@ impl Algorithm { OperatingMode::CBC | OperatingMode::CTR | OperatingMode::CFB128 => { Ok(EncryptionContext::Iv128(FixedLength::new()?)) } + OperatingMode::ECB => Ok(EncryptionContext::None), }, } } @@ -399,6 +408,9 @@ impl Algorithm { OperatingMode::CBC | OperatingMode::CTR | OperatingMode::CFB128 => { matches!(input, EncryptionContext::Iv128(_)) } + OperatingMode::ECB => { + matches!(input, EncryptionContext::None) + } }, } } @@ -410,6 +422,9 @@ impl Algorithm { OperatingMode::CBC | OperatingMode::CTR | OperatingMode::CFB128 => { matches!(input, DecryptionContext::Iv128(_)) } + OperatingMode::ECB => { + matches!(input, DecryptionContext::None) + } }, } } @@ -496,8 +511,8 @@ impl EncryptingKey { // /// # Errors /// * [`Unspecified`]: Returned if there is an error constructing the `EncryptingKey`. - pub fn ctr(key: UnboundCipherKey) -> Result { - EncryptingKey::new(key, OperatingMode::CTR) + pub fn ctr(key: UnboundCipherKey) -> Result { + Self::new(key, OperatingMode::CTR) } /// Constructs an `EncryptingKey` operating in cipher feedback 128-bit mode (CFB128) using the provided key. @@ -509,15 +524,32 @@ impl EncryptingKey { // /// # Errors /// * [`Unspecified`]: Returned if there is an error constructing the `EncryptingKey`. - pub fn cfb128(key: UnboundCipherKey) -> Result { - EncryptingKey::new(key, OperatingMode::CFB128) + pub fn cfb128(key: UnboundCipherKey) -> Result { + Self::new(key, OperatingMode::CFB128) + } + + /// Constructs an `EncryptingKey` operating in electronic code book mode (ECB) using the provided key. + /// + /// # ☠️ ️️️DANGER ☠️ + /// Offered for computability purposes only. This is an extremely dangerous mode, and + /// very likely not what you want to use. + /// + // # 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 ecb(key: UnboundCipherKey) -> Result { + Self::new(key, OperatingMode::ECB) } #[allow(clippy::unnecessary_wraps)] - fn new(key: UnboundCipherKey, mode: OperatingMode) -> Result { + fn new(key: UnboundCipherKey, mode: OperatingMode) -> Result { let algorithm = key.algorithm(); let key = key.try_into()?; - Ok(EncryptingKey { + Ok(Self { algorithm, key, mode, @@ -539,6 +571,9 @@ impl EncryptingKey { /// Encrypts the data provided in `in_out` in-place. /// Returns a [`DecryptionContext`] with the randomly generated IV that was used to encrypt /// the data provided. + /// + /// If `EncryptingKey` is operating in `OperatingMode::ECB`, then `in_out.len()` must be a multiple + /// of the block length. /// /// # Errors /// * [`Unspecified`]: Returned if cipher mode requires input to be a multiple of the block length, @@ -552,6 +587,9 @@ impl EncryptingKey { /// This is considered "less safe" because the caller could potentially construct /// a `EncryptionContext` from a previously used IV (initialization vector). /// Returns a [`DecryptionContext`] produced from the provided `EncryptionContext`. + /// + /// If `EncryptingKey` is operating in `OperatingMode::ECB`, then `in_out.len()` must be a multiple + /// of the block length. /// /// # Errors /// * [`Unspecified`]: Returned if cipher mode requires input to be a multiple of the block length, @@ -598,7 +636,7 @@ impl DecryptingKey { /// # Errors /// * [`Unspecified`]: Returned if there is an error during decryption. pub fn ctr(key: UnboundCipherKey) -> Result { - DecryptingKey::new(key, OperatingMode::CTR) + Self::new(key, OperatingMode::CTR) } /// Constructs a cipher decrypting key operating in cipher feedback 128-bit mode (CFB128) using the provided key and context. @@ -610,15 +648,32 @@ impl DecryptingKey { // /// # Errors /// * [`Unspecified`]: Returned if there is an error during decryption. - pub fn cfb128(key: UnboundCipherKey) -> Result { - DecryptingKey::new(key, OperatingMode::CFB128) + pub fn cfb128(key: UnboundCipherKey) -> Result { + Self::new(key, OperatingMode::CFB128) + } + + /// Constructs an `DecryptingKey` operating in electronic code book (ECB) mode using the provided key. + /// + /// # ☠️ ️️️DANGER ☠️ + /// Offered for computability purposes only. This is an extremely dangerous mode, and + /// very likely not what you want to use. + /// + // # 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 `DecryptingKey`. + pub fn ecb(key: UnboundCipherKey) -> Result { + Self::new(key, OperatingMode::ECB) } #[allow(clippy::unnecessary_wraps)] - fn new(key: UnboundCipherKey, mode: OperatingMode) -> Result { + fn new(key: UnboundCipherKey, mode: OperatingMode) -> Result { let algorithm = key.algorithm(); let key = key.try_into()?; - Ok(DecryptingKey { + Ok(Self { algorithm, key, mode, @@ -639,6 +694,9 @@ impl DecryptingKey { /// Decrypts the data provided in `in_out` in-place. /// Returns a references to the decrypted data. + /// + /// If `DecryptingKey` is operating in `OperatingMode::ECB`, then `in_out.len()` must be a multiple + /// of the block length. /// /// # Errors /// * [`Unspecified`]: Returned if cipher mode requires input to be a multiple of the block length, @@ -670,21 +728,35 @@ fn encrypt( ) -> Result { let block_len = algorithm.block_len(); - if mode == OperatingMode::CBC && (in_out.len() % block_len) != 0 { - return Err(Unspecified); + match mode { + OperatingMode::CBC | OperatingMode::ECB => { + if in_out.len() % block_len != 0 { + return Err(Unspecified); + } + } + _ => {} } match mode { OperatingMode::CBC => match algorithm.id() { - AlgorithmId::Aes128 | AlgorithmId::Aes256 => encrypt_aes_cbc_mode(key, context, in_out), + AlgorithmId::Aes128 | AlgorithmId::Aes256 => { + aes::encrypt_cbc_mode(key, context, in_out) + } }, OperatingMode::CTR => match algorithm.id() { - AlgorithmId::Aes128 | AlgorithmId::Aes256 => encrypt_aes_ctr_mode(key, context, in_out), + AlgorithmId::Aes128 | AlgorithmId::Aes256 => { + aes::encrypt_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) + aes::encrypt_cfb_mode(key, mode, context, in_out) + } + }, + OperatingMode::ECB => match algorithm.id() { + AlgorithmId::Aes128 | AlgorithmId::Aes256 => { + aes::encrypt_ecb_mode(key, context, in_out) } }, } @@ -699,254 +771,40 @@ fn decrypt<'in_out>( ) -> Result<&'in_out mut [u8], Unspecified> { let block_len = algorithm.block_len(); - if mode == OperatingMode::CBC && (in_out.len() % block_len) != 0 { - return Err(Unspecified); + match mode { + OperatingMode::CBC | OperatingMode::ECB => { + if in_out.len() % block_len != 0 { + return Err(Unspecified); + } + } + _ => {} } match mode { OperatingMode::CBC => match algorithm.id() { - AlgorithmId::Aes128 | AlgorithmId::Aes256 => decrypt_aes_cbc_mode(key, context, in_out), + AlgorithmId::Aes128 | AlgorithmId::Aes256 => { + aes::decrypt_cbc_mode(key, context, in_out) + } }, OperatingMode::CTR => match algorithm.id() { - AlgorithmId::Aes128 | AlgorithmId::Aes256 => decrypt_aes_ctr_mode(key, context, in_out), + AlgorithmId::Aes128 | AlgorithmId::Aes256 => { + aes::decrypt_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) + aes::decrypt_cfb_mode(key, mode, context, in_out) + } + }, + OperatingMode::ECB => match algorithm.id() { + AlgorithmId::Aes128 | AlgorithmId::Aes256 => { + aes::decrypt_ecb_mode(key, context, in_out) } }, } } -fn encrypt_aes_ctr_mode( - key: &SymmetricCipherKey, - 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_CTR_IV_LEN]; - iv.copy_from_slice((&context).try_into()?); - iv - }; - - let mut buffer = [0u8; AES_BLOCK_LEN]; - - aes_ctr128_encrypt(key, &mut iv, &mut buffer, in_out); - iv.zeroize(); - - Ok(context.into()) -} - -fn decrypt_aes_ctr_mode<'in_out>( - key: &SymmetricCipherKey, - context: DecryptionContext, - in_out: &'in_out mut [u8], -) -> Result<&'in_out mut [u8], Unspecified> { - // it's the same in CTR, just providing a nice named wrapper to match - encrypt_aes_ctr_mode(key, context.into(), in_out).map(|_| in_out) -} - -fn encrypt_aes_cbc_mode( - key: &SymmetricCipherKey, - 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_CBC_IV_LEN]; - iv.copy_from_slice((&context).try_into()?); - iv - }; - - aes_cbc_encrypt(key, &mut iv, in_out); - iv.zeroize(); - - Ok(context.into()) -} - -#[allow(clippy::needless_pass_by_value)] -fn decrypt_aes_cbc_mode<'in_out>( - key: &SymmetricCipherKey, - 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 { dec_key, .. } | SymmetricCipherKey::Aes256 { dec_key, .. } => { - dec_key - } - _ => unreachable!(), - }; - - let mut iv = { - let mut iv = [0u8; AES_CBC_IV_LEN]; - iv.copy_from_slice((&context).try_into()?); - iv - }; - - aes_cbc_decrypt(key, &mut iv, in_out); - iv.zeroize(); - - 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: u32 = 0; - - indicator_check!(unsafe { - AES_ctr128_encrypt( - in_out.as_ptr(), - in_out.as_mut_ptr(), - in_out.len(), - key, - iv.as_mut_ptr(), - block_buffer.as_mut_ptr(), - &mut num, - ); - }); - - Zeroize::zeroize(block_buffer); -} - -fn aes_cbc_encrypt(key: &AES_KEY, iv: &mut [u8], in_out: &mut [u8]) { - indicator_check!(unsafe { - AES_cbc_encrypt( - in_out.as_ptr(), - in_out.as_mut_ptr(), - in_out.len(), - key, - iv.as_mut_ptr(), - AES_ENCRYPT, - ); - }); -} - -fn aes_cbc_decrypt(key: &AES_KEY, iv: &mut [u8], in_out: &mut [u8]) { - indicator_check!(unsafe { - AES_cbc_encrypt( - in_out.as_ptr(), - in_out.as_mut_ptr(), - in_out.len(), - key, - iv.as_mut_ptr(), - AES_DECRYPT, - ); - }); -} - -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::*; @@ -1040,6 +898,23 @@ mod tests { } } + #[test] + fn test_aes_128_cfb128() { + let key = from_hex("000102030405060708090a0b0c0d0e0f").unwrap(); + for i in 0..=50 { + helper_test_cipher_n_bytes(key.as_slice(), &AES_128, OperatingMode::CFB128, i); + } + } + + #[test] + fn test_aes_256_cfb128() { + let key = + from_hex("000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f").unwrap(); + for i in 0..=50 { + helper_test_cipher_n_bytes(key.as_slice(), &AES_256, OperatingMode::CFB128, i); + } + } + #[test] fn test_aes_256_ctr() { let key = @@ -1049,6 +924,12 @@ mod tests { } } + #[test] + fn test_aes_128_ecb() { + let key = from_hex("000102030405060708090a0b0c0d0e0f").unwrap(); + _ = key; + } + macro_rules! cipher_kat { ($name:ident, $alg:expr, $mode:expr, $key:literal, $iv: literal, $plaintext:literal, $ciphertext:literal) => { #[test] @@ -1084,6 +965,34 @@ mod tests { let unbound_key2 = UnboundCipherKey::new(alg, &key).unwrap(); let decrypting_key = DecryptingKey::new(unbound_key2, $mode).unwrap(); + let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap(); + assert_eq!(input.as_slice(), plaintext); + } + }; + ($name:ident, $alg:expr, $mode:expr, $key:literal, $plaintext:literal, $ciphertext:literal) => { + #[test] + fn $name() { + let key = from_hex($key).unwrap(); + let input = from_hex($plaintext).unwrap(); + let expected_ciphertext = from_hex($ciphertext).unwrap(); + + let alg = $alg; + + let unbound_key = UnboundCipherKey::new(alg, &key).unwrap(); + + let encrypting_key = EncryptingKey::new(unbound_key, $mode).unwrap(); + + let mut in_out = input.clone(); + + let context = encrypting_key + .less_safe_encrypt(&mut in_out, EncryptionContext::None) + .unwrap(); + + assert_eq!(expected_ciphertext, in_out); + + let unbound_key2 = UnboundCipherKey::new(alg, &key).unwrap(); + let decrypting_key = DecryptingKey::new(unbound_key2, $mode).unwrap(); + let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap(); assert_eq!(input.as_slice(), plaintext); } @@ -1149,4 +1058,22 @@ mod tests { "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", "dc7e84bfda79164b7ecd8486985d386039ffed143b28b1c832113c6331e5407bdf10132415e54b92a13ed0a8267ae2f975a385741ab9cef82031623d55b1e471" ); + + cipher_kat!( + test_sp800_38a_ecb_aes128, + &AES_128, + OperatingMode::ECB, + "2b7e151628aed2a6abf7158809cf4f3c", + "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + "3ad77bb40d7a3660a89ecaf32466ef97f5d3d58503b9699de785895a96fdbaaf43b1cd7f598ece23881b00e3ed0306887b0c785e27e8ad3f8223207104725dd4" + ); + + cipher_kat!( + test_sp800_38a_ecb_aes256, + &AES_256, + OperatingMode::ECB, + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + "f3eed1bdb5d2a03c064b5a7e3db181f8591ccb10d410ed26dc5ba74a31362870b6ed21b99ca6f4f9f153e7b1beafed1d23304b7a39f9f3ff067d8d8f9e24ecc7" + ); } diff --git a/aws-lc-rs/src/cipher/aes.rs b/aws-lc-rs/src/cipher/aes.rs index d13941a820..d2e8202373 100644 --- a/aws-lc-rs/src/cipher/aes.rs +++ b/aws-lc-rs/src/cipher/aes.rs @@ -3,12 +3,14 @@ // Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR ISC -use crate::{ - cipher::block::{Block, BLOCK_LEN}, - fips::indicator_check, +use crate::{cipher::block::Block, error::Unspecified, fips::indicator_check}; +use aws_lc::{ + AES_cbc_encrypt, AES_cfb128_encrypt, AES_ctr128_encrypt, AES_ecb_encrypt, AES_DECRYPT, + AES_ENCRYPT, AES_KEY, }; -use aws_lc::{AES_ecb_encrypt, AES_ENCRYPT, AES_KEY}; -use core::mem::MaybeUninit; +use zeroize::Zeroize; + +use super::{DecryptionContext, EncryptionContext, OperatingMode, SymmetricCipherKey}; /// Length of an AES-128 key in bytes. pub const AES_128_KEY_LEN: usize = 16; @@ -16,19 +18,327 @@ pub const AES_128_KEY_LEN: usize = 16; /// Length of an AES-256 key in bytes. pub const AES_256_KEY_LEN: usize = 32; +/// The number of bytes for an AES-CBC initialization vector (IV) +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; + +pub const AES_BLOCK_LEN: usize = 16; + #[inline] -pub(crate) fn encrypt_block_aes(aes_key: &AES_KEY, block: Block) -> Block { - unsafe { - let mut cipher_text = MaybeUninit::<[u8; BLOCK_LEN]>::uninit(); - let plain_bytes = block.as_ref(); - - indicator_check!(AES_ecb_encrypt( - plain_bytes.as_ptr(), - cipher_text.as_mut_ptr().cast(), - aes_key, - AES_ENCRYPT, - )); +pub(crate) fn encrypt_block(aes_key: &AES_KEY, mut block: Block) -> Block { + { + let block_ref = block.as_mut(); + debug_assert_eq!(block_ref.len(), AES_BLOCK_LEN); + aes_ecb_encrypt(aes_key, block_ref); + } + block +} + +pub(super) fn encrypt_ctr_mode( + key: &SymmetricCipherKey, + 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_CTR_IV_LEN]; + iv.copy_from_slice((&context).try_into()?); + iv + }; + + let mut buffer = [0u8; AES_BLOCK_LEN]; + + aes_ctr128_encrypt(key, &mut iv, &mut buffer, in_out); + iv.zeroize(); + + Ok(context.into()) +} + +pub(super) fn decrypt_ctr_mode<'in_out>( + key: &SymmetricCipherKey, + context: DecryptionContext, + in_out: &'in_out mut [u8], +) -> Result<&'in_out mut [u8], Unspecified> { + // it's the same in CTR, just providing a nice named wrapper to match + encrypt_ctr_mode(key, context.into(), in_out).map(|_| in_out) +} + +pub(super) fn encrypt_cbc_mode( + key: &SymmetricCipherKey, + 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_CBC_IV_LEN]; + iv.copy_from_slice((&context).try_into()?); + iv + }; + + aes_cbc_encrypt(key, &mut iv, in_out); + iv.zeroize(); + + Ok(context.into()) +} + +#[allow(clippy::needless_pass_by_value)] +pub(super) fn decrypt_cbc_mode<'in_out>( + key: &SymmetricCipherKey, + 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 { dec_key, .. } | SymmetricCipherKey::Aes256 { dec_key, .. } => { + dec_key + } + _ => unreachable!(), + }; + + let mut iv = { + let mut iv = [0u8; AES_CBC_IV_LEN]; + iv.copy_from_slice((&context).try_into()?); + iv + }; + + aes_cbc_decrypt(key, &mut iv, in_out); + iv.zeroize(); + + Ok(in_out) +} + +#[allow(clippy::needless_pass_by_value)] +pub(super) fn encrypt_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)] +pub(super) fn decrypt_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); - Block::from(cipher_text.assume_init()) + iv.zeroize(); + + Ok(in_out) +} + +#[allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)] +pub(super) fn encrypt_ecb_mode( + key: &SymmetricCipherKey, + context: EncryptionContext, + in_out: &mut [u8], +) -> Result { + if !matches!(context, EncryptionContext::None) { + unreachable!(); + } + + #[allow(clippy::match_wildcard_for_single_variants)] + let key = match &key { + SymmetricCipherKey::Aes128 { enc_key, .. } | SymmetricCipherKey::Aes256 { enc_key, .. } => { + enc_key + } + _ => unreachable!(), + }; + + let mut in_out_iter = in_out.chunks_exact_mut(AES_BLOCK_LEN); + + for block in in_out_iter.by_ref() { + aes_ecb_encrypt(key, block); + } + + // This is a sanity check that should not happen. We validate in `encrypt` that in_out.len() % block_len == 0 + // for this mode. + debug_assert!(in_out_iter.into_remainder().is_empty()); + + Ok(context.into()) +} + +#[allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)] +pub(super) fn decrypt_ecb_mode<'in_out>( + key: &SymmetricCipherKey, + context: DecryptionContext, + in_out: &'in_out mut [u8], +) -> Result<&'in_out mut [u8], Unspecified> { + if !matches!(context, DecryptionContext::None) { + unreachable!(); } + + #[allow(clippy::match_wildcard_for_single_variants)] + let key = match &key { + SymmetricCipherKey::Aes128 { dec_key, .. } | SymmetricCipherKey::Aes256 { dec_key, .. } => { + dec_key + } + _ => unreachable!(), + }; + + { + let mut in_out_iter = in_out.chunks_exact_mut(AES_BLOCK_LEN); + + for block in in_out_iter.by_ref() { + aes_ecb_decrypt(key, block); + } + + // This is a sanity check hat should not fail. We validate in `decrypt` that in_out.len() % block_len == 0 for + // this mode. + debug_assert!(in_out_iter.into_remainder().is_empty()); + } + + Ok(in_out) +} + +fn aes_ecb_encrypt(key: &AES_KEY, in_out: &mut [u8]) { + indicator_check!(unsafe { + AES_ecb_encrypt(in_out.as_ptr(), in_out.as_mut_ptr(), key, AES_ENCRYPT); + }); +} + +fn aes_ecb_decrypt(key: &AES_KEY, in_out: &mut [u8]) { + indicator_check!(unsafe { + AES_ecb_encrypt(in_out.as_ptr(), in_out.as_mut_ptr(), key, AES_DECRYPT); + }); +} + +fn aes_ctr128_encrypt(key: &AES_KEY, iv: &mut [u8], block_buffer: &mut [u8], in_out: &mut [u8]) { + let mut num: u32 = 0; + + indicator_check!(unsafe { + AES_ctr128_encrypt( + in_out.as_ptr(), + in_out.as_mut_ptr(), + in_out.len(), + key, + iv.as_mut_ptr(), + block_buffer.as_mut_ptr(), + &mut num, + ); + }); + + Zeroize::zeroize(block_buffer); +} + +fn aes_cbc_encrypt(key: &AES_KEY, iv: &mut [u8], in_out: &mut [u8]) { + indicator_check!(unsafe { + AES_cbc_encrypt( + in_out.as_ptr(), + in_out.as_mut_ptr(), + in_out.len(), + key, + iv.as_mut_ptr(), + AES_ENCRYPT, + ); + }); +} + +fn aes_cbc_decrypt(key: &AES_KEY, iv: &mut [u8], in_out: &mut [u8]) { + indicator_check!(unsafe { + AES_cbc_encrypt( + in_out.as_ptr(), + in_out.as_mut_ptr(), + in_out.len(), + key, + iv.as_mut_ptr(), + AES_DECRYPT, + ); + }); +} + +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, + ); + }); } diff --git a/aws-lc-rs/src/cipher/block.rs b/aws-lc-rs/src/cipher/block.rs index 84a798d1fd..714751f1b9 100644 --- a/aws-lc-rs/src/cipher/block.rs +++ b/aws-lc-rs/src/cipher/block.rs @@ -38,6 +38,14 @@ impl AsRef<[u8; BLOCK_LEN]> for Block { } } +impl AsMut<[u8; BLOCK_LEN]> for Block { + #[allow(clippy::transmute_ptr_to_ptr)] + #[inline] + fn as_mut(&mut self) -> &mut [u8; BLOCK_LEN] { + unsafe { core::mem::transmute(self) } + } +} + #[cfg(test)] mod tests { #[test] @@ -51,4 +59,16 @@ mod tests { assert_eq!(block_a.as_ref()[i], block_b.as_ref()[i]); } } + + #[test] + fn test_block_clone_mut_ref() { + use super::{Block, BLOCK_LEN}; + let mut block_a = Block::from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); + #[allow(clippy::clone_on_copy)] + let mut block_b = block_a.clone(); + + for i in 0..BLOCK_LEN { + assert_eq!(block_a.as_mut()[i], block_b.as_mut()[i]); + } + } } diff --git a/aws-lc-rs/src/cipher/key.rs b/aws-lc-rs/src/cipher/key.rs index c7d735b331..73ed178c0f 100644 --- a/aws-lc-rs/src/cipher/key.rs +++ b/aws-lc-rs/src/cipher/key.rs @@ -1,7 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR ISC -use crate::cipher::aes::encrypt_block_aes; use crate::cipher::block::Block; use crate::cipher::chacha::ChaCha20Key; use crate::cipher::{AES_128_KEY_LEN, AES_256_KEY_LEN}; @@ -133,7 +132,9 @@ impl SymmetricCipherKey { pub(crate) fn encrypt_block(&self, block: Block) -> Block { match self { SymmetricCipherKey::Aes128 { enc_key, .. } - | SymmetricCipherKey::Aes256 { enc_key, .. } => encrypt_block_aes(enc_key, block), + | SymmetricCipherKey::Aes256 { enc_key, .. } => { + super::aes::encrypt_block(enc_key, block) + } SymmetricCipherKey::ChaCha20 { .. } => panic!("Unsupported algorithm!"), } } diff --git a/aws-lc-rs/src/cipher/padded.rs b/aws-lc-rs/src/cipher/padded.rs index 379532ffaa..0dc137437a 100644 --- a/aws-lc-rs/src/cipher/padded.rs +++ b/aws-lc-rs/src/cipher/padded.rs @@ -85,8 +85,21 @@ impl PaddedBlockEncryptingKey { // /// # Errors /// * [`Unspecified`]: Returned if there is an error constructing a `PaddedBlockEncryptingKey`. - pub fn cbc_pkcs7(key: UnboundCipherKey) -> Result { - PaddedBlockEncryptingKey::new(key, OperatingMode::CBC, PaddingStrategy::PKCS7) + pub fn cbc_pkcs7(key: UnboundCipherKey) -> Result { + Self::new(key, OperatingMode::CBC, PaddingStrategy::PKCS7) + } + + /// Constructs a new `PaddedBlockEncryptingKey` cipher with electronic code book (ECB) mode. + /// Plaintext data is padded following the PKCS#7 scheme. + /// + /// # ☠️ ️️️DANGER ☠️ + /// Offered for computability purposes only. This is an extremely dangerous mode, and + /// very likely not what you want to use. + /// + /// # Errors + /// * [`Unspecified`]: Returned if there is an error constructing a `PaddedBlockEncryptingKey`. + pub fn ecb_pkcs7(key: UnboundCipherKey) -> Result { + Self::new(key, OperatingMode::ECB, PaddingStrategy::PKCS7) } #[allow(clippy::unnecessary_wraps)] @@ -97,7 +110,7 @@ impl PaddedBlockEncryptingKey { ) -> Result { let algorithm = key.algorithm(); let key = key.try_into()?; - Ok(PaddedBlockEncryptingKey { + Ok(Self { algorithm, key, mode, @@ -191,8 +204,26 @@ impl PaddedBlockDecryptingKey { // /// # Errors /// * [`Unspecified`]: Returned if there is an error constructing the `PaddedBlockDecryptingKey`. - pub fn cbc_pkcs7(key: UnboundCipherKey) -> Result { - PaddedBlockDecryptingKey::new(key, OperatingMode::CBC, PaddingStrategy::PKCS7) + pub fn cbc_pkcs7(key: UnboundCipherKey) -> Result { + Self::new(key, OperatingMode::CBC, PaddingStrategy::PKCS7) + } + + /// Constructs a new `PaddedBlockDecryptingKey` cipher with electronic code book (ECB) mode. + /// Decrypted data is unpadded following the PKCS#7 scheme. + /// + /// # ☠️ ️️️DANGER ☠️ + /// Offered for computability purposes only. This is an extremely dangerous mode, and + /// very likely not what you want to use. + /// + // # 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 `PaddedBlockDecryptingKey`. + pub fn ecb_pkcs7(key: UnboundCipherKey) -> Result { + Self::new(key, OperatingMode::ECB, PaddingStrategy::PKCS7) } #[allow(clippy::unnecessary_wraps)] diff --git a/aws-lc-rs/src/cipher/streaming.rs b/aws-lc-rs/src/cipher/streaming.rs index e990678bc0..175acddcba 100644 --- a/aws-lc-rs/src/cipher/streaming.rs +++ b/aws-lc-rs/src/cipher/streaming.rs @@ -10,9 +10,11 @@ use crate::ptr::LcPtr; use aws_lc::{ EVP_CIPHER_CTX_new, EVP_CIPHER_iv_length, EVP_CIPHER_key_length, EVP_DecryptFinal_ex, EVP_DecryptInit_ex, EVP_DecryptUpdate, EVP_EncryptFinal_ex, EVP_EncryptInit_ex, - EVP_EncryptUpdate, EVP_CIPHER_CTX, + EVP_EncryptUpdate, EVP_CIPHER, EVP_CIPHER_CTX, }; -use std::ptr::null_mut; +use std::ptr::{null, null_mut}; + +use super::ConstPointer; /// A key for streaming encryption operations. pub struct StreamingEncryptingKey { @@ -56,6 +58,62 @@ impl BufferUpdate<'_> { } } +fn evp_encrypt_init( + cipher_ctx: &mut LcPtr, + cipher: &ConstPointer, + key: &[u8], + iv: Option<&[u8]>, +) -> Result<(), Unspecified> { + let iv_ptr: *const u8 = if let Some(iv) = iv { + iv.as_ptr() + } else { + null() + }; + + // AWS-LC copies the key and iv values into the EVP_CIPHER_CTX, and thus can be dropped after this. + if 1 != unsafe { + EVP_EncryptInit_ex( + *cipher_ctx.as_mut(), + **cipher, + null_mut(), + key.as_ptr(), + iv_ptr, + ) + } { + return Err(Unspecified); + } + + Ok(()) +} + +fn evp_decrypt_init( + cipher_ctx: &mut LcPtr, + cipher: &ConstPointer, + key: &[u8], + iv: Option<&[u8]>, +) -> Result<(), Unspecified> { + let iv_ptr: *const u8 = if let Some(iv) = iv { + iv.as_ptr() + } else { + null() + }; + + // AWS-LC copies the key and iv values into the EVP_CIPHER_CTX, and thus can be dropped after this. + if 1 != unsafe { + EVP_DecryptInit_ex( + *cipher_ctx.as_mut(), + **cipher, + null_mut(), + key.as_ptr(), + iv_ptr, + ) + } { + return Err(Unspecified); + } + + Ok(()) +} + impl StreamingEncryptingKey { #[allow(clippy::needless_pass_by_value)] fn new( @@ -71,23 +129,19 @@ impl StreamingEncryptingKey { key_bytes.len(), ::try_from(unsafe { EVP_CIPHER_key_length(*cipher) }).unwrap() ); - let iv = <&[u8]>::try_from(&context)?; - debug_assert_eq!( - iv.len(), - ::try_from(unsafe { EVP_CIPHER_iv_length(*cipher) }).unwrap() - ); - // AWS-LC copies the key and iv values into the EVP_CIPHER_CTX, and thus can be dropped after this. - if 1 != unsafe { - EVP_EncryptInit_ex( - *cipher_ctx.as_mut(), - *cipher, - null_mut(), - key_bytes.as_ptr(), - iv.as_ptr(), - ) - } { - return Err(Unspecified); + match &context { + ctx @ EncryptionContext::Iv128(..) => { + let iv = <&[u8]>::try_from(ctx)?; + debug_assert_eq!( + iv.len(), + ::try_from(unsafe { EVP_CIPHER_iv_length(*cipher) }).unwrap() + ); + evp_encrypt_init(&mut cipher_ctx, &cipher, key_bytes, Some(iv))?; + } + EncryptionContext::None => { + evp_encrypt_init(&mut cipher_ctx, &cipher, key_bytes, None)?; + } } Ok(Self { @@ -229,6 +283,20 @@ impl StreamingEncryptingKey { Self::less_safe_cfb128(key, context) } + /// Constructs a `StreamingEncryptingKey` for encrypting using ECB cipher mode with PKCS7 padding. + /// The resulting plaintext will be the same length as the ciphertext. + /// + /// # ☠️ ️️️DANGER ☠️ + /// Offered for computability purposes only. This is an extremely dangerous mode, and + /// very likely not what you want to use. + /// + /// # Errors + /// Returns an error on an internal failure. + pub fn ecb_pkcs7(key: UnboundCipherKey) -> Result { + let context = key.algorithm().new_encryption_context(OperatingMode::ECB)?; + Self::new(key, OperatingMode::ECB, context) + } + /// Constructs a `StreamingEncryptingKey` for encrypting data using the CFB128 cipher mode. /// The resulting ciphertext will be the same length as the plaintext. /// @@ -283,23 +351,19 @@ impl StreamingDecryptingKey { key_bytes.len(), ::try_from(unsafe { EVP_CIPHER_key_length(*cipher) }).unwrap() ); - let iv = <&[u8]>::try_from(&context)?; - debug_assert_eq!( - iv.len(), - ::try_from(unsafe { EVP_CIPHER_iv_length(*cipher) }).unwrap() - ); - // AWS-LC copies the key and iv values into the EVP_CIPHER_CTX, and thus can be dropped after this. - if 1 != unsafe { - EVP_DecryptInit_ex( - *cipher_ctx.as_mut(), - *cipher, - null_mut(), - key_bytes.as_ptr(), - iv.as_ptr(), - ) - } { - return Err(Unspecified); + match &context { + ctx @ DecryptionContext::Iv128(..) => { + let iv = <&[u8]>::try_from(ctx)?; + debug_assert_eq!( + iv.len(), + ::try_from(unsafe { EVP_CIPHER_iv_length(*cipher) }).unwrap() + ); + evp_decrypt_init(&mut cipher_ctx, &cipher, key_bytes, Some(iv))?; + } + DecryptionContext::None => { + evp_decrypt_init(&mut cipher_ctx, &cipher, key_bytes, None)?; + } } Ok(Self { @@ -416,6 +480,22 @@ impl StreamingDecryptingKey { pub fn cfb128(key: UnboundCipherKey, context: DecryptionContext) -> Result { Self::new(key, OperatingMode::CFB128, context) } + + /// Constructs a `StreamingDecryptingKey` for decrypting using the ECB cipher mode. + /// The resulting plaintext will be the same length as the ciphertext. + /// + /// # ☠️ ️️️DANGER ☠️ + /// Offered for computability purposes only. This is an extremely dangerous mode, and + /// very likely not what you want to use. + /// + /// # Errors + /// Returns an error on an internal failure. + pub fn ecb_pkcs7( + key: UnboundCipherKey, + context: DecryptionContext, + ) -> Result { + Self::new(key, OperatingMode::ECB, context) + } } #[cfg(test)] @@ -466,7 +546,7 @@ mod tests { let outlen = output.written().len(); ciphertext.truncate(out_idx + outlen); match mode { - OperatingMode::CBC => { + OperatingMode::CBC | OperatingMode::ECB => { assert!(ciphertext.len() > plaintext.len()); assert!(ciphertext.len() <= plaintext.len() + alg.block_len()); } @@ -515,7 +595,7 @@ mod tests { let outlen = output.written().len(); plaintext.truncate(out_idx + outlen); match mode { - OperatingMode::CBC => { + OperatingMode::CBC | OperatingMode::ECB => { assert!(ciphertext.len() > plaintext.len()); assert!(ciphertext.len() <= plaintext.len() + alg.block_len()); } @@ -556,6 +636,7 @@ mod tests { helper_stream_step_encrypt_test!(cbc_pkcs7); helper_stream_step_encrypt_test!(ctr); helper_stream_step_encrypt_test!(cfb128); + helper_stream_step_encrypt_test!(ecb_pkcs7); #[test] fn test_step_cbc() { @@ -723,6 +804,61 @@ mod tests { } } + #[test] + fn test_step_ecb_pkcs7() { + 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::ecb_pkcs7(key).unwrap() + }; + let decrypting_key_creator = |decryption_ctx: DecryptionContext| { + let key = UnboundCipherKey::new(&AES_256, &key.clone()).unwrap(); + StreamingDecryptingKey::ecb_pkcs7(key, decryption_ctx).unwrap() + }; + + for i in 13..=21 { + for j in 124..=131 { + helper_test_ecb_pkcs7_stream_encrypt_step_n_bytes( + encrypting_key_creator, + decrypting_key_creator, + j, + i, + ); + } + for j in 124..=131 { + helper_test_ecb_pkcs7_stream_encrypt_step_n_bytes( + encrypting_key_creator, + decrypting_key_creator, + j, + j - i, + ); + } + } + for j in 124..=131 { + helper_test_ecb_pkcs7_stream_encrypt_step_n_bytes( + encrypting_key_creator, + decrypting_key_creator, + j, + j, + ); + helper_test_ecb_pkcs7_stream_encrypt_step_n_bytes( + encrypting_key_creator, + decrypting_key_creator, + j, + 256, + ); + helper_test_ecb_pkcs7_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] @@ -746,6 +882,33 @@ mod tests { assert_eq!(expected_ciphertext.as_slice(), ciphertext.as_ref()); + let unbound_key2 = UnboundCipherKey::new($alg, &key).unwrap(); + let decrypting_key = + StreamingDecryptingKey::new(unbound_key2, $mode, decrypt_ctx).unwrap(); + + let plaintext = step_decrypt(decrypting_key, &ciphertext, step); + assert_eq!(input.as_slice(), plaintext.as_ref()); + } + } + }; + ($name:ident, $alg:expr, $mode:expr, $key:literal, $plaintext:literal, $ciphertext:literal, $from_step:literal, $to_step:literal) => { + #[test] + fn $name() { + let key = from_hex($key).unwrap(); + let input = from_hex($plaintext).unwrap(); + let expected_ciphertext = from_hex($ciphertext).unwrap(); + + for step in ($from_step..=$to_step) { + let unbound_key = UnboundCipherKey::new($alg, &key).unwrap(); + + let encrypting_key = + StreamingEncryptingKey::new(unbound_key, $mode, EncryptionContext::None) + .unwrap(); + + let (ciphertext, decrypt_ctx) = step_encrypt(encrypting_key, &input, step); + + assert_eq!(expected_ciphertext.as_slice(), ciphertext.as_ref()); + let unbound_key2 = UnboundCipherKey::new($alg, &key).unwrap(); let decrypting_key = StreamingDecryptingKey::new(unbound_key2, $mode, decrypt_ctx).unwrap(); @@ -923,4 +1086,48 @@ mod tests { 2, 9 ); + + streaming_cipher_kat!( + test_openssl_aes_128_ecb_pkcs7_16_bytes, + &AES_128, + OperatingMode::ECB, + "a1b7cd124f9824a1532d8440f8136788", + "388118e6848b0cea97401707a754d7a1", + "19b7c7f5d9c2bda3f957e9e7d20847828d5eb5624bcbf221014063a87b38d133", + 2, + 9 + ); + + streaming_cipher_kat!( + test_openssl_aes_128_ecb_pkcs7_15_bytes, + &AES_128, + OperatingMode::ECB, + "d10e12accb837aaffbb284448e53138c", + "b21cfd1c9e6e7e6e912c82c7dd1aa8", + "3d1168e61df34b51c6ab6745c20ee881", + 2, + 9 + ); + + streaming_cipher_kat!( + test_openssl_aes_256_ecb_pkcs7_16_bytes, + &AES_256, + OperatingMode::ECB, + "0600f4ad4eda4bc8e3e99592abdfce7eb08fee0ccc801c5ccee26134bcaafbbd", + "516b45cb1342239a549bd8c1d5998f98", + "854c593555a213e4a862c6f66aa4a79631faca131eba6f163e5cd3940e9c0a57", + 2, + 9 + ); + + streaming_cipher_kat!( + test_openssl_aes_256_ecb_pkcs7_15_bytes, + &AES_256, + OperatingMode::ECB, + "80f235756c8f70094ae1f99a95a599c27c4452a4b8412fd934e2b253f7098508", + "2235590b90190d7a1dc2464a0205ad", + "8547d8ac8dc6d9cebb2dc77a7034bb67", + 2, + 9 + ); } diff --git a/aws-lc-rs/tests/cipher_test.rs b/aws-lc-rs/tests/cipher_test.rs index bbf844d89c..4d496e6908 100644 --- a/aws-lc-rs/tests/cipher_test.rs +++ b/aws-lc-rs/tests/cipher_test.rs @@ -47,11 +47,11 @@ fn step_encrypt( let outlen = output.written().len(); ciphertext.truncate(out_idx + outlen); match mode { - OperatingMode::CBC => { + OperatingMode::CBC | OperatingMode::ECB => { assert!(ciphertext.len() > plaintext.len()); assert!(ciphertext.len() <= plaintext.len() + alg.block_len()); } - OperatingMode::CTR => { + OperatingMode::CTR | OperatingMode::CFB128 => { assert_eq!(ciphertext.len(), plaintext.len()); } _ => panic!("Unknown cipher mode"), @@ -97,11 +97,11 @@ fn step_decrypt( let outlen = output.written().len(); plaintext.truncate(out_idx + outlen); match mode { - OperatingMode::CBC => { + OperatingMode::CBC | OperatingMode::ECB => { assert!(ciphertext.len() > plaintext.len()); assert!(ciphertext.len() <= plaintext.len() + alg.block_len()); } - OperatingMode::CTR => { + OperatingMode::CTR | OperatingMode::CFB128 => { assert_eq!(ciphertext.len(), plaintext.len()); } _ => panic!("Unknown cipher mode"), @@ -135,6 +135,32 @@ macro_rules! streaming_cipher_rt { }; } +macro_rules! streaming_ecb_pkcs7_rt { + ($name:ident, $alg:expr, $key:literal, $plaintext:literal, $from_step:literal, $to_step:literal) => { + paste! { + #[test] + fn [<$name _streaming>]() { + let key = from_hex($key).unwrap(); + let input = from_hex($plaintext).unwrap(); + + for step in ($from_step..=$to_step) { + let unbound_key = UnboundCipherKey::new($alg, &key).unwrap(); + let encrypting_key = StreamingEncryptingKey::ecb_pkcs7(unbound_key).unwrap(); + + let (ciphertext, decrypt_ctx) = step_encrypt(encrypting_key, &input, step); + + let unbound_key2 = UnboundCipherKey::new($alg, &key).unwrap(); + let decrypting_key = + StreamingDecryptingKey::ecb_pkcs7(unbound_key2, decrypt_ctx).unwrap(); + + let plaintext = step_decrypt(decrypting_key, &ciphertext, step); + assert_eq!(input.as_slice(), plaintext.as_ref()); + } + } + } + }; +} + macro_rules! streaming_cipher_kat { ($name:ident, $alg:expr, $mode:expr, $constructor:ident, $key:literal, $iv: literal, $plaintext:literal, $ciphertext:literal, $from_step:literal, $to_step:literal) => { paste! { @@ -168,6 +194,36 @@ macro_rules! streaming_cipher_kat { } }; } + +macro_rules! streaming_ecb_pkcs7_kat { + ($name:ident, $alg:expr, $key:literal, $plaintext:literal, $ciphertext:literal, $from_step:literal, $to_step:literal) => { + paste! { + #[test] + fn [<$name _streaming>]() { + let key = from_hex($key).unwrap(); + let input = from_hex($plaintext).unwrap(); + let expected_ciphertext = from_hex($ciphertext).unwrap(); + + for step in ($from_step..=$to_step) { + let unbound_key = UnboundCipherKey::new($alg, &key).unwrap(); + let encrypting_key = StreamingEncryptingKey::ecb_pkcs7(unbound_key).unwrap(); + + let (ciphertext, decrypt_ctx) = step_encrypt(encrypting_key, &input, step); + + assert_eq!(expected_ciphertext.as_slice(), ciphertext.as_ref()); + + let unbound_key2 = UnboundCipherKey::new($alg, &key).unwrap(); + let decrypting_key = + StreamingDecryptingKey::ecb_pkcs7(unbound_key2, decrypt_ctx).unwrap(); + + let plaintext = step_decrypt(decrypting_key, &ciphertext, step); + assert_eq!(input.as_slice(), plaintext.as_ref()); + } + } + } + }; +} + macro_rules! padded_cipher_kat { ($name:ident, $alg:expr, $mode:expr, $constructor:ident, $key:literal, $iv: literal, $plaintext:literal, $ciphertext:literal) => { #[test] @@ -214,6 +270,37 @@ macro_rules! padded_cipher_kat { }; } +macro_rules! padded_ecb_pkcs7_kat { + ($name:ident, $alg:expr, $key:literal, $plaintext:literal, $ciphertext:literal) => { + #[test] + fn $name() { + let key = from_hex($key).unwrap(); + let input = from_hex($plaintext).unwrap(); + let expected_ciphertext = from_hex($ciphertext).unwrap(); + + let unbound_key = UnboundCipherKey::new($alg, &key).unwrap(); + + let encrypting_key = PaddedBlockEncryptingKey::ecb_pkcs7(unbound_key).unwrap(); + assert_eq!(OperatingMode::ECB, encrypting_key.mode()); + assert_eq!($alg, encrypting_key.algorithm()); + let mut in_out = input.clone(); + let context = encrypting_key + .less_safe_encrypt(&mut in_out, EncryptionContext::None) + .unwrap(); + assert_eq!(expected_ciphertext.as_slice(), in_out.as_slice()); + + let unbound_key2 = UnboundCipherKey::new($alg, &key).unwrap(); + let decrypting_key = PaddedBlockDecryptingKey::ecb_pkcs7(unbound_key2).unwrap(); + assert_eq!(OperatingMode::ECB, decrypting_key.mode()); + assert_eq!($alg, decrypting_key.algorithm()); + let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap(); + assert_eq!(input.as_slice(), plaintext); + } + + streaming_ecb_pkcs7_kat!($name, $alg, $key, $plaintext, $ciphertext, 2, 9); + }; +} + macro_rules! cipher_kat { ($name:ident, $alg:expr, $mode:expr, $constructor:ident, $key:literal, $iv: literal, $plaintext:literal, $ciphertext:literal) => { #[test] @@ -260,6 +347,52 @@ macro_rules! cipher_kat { }; } +macro_rules! ecb_kat { + ($name:ident, $alg:expr, $key:literal, $plaintext:literal, $ciphertext:literal) => { + #[test] + fn $name() { + let key = from_hex($key).unwrap(); + let input = from_hex($plaintext).unwrap(); + let expected_ciphertext = from_hex($ciphertext).unwrap(); + + let unbound_key = UnboundCipherKey::new($alg, &key).unwrap(); + + let encrypting_key = EncryptingKey::ecb(unbound_key).unwrap(); + assert_eq!(OperatingMode::ECB, encrypting_key.mode()); + assert_eq!($alg, encrypting_key.algorithm()); + let mut in_out = input.clone(); + let context = encrypting_key + .less_safe_encrypt(in_out.as_mut_slice(), EncryptionContext::None) + .unwrap(); + assert_eq!(expected_ciphertext.as_slice(), in_out); + + let unbound_key2 = UnboundCipherKey::new($alg, &key).unwrap(); + let decrypting_key = DecryptingKey::ecb(unbound_key2).unwrap(); + assert_eq!(OperatingMode::ECB, decrypting_key.mode()); + assert_eq!($alg, decrypting_key.algorithm()); + let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap(); + assert_eq!(input.as_slice(), plaintext); + } + }; + ($name:ident, $alg:expr, $key:literal, $plaintext:literal) => { + #[test] + fn $name() { + let key = from_hex($key).unwrap(); + let input = from_hex($plaintext).unwrap(); + + let unbound_key = UnboundCipherKey::new($alg, &key).unwrap(); + + let encrypting_key = EncryptingKey::ecb(unbound_key).unwrap(); + assert_eq!(OperatingMode::ECB, encrypting_key.mode()); + assert_eq!($alg, encrypting_key.algorithm()); + let mut in_out = input.clone(); + encrypting_key + .less_safe_encrypt(in_out.as_mut_slice(), EncryptionContext::None) + .expect_err("expected encryption failure"); + } + }; +} + macro_rules! padded_cipher_rt { ($name:ident, $alg:expr, $mode:expr, $constructor:ident, $key:literal, $plaintext:literal) => { #[test] @@ -311,6 +444,56 @@ macro_rules! cipher_rt { }; } +macro_rules! ecb_rt { + ($name:ident, $alg:expr, $key:literal, $plaintext:literal) => { + #[test] + fn $name() { + let key = from_hex($key).unwrap(); + let input = from_hex($plaintext).unwrap(); + let unbound_key = UnboundCipherKey::new($alg, &key).unwrap(); + + let encrypting_key = EncryptingKey::ecb(unbound_key).unwrap(); + assert_eq!(OperatingMode::ECB, encrypting_key.mode()); + assert_eq!($alg, encrypting_key.algorithm()); + let mut in_out = input.clone(); + let context = encrypting_key.encrypt(in_out.as_mut_slice()).unwrap(); + + let unbound_key2 = UnboundCipherKey::new($alg, &key).unwrap(); + let decrypting_key = DecryptingKey::ecb(unbound_key2).unwrap(); + assert_eq!(OperatingMode::ECB, decrypting_key.mode()); + assert_eq!($alg, decrypting_key.algorithm()); + let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap(); + assert_eq!(input.as_slice(), plaintext); + } + }; +} + +macro_rules! padded_ecb_pkcs7_rt { + ($name:ident, $alg:expr, $key:literal, $plaintext:literal) => { + #[test] + fn $name() { + let key = from_hex($key).unwrap(); + let input = from_hex($plaintext).unwrap(); + let unbound_key = UnboundCipherKey::new($alg, &key).unwrap(); + + let encrypting_key = PaddedBlockEncryptingKey::ecb_pkcs7(unbound_key).unwrap(); + assert_eq!(OperatingMode::ECB, encrypting_key.mode()); + assert_eq!($alg, encrypting_key.algorithm()); + let mut in_out = input.clone(); + let context = encrypting_key.encrypt(&mut in_out).unwrap(); + + let unbound_key2 = UnboundCipherKey::new($alg, &key).unwrap(); + let decrypting_key = PaddedBlockDecryptingKey::ecb_pkcs7(unbound_key2).unwrap(); + assert_eq!(OperatingMode::ECB, decrypting_key.mode()); + assert_eq!($alg, decrypting_key.algorithm()); + let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap(); + assert_eq!(input.as_slice(), plaintext); + } + + streaming_ecb_pkcs7_rt!($name, $alg, $key, $plaintext, 2, 9); + }; +} + padded_cipher_kat!( test_kat_aes_128_cbc_16_bytes, &AES_128, @@ -526,3 +709,151 @@ padded_cipher_rt!( "d4a8206dcae01242f9db79a4ecfe277d0f7bb8ccbafd8f9809adb39f35aa9b41", "a39c1fdf77ea3e1f18178c0ec237c70a34" ); + +padded_ecb_pkcs7_kat!( + test_kat_aes_128_ecb_pkcs7_16_bytes, + &AES_128, + "4da1ed87937836de98836433b11eb5a7", + "61f17a594bd5b55ae3fa8efaae6e83d6", + "e5e18734e84530de94b1636d938e5d6f6b4027b4321685a9195b4ddbf25530bf" +); + +padded_ecb_pkcs7_kat!( + test_kat_aes_128_ecb_pkcs7_15_bytes, + &AES_128, + "50f0fb9c8bfcedd0424a4932fdb4578d", + "4badb333837326d75406a0cd6149f0", + "593a39d148e106ebc4a429b97b5033bc" +); + +padded_ecb_pkcs7_kat!( + test_kat_aes_256_ecb_pkcs7_16_bytes, + &AES_256, + "13b2cc03ba601f45b7b1927a7b8566abfae0d97220cb7d5193725ab12e1b23ac", + "6a3867fbd39bd3345df4aec929c8843a", + "615c152b5655499a1d94993e9c220a7e9430ed4d48f2c5b408878beed2c90cf7" +); + +padded_ecb_pkcs7_kat!( + test_kat_aes_256_ecb_pkcs7_15_bytes, + &AES_256, + "f636aefc30bfe19e7fda3ea399be6529f102b965523719e7e717648ec8451c86", + "8d6e85663f99f22bb293582f81ae45", + "f6dc9e368d2cdf6a2e97a022876eb9f2" +); + +ecb_kat!( + test_kat_aes_128_ecb_16_bytes, + &AES_128, + "f8efb984d9e813c96a79020bdfbb6032", + "c4a500e39307dbe7727b5b3a36660f70", + "1eea416d959f747da26d48d2df11d205" +); + +ecb_kat!( + test_kat_aes_128_ecb_15_bytes, + &AES_128, + "f8efb984d9e813c96a79020bdfbb6032", + "c4a500e39307dbe7727b5b3a36660f" +); + +ecb_kat!( + test_kat_aes_256_ecb_16_bytes, + &AES_256, + "d3c9173cbfc65d0e2b6f43ae57c2a6550b756f487bbb7b6404efec69aa74d411", + "109082176cf2a9488b0cd887386bb84a", + "c8c9fece9883b26c0ca58e610493a318" +); + +ecb_kat!( + test_kat_aes_256_ecb_15_bytes, + &AES_256, + "d3c9173cbfc65d0e2b6f43ae57c2a6550b756f487bbb7b6404efec69aa74d411", + "109082176cf2a9488b0cd887386bb8" +); + +ecb_rt!( + test_rt_aes_128_16_bytes, + &AES_128, + "9dfb64e0d2b94ba6df41ef5f72413cb1", + "81c7241bbbf37d9b50e5072858fc498d" +); + +ecb_rt!( + test_rt_aes_256_16_bytes, + &AES_256, + "e6be82de1addbf40550abad4b613b2e77dd498ecaeff5251d4773fcfa00cc1f4", + "eb80cab07da4d9ce53c27903dd070b28" +); + +padded_ecb_pkcs7_rt!( + test_rt_aes_128_ecb_pkcs7_16_bytes, + &AES_128, + "c6fcad04ac45dc5801277484279396c0", + "e6a32546cb537cf589ac65aac84815ae" +); + +padded_ecb_pkcs7_rt!( + test_rt_aes_128_ecb_pkcs7_15_bytes, + &AES_128, + "41647c63411930c483be063ca890472e", + "6194b065db9003381c0c736130188e" +); + +padded_ecb_pkcs7_rt!( + test_rt_aes_256_ecb_pkcs7_16_bytes, + &AES_256, + "c0b52e26961ec041bcea9b3d066c42ea97023a7cfb3e1f3b2e3a8a6427284a47", + "10420ee900c0045bde1218e17008bdb0" +); + +padded_ecb_pkcs7_rt!( + test_rt_aes_256_ecb_pkcs7_15_bytes, + &AES_256, + "48c8511ea06d6b4f594870cdf30fd60e7a88eee09f62d7c359e26e475292ec64", + "9ac8559493ee5274ae9ca03a47618d" +); + +cipher_kat!( + test_kat_aes_128_cfb128_16_bytes, + &AES_128, + OperatingMode::CFB128, + cfb128, + "679816318e2f095262ee93c4552490a2", + "dd41c2fb73e61bdc02da6c70eb5ac729", + "6a841482cf079c4a4b8c59b6c6bda6a4", + "0cebe979ac61df66bb190bf9ed22e363" +); + +cipher_kat!( + test_kat_aes_128_cfb128_15_bytes, + &AES_128, + OperatingMode::CFB128, + cfb128, + "27d4285e8315857653833f63b0bcc034", + "bbdeef0d1837c33971f04eb0a8cde0a2", + "750cf377093fb05f84875a240154f7", + "187a9d7d922afa9f3294ad12669df1" +); + +cipher_kat!( + test_kat_aes_256_cfb128_16_bytes, + &AES_256, + OperatingMode::CFB128, + cfb128, + "c7faca2e29734b069d19c0e95b9a93efa2512925ccebba8622fb321a93d50cd0", + "6f5aad20a97c694b0e0c93457f81b5c8", + "2bc1196172ca0bad4a349198f8abd925", + "21e1e308bfec28a24520a963aa0f4c57" +); + +cipher_kat!( + test_kat_aes_256_cfb128_15_bytes, + &AES_256, + OperatingMode::CFB128, + cfb128, + "8ac86dfe6fa15d71c5c5c4a88ae182fd3a1636818f6a8bcd62c85a599329649c", + "f1b6b55e908d39b769968ae6c3c05c4f", + "9c1675a95f573b4504e6bc5275d0df", + "b8e816bd9e74adebdacf9036cbda41" +);