From cabc81cd9d0b3f3b7e20d9c18d542c157ca32314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Wed, 3 Jun 2026 17:40:29 +0300 Subject: [PATCH 1/2] aead: rework traits API --- .github/workflows/aead.yml | 3 - Cargo.lock | 18 +- aead/Cargo.toml | 10 +- aead/src/aead.rs | 90 +++ aead/src/dev.rs | 94 +-- aead/src/lib.rs | 563 +++++++----------- aead/src/variable.rs | 275 +++++++++ ...x_fail.blb => dummy_aead_postfix_fail.blb} | Bin ...x_pass.blb => dummy_aead_postfix_pass.blb} | Bin ...ix_fail.blb => dummy_aead_prefix_fail.blb} | Bin ...ix_pass.blb => dummy_aead_prefix_pass.blb} | Bin aead/tests/dummy.rs | 33 +- 12 files changed, 627 insertions(+), 459 deletions(-) create mode 100644 aead/src/aead.rs create mode 100644 aead/src/variable.rs rename aead/tests/data/{postfix_fail.blb => dummy_aead_postfix_fail.blb} (100%) rename aead/tests/data/{postfix_pass.blb => dummy_aead_postfix_pass.blb} (100%) rename aead/tests/data/{prefix_fail.blb => dummy_aead_prefix_fail.blb} (100%) rename aead/tests/data/{prefix_pass.blb => dummy_aead_prefix_pass.blb} (100%) diff --git a/.github/workflows/aead.yml b/.github/workflows/aead.yml index e9b74ba6f..7902c2ffa 100644 --- a/.github/workflows/aead.yml +++ b/.github/workflows/aead.yml @@ -43,9 +43,6 @@ jobs: - run: cargo check --all-features - run: cargo build --target ${{ matrix.target }} --release --no-default-features - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc - - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features bytes - # TODO: re-enable in v0.6.1 - # - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features heapless - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features rand_core minimal-versions: diff --git a/Cargo.lock b/Cargo.lock index 02612ebfd..bda80551a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,9 +6,7 @@ version = 4 name = "aead" version = "0.6.0-rc.10" dependencies = [ - "arrayvec", "blobby", - "bytes", "crypto-common", "inout", ] @@ -19,12 +17,6 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - [[package]] name = "autocfg" version = "1.5.1" @@ -74,12 +66,6 @@ dependencies = [ "hybrid-array", ] -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - [[package]] name = "cfg-if" version = "1.0.4" @@ -557,9 +543,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", diff --git a/aead/Cargo.toml b/aead/Cargo.toml index c3937a96a..7e6c21716 100644 --- a/aead/Cargo.toml +++ b/aead/Cargo.toml @@ -10,25 +10,19 @@ repository = "https://github.com/RustCrypto/traits" license = "MIT OR Apache-2.0" keywords = ["crypto", "encryption"] categories = ["cryptography", "no-std"] -description = """ -Traits for Authenticated Encryption with Associated Data (AEAD) algorithms, -such as AES-GCM as ChaCha20Poly1305, which provide a high-level API -""" +description = "Traits for Authenticated Encryption with Associated Data (AEAD) algorithms" [dependencies] common = { version = "0.2", package = "crypto-common" } inout = "0.2.2" # optional dependencies -arrayvec = { version = "0.7", optional = true, default-features = false } blobby = { version = "0.4", optional = true } -bytes = { version = "1.11.1", optional = true, default-features = false } [features] -default = ["rand_core"] alloc = [] dev = ["blobby", "alloc"] -getrandom = ["common/getrandom"] +getrandom = ["common/getrandom", "rand_core"] rand_core = ["common/rand_core"] [lints] diff --git a/aead/src/aead.rs b/aead/src/aead.rs new file mode 100644 index 000000000..7884e98b8 --- /dev/null +++ b/aead/src/aead.rs @@ -0,0 +1,90 @@ +use crate::{Result, VariableAead}; +use alloc::vec::Vec; +use common::typenum::Unsigned; + +/// High-level functionality of Authenticated Encryption with Associated Data (AEAD) algorithms. +pub trait Aead { + /// Encrypt the given plaintext payload, and return the resulting + /// ciphertext as a vector of bytes. + /// + /// # Errors + /// AEAD algorithm implementations may return an error if the plaintext or AAD are too long. + fn encrypt_into_vec(&self, nonce: &[u8], aad: &[u8], plaintext: &[u8]) -> Result>; + + /// Decrypt the given ciphertext slice, and return the resulting plaintext + /// as a vector of bytes. + /// + /// # Errors + /// - if the `ciphertext` is inauthentic (i.e. tag verification failure) + /// - if the `ciphertext` is too long + /// - if the `aad` is too long + fn decrypt_into_vec(&self, nonce: &[u8], aad: &[u8], ciphertext: &[u8]) -> Result>; + + /// Encrypt plaintext in `buf` extending it as necessary. + /// + /// On success, `buf` will contain the resulting ciphertext, + /// while on error it will be left intact. + /// + /// # Errors + /// AEAD algorithm implementations may return an error if the plaintext or AAD are too long. + #[inline] + fn encrypt_within_vec(&self, nonce: &[u8], aad: &[u8], buf: &mut Vec) -> Result<()> { + let res = self.encrypt_into_vec(nonce, aad, buf)?; + *buf = res; + Ok(()) + } + + /// Decrypt ciphertext in `buf` truncating it as necessary. + /// + /// On success, `buf` will contain the resulting plaintext, + /// while on error it will be zeroized. + /// + /// # Errors + /// - if the `ciphertext` is inauthentic (i.e. tag verification failure) + /// - if the `ciphertext` is too long + /// - if the `aad` is too long + #[inline] + fn decrypt_within_vec(&self, nonce: &[u8], aad: &[u8], buf: &mut Vec) -> Result<()> { + let res = self.decrypt_into_vec(nonce, aad, buf); + match res { + Ok(pt) => { + *buf = pt; + Ok(()) + } + Err(err) => { + buf.fill(0); + Err(err) + } + } + } +} + +impl Aead for T { + #[inline] + fn encrypt_into_vec(&self, nonce: &[u8], aad: &[u8], plaintext: &[u8]) -> Result> { + self.variable_encrypt_into(nonce, aad, plaintext, T::TagSize::USIZE, alloc_vec) + } + + #[inline] + fn decrypt_into_vec(&self, nonce: &[u8], aad: &[u8], ciphertext: &[u8]) -> Result> { + self.variable_decrypt_into(nonce, aad, ciphertext, T::TagSize::USIZE, alloc_vec) + } + + #[inline] + fn encrypt_within_vec(&self, nonce: &[u8], aad: &[u8], buf: &mut Vec) -> Result<()> { + self.variable_encrypt_within(nonce, aad, buf, T::TagSize::USIZE, extend_vec) + } + + #[inline] + fn decrypt_within_vec(&self, nonce: &[u8], aad: &[u8], buf: &mut Vec) -> Result<()> { + self.variable_decrypt_within(nonce, aad, buf, T::TagSize::USIZE, Vec::truncate) + } +} + +fn alloc_vec(len: usize) -> Vec { + alloc::vec![0u8; len] +} + +fn extend_vec(buf: &mut Vec, len: usize) { + buf.resize(len, 0); +} diff --git a/aead/src/dev.rs b/aead/src/dev.rs index 903c95c61..76df5f5ed 100644 --- a/aead/src/dev.rs +++ b/aead/src/dev.rs @@ -1,13 +1,8 @@ //! Development-related functionality -#![allow(clippy::missing_errors_doc, reason = "dev module")] -#![allow(clippy::missing_panics_doc, reason = "dev module")] -#![allow(clippy::unwrap_in_result, reason = "dev module")] - -use crate::{ - Aead, AeadInOut, Payload, Tag, TagPosition, array::typenum::Unsigned, inout::InOutBuf, -}; pub use blobby; + +use crate::Aead; use common::KeyInit; /// AEAD test vector @@ -26,8 +21,11 @@ pub struct TestVector { } /// Run AEAD test for the provided passing test vector -#[allow(clippy::cast_possible_truncation)] -pub fn pass_test( +/// +/// # Errors +/// - If the cipher has failed initialization with the provided key. +/// - If the AEAD mode has failed to pass the test vector. +pub fn pass_test( &TestVector { key, nonce, @@ -36,76 +34,31 @@ pub fn pass_test( ciphertext, }: &TestVector, ) -> Result<(), &'static str> { - let nonce = nonce.try_into().expect("wrong nonce size"); - let cipher = ::new_from_slice(key).expect("failed to initialize the cipher"); + let cipher: C = KeyInit::new_from_slice(key).map_err(|_| "failed to initialize the cipher")?; let res = cipher - .encrypt( - nonce, - Payload { - aad, - msg: plaintext, - }, - ) + .encrypt_into_vec(nonce, aad, plaintext) .map_err(|_| "encryption failure")?; if res != ciphertext { return Err("encrypted data is different from target ciphertext"); } let res = cipher - .decrypt( - nonce, - Payload { - aad, - msg: ciphertext, - }, - ) + .decrypt_into_vec(nonce, aad, ciphertext) .map_err(|_| "decryption failure")?; if res != plaintext { return Err("decrypted data is different from target plaintext"); } - let (ct, tag) = match C::TAG_POSITION { - TagPosition::Prefix => { - let (tag, ct) = ciphertext.split_at(C::TagSize::USIZE); - (ct, tag) - } - TagPosition::Postfix => ciphertext.split_at(plaintext.len()), - }; - let tag: &Tag = tag.try_into().expect("tag has correct length"); - - // Fill output buffer with "garbage" to test that its data does not get read during encryption - let mut buf: alloc::vec::Vec = (0..plaintext.len()).map(|i| i as u8).collect(); - let inout_buf = InOutBuf::new(plaintext, &mut buf).expect("pt and buf have the same length"); - - let calc_tag = cipher - .encrypt_inout_detached(nonce, aad, inout_buf) - .map_err(|_| "encrypt_inout_detached: encryption failure")?; - if tag != &calc_tag { - return Err("encrypt_inout_detached: tag mismatch"); - } - if ct != buf { - return Err("encrypt_inout_detached: ciphertext mismatch"); - } - - // Fill output buffer with "garbage" - buf.iter_mut() - .enumerate() - .for_each(|(i, v): (usize, &mut u8)| *v = i as u8); - - let inout_buf = InOutBuf::new(ct, &mut buf).expect("ct and buf have the same length"); - cipher - .decrypt_inout_detached(nonce, aad, inout_buf, tag) - .map_err(|_| "decrypt_inout_detached: decryption failure")?; - if plaintext != buf { - return Err("decrypt_inout_detached: plaintext mismatch"); - } - Ok(()) } /// Run AEAD test for the provided failing test vector -pub fn fail_test( +/// +/// # Errors +/// - If the cipher has failed initialization with the provided key. +/// - If the cipher has passed the test vector. +pub fn fail_test( &TestVector { key, nonce, @@ -114,16 +67,9 @@ pub fn fail_test( .. }: &TestVector, ) -> Result<(), &'static str> { - let nonce = nonce.try_into().expect("wrong nonce size"); - let cipher = ::new_from_slice(key).expect("failed to initialize the cipher"); + let cipher: C = KeyInit::new_from_slice(key).map_err(|_| "failed to initialize the cipher")?; - let res = cipher.decrypt( - nonce, - Payload { - aad, - msg: ciphertext, - }, - ); + let res = cipher.decrypt_into_vec(nonce, aad, ciphertext); if res.is_ok() { Err("decryption must return error") } else { @@ -134,6 +80,9 @@ pub fn fail_test( /// Define AEAD test for passing test vectors #[macro_export] macro_rules! new_pass_test { + ($name:ident, $cipher:ty $(,)?) => { + $crate::new_pass_test!($name, stringify!($name), $cipher); + }; ($name:ident, $test_name:expr, $cipher:ty $(,)?) => { #[test] fn $name() { @@ -165,6 +114,9 @@ macro_rules! new_pass_test { /// Define AEAD test for failing test vectors #[macro_export] macro_rules! new_fail_test { + ($name:ident, $cipher:ty $(,)?) => { + $crate::new_fail_test!($name, stringify!($name), $cipher); + }; ($name:ident, $test_name:expr, $cipher:ty $(,)?) => { #[test] fn $name() { diff --git a/aead/src/lib.rs b/aead/src/lib.rs index bc69494e7..cc114e1b8 100644 --- a/aead/src/lib.rs +++ b/aead/src/lib.rs @@ -13,45 +13,29 @@ extern crate alloc; #[cfg(feature = "dev")] pub mod dev; -pub use common::{ - self, Key, KeyInit, KeySizeUser, - array::{self, typenum::consts}, -}; +#[cfg(feature = "alloc")] +mod aead; +#[cfg(feature = "alloc")] +pub use aead::Aead; + +mod variable; + +pub use variable::VariableAead; -#[cfg(feature = "arrayvec")] -pub use arrayvec; -#[cfg(feature = "bytes")] -pub use bytes; #[cfg(feature = "rand_core")] pub use common::{Generate, rand_core}; pub use inout; -use common::array::{Array, ArraySize, typenum::Unsigned}; -use core::fmt; -use inout::InOutBuf; - -#[cfg(feature = "alloc")] -use alloc::vec::Vec; -#[cfg(feature = "bytes")] -use bytes::BytesMut; - -/// Error type. -/// -/// This type is deliberately opaque as to avoid potential side-channel -/// leakage (e.g. padding oracle). -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct Error; - -/// Result type alias with [`Error`]. -pub type Result = core::result::Result; - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("aead::Error") - } -} +pub use common::{ + self, Key, KeyInit, KeySizeUser, + array::{self, typenum::consts}, + typenum::Unsigned, +}; +pub use inout::InOutBuf; -impl core::error::Error for Error {} +use TagPosition::{Postfix, Prefix}; +use array::{Array, ArraySize}; +use core::fmt; /// Nonce: single-use value for ensuring ciphertexts are unique. /// @@ -105,119 +89,19 @@ pub type Nonce = Array::NonceSize>; /// Tag: authentication code which ensures ciphertexts are authentic pub type Tag = Array::TagSize>; -/// Enum which specifies tag position used by an AEAD algorithm. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub enum TagPosition { - /// Postfix tag - Postfix, - /// Prefix tag - Prefix, -} - -/// Authenticated Encryption with Associated Data (AEAD) algorithm. +/// Low-level functionality of Authenticated Encryption with Associated Data (AEAD) algorithms. pub trait AeadCore { - /// The length of a nonce. + /// The nonce length in bytes. type NonceSize: ArraySize; - /// The maximum length of the tag. + /// The tag length in bytes. type TagSize: ArraySize; - /// The AEAD tag position. - const TAG_POSITION: TagPosition; -} - -/// Authenticated Encryption with Associated Data (AEAD) algorithm. -#[cfg(feature = "alloc")] -pub trait Aead: AeadCore { - /// Encrypt the given plaintext payload, and return the resulting - /// ciphertext as a vector of bytes. - /// - /// The [`Payload`] type can be used to provide Additional Associated Data - /// (AAD) along with the message: this is an optional bytestring which is - /// not encrypted, but *is* authenticated along with the message. Failure - /// to pass the same AAD that was used during encryption will cause - /// decryption to fail, which is useful if you would like to "bind" the - /// ciphertext to some other identifier, like a digital signature key - /// or other identifier. - /// - /// If you don't care about AAD and just want to encrypt a plaintext - /// message, `&[u8]` will automatically be coerced into a `Payload`: + /// The recommended tag position (postfix or prefix) in resulting ciphertexts. /// - /// ```nobuild - /// let plaintext = b"Top secret message, handle with care"; - /// let ciphertext = cipher.encrypt(nonce, plaintext); - /// ``` - /// - /// The default implementation assumes a postfix tag (ala AES-GCM, - /// AES-GCM-SIV, ChaCha20Poly1305). [`Aead`] implementations which do not - /// use a postfix tag will need to override this to correctly assemble the - /// ciphertext message. - /// - /// # Errors - /// AEAD algorithm implementations may return an error if the plaintext or AAD are too long. - fn encrypt<'msg, 'aad>( - &self, - nonce: &Nonce, - plaintext: impl Into>, - ) -> Result>; - - /// Decrypt the given ciphertext slice, and return the resulting plaintext - /// as a vector of bytes. - /// - /// See notes on [`Aead::encrypt()`] about allowable message payloads and - /// Associated Additional Data (AAD). - /// - /// If you have no AAD, you can call this as follows: - /// - /// ```nobuild - /// let ciphertext = b"..."; - /// let plaintext = cipher.decrypt(nonce, ciphertext)?; - /// ``` - /// - /// The default implementation assumes a postfix tag (ala AES-GCM, - /// AES-GCM-SIV, ChaCha20Poly1305). [`Aead`] implementations which do not - /// use a postfix tag will need to override this to correctly parse the - /// ciphertext message. - /// - /// # Errors - /// - if the `ciphertext` is inauthentic (i.e. tag verification failure) - /// - if the `ciphertext` is too long - /// - if the `aad` is too long - fn decrypt<'msg, 'aad>( - &self, - nonce: &Nonce, - ciphertext: impl Into>, - ) -> Result>; -} - -#[cfg(feature = "alloc")] -impl Aead for T { - fn encrypt<'msg, 'aad>( - &self, - nonce: &Nonce, - plaintext: impl Into>, - ) -> Result> { - let payload = plaintext.into(); - let mut buffer = Vec::with_capacity(payload.msg.len() + Self::TagSize::to_usize()); - buffer.extend_from_slice(payload.msg); - self.encrypt_in_place(nonce, payload.aad, &mut buffer)?; - Ok(buffer) - } - - fn decrypt<'msg, 'aad>( - &self, - nonce: &Nonce, - ciphertext: impl Into>, - ) -> Result> { - let payload = ciphertext.into(); - let mut buffer = Vec::from(payload.msg); - self.decrypt_in_place(nonce, payload.aad, &mut buffer)?; - Ok(buffer) - } -} + /// If tag position is not explicitly specified, we use postfix tags by default. + const TAG_POSITION: TagPosition; -/// In-place and inout AEAD trait which handles the authentication tag as a return value/separate parameter. -pub trait AeadInOut: AeadCore { /// Encrypt the data in the provided [`InOutBuf`], returning the authentication tag. /// /// # Errors @@ -225,7 +109,7 @@ pub trait AeadInOut: AeadCore { fn encrypt_inout_detached( &self, nonce: &Nonce, - associated_data: &[u8], + aad: &[u8], buffer: InOutBuf<'_, '_, u8>, ) -> Result>; @@ -240,265 +124,242 @@ pub trait AeadInOut: AeadCore { fn decrypt_inout_detached( &self, nonce: &Nonce, - associated_data: &[u8], + aad: &[u8], buffer: InOutBuf<'_, '_, u8>, tag: &Tag, ) -> Result<()>; - /// Encrypt the given buffer containing a plaintext message in-place. - /// - /// The buffer must have sufficient capacity to store the ciphertext - /// message, which will always be larger than the original plaintext. - /// The exact size needed is cipher-dependent, but generally includes - /// the size of an authentication tag. + /// Encrypt data in-place in `buf`, returning the authentication tag. /// /// # Errors - /// Returns an error if the buffer has insufficient capacity to store the - /// resulting ciphertext message. - fn encrypt_in_place( + /// AEAD algorithm implementations may return an error if the plaintext or AAD are too long. + #[inline] + fn encrypt_detached( &self, nonce: &Nonce, - associated_data: &[u8], - buffer: &mut dyn Buffer, - ) -> Result<()> { - match Self::TAG_POSITION { - TagPosition::Prefix => { - let msg_len = buffer.len(); - buffer.extend_from_slice(&Tag::::default())?; - let buffer = buffer.as_mut(); - let tag_size = Self::TagSize::USIZE; - buffer.copy_within(..msg_len, tag_size); - let (tag_dst, msg) = buffer.split_at_mut(tag_size); - let tag = self.encrypt_inout_detached(nonce, associated_data, msg.into())?; - tag_dst.copy_from_slice(&tag); - } - TagPosition::Postfix => { - let tag = - self.encrypt_inout_detached(nonce, associated_data, buffer.as_mut().into())?; - buffer.extend_from_slice(tag.as_slice())?; - } - } - Ok(()) + aad: &[u8], + buf: &mut [u8], + ) -> Result> { + self.encrypt_inout_detached(nonce, aad, buf.into()) } - /// Decrypt the message in-place, returning an error in the event the - /// provided authentication tag does not match the given ciphertext. - /// - /// The buffer will be truncated to the length of the original plaintext - /// message upon success. + /// Decrypt the data in-place in the provided buffer, returning an error in the event the + /// provided authentication tag is invalid for the given ciphertext (i.e. ciphertext + /// is modified/unauthentic). /// /// # Errors /// - if the `ciphertext` is inauthentic (i.e. tag verification failure) - fn decrypt_in_place( + /// - if the `ciphertext` is too long + /// - if the `aad` is too long + #[inline] + fn decrypt_detached( &self, nonce: &Nonce, - associated_data: &[u8], - buffer: &mut dyn Buffer, + aad: &[u8], + buffer: &mut [u8], + tag: &Tag, ) -> Result<()> { - let tag_size = Self::TagSize::USIZE; - let tagless_len = buffer.len().checked_sub(tag_size).ok_or(Error)?; - - match Self::TAG_POSITION { - TagPosition::Prefix => { - let (tag, msg) = buffer.as_mut().split_at_mut(tag_size); - let tag = Tag::::try_from(&*tag).expect("tag length mismatch"); - self.decrypt_inout_detached(nonce, associated_data, msg.into(), &tag)?; - buffer.as_mut().copy_within(tag_size.., 0); - } - TagPosition::Postfix => { - let (msg, tag) = buffer.as_mut().split_at_mut(tagless_len); - let tag = Tag::::try_from(&*tag).expect("tag length mismatch"); - self.decrypt_inout_detached(nonce, associated_data, msg.into(), &tag)?; - } - } - buffer.truncate(tagless_len); - Ok(()) + self.decrypt_inout_detached(nonce, aad, buffer.into(), tag) } -} -/// Legacy in-place stateless AEAD trait. -/// -/// NOTE: deprecated! Please migrate to [`AeadInOut`]. -#[deprecated(since = "0.6.0", note = "use `AeadInOut` instead")] -#[allow(clippy::missing_errors_doc)] -pub trait AeadInPlace: AeadCore { - /// Encrypt the given buffer containing a plaintext message in-place. - #[deprecated(since = "0.6.0", note = "use `AeadInOut::encrypt_in_place` instead")] - fn encrypt_in_place( + /// Encrypt `plaintext` into a buffer allocated with `allocate`. + /// + /// # Errors + /// AEAD algorithm implementations may return an error if the plaintext or AAD are too long. + /// + /// # Panics + /// If `allocate` returns a buffer with length in bytes not equal to the provided argument. + #[inline] + fn encrypt_into>( &self, nonce: &Nonce, - associated_data: &[u8], - buffer: &mut dyn Buffer, - ) -> Result<()>; + aad: &[u8], + plaintext: &[u8], + allocate: impl FnOnce(usize) -> B, + ) -> Result { + let tag_len = Self::TagSize::USIZE; + let ct_len = plaintext.len().checked_add(tag_len).ok_or(Error)?; + + let mut ct_tag = allocate(ct_len); + assert_eq!( + ct_tag.as_mut().len(), + ct_len, + "`allocate` function did not allocate a buffer with the requested size", + ); + + let (ct_dst, tag_dst) = match Self::TAG_POSITION { + Postfix => ct_tag.as_mut().split_at_mut(plaintext.len()), + Prefix => { + let (tag_dst, ct_dst) = ct_tag.as_mut().split_at_mut(tag_len); + (ct_dst, tag_dst) + } + }; - /// Encrypt the data in-place, returning the authentication tag - #[deprecated( - since = "0.6.0", - note = "use `AeadInOut::encrypt_inout_detached` instead" - )] - fn encrypt_in_place_detached( - &self, - nonce: &Nonce, - associated_data: &[u8], - buffer: &mut [u8], - ) -> Result>; + let buf = InOutBuf::new(plaintext, ct_dst) + .expect("`plaintext` and `ct_dst` always have the same length"); + let tag = self.encrypt_inout_detached(nonce, aad, buf)?; + tag_dst.copy_from_slice(&tag); - /// Decrypt the message in-place, returning an error in the event the - /// provided authentication tag does not match the given ciphertext. - #[deprecated(since = "0.6.0", note = "use `AeadInOut::decrypt_in_place` instead")] - fn decrypt_in_place( - &self, - nonce: &Nonce, - associated_data: &[u8], - buffer: &mut dyn Buffer, - ) -> Result<()>; + Ok(ct_tag) + } - /// Decrypt the message in-place, returning an error in the event the provided - /// authentication tag does not match the given ciphertext (i.e. ciphertext - /// is modified/unauthentic) - #[deprecated( - since = "0.6.0", - note = "use `AeadInOut::decrypt_inout_detached` instead" - )] - fn decrypt_in_place_detached( + /// Decrypt `ciphertext` into a buffer allocated with `allocate`. + /// + /// # Errors + /// - if the `ciphertext` is inauthentic (i.e. tag verification failure) + /// - if the `ciphertext` is too long + /// - if the `aad` is too long + /// + /// # Panics + /// If `allocate` returns a buffer with length in bytes not equal to the provided argument. + #[inline] + fn decrypt_into>( &self, nonce: &Nonce, - associated_data: &[u8], - buffer: &mut [u8], - tag: &Tag, - ) -> Result<()>; -} + aad: &[u8], + ciphertext: &[u8], + allocate: impl FnOnce(usize) -> B, + ) -> Result { + let tag_size = Self::TagSize::USIZE; + let pt_len = ciphertext.len().checked_sub(tag_size).ok_or(Error)?; -#[allow(deprecated)] -impl AeadInPlace for T { - fn encrypt_in_place( - &self, - nonce: &Nonce, - associated_data: &[u8], - buffer: &mut dyn Buffer, - ) -> Result<()> { - ::encrypt_in_place(self, nonce, associated_data, buffer) - } + let (ct, tag) = match Self::TAG_POSITION { + Postfix => ciphertext.split_at(pt_len), + Prefix => { + let (tag, ct) = ciphertext.split_at(tag_size); + (ct, tag) + } + }; - fn encrypt_in_place_detached( - &self, - nonce: &Nonce, - associated_data: &[u8], - buffer: &mut [u8], - ) -> Result> { - self.encrypt_inout_detached(nonce, associated_data, buffer.into()) + let mut pt_dst = allocate(pt_len); + assert_eq!( + pt_dst.as_mut().len(), + pt_len, + "`allocate` function did not allocate a buffer with the requested size", + ); + + let tag = tag.try_into().expect("`tag` has correct length"); + let buf = InOutBuf::new(ct, pt_dst.as_mut()) + .expect("`ct` and `pt_dst` should always have the same length"); + self.decrypt_inout_detached(nonce, aad, buf, tag)?; + + Ok(pt_dst) } - fn decrypt_in_place( + /// Encrypt data in `buf` extending the buffer with `extend`. + /// + /// # Errors + /// AEAD algorithm implementations may return an error if the plaintext or AAD are too long. + /// + /// # Panics + /// If `extend` does not extend the buffer to the specified length in bytes. + #[inline] + fn encrypt_within>( &self, nonce: &Nonce, - associated_data: &[u8], - buffer: &mut dyn Buffer, + aad: &[u8], + buf: &mut B, + extend: impl FnOnce(&mut B, usize), ) -> Result<()> { - ::decrypt_in_place(self, nonce, associated_data, buffer) + let tag_size = Self::TagSize::USIZE; + let pt_len = buf.as_mut().len(); + let ct_len = pt_len.checked_add(tag_size).ok_or(Error)?; + + extend(buf, ct_len); + let buf = buf.as_mut(); + assert_eq!( + buf.len(), + ct_len, + "`extend` function did not extend the buffer to the requested size", + ); + + let (pt, tag_dst) = match Self::TAG_POSITION { + Postfix => buf.split_at_mut(pt_len), + Prefix => { + buf.copy_within(..pt_len, tag_size); + let (tag_dst, pt) = buf.split_at_mut(tag_size); + (pt, tag_dst) + } + }; + + self.encrypt_detached(nonce, aad, pt) + .map(|tag| tag_dst.copy_from_slice(&tag)) + // On failure the `pt` part should be zeroized by the encrypt function + .inspect_err(|_| tag_dst.fill(0)) } - fn decrypt_in_place_detached( + /// Decrypt data in `buf` truncating the buffer with `truncate`. + /// + /// # Errors + /// - if the `ciphertext` is inauthentic (i.e. tag verification failure) + /// - if the `ciphertext` is too long + /// - if the `aad` is too long + /// + /// # Panics + /// If `truncate` does not truncate the buffer to the specified length in bytes. + #[inline] + fn decrypt_within>( &self, nonce: &Nonce, - associated_data: &[u8], - buffer: &mut [u8], - tag: &Tag, + aad: &[u8], + buf: &mut B, + truncate: impl FnOnce(&mut B, usize), ) -> Result<()> { - self.decrypt_inout_detached(nonce, associated_data, buffer.into(), tag) - } -} - -/// AEAD payloads (message + AAD). -/// -/// Combination of a message (plaintext or ciphertext) and -/// "additional associated data" (AAD) to be authenticated (in cleartext) -/// along with the message. -/// -/// If you don't care about AAD, you can pass a `&[u8]` as the payload to -/// `encrypt`/`decrypt` and it will automatically be coerced to this type. -#[derive(Debug)] -pub struct Payload<'msg, 'aad> { - /// Message to be encrypted/decrypted - pub msg: &'msg [u8], - - /// Optional "additional associated data" to authenticate along with - /// this message. If AAD is provided at the time the message is encrypted, - /// the same AAD *MUST* be provided at the time the message is decrypted, - /// or decryption will fail. - pub aad: &'aad [u8], -} - -impl<'msg> From<&'msg [u8]> for Payload<'msg, '_> { - fn from(msg: &'msg [u8]) -> Self { - Self { msg, aad: b"" } - } -} - -/// In-place encryption/decryption byte buffers. -/// -/// This trait defines the set of methods needed to support in-place operations -/// on a `Vec`-like data type. -pub trait Buffer: AsRef<[u8]> + AsMut<[u8]> { - /// Get the length of the buffer - fn len(&self) -> usize { - self.as_ref().len() - } + let tag_size = Self::TagSize::USIZE; + let buf_mut = buf.as_mut(); + let ct_len = buf_mut.len().checked_sub(tag_size).ok_or(Error)?; + + let (ct, tag) = match Self::TAG_POSITION { + Postfix => buf_mut.split_at_mut(ct_len), + Prefix => { + let (tag, ct) = buf_mut.split_at_mut(tag_size); + (ct, tag) + } + }; - /// Is the buffer empty? - fn is_empty(&self) -> bool { - self.as_ref().is_empty() - } + let tag: &mut Tag = tag.try_into().expect("`tag` has correct length"); + self.decrypt_detached(nonce, aad, ct, tag) + // On failure the `ct` part should be zeroized by the decryption function + .inspect_err(|_| tag.fill(0))?; - /// Extend this buffer from the given slice. - /// - /// # Errors - /// If the buffer has insufficient capacity. - fn extend_from_slice(&mut self, other: &[u8]) -> Result<()>; + if Self::TAG_POSITION == Prefix { + buf_mut.copy_within(tag_size.., 0); + } - /// Truncate this buffer to the given size. - fn truncate(&mut self, len: usize); -} + truncate(buf, ct_len); + assert_eq!( + buf.as_mut().len(), + ct_len, + "`truncate` function did not truncate the buffer to the requested size", + ); -#[cfg(feature = "alloc")] -impl Buffer for Vec { - fn extend_from_slice(&mut self, other: &[u8]) -> Result<()> { - Vec::extend_from_slice(self, other); Ok(()) } - - fn truncate(&mut self, len: usize) { - Vec::truncate(self, len); - } } -#[cfg(feature = "bytes")] -impl Buffer for BytesMut { - fn len(&self) -> usize { - BytesMut::len(self) - } +/// Enum which specifies tag position used by an AEAD algorithm. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum TagPosition { + /// Postfix tag + Postfix, + /// Prefix tag + Prefix, +} - fn is_empty(&self) -> bool { - BytesMut::is_empty(self) - } +/// Error type. +/// +/// This type is deliberately opaque as to avoid potential side-channel +/// leakage (e.g. padding oracle). +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Error; - fn extend_from_slice(&mut self, other: &[u8]) -> Result<()> { - BytesMut::extend_from_slice(self, other); - Ok(()) - } +/// Result type alias with [`Error`]. +pub type Result = core::result::Result; - fn truncate(&mut self, len: usize) { - BytesMut::truncate(self, len); +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("aead::Error") } } -#[cfg(feature = "arrayvec")] -impl Buffer for arrayvec::ArrayVec { - fn extend_from_slice(&mut self, other: &[u8]) -> Result<()> { - arrayvec::ArrayVec::try_extend_from_slice(self, other).map_err(|_| Error) - } - - fn truncate(&mut self, len: usize) { - arrayvec::ArrayVec::truncate(self, len); - } -} +impl core::error::Error for Error {} diff --git a/aead/src/variable.rs b/aead/src/variable.rs new file mode 100644 index 000000000..6428d842e --- /dev/null +++ b/aead/src/variable.rs @@ -0,0 +1,275 @@ +use crate::{ + AeadCore, Error, Result, Tag, + TagPosition::{Postfix, Prefix}, +}; +use common::typenum::Unsigned; +use inout::InOutBuf; + +/// Functionality of Authenticated Encryption with Associated Data (AEAD) algorithms +/// with variable nonce and tag size support. +/// +///
+/// Some algorithms support very short nonce and tag sizes. Users should exercise extreme caution +/// while using this trait since incorrect handling of nonces and tags may defeat security +/// provided by the algorithm. +///
+pub trait VariableAead: AeadCore { + /// Check if the provided nonce size is supported by the implementation. + /// + /// # Errors + /// If the nonce size is not supported. + fn check_nonce_size(nonce_size: usize) -> Result<()> { + if nonce_size == Self::NonceSize::USIZE { + Ok(()) + } else { + Err(Error) + } + } + + /// Check if the provided tag size is supported by the implementation. + /// + /// # Errors + /// If the tag size is not supported. + fn check_tag_size(tag_size: usize) -> Result<()> { + if tag_size == Self::TagSize::USIZE { + Ok(()) + } else { + Err(Error) + } + } + + /// Encrypt the data in the provided [`InOutBuf`] with variable nonce and tag sizes, + /// writing the resulting tag into `tag_dst`. + /// + /// # Errors + /// AEAD algorithm implementations may return an error if the plaintext or AAD are too long + /// or an invalid nonce is used. + #[inline] + fn variable_encrypt_inout_detached( + &self, + nonce: &[u8], + aad: &[u8], + buf: InOutBuf<'_, '_, u8>, + tag_dst: &mut [u8], + ) -> Result<()> { + let nonce = nonce.try_into().map_err(|_| Error)?; + let tag_dst: &mut Tag = tag_dst.try_into().map_err(|_| Error)?; + *tag_dst = self.encrypt_inout_detached(nonce, aad, buf)?; + Ok(()) + } + + /// Decrypt the data in the provided [`InOutBuf`] with variable nonce and tag sizes, + /// returning an error in the event the provided authentication tag is invalid + /// for the given ciphertext (i.e. ciphertext is modified/unauthentic). + /// + /// # Errors + /// - if the `ciphertext` is inauthentic (i.e. tag verification failure) + /// - if the `ciphertext` is too long + /// - if the `aad` is too long + #[inline] + fn variable_decrypt_inout_detached( + &self, + nonce: &[u8], + aad: &[u8], + buf: InOutBuf<'_, '_, u8>, + tag: &[u8], + ) -> Result<()> { + let nonce = nonce.try_into().map_err(|_| Error)?; + let tag = tag.try_into().map_err(|_| Error)?; + self.decrypt_inout_detached(nonce, aad, buf, tag) + } + + /// Encrypt the data in-place in the provided buffer with variable nonce and tag sizes, + /// returning the authentication tag. + /// + /// # Errors + /// AEAD algorithm implementations may return an error if the plaintext or AAD are too long + /// or if provided nonce size is not supported. + #[inline] + fn variable_encrypt_detached( + &self, + nonce: &[u8], + aad: &[u8], + buf: &mut [u8], + tag_dst: &mut [u8], + ) -> Result<()> { + self.variable_encrypt_inout_detached(nonce, aad, buf.into(), tag_dst) + } + + /// Decrypt the data in-place in the provided buffer with variable nonce and tag sizes, + /// returning an error in the event the provided authentication tag is invalid + /// for the given ciphertext (i.e. ciphertext is modified/unauthentic). + /// + /// # Errors + /// - if the `ciphertext` is inauthentic (i.e. tag verification failure) + /// - if the `ciphertext` is too long + /// - if the `aad` is too long + #[inline] + fn variable_decrypt_detached( + &self, + nonce: &[u8], + aad: &[u8], + buf: &mut [u8], + tag: &[u8], + ) -> Result<()> { + self.variable_decrypt_inout_detached(nonce, aad, buf.into(), tag) + } + + /// Encrypt `plaintext` into a buffer allocated with `allocate` + /// with variable nonce and tag sizes. + /// + /// # Errors + /// AEAD algorithm implementations may return an error if the plaintext or AAD are too long. + /// + /// # Panics + /// If `allocate` returns a buffer with length in bytes not equal to the provided argument. + #[inline] + fn variable_encrypt_into>( + &self, + nonce: &[u8], + aad: &[u8], + plaintext: &[u8], + tag_size: usize, + allocate: impl FnOnce(usize) -> B, + ) -> Result { + let ct_len = plaintext.len().checked_add(tag_size).ok_or(Error)?; + + let mut ct_tag = allocate(ct_len); + assert_eq!(ct_tag.as_mut().len(), ct_len); + + let (ct_dst, tag_dst) = match Self::TAG_POSITION { + Postfix => ct_tag.as_mut().split_at_mut(plaintext.len()), + Prefix => { + let (tag_dst, ct_dst) = ct_tag.as_mut().split_at_mut(tag_size); + (ct_dst, tag_dst) + } + }; + + let buf = InOutBuf::new(plaintext, ct_dst) + .expect("`plaintext` and `ct_dst` always have the same length"); + + self.variable_encrypt_inout_detached(nonce, aad, buf, tag_dst)?; + + Ok(ct_tag) + } + + /// Decrypt `ciphertext` into a buffer allocated with `allocate` + /// with variable nonce and tag sizes. + /// + /// # Errors + /// - if the `ciphertext` is inauthentic (i.e. tag verification failure) + /// - if the `ciphertext` is too long + /// - if the `aad` is too long + /// + /// # Panics + /// If `allocate` returns a buffer with length in bytes not equal to the provided argument. + #[inline] + fn variable_decrypt_into>( + &self, + nonce: &[u8], + aad: &[u8], + ciphertext: &[u8], + tag_size: usize, + allocate: impl FnOnce(usize) -> B, + ) -> Result { + let pt_len = ciphertext.len().checked_sub(tag_size).ok_or(Error)?; + + let (ct, tag) = match Self::TAG_POSITION { + Postfix => ciphertext.split_at(pt_len), + Prefix => { + let (tag, ct) = ciphertext.split_at(tag_size); + (ct, tag) + } + }; + + let mut pt_dst = allocate(pt_len); + assert_eq!(pt_dst.as_mut().len(), pt_len); + + let buf = InOutBuf::new(ct, pt_dst.as_mut()) + .expect("`ct` and `pt_dst` should always have the same length"); + self.variable_decrypt_inout_detached(nonce, aad, buf, tag)?; + + Ok(pt_dst) + } + + /// Encrypt data in `buf` with variable nonce and tag sizes extending the buffer with `extend`. + /// + /// # Errors + /// AEAD algorithm implementations may return an error if the plaintext or AAD are too long. + /// + /// # Panics + /// If `extend` does not extend the buffer to the specified length in bytes. + #[inline] + fn variable_encrypt_within>( + &self, + nonce: &[u8], + aad: &[u8], + buf: &mut B, + tag_size: usize, + extend: impl FnOnce(&mut B, usize), + ) -> Result<()> { + let pt_len = buf.as_mut().len(); + let ct_len = pt_len.checked_add(tag_size).ok_or(Error)?; + + extend(buf, ct_len); + let buf = buf.as_mut(); + assert_eq!(buf.len(), ct_len); + + let (pt, tag_dst) = match Self::TAG_POSITION { + Postfix => buf.split_at_mut(pt_len), + Prefix => { + buf.copy_within(..pt_len, tag_size); + let (tag_dst, pt) = buf.split_at_mut(tag_size); + (pt, tag_dst) + } + }; + + self.variable_encrypt_inout_detached(nonce, aad, pt.into(), tag_dst) + // On failure the `pt` part should be zeroized by the encrypt function + .inspect_err(|_| tag_dst.fill(0)) + } + + /// Decrypt data in `buf` with variable nonce and tag sizes + /// truncating the buffer with `truncate`. + /// + /// # Errors + /// - if the `ciphertext` is inauthentic (i.e. tag verification failure) + /// - if the `ciphertext` is too long + /// - if the `aad` is too long + /// + /// # Panics + /// If `truncate` does not truncate the buffer to the specified length in bytes. + #[inline] + fn variable_decrypt_within>( + &self, + nonce: &[u8], + aad: &[u8], + buf: &mut B, + tag_size: usize, + truncate: impl FnOnce(&mut B, usize), + ) -> Result<()> { + let buf_mut = buf.as_mut(); + let ct_len = buf_mut.len().checked_sub(tag_size).ok_or(Error)?; + + let (ct, tag) = match Self::TAG_POSITION { + Postfix => buf_mut.split_at_mut(ct_len), + Prefix => { + let (tag, ct) = buf_mut.split_at_mut(tag_size); + (ct, tag) + } + }; + + self.variable_decrypt_inout_detached(nonce, aad, ct.into(), tag) + // On failure the `ct` part should be zeroized by the decryption function + .inspect_err(|_| tag.fill(0))?; + + if Self::TAG_POSITION == Prefix { + buf_mut.copy_within(tag_size.., 0); + } + + truncate(buf, ct_len); + assert_eq!(buf.as_mut().len(), ct_len); + + Ok(()) + } +} diff --git a/aead/tests/data/postfix_fail.blb b/aead/tests/data/dummy_aead_postfix_fail.blb similarity index 100% rename from aead/tests/data/postfix_fail.blb rename to aead/tests/data/dummy_aead_postfix_fail.blb diff --git a/aead/tests/data/postfix_pass.blb b/aead/tests/data/dummy_aead_postfix_pass.blb similarity index 100% rename from aead/tests/data/postfix_pass.blb rename to aead/tests/data/dummy_aead_postfix_pass.blb diff --git a/aead/tests/data/prefix_fail.blb b/aead/tests/data/dummy_aead_prefix_fail.blb similarity index 100% rename from aead/tests/data/prefix_fail.blb rename to aead/tests/data/dummy_aead_prefix_fail.blb diff --git a/aead/tests/data/prefix_pass.blb b/aead/tests/data/dummy_aead_prefix_pass.blb similarity index 100% rename from aead/tests/data/prefix_pass.blb rename to aead/tests/data/dummy_aead_prefix_pass.blb diff --git a/aead/tests/dummy.rs b/aead/tests/dummy.rs index 11cf14545..d9c28a1a6 100644 --- a/aead/tests/dummy.rs +++ b/aead/tests/dummy.rs @@ -7,8 +7,8 @@ #![allow(clippy::unwrap_used, reason = "tests")] use aead::{ - AeadCore, AeadInOut, Error, Key, KeyInit, KeySizeUser, Nonce, Result, Tag, TagPosition, - array::Array, consts::U8, + Aead, AeadCore, Error, Key, KeyInit, KeySizeUser, Nonce, Result, Tag, TagPosition, + VariableAead, array::Array, consts::U8, }; use core::fmt; use inout::InOutBuf; @@ -121,9 +121,7 @@ impl AeadCore for PrefixDummyAead { type NonceSize = U8; type TagSize = U8; const TAG_POSITION: TagPosition = TagPosition::Prefix; -} -impl AeadInOut for PrefixDummyAead { fn encrypt_inout_detached( &self, nonce: &Nonce, @@ -144,6 +142,8 @@ impl AeadInOut for PrefixDummyAead { } } +impl VariableAead for PrefixDummyAead {} + #[derive(Debug)] pub struct PostfixDummyAead(DummyAead); @@ -161,9 +161,7 @@ impl AeadCore for PostfixDummyAead { type NonceSize = U8; type TagSize = U8; const TAG_POSITION: TagPosition = TagPosition::Postfix; -} -impl AeadInOut for PostfixDummyAead { fn encrypt_inout_detached( &self, nonce: &Nonce, @@ -184,12 +182,27 @@ impl AeadInOut for PostfixDummyAead { } } +impl VariableAead for PostfixDummyAead {} + +#[cfg(feature = "alloc")] +#[test] +fn dyn_compat() { + let key = &[0u8; 8].into(); + let c1 = PrefixDummyAead::new(key); + let c2 = PostfixDummyAead::new(key); + + fn take_dyn_aead(_: &dyn Aead) {} + + take_dyn_aead(&c1); + take_dyn_aead(&c2); +} + #[cfg(feature = "dev")] mod tests { use super::{PostfixDummyAead, PrefixDummyAead}; - aead::new_pass_test!(dummy_prefix_pass, "prefix_pass", PrefixDummyAead); - aead::new_fail_test!(dummy_prefix_fail, "prefix_fail", PrefixDummyAead); - aead::new_pass_test!(dummy_postfix_pass, "postfix_pass", PostfixDummyAead); - aead::new_fail_test!(dummy_postfix_fail, "postfix_fail", PostfixDummyAead); + aead::new_pass_test!(dummy_aead_prefix_pass, PrefixDummyAead); + aead::new_fail_test!(dummy_aead_prefix_fail, PrefixDummyAead); + aead::new_pass_test!(dummy_aead_postfix_pass, PostfixDummyAead); + aead::new_fail_test!(dummy_aead_postfix_fail, PostfixDummyAead); } From 7a82df0cc90d90df5569aa150c02930a63d474b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Wed, 3 Jun 2026 18:21:33 +0300 Subject: [PATCH 2/2] test zeroization on failure --- aead/src/dev.rs | 30 +++++++++++++++++++++++++++--- aead/src/variable.rs | 33 ++++++++++++++++++++++----------- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/aead/src/dev.rs b/aead/src/dev.rs index 76df5f5ed..c491daf60 100644 --- a/aead/src/dev.rs +++ b/aead/src/dev.rs @@ -50,6 +50,20 @@ pub fn pass_test( return Err("decrypted data is different from target plaintext"); } + let mut buf = ciphertext.to_vec(); + + // Flip one bit + buf[0] ^= 1; + + let res = cipher.decrypt_within_vec(nonce, aad, &mut buf); + + if res.is_ok() { + return Err("did not detect corrupted ciphertext"); + } + if buf.iter().any(|&b| b != 0) { + return Err("the buffer was not zeroized after failure"); + } + Ok(()) } @@ -71,10 +85,20 @@ pub fn fail_test( let res = cipher.decrypt_into_vec(nonce, aad, ciphertext); if res.is_ok() { - Err("decryption must return error") - } else { - Ok(()) + return Err("decryption must return error"); } + + let mut buf = ciphertext.to_vec(); + let res = cipher.decrypt_within_vec(nonce, aad, &mut buf); + + if res.is_ok() { + return Err("decryption must return error"); + } + if buf.iter().any(|&b| b != 0) { + return Err("the buffer was not zeroized after failure"); + } + + Ok(()) } /// Define AEAD test for passing test vectors diff --git a/aead/src/variable.rs b/aead/src/variable.rs index 6428d842e..b9475fa77 100644 --- a/aead/src/variable.rs +++ b/aead/src/variable.rs @@ -49,13 +49,22 @@ pub trait VariableAead: AeadCore { &self, nonce: &[u8], aad: &[u8], - buf: InOutBuf<'_, '_, u8>, + mut buf: InOutBuf<'_, '_, u8>, tag_dst: &mut [u8], ) -> Result<()> { - let nonce = nonce.try_into().map_err(|_| Error)?; - let tag_dst: &mut Tag = tag_dst.try_into().map_err(|_| Error)?; - *tag_dst = self.encrypt_inout_detached(nonce, aad, buf)?; - Ok(()) + match (nonce.try_into(), tag_dst.try_into()) { + (Ok(nonce), Ok(tag_dst)) => { + let tag_dst: &mut Tag = tag_dst; + self.encrypt_inout_detached(nonce, aad, buf) + .map(|tag| *tag_dst = tag) + .inspect_err(|_| tag_dst.fill(0)) + } + _ => { + buf.get_out().fill(0); + tag_dst.fill(0); + Err(Error) + } + } } /// Decrypt the data in the provided [`InOutBuf`] with variable nonce and tag sizes, @@ -71,12 +80,16 @@ pub trait VariableAead: AeadCore { &self, nonce: &[u8], aad: &[u8], - buf: InOutBuf<'_, '_, u8>, + mut buf: InOutBuf<'_, '_, u8>, tag: &[u8], ) -> Result<()> { - let nonce = nonce.try_into().map_err(|_| Error)?; - let tag = tag.try_into().map_err(|_| Error)?; - self.decrypt_inout_detached(nonce, aad, buf, tag) + match (nonce.try_into(), tag.try_into()) { + (Ok(nonce), Ok(tag)) => self.decrypt_inout_detached(nonce, aad, buf, tag), + _ => { + buf.get_out().fill(0); + Err(Error) + } + } } /// Encrypt the data in-place in the provided buffer with variable nonce and tag sizes, @@ -225,8 +238,6 @@ pub trait VariableAead: AeadCore { }; self.variable_encrypt_inout_detached(nonce, aad, pt.into(), tag_dst) - // On failure the `pt` part should be zeroized by the encrypt function - .inspect_err(|_| tag_dst.fill(0)) } /// Decrypt data in `buf` with variable nonce and tag sizes