From 7fd57b094f722d99bd36def586e76241bc37dfc8 Mon Sep 17 00:00:00 2001 From: Justin Smith Date: Tue, 7 May 2024 15:54:45 -0400 Subject: [PATCH 1/8] Stream cipher API --- aws-lc-rs/src/cipher.rs | 96 +++++- aws-lc-rs/src/cipher/streaming.rs | 527 ++++++++++++++++++++++++++++++ aws-lc-rs/src/ptr.rs | 6 +- 3 files changed, 626 insertions(+), 3 deletions(-) create mode 100644 aws-lc-rs/src/cipher/streaming.rs diff --git a/aws-lc-rs/src/cipher.rs b/aws-lc-rs/src/cipher.rs index 6acdc890d49..05b1870c1db 100644 --- a/aws-lc-rs/src/cipher.rs +++ b/aws-lc-rs/src/cipher.rs @@ -83,6 +83,53 @@ //! # } //! ``` //! +//! ### AES-128 CBC Streaming Cipher +//! +//! ```rust +//! # use std::error::Error; +//! # +//! # fn main() -> Result<(), Box> { +//! use aws_lc_rs::cipher::{StreamingDecryptingKey, StreamingEncryptingKey, UnboundCipherKey, AES_128}; +//! +//! let original_message = "This is a secret message!".as_bytes(); +//! +//! let key_bytes: &[u8] = &[ +//! 0xff, 0x0b, 0xe5, 0x84, 0x64, 0x0b, 0x00, 0xc8, 0x90, 0x7a, 0x4b, 0xbf, 0x82, 0x7c, 0xb6, +//! 0xd1, +//! ]; +//! +//! // Encrypt +//! let mut ciphertext_buffer = vec![0u8; original_message.len() + AES_128.block_len()]; +//! let ciphertext_slice = ciphertext_buffer.as_mut_slice(); +//! +//! let key = UnboundCipherKey::new(&AES_128, key_bytes)?; +//! let mut encrypting_key = StreamingEncryptingKey::cbc_pkcs7(key)?; +//! let written_slice = encrypting_key.update(original_message, ciphertext_slice)?; +//! let written_len = written_slice.len(); +//! let remaining_slice = &mut ciphertext_slice[written_len..]; +//! let (context, written_slice) = encrypting_key.finish(remaining_slice)?; +//! let ciphertext_len = written_len + written_slice.len(); +//! let ciphertext = &ciphertext_slice[0..ciphertext_len]; +//! +//! // Decrypt +//! let mut plaintext_buffer = vec![0u8; ciphertext_len + AES_128.block_len()]; +//! let plaintext_slice = plaintext_buffer.as_mut_slice(); +//! +//! let key = UnboundCipherKey::new(&AES_128, key_bytes)?; +//! let mut decrypting_key = StreamingDecryptingKey::cbc_pkcs7(key, context)?; +//! let written_slice = decrypting_key.update(ciphertext, plaintext_slice)?; +//! let written_len = written_slice.len(); +//! let remaining_slice = &mut plaintext_slice[written_len..]; +//! let written_slice = decrypting_key.finish(remaining_slice)?; +//! let plaintext_len = written_len + written_slice.len(); +//! let plaintext = &plaintext_slice[0..plaintext_len]; +//! +//! assert_eq!(original_message, plaintext); +//! # +//! # Ok(()) +//! # } +//! ``` +//! //! ## Constructing a `DecryptionContext` for decryption. //! //! ```rust @@ -142,8 +189,10 @@ pub(crate) mod block; pub(crate) mod chacha; pub(crate) mod key; mod padded; +mod streaming; pub use padded::{PaddedBlockDecryptingKey, PaddedBlockEncryptingKey}; +pub use streaming::{StreamingDecryptingKey, StreamingEncryptingKey}; use crate::buffer::Buffer; use crate::error::Unspecified; @@ -280,7 +329,9 @@ impl Algorithm { &self.id } - const fn block_len(&self) -> usize { + /// The block length of this cipher algorithm. + #[must_use] + pub const fn block_len(&self) -> usize { self.block_len } @@ -905,4 +956,47 @@ mod tests { "eca7285d19f3c20e295378460e8729", "b5098e5e788de6ac2f2098eb2fc6f8" ); + + #[test] + fn streaming_cipher() { + use crate::cipher::{ + StreamingDecryptingKey, StreamingEncryptingKey, UnboundCipherKey, AES_128, + }; + + let original_message = "This is a secret message!".as_bytes(); + + let key_bytes: &[u8] = &[ + 0xff, 0x0b, 0xe5, 0x84, 0x64, 0x0b, 0x00, 0xc8, 0x90, 0x7a, 0x4b, 0xbf, 0x82, 0x7c, + 0xb6, 0xd1, + ]; + + // Encrypt + let mut ciphertext_buffer = vec![0u8; original_message.len() + AES_128.block_len()]; + let ciphertext_slice = ciphertext_buffer.as_mut_slice(); + + let key = UnboundCipherKey::new(&AES_128, key_bytes).unwrap(); + let mut encrypting_key = StreamingEncryptingKey::cbc_pkcs7(key).unwrap(); + let written_slice = encrypting_key + .update(original_message, ciphertext_slice) + .unwrap(); + let written_len = written_slice.len(); + let remaining_slice = &mut ciphertext_slice[written_len..]; + let (context, written_slice) = encrypting_key.finish(remaining_slice).unwrap(); + let ciphertext_len = written_len + written_slice.len(); + let ciphertext = &ciphertext_slice[0..ciphertext_len]; + + // Decrypt + let mut plaintext_buffer = vec![0u8; ciphertext_len + AES_128.block_len()]; + let plaintext_slice = plaintext_buffer.as_mut_slice(); + let key = UnboundCipherKey::new(&AES_128, key_bytes).unwrap(); + let mut decrypting_key = StreamingDecryptingKey::cbc_pkcs7(key, context).unwrap(); + let written_slice = decrypting_key.update(ciphertext, plaintext_slice).unwrap(); + let written_len = written_slice.len(); + let remaining_slice = &mut plaintext_slice[written_len..]; + let written_slice = decrypting_key.finish(remaining_slice).unwrap(); + let plaintext_len = written_len + written_slice.len(); + let plaintext = &plaintext_slice[0..plaintext_len]; + + assert_eq!(original_message, plaintext); + } } diff --git a/aws-lc-rs/src/cipher/streaming.rs b/aws-lc-rs/src/cipher/streaming.rs new file mode 100644 index 00000000000..ef5b5d0f2fc --- /dev/null +++ b/aws-lc-rs/src/cipher/streaming.rs @@ -0,0 +1,527 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +use crate::cipher::{ + Algorithm, DecryptionContext, EncryptionContext, OperatingMode, UnboundCipherKey, +}; +use crate::error::Unspecified; +use crate::ptr::{LcPtr, Pointer}; +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, +}; +use std::ptr::null_mut; + +/// A cipher encryption key for streaming encryption operations. +pub struct StreamingEncryptingKey { + algorithm: &'static Algorithm, + mode: OperatingMode, + cipher_ctx: LcPtr, + context: EncryptionContext, +} + +impl StreamingEncryptingKey { + #[allow(clippy::needless_pass_by_value)] + fn new( + key: UnboundCipherKey, + mode: OperatingMode, + context: EncryptionContext, + ) -> Result { + let algorithm = key.algorithm(); + let cipher_ctx = LcPtr::new(unsafe { EVP_CIPHER_CTX_new() })?; + let cipher = mode.evp_cipher(key.algorithm); + let key_bytes = key.key_bytes.as_ref(); + debug_assert_eq!( + 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() + ); + + if 1 != unsafe { + EVP_EncryptInit_ex( + cipher_ctx.as_mut_ptr(), + *cipher, + null_mut(), + key_bytes.as_ptr(), + iv.as_ptr(), + ) + } { + return Err(Unspecified); + } + + Ok(Self { + algorithm, + mode, + cipher_ctx, + context, + }) + } + + /// Encrypt the input and return the output. + /// # Errors + /// Returns an error if the output buffer is too small. + pub fn update<'a>( + &mut self, + input: &[u8], + output: &'a mut [u8], + ) -> Result<&'a [u8], Unspecified> { + if output.len() < (input.len() + self.algorithm.block_len) { + return Err(Unspecified); + } + + let mut outlen: i32 = output.len().try_into()?; + let inlen: i32 = input.len().try_into()?; + if 1 != unsafe { + EVP_EncryptUpdate( + self.cipher_ctx.as_mut_ptr(), + output.as_mut_ptr(), + &mut outlen, + input.as_ptr(), + inlen, + ) + } { + return Err(Unspecified); + } + let outlen: usize = outlen.try_into()?; + Ok(&output[0..outlen]) + } + + /// Finish the encryption and return the output. + /// # Errors + /// Returns an error if the output buffer is too small. + pub fn finish(self, output: &mut [u8]) -> Result<(DecryptionContext, &[u8]), Unspecified> { + if output.len() < self.algorithm.block_len { + return Err(Unspecified); + } + let mut outlen: i32 = output.len().try_into()?; + if 1 != unsafe { + EVP_EncryptFinal_ex( + self.cipher_ctx.as_mut_ptr(), + output.as_mut_ptr(), + &mut outlen, + ) + } { + return Err(Unspecified); + } + let outlen: usize = outlen.try_into()?; + Ok((self.context.into(), &output[0..outlen])) + } + + /// Returns the cipher operating mode. + #[must_use] + pub fn mode(&self) -> OperatingMode { + self.mode + } + + /// Returns the cipher algorithm + #[must_use] + pub fn algorithm(&self) -> &'static Algorithm { + self.algorithm + } + + /// CTR cipher mode + /// # Errors + /// If the key is not valid for the cipher algorithm + pub fn ctr(key: UnboundCipherKey) -> Result { + let context = key.algorithm().new_encryption_context(OperatingMode::CTR)?; + Self::less_safe_ctr(key, context) + } + + /// CTR cipher mode + /// # Errors + /// If the key is not valid for the cipher algorithm + pub fn less_safe_ctr( + key: UnboundCipherKey, + context: EncryptionContext, + ) -> Result { + Self::new(key, OperatingMode::CTR, context) + } + + /// CBC cipher mode + /// # Errors + /// If the key is not valid for the cipher algorithm + pub fn cbc_pkcs7(key: UnboundCipherKey) -> Result { + let context = key.algorithm().new_encryption_context(OperatingMode::CBC)?; + Self::less_safe_cbc_pkcs7(key, context) + } + + /// CBC cipher mode + /// # Errors + /// If the key is not valid for the cipher algorithm + pub fn less_safe_cbc_pkcs7( + key: UnboundCipherKey, + context: EncryptionContext, + ) -> Result { + Self::new(key, OperatingMode::CBC, context) + } +} + +/// A cipher decryption key for streaming encryption operations. +pub struct StreamingDecryptingKey { + algorithm: &'static Algorithm, + mode: OperatingMode, + cipher_ctx: LcPtr, +} +impl StreamingDecryptingKey { + #[allow(clippy::needless_pass_by_value)] + fn new( + key: UnboundCipherKey, + mode: OperatingMode, + context: DecryptionContext, + ) -> Result { + let cipher_ctx = LcPtr::new(unsafe { EVP_CIPHER_CTX_new() })?; + let algorithm = key.algorithm(); + let cipher = mode.evp_cipher(key.algorithm); + let key_bytes = key.key_bytes.as_ref(); + debug_assert_eq!( + 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() + ); + + if 1 != unsafe { + EVP_DecryptInit_ex( + cipher_ctx.as_mut_ptr(), + *cipher, + null_mut(), + key_bytes.as_ptr(), + iv.as_ptr(), + ) + } { + return Err(Unspecified); + } + + Ok(Self { + algorithm, + mode, + cipher_ctx, + }) + } + + /// Decrypt the input and return the output. + /// # Errors + /// Returns an error if the output buffer is too small. + pub fn update<'a>( + &mut self, + input: &[u8], + output: &'a mut [u8], + ) -> Result<&'a [u8], Unspecified> { + if output.len() < (input.len() + self.algorithm.block_len) { + return Err(Unspecified); + } + + let mut outlen: i32 = output.len().try_into()?; + let inlen: i32 = input.len().try_into()?; + if 1 != unsafe { + EVP_DecryptUpdate( + self.cipher_ctx.as_mut_ptr(), + output.as_mut_ptr(), + &mut outlen, + input.as_ptr(), + inlen, + ) + } { + return Err(Unspecified); + } + let outlen: usize = outlen.try_into()?; + Ok(&output[0..outlen]) + } + + /// Finish the decryption and return the output. + /// # Errors + /// Returns an error if the output buffer is too small. + pub fn finish(self, output: &mut [u8]) -> Result<&[u8], Unspecified> { + let mut outlen: i32 = output.len().try_into()?; + if 1 != unsafe { EVP_DecryptFinal_ex(*self.cipher_ctx, output.as_mut_ptr(), &mut outlen) } { + return Err(Unspecified); + } + let outlen: usize = outlen.try_into()?; + Ok(&output[0..outlen]) + } + + /// Returns the cipher operating mode. + #[must_use] + pub fn mode(&self) -> OperatingMode { + self.mode + } + + /// Returns the cipher algorithm + #[must_use] + pub fn algorithm(&self) -> &'static Algorithm { + self.algorithm + } + + /// CTR cipher mode + /// # Errors + /// If the key is not valid for the cipher algorithm + pub fn ctr(key: UnboundCipherKey, context: DecryptionContext) -> Result { + Self::new(key, OperatingMode::CTR, context) + } + + /// CBC cipher mode + /// # Errors + /// If the key is not valid for the cipher algorithm + pub fn cbc_pkcs7( + key: UnboundCipherKey, + context: DecryptionContext, + ) -> Result { + Self::new(key, OperatingMode::CBC, context) + } +} + +#[cfg(test)] +mod tests { + use crate::cipher::streaming::{StreamingDecryptingKey, StreamingEncryptingKey}; + use crate::cipher::{ + DecryptionContext, OperatingMode, UnboundCipherKey, AES_256, AES_256_KEY_LEN, + }; + use crate::rand::{SecureRandom, SystemRandom}; + use paste::*; + + fn step_encrypt( + mut encrypting_key: StreamingEncryptingKey, + plaintext: &[u8], + step: usize, + ) -> (Box<[u8]>, DecryptionContext) { + let alg = encrypting_key.algorithm(); + let mode = encrypting_key.mode(); + let n = plaintext.len(); + let mut ciphertext = vec![0u8; n + alg.block_len()]; + + let mut in_idx: usize = 0; + let mut out_idx: usize = 0; + loop { + let mut in_end = in_idx + step; + if in_end > n { + in_end = n; + } + let out_end = out_idx + (in_end - in_idx) + alg.block_len(); + let output = encrypting_key + .update( + &plaintext[in_idx..in_end], + &mut ciphertext[out_idx..out_end], + ) + .unwrap(); + in_idx += step; + out_idx += output.len(); + if in_idx >= n { + break; + } + } + let out_end = out_idx + alg.block_len(); + let (decrypt_iv, output) = encrypting_key + .finish(&mut ciphertext[out_idx..out_end]) + .unwrap(); + let outlen = output.len(); + ciphertext.truncate(out_idx + outlen); + match mode { + OperatingMode::CBC => { + assert!(ciphertext.len() > plaintext.len()); + assert!(ciphertext.len() <= plaintext.len() + alg.block_len()); + } + OperatingMode::CTR => { + assert_eq!(ciphertext.len(), plaintext.len()); + } + } + + (ciphertext.into_boxed_slice(), decrypt_iv) + } + + fn step_decrypt( + mut decrypting_key: StreamingDecryptingKey, + ciphertext: &[u8], + step: usize, + ) -> Box<[u8]> { + let alg = decrypting_key.algorithm; + let mode = decrypting_key.mode; + let n = ciphertext.len(); + let mut plaintext = vec![0u8; n + alg.block_len()]; + + let mut in_idx: usize = 0; + let mut out_idx: usize = 0; + loop { + let mut in_end = in_idx + step; + if in_end > n { + in_end = n; + } + let out_end = out_idx + (in_end - in_idx) + alg.block_len(); + let output = decrypting_key + .update( + &ciphertext[in_idx..in_end], + &mut plaintext[out_idx..out_end], + ) + .unwrap(); + in_idx += step; + out_idx += output.len(); + if in_idx >= n { + break; + } + } + let out_end = out_idx + alg.block_len(); + let output = decrypting_key + .finish(&mut plaintext[out_idx..out_end]) + .unwrap(); + let outlen = output.len(); + plaintext.truncate(out_idx + outlen); + match mode { + OperatingMode::CBC => { + assert!(ciphertext.len() > plaintext.len()); + assert!(ciphertext.len() <= plaintext.len() + alg.block_len()); + } + OperatingMode::CTR => { + assert_eq!(ciphertext.len(), plaintext.len()); + } + } + plaintext.into_boxed_slice() + } + + macro_rules! helper_stream_step_encrypt_test { + ($mode:ident) => { + paste! { + fn []( + encrypting_key_creator: impl Fn() -> StreamingEncryptingKey, + decrypting_key_creator: impl Fn(DecryptionContext) -> StreamingDecryptingKey, + n: usize, + step: usize, + ) { + let mut input = vec![0u8; n]; + let random = SystemRandom::new(); + random.fill(&mut input).unwrap(); + + let encrypting_key = encrypting_key_creator(); + + let (ciphertext, decrypt_iv) = step_encrypt(encrypting_key, &input, step); + + let decrypting_key = decrypting_key_creator(decrypt_iv); + + let plaintext = step_decrypt(decrypting_key, &ciphertext, step); + + assert_eq!(input.as_slice(), &*plaintext); + } + } + }; + } + + helper_stream_step_encrypt_test!(cbc_pkcs7); + helper_stream_step_encrypt_test!(ctr); + + #[test] + fn test_step_cbc() { + let random = SystemRandom::new(); + let mut key = [0u8; AES_256_KEY_LEN]; + random.fill(&mut key).unwrap(); + let key = key; + + let encrypting_key_creator = || { + let key = UnboundCipherKey::new(&AES_256, &key.clone()).unwrap(); + StreamingEncryptingKey::cbc_pkcs7(key).unwrap() + }; + let decrypting_key_creator = |decryption_ctx: DecryptionContext| { + let key = UnboundCipherKey::new(&AES_256, &key.clone()).unwrap(); + StreamingDecryptingKey::cbc_pkcs7(key, decryption_ctx).unwrap() + }; + + for i in 13..=21 { + for j in 124..=131 { + helper_test_cbc_pkcs7_stream_encrypt_step_n_bytes( + encrypting_key_creator, + decrypting_key_creator, + j, + i, + ); + } + for j in 124..=131 { + helper_test_cbc_pkcs7_stream_encrypt_step_n_bytes( + encrypting_key_creator, + decrypting_key_creator, + j, + j - i, + ); + } + } + for j in 124..=131 { + helper_test_cbc_pkcs7_stream_encrypt_step_n_bytes( + encrypting_key_creator, + decrypting_key_creator, + j, + j, + ); + helper_test_cbc_pkcs7_stream_encrypt_step_n_bytes( + encrypting_key_creator, + decrypting_key_creator, + j, + 256, + ); + helper_test_cbc_pkcs7_stream_encrypt_step_n_bytes( + encrypting_key_creator, + decrypting_key_creator, + j, + 1, + ); + } + } + + #[test] + fn test_step_ctr() { + 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::ctr(key).unwrap() + }; + let decrypting_key_creator = |decryption_ctx: DecryptionContext| { + let key = UnboundCipherKey::new(&AES_256, &key.clone()).unwrap(); + StreamingDecryptingKey::ctr(key, decryption_ctx).unwrap() + }; + + for i in 13..=21 { + for j in 124..=131 { + helper_test_ctr_stream_encrypt_step_n_bytes( + encrypting_key_creator, + decrypting_key_creator, + j, + i, + ); + } + for j in 124..=131 { + helper_test_ctr_stream_encrypt_step_n_bytes( + encrypting_key_creator, + decrypting_key_creator, + j, + j - i, + ); + } + } + for j in 124..=131 { + helper_test_ctr_stream_encrypt_step_n_bytes( + encrypting_key_creator, + decrypting_key_creator, + j, + j, + ); + helper_test_ctr_stream_encrypt_step_n_bytes( + encrypting_key_creator, + decrypting_key_creator, + j, + 256, + ); + helper_test_ctr_stream_encrypt_step_n_bytes( + encrypting_key_creator, + decrypting_key_creator, + j, + 1, + ); + } + } +} diff --git a/aws-lc-rs/src/ptr.rs b/aws-lc-rs/src/ptr.rs index 6d8d848d08f..4be8ce54de9 100644 --- a/aws-lc-rs/src/ptr.rs +++ b/aws-lc-rs/src/ptr.rs @@ -5,8 +5,9 @@ use core::ops::Deref; use aws_lc::{ BN_free, ECDSA_SIG_free, EC_GROUP_free, EC_KEY_free, EC_POINT_free, EVP_AEAD_CTX_free, - EVP_PKEY_CTX_free, EVP_PKEY_free, OPENSSL_free, RSA_free, BIGNUM, ECDSA_SIG, EC_GROUP, EC_KEY, - EC_POINT, EVP_AEAD_CTX, EVP_PKEY, EVP_PKEY_CTX, RSA, + EVP_CIPHER_CTX_free, EVP_PKEY_CTX_free, EVP_PKEY_free, OPENSSL_free, RSA_free, BIGNUM, + ECDSA_SIG, EC_GROUP, EC_KEY, EC_POINT, EVP_AEAD_CTX, EVP_CIPHER_CTX, EVP_PKEY, EVP_PKEY_CTX, + RSA, }; use mirai_annotations::verify_unreachable; @@ -207,6 +208,7 @@ create_pointer!(EVP_PKEY, EVP_PKEY_free); create_pointer!(EVP_PKEY_CTX, EVP_PKEY_CTX_free); create_pointer!(RSA, RSA_free); create_pointer!(EVP_AEAD_CTX, EVP_AEAD_CTX_free); +create_pointer!(EVP_CIPHER_CTX, EVP_CIPHER_CTX_free); #[cfg(test)] mod tests { From 2e5d294f8052127b4c6fce7c101ccd6b9a78369d Mon Sep 17 00:00:00 2001 From: Justin Smith Date: Thu, 9 May 2024 09:37:10 -0400 Subject: [PATCH 2/8] Return a BufferUpdate struct --- aws-lc-rs/src/cipher.rs | 80 +++++++++++++++++-------------- aws-lc-rs/src/cipher/streaming.rs | 65 +++++++++++++++++++------ 2 files changed, 96 insertions(+), 49 deletions(-) diff --git a/aws-lc-rs/src/cipher.rs b/aws-lc-rs/src/cipher.rs index 05b1870c1db..d453e867665 100644 --- a/aws-lc-rs/src/cipher.rs +++ b/aws-lc-rs/src/cipher.rs @@ -89,39 +89,44 @@ //! # use std::error::Error; //! # //! # fn main() -> Result<(), Box> { -//! use aws_lc_rs::cipher::{StreamingDecryptingKey, StreamingEncryptingKey, UnboundCipherKey, AES_128}; -//! +//! use aws_lc_rs::cipher::{ +//! StreamingDecryptingKey, StreamingEncryptingKey, UnboundCipherKey, AES_128, +//! }; //! let original_message = "This is a secret message!".as_bytes(); -//! //! let key_bytes: &[u8] = &[ -//! 0xff, 0x0b, 0xe5, 0x84, 0x64, 0x0b, 0x00, 0xc8, 0x90, 0x7a, 0x4b, 0xbf, 0x82, 0x7c, 0xb6, -//! 0xd1, +//! 0xff, 0x0b, 0xe5, 0x84, 0x64, 0x0b, 0x00, 0xc8, 0x90, 0x7a, 0x4b, 0xbf, 0x82, 0x7c, +//! 0xb6, 0xd1, //! ]; -//! -//! // Encrypt +//! // Prepare ciphertext buffer //! let mut ciphertext_buffer = vec![0u8; original_message.len() + AES_128.block_len()]; //! let ciphertext_slice = ciphertext_buffer.as_mut_slice(); //! -//! let key = UnboundCipherKey::new(&AES_128, key_bytes)?; -//! let mut encrypting_key = StreamingEncryptingKey::cbc_pkcs7(key)?; -//! let written_slice = encrypting_key.update(original_message, ciphertext_slice)?; -//! let written_len = written_slice.len(); -//! let remaining_slice = &mut ciphertext_slice[written_len..]; -//! let (context, written_slice) = encrypting_key.finish(remaining_slice)?; -//! let ciphertext_len = written_len + written_slice.len(); +//! // Create StreamingEncryptingKey +//! let key = UnboundCipherKey::new(&AES_128, key_bytes).unwrap(); +//! let mut encrypting_key = StreamingEncryptingKey::cbc_pkcs7(key).unwrap(); +//! +//! // Encrypt +//! let mut first_update = encrypting_key +//! .update(original_message, ciphertext_slice) +//! .unwrap(); +//! let first_update_len = first_update.written().len(); +//! let (context, final_update) = encrypting_key.finish(first_update.remainder_mut()).unwrap(); +//! let ciphertext_len = first_update_len + final_update.written().len(); //! let ciphertext = &ciphertext_slice[0..ciphertext_len]; //! -//! // Decrypt +//! // Prepare plaintext buffer //! let mut plaintext_buffer = vec![0u8; ciphertext_len + AES_128.block_len()]; //! let plaintext_slice = plaintext_buffer.as_mut_slice(); //! -//! let key = UnboundCipherKey::new(&AES_128, key_bytes)?; -//! let mut decrypting_key = StreamingDecryptingKey::cbc_pkcs7(key, context)?; -//! let written_slice = decrypting_key.update(ciphertext, plaintext_slice)?; -//! let written_len = written_slice.len(); -//! let remaining_slice = &mut plaintext_slice[written_len..]; -//! let written_slice = decrypting_key.finish(remaining_slice)?; -//! let plaintext_len = written_len + written_slice.len(); +//! // Create StreamingEncryptingKey +//! let key = UnboundCipherKey::new(&AES_128, key_bytes).unwrap(); +//! let mut decrypting_key = StreamingDecryptingKey::cbc_pkcs7(key, context).unwrap(); +//! +//! // Decrypt +//! let mut first_update = decrypting_key.update(ciphertext, plaintext_slice).unwrap(); +//! let first_update_len = first_update.written().len(); +//! let final_update = decrypting_key.finish(first_update.remainder_mut()).unwrap(); +//! let plaintext_len = first_update_len + final_update.written().len(); //! let plaintext = &plaintext_slice[0..plaintext_len]; //! //! assert_eq!(original_message, plaintext); @@ -192,7 +197,7 @@ mod padded; mod streaming; pub use padded::{PaddedBlockDecryptingKey, PaddedBlockEncryptingKey}; -pub use streaming::{StreamingDecryptingKey, StreamingEncryptingKey}; +pub use streaming::{BufferUpdate, StreamingDecryptingKey, StreamingEncryptingKey}; use crate::buffer::Buffer; use crate::error::Unspecified; @@ -970,31 +975,36 @@ mod tests { 0xb6, 0xd1, ]; - // Encrypt + // Prepare ciphertext buffer let mut ciphertext_buffer = vec![0u8; original_message.len() + AES_128.block_len()]; let ciphertext_slice = ciphertext_buffer.as_mut_slice(); + // Create StreamingEncryptingKey let key = UnboundCipherKey::new(&AES_128, key_bytes).unwrap(); let mut encrypting_key = StreamingEncryptingKey::cbc_pkcs7(key).unwrap(); - let written_slice = encrypting_key + + // Encrypt + let mut first_update = encrypting_key .update(original_message, ciphertext_slice) .unwrap(); - let written_len = written_slice.len(); - let remaining_slice = &mut ciphertext_slice[written_len..]; - let (context, written_slice) = encrypting_key.finish(remaining_slice).unwrap(); - let ciphertext_len = written_len + written_slice.len(); + let first_update_len = first_update.written().len(); + let (context, final_update) = encrypting_key.finish(first_update.remainder_mut()).unwrap(); + let ciphertext_len = first_update_len + final_update.written().len(); let ciphertext = &ciphertext_slice[0..ciphertext_len]; - // Decrypt + // Prepare plaintext buffer let mut plaintext_buffer = vec![0u8; ciphertext_len + AES_128.block_len()]; let plaintext_slice = plaintext_buffer.as_mut_slice(); + + // Create StreamingEncryptingKey let key = UnboundCipherKey::new(&AES_128, key_bytes).unwrap(); let mut decrypting_key = StreamingDecryptingKey::cbc_pkcs7(key, context).unwrap(); - let written_slice = decrypting_key.update(ciphertext, plaintext_slice).unwrap(); - let written_len = written_slice.len(); - let remaining_slice = &mut plaintext_slice[written_len..]; - let written_slice = decrypting_key.finish(remaining_slice).unwrap(); - let plaintext_len = written_len + written_slice.len(); + + // Decrypt + let mut first_update = decrypting_key.update(ciphertext, plaintext_slice).unwrap(); + let first_update_len = first_update.written().len(); + let final_update = decrypting_key.finish(first_update.remainder_mut()).unwrap(); + let plaintext_len = first_update_len + final_update.written().len(); let plaintext = &plaintext_slice[0..plaintext_len]; assert_eq!(original_message, plaintext); diff --git a/aws-lc-rs/src/cipher/streaming.rs b/aws-lc-rs/src/cipher/streaming.rs index ef5b5d0f2fc..e188cac6298 100644 --- a/aws-lc-rs/src/cipher/streaming.rs +++ b/aws-lc-rs/src/cipher/streaming.rs @@ -21,6 +21,40 @@ pub struct StreamingEncryptingKey { context: EncryptionContext, } +/// A struct indicating the portion of a buffer written to, and/or not written to, during an +/// encryption/decryption operation. +pub struct BufferUpdate<'a> { + written: &'a [u8], + remainder: &'a mut [u8], +} + +impl<'a> BufferUpdate<'a> { + fn new(out_buffer: &'a mut [u8], written_len: usize) -> Self { + let (written, remainder) = out_buffer.split_at_mut(written_len); + Self { written, remainder } + } +} + +impl BufferUpdate<'_> { + /// Returns the slice from the buffer that was modified by the operation. + #[must_use] + pub fn written(&self) -> &[u8] { + self.written + } + + /// Returns the slice of the buffer that was not modified by the operation. + #[must_use] + pub fn remainder(&self) -> &[u8] { + self.remainder + } + + /// Returns a mutable slice of the buffer that was not modified by the operation. + #[must_use] + pub fn remainder_mut(&mut self) -> &mut [u8] { + self.remainder + } +} + impl StreamingEncryptingKey { #[allow(clippy::needless_pass_by_value)] fn new( @@ -69,7 +103,7 @@ impl StreamingEncryptingKey { &mut self, input: &[u8], output: &'a mut [u8], - ) -> Result<&'a [u8], Unspecified> { + ) -> Result, Unspecified> { if output.len() < (input.len() + self.algorithm.block_len) { return Err(Unspecified); } @@ -88,13 +122,16 @@ impl StreamingEncryptingKey { return Err(Unspecified); } let outlen: usize = outlen.try_into()?; - Ok(&output[0..outlen]) + Ok(BufferUpdate::new(output, outlen)) } /// Finish the encryption and return the output. /// # Errors /// Returns an error if the output buffer is too small. - pub fn finish(self, output: &mut [u8]) -> Result<(DecryptionContext, &[u8]), Unspecified> { + pub fn finish( + self, + output: &mut [u8], + ) -> Result<(DecryptionContext, BufferUpdate), Unspecified> { if output.len() < self.algorithm.block_len { return Err(Unspecified); } @@ -109,7 +146,7 @@ impl StreamingEncryptingKey { return Err(Unspecified); } let outlen: usize = outlen.try_into()?; - Ok((self.context.into(), &output[0..outlen])) + Ok((self.context.into(), BufferUpdate::new(output, outlen))) } /// Returns the cipher operating mode. @@ -214,7 +251,7 @@ impl StreamingDecryptingKey { &mut self, input: &[u8], output: &'a mut [u8], - ) -> Result<&'a [u8], Unspecified> { + ) -> Result, Unspecified> { if output.len() < (input.len() + self.algorithm.block_len) { return Err(Unspecified); } @@ -233,19 +270,19 @@ impl StreamingDecryptingKey { return Err(Unspecified); } let outlen: usize = outlen.try_into()?; - Ok(&output[0..outlen]) + Ok(BufferUpdate::new(output, outlen)) } /// Finish the decryption and return the output. /// # Errors /// Returns an error if the output buffer is too small. - pub fn finish(self, output: &mut [u8]) -> Result<&[u8], Unspecified> { + pub fn finish(self, output: &mut [u8]) -> Result { let mut outlen: i32 = output.len().try_into()?; if 1 != unsafe { EVP_DecryptFinal_ex(*self.cipher_ctx, output.as_mut_ptr(), &mut outlen) } { return Err(Unspecified); } let outlen: usize = outlen.try_into()?; - Ok(&output[0..outlen]) + Ok(BufferUpdate::new(output, outlen)) } /// Returns the cipher operating mode. @@ -280,9 +317,9 @@ impl StreamingDecryptingKey { #[cfg(test)] mod tests { - use crate::cipher::streaming::{StreamingDecryptingKey, StreamingEncryptingKey}; use crate::cipher::{ - DecryptionContext, OperatingMode, UnboundCipherKey, AES_256, AES_256_KEY_LEN, + DecryptionContext, OperatingMode, StreamingDecryptingKey, StreamingEncryptingKey, + UnboundCipherKey, AES_256, AES_256_KEY_LEN, }; use crate::rand::{SecureRandom, SystemRandom}; use paste::*; @@ -312,7 +349,7 @@ mod tests { ) .unwrap(); in_idx += step; - out_idx += output.len(); + out_idx += output.written().len(); if in_idx >= n { break; } @@ -321,7 +358,7 @@ mod tests { let (decrypt_iv, output) = encrypting_key .finish(&mut ciphertext[out_idx..out_end]) .unwrap(); - let outlen = output.len(); + let outlen = output.written().len(); ciphertext.truncate(out_idx + outlen); match mode { OperatingMode::CBC => { @@ -361,7 +398,7 @@ mod tests { ) .unwrap(); in_idx += step; - out_idx += output.len(); + out_idx += output.written().len(); if in_idx >= n { break; } @@ -370,7 +407,7 @@ mod tests { let output = decrypting_key .finish(&mut plaintext[out_idx..out_end]) .unwrap(); - let outlen = output.len(); + let outlen = output.written().len(); plaintext.truncate(out_idx + outlen); match mode { OperatingMode::CBC => { From 6146587124e3d761782100822f819cedbfde61cb Mon Sep 17 00:00:00 2001 From: Justin Smith Date: Wed, 22 May 2024 12:46:48 -0400 Subject: [PATCH 3/8] Cleanup docs --- aws-lc-rs/src/cipher.rs | 54 ++------------ aws-lc-rs/src/cipher/streaming.rs | 113 +++++++++++++++++++++--------- 2 files changed, 83 insertions(+), 84 deletions(-) diff --git a/aws-lc-rs/src/cipher.rs b/aws-lc-rs/src/cipher.rs index d453e867665..38fbd5a7562 100644 --- a/aws-lc-rs/src/cipher.rs +++ b/aws-lc-rs/src/cipher.rs @@ -118,7 +118,7 @@ //! let mut plaintext_buffer = vec![0u8; ciphertext_len + AES_128.block_len()]; //! let plaintext_slice = plaintext_buffer.as_mut_slice(); //! -//! // Create StreamingEncryptingKey +//! // Create StreamingDecryptingKey //! let key = UnboundCipherKey::new(&AES_128, key_bytes).unwrap(); //! let mut decrypting_key = StreamingDecryptingKey::cbc_pkcs7(key, context).unwrap(); //! @@ -494,7 +494,9 @@ impl EncryptingKey { self.less_safe_encrypt(in_out, context) } - /// Encrypts the data provided in `in_out` in-place using the provided `CipherContext`. + /// Encrypts the data provided in `in_out` in-place using the provided `EncryptionContext`. + /// This is considered "less safe" because the caller could potentially construct + /// a `EncryptionContext` from a previously used IV (initialization vector). /// Returns a references to the decrypted data. /// /// # Errors @@ -961,52 +963,4 @@ mod tests { "eca7285d19f3c20e295378460e8729", "b5098e5e788de6ac2f2098eb2fc6f8" ); - - #[test] - fn streaming_cipher() { - use crate::cipher::{ - StreamingDecryptingKey, StreamingEncryptingKey, UnboundCipherKey, AES_128, - }; - - let original_message = "This is a secret message!".as_bytes(); - - let key_bytes: &[u8] = &[ - 0xff, 0x0b, 0xe5, 0x84, 0x64, 0x0b, 0x00, 0xc8, 0x90, 0x7a, 0x4b, 0xbf, 0x82, 0x7c, - 0xb6, 0xd1, - ]; - - // Prepare ciphertext buffer - let mut ciphertext_buffer = vec![0u8; original_message.len() + AES_128.block_len()]; - let ciphertext_slice = ciphertext_buffer.as_mut_slice(); - - // Create StreamingEncryptingKey - let key = UnboundCipherKey::new(&AES_128, key_bytes).unwrap(); - let mut encrypting_key = StreamingEncryptingKey::cbc_pkcs7(key).unwrap(); - - // Encrypt - let mut first_update = encrypting_key - .update(original_message, ciphertext_slice) - .unwrap(); - let first_update_len = first_update.written().len(); - let (context, final_update) = encrypting_key.finish(first_update.remainder_mut()).unwrap(); - let ciphertext_len = first_update_len + final_update.written().len(); - let ciphertext = &ciphertext_slice[0..ciphertext_len]; - - // Prepare plaintext buffer - let mut plaintext_buffer = vec![0u8; ciphertext_len + AES_128.block_len()]; - let plaintext_slice = plaintext_buffer.as_mut_slice(); - - // Create StreamingEncryptingKey - let key = UnboundCipherKey::new(&AES_128, key_bytes).unwrap(); - let mut decrypting_key = StreamingDecryptingKey::cbc_pkcs7(key, context).unwrap(); - - // Decrypt - let mut first_update = decrypting_key.update(ciphertext, plaintext_slice).unwrap(); - let first_update_len = first_update.written().len(); - let final_update = decrypting_key.finish(first_update.remainder_mut()).unwrap(); - let plaintext_len = first_update_len + final_update.written().len(); - let plaintext = &plaintext_slice[0..plaintext_len]; - - assert_eq!(original_message, plaintext); - } } diff --git a/aws-lc-rs/src/cipher/streaming.rs b/aws-lc-rs/src/cipher/streaming.rs index e188cac6298..03512294c98 100644 --- a/aws-lc-rs/src/cipher/streaming.rs +++ b/aws-lc-rs/src/cipher/streaming.rs @@ -13,7 +13,7 @@ use aws_lc::{ }; use std::ptr::null_mut; -/// A cipher encryption key for streaming encryption operations. +/// A key for streaming encryption operations. pub struct StreamingEncryptingKey { algorithm: &'static Algorithm, mode: OperatingMode, @@ -96,18 +96,24 @@ impl StreamingEncryptingKey { }) } - /// Encrypt the input and return the output. + /// Updates the internal state of the key with the provided ciphertext `input`, + /// potentially writing bytes of ciphertext to `output`. + /// + /// The number of bytes written to `output` can be up to `input.len()` + /// plus the block length of the algorithm (e.g., 16 bytes for AES). + /// /// # Errors - /// Returns an error if the output buffer is too small. + /// * May return an error if the `output` buffer is smaller than the length of + /// the `input` plus the algorithm's block length. Certain cipher modes + /// (such as CTR) may allow the output buffer to be as small as the size + /// of the input in certain circumstances. + /// * Returns an error if the length of either `input` or `output` is larger + /// than `i32::MAX`. pub fn update<'a>( &mut self, input: &[u8], output: &'a mut [u8], ) -> Result, Unspecified> { - if output.len() < (input.len() + self.algorithm.block_len) { - return Err(Unspecified); - } - let mut outlen: i32 = output.len().try_into()?; let inlen: i32 = input.len().try_into()?; if 1 != unsafe { @@ -125,16 +131,21 @@ impl StreamingEncryptingKey { Ok(BufferUpdate::new(output, outlen)) } - /// Finish the encryption and return the output. + /// Finishes the encryption operation, writing any remaining ciphertext to + /// `output`. + /// + /// The number of bytes written to `output` can be up to the block length of + /// the algorithm (e.g., 16 bytes for AES). + /// /// # Errors - /// Returns an error if the output buffer is too small. + /// * May return an error if the `output` buffer is smaller than the algorithm's + /// block length. Certain cipher mode (such as CTR) may allow the output + /// buffer to only be large enough to fit the remainder of the ciphertext. + /// * Returns an error if the length of `output` is larger than `i32::MAX`. pub fn finish( self, output: &mut [u8], ) -> Result<(DecryptionContext, BufferUpdate), Unspecified> { - if output.len() < self.algorithm.block_len { - return Err(Unspecified); - } let mut outlen: i32 = output.len().try_into()?; if 1 != unsafe { EVP_EncryptFinal_ex( @@ -155,23 +166,30 @@ impl StreamingEncryptingKey { self.mode } - /// Returns the cipher algorithm + /// Returns the cipher algorithm. #[must_use] pub fn algorithm(&self) -> &'static Algorithm { self.algorithm } - /// CTR cipher mode + /// Constructs a `StreamingEncryptingKey` for encrypting data using the CTR cipher mode. + /// The resulting ciphertext will be the same length as the plaintext. + /// /// # Errors - /// If the key is not valid for the cipher algorithm + /// Returns and error on an internal failure. pub fn ctr(key: UnboundCipherKey) -> Result { let context = key.algorithm().new_encryption_context(OperatingMode::CTR)?; Self::less_safe_ctr(key, context) } - /// CTR cipher mode + /// Constructs a `StreamingEncryptingKey` for encrypting data using the CTR 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 - /// If the key is not valid for the cipher algorithm + /// Returns an error on an internal failure. pub fn less_safe_ctr( key: UnboundCipherKey, context: EncryptionContext, @@ -179,17 +197,28 @@ impl StreamingEncryptingKey { Self::new(key, OperatingMode::CTR, context) } - /// CBC cipher mode + /// 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 + /// to fill the next block of ciphertext. + /// /// # Errors - /// If the key is not valid for the cipher algorithm + /// Returns an error on an internal failure. pub fn cbc_pkcs7(key: UnboundCipherKey) -> Result { let context = key.algorithm().new_encryption_context(OperatingMode::CBC)?; Self::less_safe_cbc_pkcs7(key, context) } - /// CBC cipher mode + /// 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 + /// to fill the next block of ciphertext. + /// + /// This is considered less safe because the caller could potentially construct + /// an `EncryptionContext` from a previously used initialization vector (IV). + /// /// # Errors - /// If the key is not valid for the cipher algorithm + /// Returns an error on an internal failure. pub fn less_safe_cbc_pkcs7( key: UnboundCipherKey, context: EncryptionContext, @@ -198,7 +227,7 @@ impl StreamingEncryptingKey { } } -/// A cipher decryption key for streaming encryption operations. +/// A key for streaming decryption operations. pub struct StreamingDecryptingKey { algorithm: &'static Algorithm, mode: OperatingMode, @@ -244,18 +273,23 @@ impl StreamingDecryptingKey { }) } - /// Decrypt the input and return the output. + /// Updates the internal state of the key with the provided ciphertext `input`, + /// potentially also writing bytes of plaintext to `output`. + /// The number of bytes written to `output` can be up to `input.len()` + /// plus the block length of the cipher algorithm (e.g., 16 bytes for AES). + /// /// # Errors - /// Returns an error if the output buffer is too small. + /// * May return an error if the `output` buffer is smaller than the length of + /// the `input` plus the algorithm's block length. Certain cipher modes + /// (such as CTR) may allow the output buffer to be as small as the size of + /// the input in certain circumstances. + /// * Returns an error if the length of either `input` or `output` is larger + /// than `i32::MAX`. pub fn update<'a>( &mut self, input: &[u8], output: &'a mut [u8], ) -> Result, Unspecified> { - if output.len() < (input.len() + self.algorithm.block_len) { - return Err(Unspecified); - } - let mut outlen: i32 = output.len().try_into()?; let inlen: i32 = input.len().try_into()?; if 1 != unsafe { @@ -273,9 +307,16 @@ impl StreamingDecryptingKey { Ok(BufferUpdate::new(output, outlen)) } - /// Finish the decryption and return the output. + /// Finishes the decryption operation, writing the remaining plaintext to + /// `output`. + /// The number of bytes written to `output` can be up to the block length of + /// the cipher algorithm (e.g., 16 bytes for AES). + /// /// # Errors - /// Returns an error if the output buffer is too small. + /// * May return an error if the `output` buffer is smaller than the algorithm's + /// block length. Certain cipher modes (such as CTR) may allow the output buffer + /// to only be large enough to fit the remaining plaintext. + /// * Returns an error if the length of `output` is larger than `i32::MAX`. pub fn finish(self, output: &mut [u8]) -> Result { let mut outlen: i32 = output.len().try_into()?; if 1 != unsafe { EVP_DecryptFinal_ex(*self.cipher_ctx, output.as_mut_ptr(), &mut outlen) } { @@ -297,16 +338,20 @@ impl StreamingDecryptingKey { self.algorithm } - /// CTR cipher mode + /// Constructs a `StreamingDecryptingKey` for decrypting using the CTR cipher mode. + /// The resulting plaintext will be the same length as the ciphertext. + /// /// # Errors - /// If the key is not valid for the cipher algorithm + /// Returns an error on an internal failure. pub fn ctr(key: UnboundCipherKey, context: DecryptionContext) -> Result { Self::new(key, OperatingMode::CTR, context) } - /// CBC cipher mode + /// Constructs a `StreamingDecryptingKey` for decrypting using the CBC cipher mode. + /// The resulting plaintext will be shorter than the ciphertext. + /// /// # Errors - /// If the key is not valid for the cipher algorithm + /// Returns an error on an internal failure. pub fn cbc_pkcs7( key: UnboundCipherKey, context: DecryptionContext, From d50d9f7b058520131439849f7979ec2b4c337ca2 Mon Sep 17 00:00:00 2001 From: Justin Smith Date: Fri, 24 May 2024 08:02:33 -0400 Subject: [PATCH 4/8] Add more tests --- aws-lc-rs/src/cipher.rs | 4 +- aws-lc-rs/src/cipher/streaming.rs | 245 +++++++++++++++++++++++++++++- aws-lc-rs/tests/cipher_test.rs | 195 +++++++++++++++++++++++- 3 files changed, 435 insertions(+), 9 deletions(-) diff --git a/aws-lc-rs/src/cipher.rs b/aws-lc-rs/src/cipher.rs index 38fbd5a7562..dcbafe532be 100644 --- a/aws-lc-rs/src/cipher.rs +++ b/aws-lc-rs/src/cipher.rs @@ -107,8 +107,8 @@ //! //! // Encrypt //! let mut first_update = encrypting_key -//! .update(original_message, ciphertext_slice) -//! .unwrap(); +//! .update(original_message, ciphertext_slice) +//! .unwrap(); //! let first_update_len = first_update.written().len(); //! let (context, final_update) = encrypting_key.finish(first_update.remainder_mut()).unwrap(); //! let ciphertext_len = first_update_len + final_update.written().len(); diff --git a/aws-lc-rs/src/cipher/streaming.rs b/aws-lc-rs/src/cipher/streaming.rs index 03512294c98..d1d4e180346 100644 --- a/aws-lc-rs/src/cipher/streaming.rs +++ b/aws-lc-rs/src/cipher/streaming.rs @@ -363,12 +363,96 @@ impl StreamingDecryptingKey { #[cfg(test)] mod tests { use crate::cipher::{ - DecryptionContext, OperatingMode, StreamingDecryptingKey, StreamingEncryptingKey, - UnboundCipherKey, AES_256, AES_256_KEY_LEN, + DecryptionContext, EncryptionContext, OperatingMode, StreamingDecryptingKey, + StreamingEncryptingKey, UnboundCipherKey, AES_128, AES_256, AES_256_KEY_LEN, }; + use crate::iv::{FixedLength, IV_LEN_128_BIT}; use crate::rand::{SecureRandom, SystemRandom}; + use crate::test::from_hex; use paste::*; + #[test] + fn test_encrypt_ctr_exact_fit_out_buffer() { + let random = SystemRandom::new(); + + for plaintext_len in (8..200).step_by(7) { + let mut plaintext = vec![0u8; plaintext_len]; + random.fill(&mut plaintext).unwrap(); + + for cipher_alg in [&AES_128, &AES_256] { + let mut key = vec![0u8; cipher_alg.key_len]; + random.fill(&mut key).unwrap(); + + let unbound_key = UnboundCipherKey::new(cipher_alg, &key).unwrap(); + let mut encrypt_key = StreamingEncryptingKey::ctr(unbound_key).unwrap(); + + let mut ciphertext_buff = vec![0u8; plaintext_len]; + let mut buffer_update = encrypt_key + .update(&plaintext, &mut ciphertext_buff) + .unwrap(); + let (decrypt_ctx, _) = encrypt_key + .finish(&mut buffer_update.remainder_mut()) + .unwrap(); + + let unbound_key2 = UnboundCipherKey::new(cipher_alg, &key).unwrap(); + let mut decrypt_key = + StreamingDecryptingKey::ctr(unbound_key2, decrypt_ctx).unwrap(); + + let mut plaintext_buff = vec![0u8; plaintext_len]; + let mut buffer_update = decrypt_key + .update(&ciphertext_buff, &mut plaintext_buff) + .unwrap(); + let _ = decrypt_key + .finish(&mut buffer_update.remainder_mut()) + .unwrap(); + + assert_eq!(&plaintext_buff, &plaintext); + } + } + } + + #[test] + fn test_encrypt_cbc_exact_fit_out_buffer() { + let random = SystemRandom::new(); + + for plaintext_len in (8..200).step_by(7) { + let mut plaintext = vec![0u8; plaintext_len]; + random.fill(&mut plaintext).unwrap(); + + for cipher_alg in [&AES_128, &AES_256] { + let mut key = vec![0u8; cipher_alg.key_len]; + random.fill(&mut key).unwrap(); + + let unbound_key = UnboundCipherKey::new(cipher_alg, &key).unwrap(); + let mut encrypt_key = StreamingEncryptingKey::cbc_pkcs7(unbound_key).unwrap(); + + let mut ciphertext_buff = vec![0u8; plaintext_len + cipher_alg.block_len()]; + let mut ciphertext_len = 0usize; + let mut buffer_update = encrypt_key + .update(&plaintext, &mut ciphertext_buff) + .unwrap(); + ciphertext_len += buffer_update.written().len(); + let remaining_buff = buffer_update.remainder_mut(); + let (decrypt_ctx, buffer_update) = encrypt_key.finish(remaining_buff).unwrap(); + ciphertext_len += buffer_update.written().len(); + + let unbound_key2 = UnboundCipherKey::new(cipher_alg, &key).unwrap(); + let mut decrypt_key = + StreamingDecryptingKey::cbc_pkcs7(unbound_key2, decrypt_ctx).unwrap(); + + let mut plaintext_buff = vec![0u8; plaintext_len + cipher_alg.block_len()]; + let mut buffer_update = decrypt_key + .update(&ciphertext_buff[0..ciphertext_len], &mut plaintext_buff) + .unwrap(); + let _ = decrypt_key + .finish(&mut buffer_update.remainder_mut()) + .unwrap(); + + assert_eq!(&plaintext_buff[0..plaintext_len], &plaintext); + } + } + } + fn step_encrypt( mut encrypting_key: StreamingEncryptingKey, plaintext: &[u8], @@ -423,8 +507,8 @@ mod tests { ciphertext: &[u8], step: usize, ) -> Box<[u8]> { - let alg = decrypting_key.algorithm; - let mode = decrypting_key.mode; + let alg = decrypting_key.algorithm(); + let mode = decrypting_key.mode(); let n = ciphertext.len(); let mut plaintext = vec![0u8; n + alg.block_len()]; @@ -606,4 +690,157 @@ mod tests { ); } } + + 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] + fn $name() { + let key = from_hex($key).unwrap(); + let input = from_hex($plaintext).unwrap(); + let expected_ciphertext = from_hex($ciphertext).unwrap(); + let iv = from_hex($iv).unwrap(); + + for step in ($from_step..=$to_step) { + let ec = EncryptionContext::Iv128( + FixedLength::::try_from(iv.as_slice()).unwrap(), + ); + + let unbound_key = UnboundCipherKey::new($alg, &key).unwrap(); + + let encrypting_key = + StreamingEncryptingKey::new(unbound_key, $mode, ec).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(); + + let plaintext = step_decrypt(decrypting_key, &ciphertext, step); + assert_eq!(input.as_slice(), plaintext.as_ref()); + } + } + }; + } + + streaming_cipher_kat!( + test_iv_aes_128_ctr_16_bytes, + &AES_128, + OperatingMode::CTR, + "000102030405060708090a0b0c0d0e0f", + "00000000000000000000000000000000", + "00112233445566778899aabbccddeeff", + "c6b01904c3da3df5e7d62bd96d153686", + 2, + 9 + ); + streaming_cipher_kat!( + test_iv_aes_256_ctr_15_bytes, + &AES_256, + OperatingMode::CTR, + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "00000000000000000000000000000000", + "00112233445566778899aabbccddee", + "f28122856e1cf9a7216a30d111f399", + 2, + 9 + ); + + streaming_cipher_kat!( + test_openssl_aes_128_ctr_15_bytes, + &AES_128, + OperatingMode::CTR, + "244828580821c1652582c76e34d299f5", + "093145d5af233f46072a5eb5adc11aa1", + "3ee38cec171e6cf466bf0df98aa0e1", + "bd7d928f60e3422d96b3f8cd614eb2", + 2, + 9 + ); + + streaming_cipher_kat!( + test_openssl_aes_256_ctr_15_bytes, + &AES_256, + OperatingMode::CTR, + "0857db8240ea459bdf660b4cced66d1f2d3734ff2de7b81e92740e65e7cc6a1d", + "f028ecb053f801102d11fccc9d303a27", + "eca7285d19f3c20e295378460e8729", + "b5098e5e788de6ac2f2098eb2fc6f8", + 2, + 9 + ); + + streaming_cipher_kat!( + test_iv_aes_128_cbc_16_bytes, + &AES_128, + OperatingMode::CBC, + "000102030405060708090a0b0c0d0e0f", + "00000000000000000000000000000000", + "00112233445566778899aabbccddeeff", + "69c4e0d86a7b0430d8cdb78070b4c55a9e978e6d16b086570ef794ef97984232", + 2, + 9 + ); + + streaming_cipher_kat!( + test_iv_aes_256_cbc_15_bytes, + &AES_256, + OperatingMode::CBC, + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "00000000000000000000000000000000", + "00112233445566778899aabbccddee", + "2ddfb635a651a43f582997966840ca0c", + 2, + 9 + ); + + streaming_cipher_kat!( + test_openssl_aes_128_cbc_15_bytes, + &AES_128, + OperatingMode::CBC, + "053304bb3899e1d99db9d29343ea782d", + "b5313560244a4822c46c2a0c9d0cf7fd", + "a3e4c990356c01f320043c3d8d6f43", + "ad96993f248bd6a29760ec7ccda95ee1", + 2, + 9 + ); + + streaming_cipher_kat!( + test_openssl_aes_128_cbc_16_bytes, + &AES_128, + OperatingMode::CBC, + "95af71f1c63e4a1d0b0b1a27fb978283", + "89e40797dca70197ff87d3dbb0ef2802", + "aece7b5e3c3df1ffc9802d2dfe296dc7", + "301b5dab49fb11e919d0d39970d06739301919743304f23f3cbc67d28564b25b", + 2, + 9 + ); + + streaming_cipher_kat!( + test_openssl_aes_256_cbc_15_bytes, + &AES_256, + OperatingMode::CBC, + "d369e03e9752784917cc7bac1db7399598d9555e691861d9dd7b3292a693ef57", + "1399bb66b2f6ad99a7f064140eaaa885", + "7385f5784b85bf0a97768ddd896d6d", + "4351082bac9b4593ae8848cc9dfb5a01", + 2, + 9 + ); + + streaming_cipher_kat!( + test_openssl_aes_256_cbc_16_bytes, + &AES_256, + OperatingMode::CBC, + "d4a8206dcae01242f9db79a4ecfe277d0f7bb8ccbafd8f9809adb39f35aa9b41", + "24f6076548fb9d93c8f7ed9f6e661ef9", + "a39c1fdf77ea3e1f18178c0ec237c70a", + "f1af484830a149ee0387b854d65fe87ca0e62efc1c8e6909d4b9ab8666470453", + 2, + 9 + ); } diff --git a/aws-lc-rs/tests/cipher_test.rs b/aws-lc-rs/tests/cipher_test.rs index 43660f01f21..bbf844d89c2 100644 --- a/aws-lc-rs/tests/cipher_test.rs +++ b/aws-lc-rs/tests/cipher_test.rs @@ -2,12 +2,172 @@ // SPDX-License-Identifier: Apache-2.0 OR ISC use aws_lc_rs::cipher::{ - DecryptingKey, EncryptingKey, EncryptionContext, OperatingMode, PaddedBlockDecryptingKey, - PaddedBlockEncryptingKey, UnboundCipherKey, AES_128, AES_256, + DecryptingKey, DecryptionContext, EncryptingKey, EncryptionContext, OperatingMode, + PaddedBlockDecryptingKey, PaddedBlockEncryptingKey, StreamingDecryptingKey, + StreamingEncryptingKey, UnboundCipherKey, AES_128, AES_256, }; -use aws_lc_rs::iv::FixedLength; +use aws_lc_rs::iv::{FixedLength, IV_LEN_128_BIT}; use aws_lc_rs::test::from_hex; +use paste::paste; + +fn step_encrypt( + mut encrypting_key: StreamingEncryptingKey, + plaintext: &[u8], + step: usize, +) -> (Box<[u8]>, DecryptionContext) { + let alg = encrypting_key.algorithm(); + let mode = encrypting_key.mode(); + let n = plaintext.len(); + let mut ciphertext = vec![0u8; n + alg.block_len()]; + + let mut in_idx: usize = 0; + let mut out_idx: usize = 0; + loop { + let mut in_end = in_idx + step; + if in_end > n { + in_end = n; + } + let out_end = out_idx + (in_end - in_idx) + alg.block_len(); + let output = encrypting_key + .update( + &plaintext[in_idx..in_end], + &mut ciphertext[out_idx..out_end], + ) + .unwrap(); + in_idx += step; + out_idx += output.written().len(); + if in_idx >= n { + break; + } + } + let out_end = out_idx + alg.block_len(); + let (decrypt_iv, output) = encrypting_key + .finish(&mut ciphertext[out_idx..out_end]) + .unwrap(); + let outlen = output.written().len(); + ciphertext.truncate(out_idx + outlen); + match mode { + OperatingMode::CBC => { + assert!(ciphertext.len() > plaintext.len()); + assert!(ciphertext.len() <= plaintext.len() + alg.block_len()); + } + OperatingMode::CTR => { + assert_eq!(ciphertext.len(), plaintext.len()); + } + _ => panic!("Unknown cipher mode"), + } + + (ciphertext.into_boxed_slice(), decrypt_iv) +} + +fn step_decrypt( + mut decrypting_key: StreamingDecryptingKey, + ciphertext: &[u8], + step: usize, +) -> Box<[u8]> { + let alg = decrypting_key.algorithm(); + let mode = decrypting_key.mode(); + let n = ciphertext.len(); + let mut plaintext = vec![0u8; n + alg.block_len()]; + + let mut in_idx: usize = 0; + let mut out_idx: usize = 0; + loop { + let mut in_end = in_idx + step; + if in_end > n { + in_end = n; + } + let out_end = out_idx + (in_end - in_idx) + alg.block_len(); + let output = decrypting_key + .update( + &ciphertext[in_idx..in_end], + &mut plaintext[out_idx..out_end], + ) + .unwrap(); + in_idx += step; + out_idx += output.written().len(); + if in_idx >= n { + break; + } + } + let out_end = out_idx + alg.block_len(); + let output = decrypting_key + .finish(&mut plaintext[out_idx..out_end]) + .unwrap(); + let outlen = output.written().len(); + plaintext.truncate(out_idx + outlen); + match mode { + OperatingMode::CBC => { + assert!(ciphertext.len() > plaintext.len()); + assert!(ciphertext.len() <= plaintext.len() + alg.block_len()); + } + OperatingMode::CTR => { + assert_eq!(ciphertext.len(), plaintext.len()); + } + _ => panic!("Unknown cipher mode"), + } + plaintext.into_boxed_slice() +} + +macro_rules! streaming_cipher_rt { + ($name:ident, $alg:expr, $mode:expr, $constructor:ident, $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::$constructor(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::$constructor(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! { + #[test] + fn [<$name _streaming>]() { + let key = from_hex($key).unwrap(); + let input = from_hex($plaintext).unwrap(); + let expected_ciphertext = from_hex($ciphertext).unwrap(); + let iv = from_hex($iv).unwrap(); + + for step in ($from_step..=$to_step) { + let ec = EncryptionContext::Iv128( + FixedLength::::try_from(iv.as_slice()).unwrap(), + ); + + let unbound_key = UnboundCipherKey::new($alg, &key).unwrap(); + let encrypting_key = StreamingEncryptingKey::[](unbound_key, ec).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::$constructor(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] @@ -38,6 +198,19 @@ macro_rules! padded_cipher_kat { let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap(); assert_eq!(input.as_slice(), plaintext); } + + streaming_cipher_kat!( + $name, + $alg, + $mode, + $constructor, + $key, + $iv, + $plaintext, + $ciphertext, + 2, + 9 + ); }; } @@ -71,6 +244,19 @@ macro_rules! cipher_kat { let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap(); assert_eq!(input.as_slice(), plaintext); } + + streaming_cipher_kat!( + $name, + $alg, + $mode, + $constructor, + $key, + $iv, + $plaintext, + $ciphertext, + 2, + 9 + ); }; } @@ -95,6 +281,8 @@ macro_rules! padded_cipher_rt { let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap(); assert_eq!(input.as_slice(), plaintext); } + + streaming_cipher_rt!($name, $alg, $mode, $constructor, $key, $plaintext, 2, 9); }; } @@ -119,6 +307,7 @@ macro_rules! cipher_rt { let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap(); assert_eq!(input.as_slice(), plaintext); } + streaming_cipher_rt!($name, $alg, $mode, $constructor, $key, $plaintext, 2, 9); }; } From 619fbe71dad01a158e5865e51f766f83e18ed2f4 Mon Sep 17 00:00:00 2001 From: Justin Smith Date: Fri, 24 May 2024 10:06:38 -0400 Subject: [PATCH 5/8] Satisfy clippy --- aws-lc-fips-sys/aws-lc | 2 +- aws-lc-rs/src/cipher.rs | 3 +-- aws-lc-rs/src/cipher/streaming.rs | 12 +++--------- aws-lc-sys/aws-lc | 2 +- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/aws-lc-fips-sys/aws-lc b/aws-lc-fips-sys/aws-lc index 93177de1a39..e61907c65c2 160000 --- a/aws-lc-fips-sys/aws-lc +++ b/aws-lc-fips-sys/aws-lc @@ -1 +1 @@ -Subproject commit 93177de1a3994af5f649c77d867542b1520c7fe1 +Subproject commit e61907c65c2d529ec3399e719653b5d07613acb1 diff --git a/aws-lc-rs/src/cipher.rs b/aws-lc-rs/src/cipher.rs index dcbafe532be..425c44bf1c2 100644 --- a/aws-lc-rs/src/cipher.rs +++ b/aws-lc-rs/src/cipher.rs @@ -410,8 +410,7 @@ impl UnboundCipherKey { /// /// # Errors /// - /// * [`Unspecified`] if `key_bytes.len()` does not match the - /// length required by `algorithm`. + /// * [`Unspecified`] if `key_bytes.len()` does not match the length required by `algorithm`. pub fn new(algorithm: &'static Algorithm, key_bytes: &[u8]) -> Result { let key_bytes = Buffer::new(key_bytes.to_vec()); Ok(UnboundCipherKey { diff --git a/aws-lc-rs/src/cipher/streaming.rs b/aws-lc-rs/src/cipher/streaming.rs index d1d4e180346..90e5e478604 100644 --- a/aws-lc-rs/src/cipher/streaming.rs +++ b/aws-lc-rs/src/cipher/streaming.rs @@ -390,9 +390,7 @@ mod tests { let mut buffer_update = encrypt_key .update(&plaintext, &mut ciphertext_buff) .unwrap(); - let (decrypt_ctx, _) = encrypt_key - .finish(&mut buffer_update.remainder_mut()) - .unwrap(); + let (decrypt_ctx, _) = encrypt_key.finish(buffer_update.remainder_mut()).unwrap(); let unbound_key2 = UnboundCipherKey::new(cipher_alg, &key).unwrap(); let mut decrypt_key = @@ -402,9 +400,7 @@ mod tests { let mut buffer_update = decrypt_key .update(&ciphertext_buff, &mut plaintext_buff) .unwrap(); - let _ = decrypt_key - .finish(&mut buffer_update.remainder_mut()) - .unwrap(); + let _ = decrypt_key.finish(buffer_update.remainder_mut()).unwrap(); assert_eq!(&plaintext_buff, &plaintext); } @@ -444,9 +440,7 @@ mod tests { let mut buffer_update = decrypt_key .update(&ciphertext_buff[0..ciphertext_len], &mut plaintext_buff) .unwrap(); - let _ = decrypt_key - .finish(&mut buffer_update.remainder_mut()) - .unwrap(); + let _ = decrypt_key.finish(buffer_update.remainder_mut()).unwrap(); assert_eq!(&plaintext_buff[0..plaintext_len], &plaintext); } diff --git a/aws-lc-sys/aws-lc b/aws-lc-sys/aws-lc index 4e54dd83633..b434043d85c 160000 --- a/aws-lc-sys/aws-lc +++ b/aws-lc-sys/aws-lc @@ -1 +1 @@ -Subproject commit 4e54dd8363396f257d7a2317c48101e18170e6fb +Subproject commit b434043d85c0744a5f82248c4fd0fd20b4a1dae4 From b08fed7c801b62a94ef04a11ce9d260f8a7c1fee Mon Sep 17 00:00:00 2001 From: Justin Smith Date: Wed, 5 Jun 2024 17:33:35 -0400 Subject: [PATCH 6/8] Cleanup + PR feedback + rebase --- aws-lc-rs/src/cipher/streaming.rs | 153 +++++++++--------------------- aws-lc-rs/src/ptr.rs | 8 ++ 2 files changed, 55 insertions(+), 106 deletions(-) diff --git a/aws-lc-rs/src/cipher/streaming.rs b/aws-lc-rs/src/cipher/streaming.rs index 90e5e478604..aa7345bde7d 100644 --- a/aws-lc-rs/src/cipher/streaming.rs +++ b/aws-lc-rs/src/cipher/streaming.rs @@ -63,7 +63,7 @@ impl StreamingEncryptingKey { context: EncryptionContext, ) -> Result { let algorithm = key.algorithm(); - let cipher_ctx = LcPtr::new(unsafe { EVP_CIPHER_CTX_new() })?; + let mut cipher_ctx = LcPtr::new(unsafe { EVP_CIPHER_CTX_new() })?; let cipher = mode.evp_cipher(key.algorithm); let key_bytes = key.key_bytes.as_ref(); debug_assert_eq!( @@ -100,22 +100,28 @@ impl StreamingEncryptingKey { /// potentially writing bytes of ciphertext to `output`. /// /// The number of bytes written to `output` can be up to `input.len()` - /// plus the block length of the algorithm (e.g., 16 bytes for AES). + /// plus the block length of the algorithm (e.g., [`Algorithm::block_len`]). /// /// # Errors - /// * May return an error if the `output` buffer is smaller than the length of - /// the `input` plus the algorithm's block length. Certain cipher modes - /// (such as CTR) may allow the output buffer to be as small as the size - /// of the input in certain circumstances. - /// * Returns an error if the length of either `input` or `output` is larger - /// than `i32::MAX`. + /// * Returns an error if the `output` buffer is smaller than the length of + /// the `input` plus the algorithm's block length (e.g. [`Algorithm::block_len`]) minus one. + /// * May return an error if the length of `input` plus the algorithm's block length is larger than `i32::MAX`. pub fn update<'a>( &mut self, input: &[u8], output: &'a mut [u8], ) -> Result, Unspecified> { - let mut outlen: i32 = output.len().try_into()?; + let min_outsize = input + .len() + .checked_add(self.algorithm().block_len()) + .ok_or(Unspecified)? + - 1; + if output.len() < min_outsize { + return Err(Unspecified); + } + let mut outlen: i32 = 0; let inlen: i32 = input.len().try_into()?; + if 1 != unsafe { EVP_EncryptUpdate( self.cipher_ctx.as_mut_ptr(), @@ -135,18 +141,20 @@ impl StreamingEncryptingKey { /// `output`. /// /// The number of bytes written to `output` can be up to the block length of - /// the algorithm (e.g., 16 bytes for AES). + /// [`Algorithm::block_len`]. /// /// # Errors - /// * May return an error if the `output` buffer is smaller than the algorithm's - /// block length. Certain cipher mode (such as CTR) may allow the output - /// buffer to only be large enough to fit the remainder of the ciphertext. - /// * Returns an error if the length of `output` is larger than `i32::MAX`. + /// * Returns an error if the `output` buffer is smaller than the algorithm's + /// block length. pub fn finish( - self, + mut self, output: &mut [u8], ) -> Result<(DecryptionContext, BufferUpdate), Unspecified> { - let mut outlen: i32 = output.len().try_into()?; + if output.len() < self.algorithm().block_len() { + return Err(Unspecified); + } + let mut outlen: i32 = 0; + if 1 != unsafe { EVP_EncryptFinal_ex( self.cipher_ctx.as_mut_ptr(), @@ -240,7 +248,7 @@ impl StreamingDecryptingKey { mode: OperatingMode, context: DecryptionContext, ) -> Result { - let cipher_ctx = LcPtr::new(unsafe { EVP_CIPHER_CTX_new() })?; + let mut cipher_ctx = LcPtr::new(unsafe { EVP_CIPHER_CTX_new() })?; let algorithm = key.algorithm(); let cipher = mode.evp_cipher(key.algorithm); let key_bytes = key.key_bytes.as_ref(); @@ -276,22 +284,29 @@ impl StreamingDecryptingKey { /// Updates the internal state of the key with the provided ciphertext `input`, /// potentially also writing bytes of plaintext to `output`. /// The number of bytes written to `output` can be up to `input.len()` - /// plus the block length of the cipher algorithm (e.g., 16 bytes for AES). + /// plus the block length of the cipher algorithm (e.g., [`Algorithm::block_len`]). /// /// # Errors - /// * May return an error if the `output` buffer is smaller than the length of - /// the `input` plus the algorithm's block length. Certain cipher modes - /// (such as CTR) may allow the output buffer to be as small as the size of - /// the input in certain circumstances. - /// * Returns an error if the length of either `input` or `output` is larger + /// * Returns an error if the `output` buffer is smaller than the length of + /// the `input` plus the algorithm's block length. + /// * May return an error if the length of `input` plus the algorithm's block length is larger /// than `i32::MAX`. pub fn update<'a>( &mut self, input: &[u8], output: &'a mut [u8], ) -> Result, Unspecified> { - let mut outlen: i32 = output.len().try_into()?; + let mut outlen: i32 = 0; let inlen: i32 = input.len().try_into()?; + + let min_outsize = input + .len() + .checked_add(self.algorithm().block_len()) + .ok_or(Unspecified)?; + if output.len() < min_outsize { + return Err(Unspecified); + } + if 1 != unsafe { EVP_DecryptUpdate( self.cipher_ctx.as_mut_ptr(), @@ -310,15 +325,17 @@ impl StreamingDecryptingKey { /// Finishes the decryption operation, writing the remaining plaintext to /// `output`. /// The number of bytes written to `output` can be up to the block length of - /// the cipher algorithm (e.g., 16 bytes for AES). + /// the cipher algorithm (e.g., [`Algorithm::block_len`]). /// /// # Errors - /// * May return an error if the `output` buffer is smaller than the algorithm's - /// block length. Certain cipher modes (such as CTR) may allow the output buffer - /// to only be large enough to fit the remaining plaintext. - /// * Returns an error if the length of `output` is larger than `i32::MAX`. + /// * Returns an error if the `output` buffer is smaller than the algorithm's + /// block length. pub fn finish(self, output: &mut [u8]) -> Result { - let mut outlen: i32 = output.len().try_into()?; + if output.len() < self.algorithm().block_len() { + return Err(Unspecified); + } + let mut outlen: i32 = 0; + if 1 != unsafe { EVP_DecryptFinal_ex(*self.cipher_ctx, output.as_mut_ptr(), &mut outlen) } { return Err(Unspecified); } @@ -371,82 +388,6 @@ mod tests { use crate::test::from_hex; use paste::*; - #[test] - fn test_encrypt_ctr_exact_fit_out_buffer() { - let random = SystemRandom::new(); - - for plaintext_len in (8..200).step_by(7) { - let mut plaintext = vec![0u8; plaintext_len]; - random.fill(&mut plaintext).unwrap(); - - for cipher_alg in [&AES_128, &AES_256] { - let mut key = vec![0u8; cipher_alg.key_len]; - random.fill(&mut key).unwrap(); - - let unbound_key = UnboundCipherKey::new(cipher_alg, &key).unwrap(); - let mut encrypt_key = StreamingEncryptingKey::ctr(unbound_key).unwrap(); - - let mut ciphertext_buff = vec![0u8; plaintext_len]; - let mut buffer_update = encrypt_key - .update(&plaintext, &mut ciphertext_buff) - .unwrap(); - let (decrypt_ctx, _) = encrypt_key.finish(buffer_update.remainder_mut()).unwrap(); - - let unbound_key2 = UnboundCipherKey::new(cipher_alg, &key).unwrap(); - let mut decrypt_key = - StreamingDecryptingKey::ctr(unbound_key2, decrypt_ctx).unwrap(); - - let mut plaintext_buff = vec![0u8; plaintext_len]; - let mut buffer_update = decrypt_key - .update(&ciphertext_buff, &mut plaintext_buff) - .unwrap(); - let _ = decrypt_key.finish(buffer_update.remainder_mut()).unwrap(); - - assert_eq!(&plaintext_buff, &plaintext); - } - } - } - - #[test] - fn test_encrypt_cbc_exact_fit_out_buffer() { - let random = SystemRandom::new(); - - for plaintext_len in (8..200).step_by(7) { - let mut plaintext = vec![0u8; plaintext_len]; - random.fill(&mut plaintext).unwrap(); - - for cipher_alg in [&AES_128, &AES_256] { - let mut key = vec![0u8; cipher_alg.key_len]; - random.fill(&mut key).unwrap(); - - let unbound_key = UnboundCipherKey::new(cipher_alg, &key).unwrap(); - let mut encrypt_key = StreamingEncryptingKey::cbc_pkcs7(unbound_key).unwrap(); - - let mut ciphertext_buff = vec![0u8; plaintext_len + cipher_alg.block_len()]; - let mut ciphertext_len = 0usize; - let mut buffer_update = encrypt_key - .update(&plaintext, &mut ciphertext_buff) - .unwrap(); - ciphertext_len += buffer_update.written().len(); - let remaining_buff = buffer_update.remainder_mut(); - let (decrypt_ctx, buffer_update) = encrypt_key.finish(remaining_buff).unwrap(); - ciphertext_len += buffer_update.written().len(); - - let unbound_key2 = UnboundCipherKey::new(cipher_alg, &key).unwrap(); - let mut decrypt_key = - StreamingDecryptingKey::cbc_pkcs7(unbound_key2, decrypt_ctx).unwrap(); - - let mut plaintext_buff = vec![0u8; plaintext_len + cipher_alg.block_len()]; - let mut buffer_update = decrypt_key - .update(&ciphertext_buff[0..ciphertext_len], &mut plaintext_buff) - .unwrap(); - let _ = decrypt_key.finish(buffer_update.remainder_mut()).unwrap(); - - assert_eq!(&plaintext_buff[0..plaintext_len], &plaintext); - } - } - } - fn step_encrypt( mut encrypting_key: StreamingEncryptingKey, plaintext: &[u8], diff --git a/aws-lc-rs/src/ptr.rs b/aws-lc-rs/src/ptr.rs index 4be8ce54de9..93b76b62d40 100644 --- a/aws-lc-rs/src/ptr.rs +++ b/aws-lc-rs/src/ptr.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR ISC use core::ops::Deref; +use std::ops::DerefMut; use aws_lc::{ BN_free, ECDSA_SIG_free, EC_GROUP_free, EC_KEY_free, EC_POINT_free, EVP_AEAD_CTX_free, @@ -28,6 +29,13 @@ impl Deref for ManagedPointer

{ } } +impl DerefMut for ManagedPointer

{ + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.pointer + } +} + impl ManagedPointer

{ #[inline] pub fn new>(value: T) -> Result { From b99e78dfbe61036f6044954277f515400f015734 Mon Sep 17 00:00:00 2001 From: Justin Smith Date: Fri, 7 Jun 2024 10:08:06 -0400 Subject: [PATCH 7/8] Add FIPS tests --- aws-lc-rs/src/cipher/streaming.rs | 11 ++-- aws-lc-rs/src/cipher/tests/fips.rs | 84 ++++++++++++++++++++++++++++-- 2 files changed, 87 insertions(+), 8 deletions(-) diff --git a/aws-lc-rs/src/cipher/streaming.rs b/aws-lc-rs/src/cipher/streaming.rs index aa7345bde7d..03103d0c660 100644 --- a/aws-lc-rs/src/cipher/streaming.rs +++ b/aws-lc-rs/src/cipher/streaming.rs @@ -5,6 +5,7 @@ use crate::cipher::{ Algorithm, DecryptionContext, EncryptionContext, OperatingMode, UnboundCipherKey, }; use crate::error::Unspecified; +use crate::fips::indicator_check; use crate::ptr::{LcPtr, Pointer}; use aws_lc::{ EVP_CIPHER_CTX_new, EVP_CIPHER_iv_length, EVP_CIPHER_key_length, EVP_DecryptFinal_ex, @@ -76,6 +77,7 @@ impl StreamingEncryptingKey { ::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_ptr(), @@ -155,13 +157,13 @@ impl StreamingEncryptingKey { } let mut outlen: i32 = 0; - if 1 != unsafe { + if 1 != indicator_check!(unsafe { EVP_EncryptFinal_ex( self.cipher_ctx.as_mut_ptr(), output.as_mut_ptr(), &mut outlen, ) - } { + }) { return Err(Unspecified); } let outlen: usize = outlen.try_into()?; @@ -262,6 +264,7 @@ impl StreamingDecryptingKey { ::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_ptr(), @@ -336,7 +339,9 @@ impl StreamingDecryptingKey { } let mut outlen: i32 = 0; - if 1 != unsafe { EVP_DecryptFinal_ex(*self.cipher_ctx, output.as_mut_ptr(), &mut outlen) } { + if 1 != indicator_check!(unsafe { + EVP_DecryptFinal_ex(*self.cipher_ctx, output.as_mut_ptr(), &mut outlen) + }) { return Err(Unspecified); } let outlen: usize = outlen.try_into()?; diff --git a/aws-lc-rs/src/cipher/tests/fips.rs b/aws-lc-rs/src/cipher/tests/fips.rs index 0de23fd0939..6953f6a7529 100644 --- a/aws-lc-rs/src/cipher/tests/fips.rs +++ b/aws-lc-rs/src/cipher/tests/fips.rs @@ -6,7 +6,7 @@ use crate::{ cipher::{ DecryptingKey, EncryptingKey, PaddedBlockDecryptingKey, PaddedBlockEncryptingKey, - UnboundCipherKey, AES_128, AES_256, + StreamingDecryptingKey, StreamingEncryptingKey, UnboundCipherKey, AES_128, AES_256, }, fips::{assert_fips_status_indicator, FipsServiceStatus}, }; @@ -49,15 +49,89 @@ macro_rules! block_api { }; } +macro_rules! streaming_api { + ($name:ident, $alg:expr, $encrypt_mode:path, $decrypt_mode:path, $key:expr) => { + #[test] + fn $name() { + let mut key = $encrypt_mode(UnboundCipherKey::new($alg, $key).unwrap()).unwrap(); + + let input = TEST_MESSAGE.as_bytes(); + let mut encrypt_output = vec![0u8; TEST_MESSAGE.len() + $alg.block_len()]; + + let mut buffer_update = key.update(&input, &mut encrypt_output).unwrap(); + + let outlen = buffer_update.written().len(); + let (context, buffer_update) = assert_fips_status_indicator!( + key.finish(buffer_update.remainder_mut()), + FipsServiceStatus::Approved + ) + .unwrap(); + + let outlen = outlen + buffer_update.written().len(); + + let ciphertext = &encrypt_output[0..outlen]; + let mut decrypt_output = vec![0u8; outlen + $alg.block_len()]; + let mut key = + $decrypt_mode(UnboundCipherKey::new($alg, $key).unwrap(), context).unwrap(); + + let mut buffer_update = key.update(ciphertext, &mut decrypt_output).unwrap(); + + let outlen = buffer_update.written().len(); + let buffer_update = assert_fips_status_indicator!( + key.finish(buffer_update.remainder_mut()), + FipsServiceStatus::Approved + ) + .unwrap(); + + let outlen = outlen + buffer_update.written().len(); + let plaintext = &decrypt_output[0..outlen]; + + assert_eq!(TEST_MESSAGE.as_bytes(), plaintext); + } + }; +} + +streaming_api!( + streaming_aes_128_cbc_pkcs7, + &AES_128, + StreamingEncryptingKey::cbc_pkcs7, + StreamingDecryptingKey::cbc_pkcs7, + &TEST_KEY_128_BIT +); + +streaming_api!( + streaming_aes_128_ctr, + &AES_128, + StreamingEncryptingKey::ctr, + StreamingDecryptingKey::ctr, + &TEST_KEY_128_BIT +); + +streaming_api!( + streaming_aes_256_cbc_pkcs7, + &AES_256, + StreamingEncryptingKey::cbc_pkcs7, + StreamingDecryptingKey::cbc_pkcs7, + &TEST_KEY_256_BIT +); +streaming_api!( + streaming_aes_256_ctr, + &AES_256, + StreamingEncryptingKey::ctr, + StreamingDecryptingKey::ctr, + &TEST_KEY_256_BIT +); + block_api!( - aes_126_cbc_pkcs7, + block_aes_128_cbc_pkcs7, &AES_128, PaddedBlockEncryptingKey::cbc_pkcs7, PaddedBlockDecryptingKey::cbc_pkcs7, &TEST_KEY_128_BIT ); + block_api!( - aes_126_ctr, + block_aes_128_ctr, &AES_128, EncryptingKey::ctr, DecryptingKey::ctr, @@ -65,14 +139,14 @@ block_api!( ); block_api!( - aes_256_cbc_pkcs7, + block_aes_256_cbc_pkcs7, &AES_256, PaddedBlockEncryptingKey::cbc_pkcs7, PaddedBlockDecryptingKey::cbc_pkcs7, &TEST_KEY_256_BIT ); block_api!( - aes_256_ctr, + block_aes_256_ctr, &AES_256, EncryptingKey::ctr, DecryptingKey::ctr, From 0a75b01d199c4788eee96e3cbb0772fbb5535e0f Mon Sep 17 00:00:00 2001 From: Justin Smith Date: Mon, 17 Jun 2024 06:20:57 -0400 Subject: [PATCH 8/8] Add debug asserts on output lengths --- aws-lc-rs/src/cipher/streaming.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/aws-lc-rs/src/cipher/streaming.rs b/aws-lc-rs/src/cipher/streaming.rs index 03103d0c660..9d2639d60c5 100644 --- a/aws-lc-rs/src/cipher/streaming.rs +++ b/aws-lc-rs/src/cipher/streaming.rs @@ -136,6 +136,7 @@ impl StreamingEncryptingKey { return Err(Unspecified); } let outlen: usize = outlen.try_into()?; + debug_assert!(outlen <= min_outsize); Ok(BufferUpdate::new(output, outlen)) } @@ -167,6 +168,7 @@ impl StreamingEncryptingKey { return Err(Unspecified); } let outlen: usize = outlen.try_into()?; + debug_assert!(outlen <= self.algorithm().block_len()); Ok((self.context.into(), BufferUpdate::new(output, outlen))) } @@ -322,6 +324,7 @@ impl StreamingDecryptingKey { return Err(Unspecified); } let outlen: usize = outlen.try_into()?; + debug_assert!(outlen <= min_outsize); Ok(BufferUpdate::new(output, outlen)) } @@ -345,6 +348,7 @@ impl StreamingDecryptingKey { return Err(Unspecified); } let outlen: usize = outlen.try_into()?; + debug_assert!(outlen <= self.algorithm().block_len()); Ok(BufferUpdate::new(output, outlen)) }