From 9db3c098b1159df2c415bfc616a86681fe70cccb Mon Sep 17 00:00:00 2001 From: Cel <181064305+Cel-Service@users.noreply.github.com> Date: Tue, 10 Sep 2024 00:51:31 -0700 Subject: [PATCH] Implement trait AeadInto for AES-GCM and ChaCha20-Poly1305 --- aes-gcm/src/lib.rs | 60 ++++++++++++++++++++++++++++- aes-gcm/tests/aead_into.rs | 52 +++++++++++++++++++++++++ chacha20poly1305/src/cipher.rs | 46 ++++++++++++++++++++++ chacha20poly1305/src/lib.rs | 34 +++++++++++++++- chacha20poly1305/tests/aead_into.rs | 52 +++++++++++++++++++++++++ chacha20poly1305/tests/lib.rs | 2 + 6 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 aes-gcm/tests/aead_into.rs create mode 100644 chacha20poly1305/tests/aead_into.rs diff --git a/aes-gcm/src/lib.rs b/aes-gcm/src/lib.rs index b7cb8b5e..19953c9e 100644 --- a/aes-gcm/src/lib.rs +++ b/aes-gcm/src/lib.rs @@ -108,7 +108,8 @@ //! [`aead::Buffer`] for `arrayvec::ArrayVec` (re-exported from the [`aead`] crate as //! [`aead::arrayvec::ArrayVec`]). -pub use aead::{self, AeadCore, AeadInPlace, Error, Key, KeyInit, KeySizeUser}; +pub use aead::{self, AeadCore, AeadInto, AeadInPlace, Error, Key, KeyInit, KeySizeUser}; +use ::cipher::inout::InOutBuf; #[cfg(feature = "aes")] pub use aes; @@ -264,6 +265,63 @@ where type CiphertextOverhead = U0; } +impl AeadInto for AesGcm +where + Aes: BlockCipher + BlockSizeUser + BlockCipherEncrypt, + NonceSize: ArraySize, + TagSize: self::TagSize, +{ + fn encrypt_into_detached( + &self, + nonce: &Nonce, + plaintext: &[u8], + associated_data: &[u8], + ciphertext: &mut [u8], + ) -> Result, Error> { + if plaintext.len() as u64 > P_MAX || associated_data.len() as u64 > A_MAX { + return Err(Error); + } + + let buffer = InOutBuf::new(plaintext, ciphertext).map_err(|_| Error)?; + let (ctr, mask) = self.init_ctr(nonce); + + // TODO(tarcieri): interleave encryption with GHASH + // See: + ctr.apply_keystream_partial(buffer); + + let full_tag = self.compute_tag(mask, associated_data, ciphertext); + Ok(Tag::try_from(&full_tag[..TagSize::to_usize()]).expect("tag size mismatch")) + } + + fn decrypt_into_detached( + &self, + nonce: &Nonce, + ciphertext: &[u8], + associated_data: &[u8], + plaintext: &mut [u8], + tag: &Tag, + ) -> Result<(), Error> { + if ciphertext.len() as u64 > C_MAX || associated_data.len() as u64 > A_MAX { + return Err(Error); + } + + let buffer = InOutBuf::new(ciphertext, plaintext).map_err(|_| Error)?; + let (ctr, mask) = self.init_ctr(nonce); + + // TODO(tarcieri): interleave encryption with GHASH + // See: + let expected_tag = self.compute_tag(mask, associated_data, ciphertext); + + use subtle::ConstantTimeEq; + if expected_tag[..TagSize::to_usize()].ct_eq(tag).into() { + ctr.apply_keystream_partial(buffer); + Ok(()) + } else { + Err(Error) + } + } +} + impl AeadInPlace for AesGcm where Aes: BlockCipher + BlockSizeUser + BlockCipherEncrypt, diff --git a/aes-gcm/tests/aead_into.rs b/aes-gcm/tests/aead_into.rs new file mode 100644 index 00000000..97a6298d --- /dev/null +++ b/aes-gcm/tests/aead_into.rs @@ -0,0 +1,52 @@ +use aes_gcm::aead::{ Aead, AeadInto, Payload }; +use aes_gcm::{ Aes256Gcm, KeyInit }; + + +/// Confirm that the [AeadInto] implementation produces exactly the same output as the [Aead] +/// implementation. +#[test] +fn test_aeadinto() { + let key = [0x60, 0xaf, 0x5d, 0xe1, 0x8b, 0x63, 0xf8, 0xe3, 0xe9, 0xbc, 0xff, 0x93, 0xa1, 0xab, 0x69, 0x1c, 0x8c, 0x2a, 0x87, 0xb5, 0x35, 0xac, 0x2a, 0xa1, 0x4e, 0xba, 0xd1, 0xf1, 0x7d, 0x02, 0xff, 0x92]; + let aad = [0xbc, 0xf7, 0x7c, 0x42, 0x96, 0xf4, 0x96, 0x63, 0x16, 0x70, 0x02, 0x4e, 0xb2, 0x70, 0xd5, 0x0d, 0x6d, 0xef, 0xa2, 0x82, 0x59, 0xf7, 0x74, 0x60, 0xfc, 0x15, 0x05, 0xa1, 0x4c, 0x97, 0x4d, 0x4c]; + let data = [0xf6, 0xef, 0x21, 0x88, 0x8c, 0xfa, 0x75, 0x82, 0xda, 0x73, 0x7c, 0xad, 0x6a, 0x08, 0x64, 0x9b, 0xa3, 0x11, 0xfa, 0x27, 0x90, 0xdb, 0x74, 0x6f, 0xf0, 0x70, 0x57, 0xca, 0x15, 0xf8, 0xc8, 0x0a]; + + let mut trait_aeadinto_encrypted_output = [0u8; 32 + 16]; + Aes256Gcm::new((&key).try_into().unwrap()) + .encrypt_into( + (&[0u8; 12]).try_into().unwrap(), + &data, + &aad, + &mut trait_aeadinto_encrypted_output + ).unwrap(); + + let trait_aead_encrypted_output = Aes256Gcm::new((&key).try_into().unwrap()) + .encrypt( + (&[0u8; 12]).try_into().unwrap(), + Payload { + msg: &data, + aad: &aad + } + ).unwrap(); + + assert_eq!(trait_aead_encrypted_output, trait_aeadinto_encrypted_output); + + let mut trait_aeadinto_decrypted_output = [0u8; 32]; + Aes256Gcm::new((&key).try_into().unwrap()) + .decrypt_into( + (&[0u8; 12]).try_into().unwrap(), + &trait_aeadinto_encrypted_output, + &aad, + &mut trait_aeadinto_decrypted_output + ).unwrap(); + + let trait_aead_decrypted_output = Aes256Gcm::new((&key).try_into().unwrap()) + .decrypt( + (&[0u8; 12]).try_into().unwrap(), + Payload { + msg: &trait_aeadinto_encrypted_output, + aad: &aad + } + ).unwrap(); + + assert_eq!(trait_aead_decrypted_output, trait_aeadinto_decrypted_output); +} diff --git a/chacha20poly1305/src/cipher.rs b/chacha20poly1305/src/cipher.rs index 83dcd271..1b3ccfe1 100644 --- a/chacha20poly1305/src/cipher.rs +++ b/chacha20poly1305/src/cipher.rs @@ -1,6 +1,7 @@ //! Core AEAD cipher implementation for (X)ChaCha20Poly1305. use ::cipher::{StreamCipher, StreamCipherSeek}; +use ::cipher::inout::InOutBuf; use aead::array::Array; use aead::Error; use poly1305::{ @@ -46,6 +47,26 @@ where Self { cipher, mac } } + pub(crate) fn encrypt_inout_detached( + mut self, + associated_data: &[u8], + mut buffer: InOutBuf<'_, '_, u8>, + ) -> Result { + if buffer.len() / BLOCK_SIZE >= MAX_BLOCKS { + return Err(Error); + } + + self.mac.update_padded(associated_data); + + // TODO(tarcieri): interleave encryption with Poly1305 + // See: + self.cipher.apply_keystream_inout(buffer.reborrow()); + self.mac.update_padded(buffer.get_out()); + + self.authenticate_lengths(associated_data, buffer.get_out())?; + Ok(self.mac.finalize()) + } + /// Encrypt the given message in-place, returning the authentication tag pub(crate) fn encrypt_in_place_detached( mut self, @@ -67,6 +88,31 @@ where Ok(self.mac.finalize()) } + pub(crate) fn decrypt_inout_detached( + mut self, + associated_data: &[u8], + buffer: InOutBuf<'_, '_, u8>, + tag: &Tag, + ) -> Result<(), Error> { + if buffer.len() / BLOCK_SIZE >= MAX_BLOCKS { + return Err(Error); + } + + self.mac.update_padded(associated_data); + self.mac.update_padded(buffer.get_in()); + self.authenticate_lengths(associated_data, buffer.get_in())?; + + // This performs a constant-time comparison using the `subtle` crate + if self.mac.verify(tag).is_ok() { + // TODO(tarcieri): interleave decryption with Poly1305 + // See: + self.cipher.apply_keystream_inout(buffer); + Ok(()) + } else { + Err(Error) + } + } + /// Decrypt the given message, first authenticating ciphertext integrity /// and returning an error if it's been tampered with. pub(crate) fn decrypt_in_place_detached( diff --git a/chacha20poly1305/src/lib.rs b/chacha20poly1305/src/lib.rs index 55999fc9..26b362ed 100644 --- a/chacha20poly1305/src/lib.rs +++ b/chacha20poly1305/src/lib.rs @@ -143,10 +143,11 @@ mod cipher; -pub use aead::{self, consts, AeadCore, AeadInPlace, Error, KeyInit, KeySizeUser}; +pub use aead::{self, consts, AeadCore, AeadInto, AeadInPlace, Error, KeyInit, KeySizeUser}; use self::cipher::Cipher; use ::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek}; +use ::cipher::inout::InOutBuf; use aead::{ array::{Array, ArraySize}, consts::{U0, U12, U16, U24, U32}, @@ -252,6 +253,37 @@ where type CiphertextOverhead = U0; } +impl AeadInto for ChaChaPoly1305 +where + C: KeyIvInit + StreamCipher + StreamCipherSeek, + N: ArraySize, +{ + fn encrypt_into_detached( + &self, + nonce: &aead::Nonce, + plaintext: &[u8], + associated_data: &[u8], + ciphertext: &mut [u8], + ) -> Result { + Cipher::new(C::new(&self.key, nonce)).encrypt_inout_detached(associated_data, InOutBuf::new(plaintext, ciphertext).map_err(|_| Error)?) + } + + fn decrypt_into_detached( + &self, + nonce: &aead::Nonce, + ciphertext: &[u8], + associated_data: &[u8], + plaintext: &mut [u8], + tag: &Tag, + ) -> Result<(), Error> { + Cipher::new(C::new(&self.key, nonce)).decrypt_inout_detached( + associated_data, + InOutBuf::new(ciphertext, plaintext).map_err(|_| Error)?, + tag, + ) + } +} + impl AeadInPlace for ChaChaPoly1305 where C: KeyIvInit + StreamCipher + StreamCipherSeek, diff --git a/chacha20poly1305/tests/aead_into.rs b/chacha20poly1305/tests/aead_into.rs new file mode 100644 index 00000000..eacfa69a --- /dev/null +++ b/chacha20poly1305/tests/aead_into.rs @@ -0,0 +1,52 @@ +use chacha20poly1305::aead::{ Aead, AeadInto, Payload }; +use chacha20poly1305::{ ChaCha20Poly1305, KeyInit }; + + +/// Confirm that the [AeadInto] implementation produces exactly the same output as the [Aead] +/// implementation. +#[test] +fn test_aeadinto() { + let key = [0xfe, 0x14, 0xbc, 0x65, 0x16, 0xd6, 0x4a, 0x80, 0x8d, 0x58, 0xa3, 0x09, 0x8b, 0x0d, 0xa1, 0xc3, 0x2d, 0xf4, 0x62, 0xc0, 0x4d, 0x9e, 0x85, 0x55, 0x70, 0xc6, 0x83, 0xc2, 0x0f, 0xd7, 0xf4, 0x88]; + let aad = [0xfe, 0xa6, 0xa3, 0xc3, 0xae, 0x1a, 0xab, 0x0f, 0x80, 0xe7, 0xa9, 0xcb, 0x9a, 0x9e, 0x5f, 0x06, 0x16, 0x4d, 0x85, 0x46, 0x43, 0xbf, 0x74, 0xd4, 0x38, 0x19, 0xf6, 0xd3, 0x38, 0xa0, 0x5e, 0xaf]; + let data = [0xae, 0xfb, 0x8c, 0x8c, 0x38, 0xc9, 0x12, 0x04, 0x0f, 0x1d, 0xc0, 0x10, 0xf1, 0x94, 0xb0, 0x31, 0xcc, 0x00, 0xd8, 0xe4, 0xae, 0x5d, 0x04, 0x70, 0xb3, 0x6b, 0xfa, 0xb8, 0xe1, 0x23, 0x0c, 0x8c]; + + let mut trait_aeadinto_encrypted_output = [0u8; 32 + 16]; + ChaCha20Poly1305::new((&key).try_into().unwrap()) + .encrypt_into( + (&[0u8; 12]).try_into().unwrap(), + &data, + &aad, + &mut trait_aeadinto_encrypted_output + ).unwrap(); + + let trait_aead_encrypted_output = ChaCha20Poly1305::new((&key).try_into().unwrap()) + .encrypt( + (&[0u8; 12]).try_into().unwrap(), + Payload { + msg: &data, + aad: &aad + } + ).unwrap(); + + assert_eq!(trait_aead_encrypted_output, trait_aeadinto_encrypted_output); + + let mut trait_aeadinto_decrypted_output = [0u8; 32]; + ChaCha20Poly1305::new((&key).try_into().unwrap()) + .decrypt_into( + (&[0u8; 12]).try_into().unwrap(), + &trait_aeadinto_encrypted_output, + &aad, + &mut trait_aeadinto_decrypted_output + ).unwrap(); + + let trait_aead_decrypted_output = ChaCha20Poly1305::new((&key).try_into().unwrap()) + .decrypt( + (&[0u8; 12]).try_into().unwrap(), + Payload { + msg: &trait_aeadinto_encrypted_output, + aad: &aad + } + ).unwrap(); + + assert_eq!(trait_aead_decrypted_output, trait_aeadinto_decrypted_output); +} diff --git a/chacha20poly1305/tests/lib.rs b/chacha20poly1305/tests/lib.rs index 2ea43d91..c7c6dea1 100644 --- a/chacha20poly1305/tests/lib.rs +++ b/chacha20poly1305/tests/lib.rs @@ -2,6 +2,8 @@ #![cfg(feature = "alloc")] +pub mod aead_into; + use chacha20poly1305::ChaCha20Poly1305; use chacha20poly1305::XChaCha20Poly1305;