From 24987654e4edfb54c319ade295af18509dbd87ef 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: Mon, 25 May 2026 20:07:47 +0300 Subject: [PATCH 01/20] aead: minimize API surface --- Cargo.lock | 14 -- aead/Cargo.toml | 8 +- aead/src/dev.rs | 8 +- aead/src/high_aead.rs | 151 +++++++++++++++ aead/src/lib.rs | 416 +++++------------------------------------- aead/tests/dummy.rs | 29 ++- 6 files changed, 224 insertions(+), 402 deletions(-) create mode 100644 aead/src/high_aead.rs diff --git a/Cargo.lock b/Cargo.lock index d5112ed9b..b6a0db4b0 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.0" @@ -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" diff --git a/aead/Cargo.toml b/aead/Cargo.toml index c3937a96a..f06922191 100644 --- a/aead/Cargo.toml +++ b/aead/Cargo.toml @@ -10,22 +10,16 @@ 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"] diff --git a/aead/src/dev.rs b/aead/src/dev.rs index 903c95c61..36e46b1d9 100644 --- a/aead/src/dev.rs +++ b/aead/src/dev.rs @@ -4,9 +4,7 @@ #![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, -}; +use crate::{Aead, Payload, Tag, TagPosition, array::typenum::Unsigned, inout::InOutBuf}; pub use blobby; use common::KeyInit; @@ -27,7 +25,7 @@ pub struct TestVector { /// Run AEAD test for the provided passing test vector #[allow(clippy::cast_possible_truncation)] -pub fn pass_test( +pub fn pass_test( &TestVector { key, nonce, @@ -105,7 +103,7 @@ pub fn pass_test( } /// Run AEAD test for the provided failing test vector -pub fn fail_test( +pub fn fail_test( &TestVector { key, nonce, diff --git a/aead/src/high_aead.rs b/aead/src/high_aead.rs new file mode 100644 index 000000000..1d0984588 --- /dev/null +++ b/aead/src/high_aead.rs @@ -0,0 +1,151 @@ +use crate::{ + AeadCore, AeadTagPosition, Error, InOutBuf, Nonce, Result, + TagPosition::{Postfix, Prefix}, +}; +use alloc::vec::Vec; +use common::array::typenum::Unsigned; + +/// High-level functionality of Authenticated Encryption with Associated Data (AEAD) algorithms. +pub trait Aead: AeadCore + AeadTagPosition { + /// 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`: + /// + /// ```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>; +} + +impl Aead for T { + #[allow(clippy::unwrap_in_result)] + fn encrypt<'msg, 'aad>( + &self, + nonce: &Nonce, + plaintext: impl Into>, + ) -> Result> { + let Payload { msg: pt, aad } = plaintext.into(); + + let tag_len = Self::TagSize::USIZE; + let ct_len = pt.len().checked_add(tag_len).ok_or(Error)?; + let mut buffer = alloc::vec![0u8; ct_len]; + + let (ct_dst, tag_dst) = match Self::TAG_POSITION { + Postfix => buffer.split_at_mut(pt.len()), + Prefix => { + let (tag_dst, ct_dst) = buffer.split_at_mut(tag_len); + (ct_dst, tag_dst) + } + }; + + let buf = InOutBuf::new(pt, ct_dst).expect("`pt` and `ct_dst` have the same length"); + let tag = self.encrypt_inout_detached(nonce, aad, buf)?; + tag_dst.copy_from_slice(&tag); + + Ok(buffer) + } + + #[allow(clippy::unwrap_in_result)] + fn decrypt<'msg, 'aad>( + &self, + nonce: &Nonce, + ciphertext: impl Into>, + ) -> Result> { + let Payload { msg: ct_tag, aad } = ciphertext.into(); + + let tag_len = Self::TagSize::USIZE; + let ct_len = ct_tag.len().checked_sub(tag_len).ok_or(Error)?; + + let (ct, tag) = match Self::TAG_POSITION { + Postfix => ct_tag.split_at(ct_len), + Prefix => { + let (tag, ct) = ct_tag.split_at(tag_len); + (ct, tag) + } + }; + + let tag = tag.try_into().expect("`tag` has correct length"); + let mut pt_dst = alloc::vec![0u8; ct_len]; + let buf = InOutBuf::new(ct, &mut pt_dst).expect("`ct` and `pt_dst` have the same length"); + self.decrypt_inout_detached(nonce, aad, buf, tag)?; + + Ok(pt_dst) + } +} + +/// 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"" } + } +} diff --git a/aead/src/lib.rs b/aead/src/lib.rs index bc69494e7..9c9d12ab0 100644 --- a/aead/src/lib.rs +++ b/aead/src/lib.rs @@ -13,45 +13,23 @@ extern crate alloc; #[cfg(feature = "dev")] pub mod dev; -pub use common::{ - self, Key, KeyInit, KeySizeUser, - array::{self, typenum::consts}, -}; +#[cfg(feature = "alloc")] +mod high_aead; +#[cfg(feature = "alloc")] +pub use high_aead::{Aead, Payload}; -#[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}, +}; +pub use inout::InOutBuf; -impl core::error::Error for Error {} +use common::array::{Array, ArraySize}; +use core::fmt; /// Nonce: single-use value for ensuring ciphertexts are unique. /// @@ -105,119 +83,14 @@ 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 length of a nonce in bytes. type NonceSize: ArraySize; - /// The maximum length of the tag. + /// The length of a tag 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`: - /// - /// ```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) - } -} - -/// 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 @@ -245,160 +118,28 @@ pub trait AeadInOut: AeadCore { 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 the data in-place in the provided buffer, 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. + 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(()) + buf: &mut [u8], + ) -> Result> { + self.encrypt_inout_detached(nonce, associated_data, 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( - &self, - nonce: &Nonce, - associated_data: &[u8], - buffer: &mut dyn Buffer, - ) -> 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(()) - } -} - -/// 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( - &self, - nonce: &Nonce, - associated_data: &[u8], - buffer: &mut dyn Buffer, - ) -> Result<()>; - - /// 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>; - - /// 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<()>; - - /// 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( - &self, - nonce: &Nonce, - associated_data: &[u8], - buffer: &mut [u8], - tag: &Tag, - ) -> Result<()>; -} - -#[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) - } - - fn encrypt_in_place_detached( - &self, - nonce: &Nonce, - associated_data: &[u8], - buffer: &mut [u8], - ) -> Result> { - self.encrypt_inout_detached(nonce, associated_data, buffer.into()) - } - - fn decrypt_in_place( - &self, - nonce: &Nonce, - associated_data: &[u8], - buffer: &mut dyn Buffer, - ) -> Result<()> { - ::decrypt_in_place(self, nonce, associated_data, buffer) - } - - fn decrypt_in_place_detached( + /// - if the `ciphertext` is too long + /// - if the `aad` is too long + fn decrypt_detached( &self, nonce: &Nonce, associated_data: &[u8], @@ -409,96 +150,35 @@ impl AeadInPlace for T { } } -/// 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], +/// 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, } -impl<'msg> From<&'msg [u8]> for Payload<'msg, '_> { - fn from(msg: &'msg [u8]) -> Self { - Self { msg, aad: b"" } - } +/// Trait which carries the default tag position. +pub trait AeadTagPosition: AeadCore { + /// The AEAD tag position. + const TAG_POSITION: TagPosition; } -/// In-place encryption/decryption byte buffers. +/// Error type. /// -/// 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() - } - - /// Is the buffer empty? - fn is_empty(&self) -> bool { - self.as_ref().is_empty() - } - - /// Extend this buffer from the given slice. - /// - /// # Errors - /// If the buffer has insufficient capacity. - fn extend_from_slice(&mut self, other: &[u8]) -> Result<()>; - - /// Truncate this buffer to the given size. - fn truncate(&mut self, len: usize); -} - -#[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) - } - - fn is_empty(&self) -> bool { - BytesMut::is_empty(self) - } +/// 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/tests/dummy.rs b/aead/tests/dummy.rs index 11cf14545..97f7b6501 100644 --- a/aead/tests/dummy.rs +++ b/aead/tests/dummy.rs @@ -7,7 +7,7 @@ #![allow(clippy::unwrap_used, reason = "tests")] use aead::{ - AeadCore, AeadInOut, Error, Key, KeyInit, KeySizeUser, Nonce, Result, Tag, TagPosition, + AeadCore, AeadTagPosition, Error, Key, KeyInit, KeySizeUser, Nonce, Result, Tag, TagPosition, array::Array, consts::U8, }; use core::fmt; @@ -120,10 +120,7 @@ impl KeyInit for PrefixDummyAead { 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 +141,10 @@ impl AeadInOut for PrefixDummyAead { } } +impl AeadTagPosition for PrefixDummyAead { + const TAG_POSITION: TagPosition = TagPosition::Prefix; +} + #[derive(Debug)] pub struct PostfixDummyAead(DummyAead); @@ -160,10 +161,7 @@ impl KeyInit for PostfixDummyAead { 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 AeadTagPosition for PostfixDummyAead { + const TAG_POSITION: TagPosition = TagPosition::Postfix; +} + #[cfg(feature = "dev")] mod tests { - use super::{PostfixDummyAead, PrefixDummyAead}; + use super::{PostfixDummyAead, PrefixDummyAead, U8}; + use aead::{AeadCore, KeyInit}; 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); + + #[test] + fn aead_core_dyn_compact() { + fn take_dyn_aead(_: &dyn AeadCore) {} + + let c = PrefixDummyAead::new(&[0u8; 8].into()); + take_dyn_aead(&c); + let c = PostfixDummyAead::new(&[0u8; 8].into()); + take_dyn_aead(&c); + } } From ae80b2a27c1fb4e3d7181269fe41e25e67678a27 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: Mon, 25 May 2026 20:13:01 +0300 Subject: [PATCH 02/20] update CI config --- .github/workflows/aead.yml | 3 --- 1 file changed, 3 deletions(-) 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: From 664ac266e412349481080bb85cfeaa8ab0e6c638 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: Mon, 25 May 2026 20:17:03 +0300 Subject: [PATCH 03/20] Update Cargo.lock --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b6a0db4b0..3f616e10b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base16ct" @@ -355,9 +355,9 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "memchr" @@ -542,9 +542,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", From 6bbdcb757e8d2f88296a12f12707559bd19d8dbc 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, 27 May 2026 19:19:03 +0300 Subject: [PATCH 04/20] enable `rand_core` when `getrandom` is enabled --- aead/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aead/Cargo.toml b/aead/Cargo.toml index f06922191..7e6c21716 100644 --- a/aead/Cargo.toml +++ b/aead/Cargo.toml @@ -22,7 +22,7 @@ blobby = { version = "0.4", optional = true } [features] alloc = [] dev = ["blobby", "alloc"] -getrandom = ["common/getrandom"] +getrandom = ["common/getrandom", "rand_core"] rand_core = ["common/rand_core"] [lints] From 831a55af8941531e3a9f4f711cd9cc53a6636c60 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: Fri, 29 May 2026 16:33:31 +0300 Subject: [PATCH 05/20] Add support for variable size nonces to `AeadCore` --- aead/src/lib.rs | 80 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/aead/src/lib.rs b/aead/src/lib.rs index 9c9d12ab0..9005e8305 100644 --- a/aead/src/lib.rs +++ b/aead/src/lib.rs @@ -118,6 +118,46 @@ pub trait AeadCore { tag: &Tag, ) -> Result<()>; + /// Encrypt the data in the provided [`InOutBuf`] with variable size nonce, + /// returning the authentication tag. + /// + /// # Warning + /// Some algorithms support very short nonces. Users should exercise extreme caution + /// while using this method since incorrect handling of nonces may defeat security + /// provided by the algorithm. + /// + /// # Errors + /// AEAD algorithm implementations may return an error if the plaintext or AAD are too long + /// or an invalid nonce is used. + fn encrypt_inout_with_var_nonce_detached( + &self, + nonce: &[u8], + associated_data: &[u8], + buffer: InOutBuf<'_, '_, u8>, + ) -> Result> { + let nonce = nonce.try_into().map_err(|_| Error)?; + self.encrypt_inout_detached(nonce, associated_data, buffer) + } + + /// Decrypt the data in the provided [`InOutBuf`] with variable size nonce, + /// 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 + fn decrypt_inout_with_var_nonce_detached( + &self, + nonce: &[u8], + associated_data: &[u8], + buffer: InOutBuf<'_, '_, u8>, + tag: &Tag, + ) -> Result<()> { + let nonce = nonce.try_into().map_err(|_| Error)?; + self.decrypt_inout_detached(nonce, associated_data, buffer, tag) + } + /// Encrypt the data in-place in the provided buffer, returning the authentication tag. /// /// # Errors @@ -148,6 +188,46 @@ pub trait AeadCore { ) -> Result<()> { self.decrypt_inout_detached(nonce, associated_data, buffer.into(), tag) } + + /// Encrypt the data in-place in the provided buffer with variable size nonce, + /// returning the authentication tag. + /// + /// # Warning + /// Some algorithms support very short nonces. Users should exercise extreme caution + /// while using this method since incorrect handling of nonces may defeat security + /// provided by the algorithm. + /// + /// # Errors + /// AEAD algorithm implementations may return an error if the plaintext or AAD are too long + /// or if provided nonce size is not supported. + fn encrypt_with_var_nonce_detached( + &self, + nonce: &[u8], + associated_data: &[u8], + buf: &mut [u8], + ) -> Result> { + let nonce = nonce.try_into().map_err(|_| Error)?; + self.encrypt_inout_detached(nonce, associated_data, buf.into()) + } + + /// 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) + /// - if the `ciphertext` is too long + /// - if the `aad` is too long + fn decrypt_with_var_nonce_detached( + &self, + nonce: &[u8], + associated_data: &[u8], + buffer: &mut [u8], + tag: &Tag, + ) -> Result<()> { + let nonce = nonce.try_into().map_err(|_| Error)?; + self.decrypt_inout_detached(nonce, associated_data, buffer.into(), tag) + } } /// Enum which specifies tag position used by an AEAD algorithm. From 515b1957fed7e55a83191014a2804fc99d9adbf7 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: Fri, 29 May 2026 16:54:10 +0300 Subject: [PATCH 06/20] Add variable nonce support to `Aead` --- aead/src/dev.rs | 70 +++++++------------------------------ aead/src/high_aead.rs | 81 ++++++++++++++++++------------------------- 2 files changed, 46 insertions(+), 105 deletions(-) diff --git a/aead/src/dev.rs b/aead/src/dev.rs index 36e46b1d9..fd33ef53f 100644 --- a/aead/src/dev.rs +++ b/aead/src/dev.rs @@ -4,7 +4,7 @@ #![allow(clippy::missing_panics_doc, reason = "dev module")] #![allow(clippy::unwrap_in_result, reason = "dev module")] -use crate::{Aead, Payload, Tag, TagPosition, array::typenum::Unsigned, inout::InOutBuf}; +use crate::{Aead, Payload}; pub use blobby; use common::KeyInit; @@ -34,71 +34,30 @@ 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 res = cipher - .encrypt( + .encrypt(Payload { nonce, - Payload { - aad, - msg: plaintext, - }, - ) + aad, + msg: plaintext, + }) .map_err(|_| "encryption failure")?; if res != ciphertext { return Err("encrypted data is different from target ciphertext"); } let res = cipher - .decrypt( + .decrypt(Payload { nonce, - Payload { - aad, - msg: ciphertext, - }, - ) + aad, + msg: 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(()) } @@ -112,16 +71,13 @@ 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 res = cipher.decrypt( + let res = cipher.decrypt(Payload { nonce, - Payload { - aad, - msg: ciphertext, - }, - ); + aad, + msg: ciphertext, + }); if res.is_ok() { Err("decryption must return error") } else { diff --git a/aead/src/high_aead.rs b/aead/src/high_aead.rs index 1d0984588..4aff38e4d 100644 --- a/aead/src/high_aead.rs +++ b/aead/src/high_aead.rs @@ -1,17 +1,17 @@ use crate::{ - AeadCore, AeadTagPosition, Error, InOutBuf, Nonce, Result, + AeadCore, AeadTagPosition, Error, InOutBuf, Result, TagPosition::{Postfix, Prefix}, }; use alloc::vec::Vec; use common::array::typenum::Unsigned; /// High-level functionality of Authenticated Encryption with Associated Data (AEAD) algorithms. -pub trait Aead: AeadCore + AeadTagPosition { +pub trait Aead { /// 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 + /// (AAD) along with the message and nonce: 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 @@ -33,11 +33,7 @@ pub trait Aead: AeadCore + AeadTagPosition { /// /// # 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>; + fn encrypt(&self, payload: Payload<'_>) -> Result>; /// Decrypt the given ciphertext slice, and return the resulting plaintext /// as a vector of bytes. @@ -61,56 +57,44 @@ pub trait Aead: AeadCore + AeadTagPosition { /// - 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>; + fn decrypt(&self, payload: Payload<'_>) -> Result>; } impl Aead for T { #[allow(clippy::unwrap_in_result)] - fn encrypt<'msg, 'aad>( - &self, - nonce: &Nonce, - plaintext: impl Into>, - ) -> Result> { - let Payload { msg: pt, aad } = plaintext.into(); + fn encrypt(&self, payload: Payload<'_>) -> Result> { + let Payload { nonce, msg, aad } = payload; - let tag_len = Self::TagSize::USIZE; - let ct_len = pt.len().checked_add(tag_len).ok_or(Error)?; + let tag_len = T::TagSize::USIZE; + let ct_len = msg.len().checked_add(tag_len).ok_or(Error)?; let mut buffer = alloc::vec![0u8; ct_len]; let (ct_dst, tag_dst) = match Self::TAG_POSITION { - Postfix => buffer.split_at_mut(pt.len()), + Postfix => buffer.split_at_mut(msg.len()), Prefix => { let (tag_dst, ct_dst) = buffer.split_at_mut(tag_len); (ct_dst, tag_dst) } }; - let buf = InOutBuf::new(pt, ct_dst).expect("`pt` and `ct_dst` have the same length"); - let tag = self.encrypt_inout_detached(nonce, aad, buf)?; + let buf = InOutBuf::new(msg, ct_dst).expect("`msg` and `ct_dst` have the same length"); + let tag = self.encrypt_inout_with_var_nonce_detached(nonce, aad, buf)?; tag_dst.copy_from_slice(&tag); Ok(buffer) } #[allow(clippy::unwrap_in_result)] - fn decrypt<'msg, 'aad>( - &self, - nonce: &Nonce, - ciphertext: impl Into>, - ) -> Result> { - let Payload { msg: ct_tag, aad } = ciphertext.into(); + fn decrypt(&self, payload: Payload<'_>) -> Result> { + let Payload { nonce, msg, aad } = payload; - let tag_len = Self::TagSize::USIZE; - let ct_len = ct_tag.len().checked_sub(tag_len).ok_or(Error)?; + let tag_len = T::TagSize::USIZE; + let ct_len = msg.len().checked_sub(tag_len).ok_or(Error)?; let (ct, tag) = match Self::TAG_POSITION { - Postfix => ct_tag.split_at(ct_len), + Postfix => msg.split_at(ct_len), Prefix => { - let (tag, ct) = ct_tag.split_at(tag_len); + let (tag, ct) = msg.split_at(tag_len); (ct, tag) } }; @@ -118,34 +102,35 @@ impl Aead for T { let tag = tag.try_into().expect("`tag` has correct length"); let mut pt_dst = alloc::vec![0u8; ct_len]; let buf = InOutBuf::new(ct, &mut pt_dst).expect("`ct` and `pt_dst` have the same length"); - self.decrypt_inout_detached(nonce, aad, buf, tag)?; + self.decrypt_inout_with_var_nonce_detached(nonce, aad, buf, tag)?; Ok(pt_dst) } } -/// AEAD payloads (message + AAD). +/// AEAD payloads (nonce + message + AAD). /// -/// Combination of a message (plaintext or ciphertext) and +/// Combination of a nonce, 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. +/// If you don't care about AAD, you can pass an empty slice. +/// +/// This type is used to appoximate named function arguments to guard against potential bugs +/// caused by the use of the same type (`&[u8]`) by all payload parts. #[derive(Debug)] -pub struct Payload<'msg, 'aad> { +pub struct Payload<'a> { + /// Nonce used for encryption/decryption. + /// + /// See [`Nonce`][crate::Nonce] for additional information. + pub nonce: &'a [u8], + /// Message to be encrypted/decrypted - pub msg: &'msg [u8], + pub msg: &'a [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"" } - } + pub aad: &'a [u8], } From a51447195acd01345bf92cb3e1c65300c468d42c 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: Fri, 29 May 2026 16:55:53 +0300 Subject: [PATCH 07/20] fix typo --- aead/src/high_aead.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aead/src/high_aead.rs b/aead/src/high_aead.rs index 4aff38e4d..01f64a419 100644 --- a/aead/src/high_aead.rs +++ b/aead/src/high_aead.rs @@ -116,7 +116,7 @@ impl Aead for T { /// /// If you don't care about AAD, you can pass an empty slice. /// -/// This type is used to appoximate named function arguments to guard against potential bugs +/// This type is used to emulate named function arguments to guard against potential bugs /// caused by the use of the same type (`&[u8]`) by all payload parts. #[derive(Debug)] pub struct Payload<'a> { From 212534346b692f8f2fa9eb6fef66ed23859098ac 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: Fri, 29 May 2026 17:09:59 +0300 Subject: [PATCH 08/20] test `dyn Aead` --- aead/tests/dummy.rs | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/aead/tests/dummy.rs b/aead/tests/dummy.rs index 97f7b6501..e5cab9f4c 100644 --- a/aead/tests/dummy.rs +++ b/aead/tests/dummy.rs @@ -7,8 +7,8 @@ #![allow(clippy::unwrap_used, reason = "tests")] use aead::{ - AeadCore, AeadTagPosition, Error, Key, KeyInit, KeySizeUser, Nonce, Result, Tag, TagPosition, - array::Array, consts::U8, + Aead, AeadCore, AeadTagPosition, Error, Key, KeyInit, KeySizeUser, Nonce, Result, Tag, + TagPosition, array::Array, consts::U8, }; use core::fmt; use inout::InOutBuf; @@ -186,23 +186,31 @@ impl AeadTagPosition for PostfixDummyAead { const TAG_POSITION: TagPosition = TagPosition::Postfix; } +#[test] +fn aead_core_dyn_compact() { + fn take_dyn_aead_core(_: &dyn AeadCore) {} + + let c1 = PrefixDummyAead::new(&[0u8; 8].into()); + let c2 = PostfixDummyAead::new(&[0u8; 8].into()); + + take_dyn_aead_core(&c1); + take_dyn_aead_core(&c2); + + #[cfg(feature = "alloc")] + { + fn take_dyn_aead(_: &dyn Aead) {} + + take_dyn_aead(&c1); + take_dyn_aead(&c2); + } +} + #[cfg(feature = "dev")] mod tests { - use super::{PostfixDummyAead, PrefixDummyAead, U8}; - use aead::{AeadCore, KeyInit}; + 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); - - #[test] - fn aead_core_dyn_compact() { - fn take_dyn_aead(_: &dyn AeadCore) {} - - let c = PrefixDummyAead::new(&[0u8; 8].into()); - take_dyn_aead(&c); - let c = PostfixDummyAead::new(&[0u8; 8].into()); - take_dyn_aead(&c); - } } From 43ab6c77e02bf99033fed157616efe95d4b1d402 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: Fri, 29 May 2026 19:32:09 +0300 Subject: [PATCH 09/20] Add `into` and `within` methods --- aead/src/high_aead.rs | 44 +---------- aead/src/lib.rs | 20 +---- aead/src/tag_pos.rs | 174 ++++++++++++++++++++++++++++++++++++++++++ aead/tests/dummy.rs | 5 +- 4 files changed, 183 insertions(+), 60 deletions(-) create mode 100644 aead/src/tag_pos.rs diff --git a/aead/src/high_aead.rs b/aead/src/high_aead.rs index 01f64a419..b951018a0 100644 --- a/aead/src/high_aead.rs +++ b/aead/src/high_aead.rs @@ -1,9 +1,5 @@ -use crate::{ - AeadCore, AeadTagPosition, Error, InOutBuf, Result, - TagPosition::{Postfix, Prefix}, -}; +use crate::{AeadCore, AeadTagPosition, Result}; use alloc::vec::Vec; -use common::array::typenum::Unsigned; /// High-level functionality of Authenticated Encryption with Associated Data (AEAD) algorithms. pub trait Aead { @@ -64,47 +60,13 @@ impl Aead for T { #[allow(clippy::unwrap_in_result)] fn encrypt(&self, payload: Payload<'_>) -> Result> { let Payload { nonce, msg, aad } = payload; - - let tag_len = T::TagSize::USIZE; - let ct_len = msg.len().checked_add(tag_len).ok_or(Error)?; - let mut buffer = alloc::vec![0u8; ct_len]; - - let (ct_dst, tag_dst) = match Self::TAG_POSITION { - Postfix => buffer.split_at_mut(msg.len()), - Prefix => { - let (tag_dst, ct_dst) = buffer.split_at_mut(tag_len); - (ct_dst, tag_dst) - } - }; - - let buf = InOutBuf::new(msg, ct_dst).expect("`msg` and `ct_dst` have the same length"); - let tag = self.encrypt_inout_with_var_nonce_detached(nonce, aad, buf)?; - tag_dst.copy_from_slice(&tag); - - Ok(buffer) + self.encrypt_into_with_var_nonce(nonce, aad, msg, |n| alloc::vec![0u8; n]) } #[allow(clippy::unwrap_in_result)] fn decrypt(&self, payload: Payload<'_>) -> Result> { let Payload { nonce, msg, aad } = payload; - - let tag_len = T::TagSize::USIZE; - let ct_len = msg.len().checked_sub(tag_len).ok_or(Error)?; - - let (ct, tag) = match Self::TAG_POSITION { - Postfix => msg.split_at(ct_len), - Prefix => { - let (tag, ct) = msg.split_at(tag_len); - (ct, tag) - } - }; - - let tag = tag.try_into().expect("`tag` has correct length"); - let mut pt_dst = alloc::vec![0u8; ct_len]; - let buf = InOutBuf::new(ct, &mut pt_dst).expect("`ct` and `pt_dst` have the same length"); - self.decrypt_inout_with_var_nonce_detached(nonce, aad, buf, tag)?; - - Ok(pt_dst) + self.decrypt_into_with_var_nonce(nonce, aad, msg, |n| alloc::vec![0u8; n]) } } diff --git a/aead/src/lib.rs b/aead/src/lib.rs index 9005e8305..5dc8ffeac 100644 --- a/aead/src/lib.rs +++ b/aead/src/lib.rs @@ -18,6 +18,9 @@ mod high_aead; #[cfg(feature = "alloc")] pub use high_aead::{Aead, Payload}; +mod tag_pos; +pub use tag_pos::{AeadTagPosition, TagPosition}; + #[cfg(feature = "rand_core")] pub use common::{Generate, rand_core}; pub use inout; @@ -28,7 +31,7 @@ pub use common::{ }; pub use inout::InOutBuf; -use common::array::{Array, ArraySize}; +use array::{Array, ArraySize}; use core::fmt; /// Nonce: single-use value for ensuring ciphertexts are unique. @@ -230,21 +233,6 @@ pub trait AeadCore { } } -/// 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, -} - -/// Trait which carries the default tag position. -pub trait AeadTagPosition: AeadCore { - /// The AEAD tag position. - const TAG_POSITION: TagPosition; -} - /// Error type. /// /// This type is deliberately opaque as to avoid potential side-channel diff --git a/aead/src/tag_pos.rs b/aead/src/tag_pos.rs new file mode 100644 index 000000000..51727bd4a --- /dev/null +++ b/aead/src/tag_pos.rs @@ -0,0 +1,174 @@ +use crate::{AeadCore, Error, Nonce, Result}; +use common::typenum::Unsigned; +use inout::InOutBuf; + +/// 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, +} + +/// Trait implemented for AEAD modes which specify tag position in ciphertext. +#[allow(missing_docs, clippy::missing_errors_doc)] // TODO: fix +pub trait AeadTagPosition: AeadCore { + /// The AEAD tag position. + const TAG_POSITION: TagPosition; + + fn encrypt_into>( + &self, + nonce: &Nonce, + aad: &[u8], + msg: &[u8], + allocate: impl FnOnce(usize) -> B, + ) -> Result { + self.encrypt_into_with_var_nonce(nonce, aad, msg, allocate) + } + + fn decrypt_into>( + &self, + nonce: &Nonce, + aad: &[u8], + msg: &[u8], + allocate: impl FnOnce(usize) -> B, + ) -> Result { + self.decrypt_into_with_var_nonce(nonce, aad, msg, allocate) + } + + fn encrypt_into_with_var_nonce>( + &self, + nonce: &[u8], + aad: &[u8], + msg: &[u8], + allocate: impl FnOnce(usize) -> B, + ) -> Result { + let tag_len = Self::TagSize::USIZE; + let ct_tag_len = msg.len().checked_add(tag_len).ok_or(Error)?; + let mut ct_tag = allocate(ct_tag_len); + assert_eq!(ct_tag.as_mut().len(), ct_tag_len); + + let (ct_dst, tag_dst) = match Self::TAG_POSITION { + TagPosition::Postfix => ct_tag.as_mut().split_at_mut(msg.len()), + TagPosition::Prefix => { + let (tag_dst, ct_dst) = ct_tag.as_mut().split_at_mut(tag_len); + (ct_dst, tag_dst) + } + }; + + let buf = InOutBuf::new(msg, ct_dst).expect("`msg` and `ct_dst` have the same length"); + let tag = self.encrypt_inout_with_var_nonce_detached(nonce, aad, buf)?; + tag_dst.copy_from_slice(&tag); + + Ok(ct_tag) + } + + fn decrypt_into_with_var_nonce>( + &self, + nonce: &[u8], + aad: &[u8], + msg: &[u8], + allocate: impl FnOnce(usize) -> B, + ) -> Result { + let tag_len = Self::TagSize::USIZE; + let ct_len = msg.len().checked_sub(tag_len).ok_or(Error)?; + + let (ct, tag) = match Self::TAG_POSITION { + TagPosition::Postfix => msg.split_at(ct_len), + TagPosition::Prefix => { + let (tag, ct) = msg.split_at(tag_len); + (ct, tag) + } + }; + + let tag = tag.try_into().expect("`tag` has correct length"); + let mut pt_dst = allocate(ct_len); + let buf = InOutBuf::new(ct, pt_dst.as_mut()) + .expect("`ct` and `pt_dst` should have the same length"); + self.decrypt_inout_with_var_nonce_detached(nonce, aad, buf, tag)?; + + Ok(pt_dst) + } + + fn encrypt_within>( + &self, + nonce: &Nonce, + aad: &[u8], + buf: &mut B, + extend: impl FnOnce(&mut B, usize), + ) -> Result<()> { + self.encrypt_within_with_var_nonce(nonce, aad, buf, extend) + } + + fn decrypt_within>( + &self, + nonce: &Nonce, + aad: &[u8], + buf: &mut B, + truncate: impl FnOnce(&mut B, usize), + ) -> Result<()> { + self.decrypt_within_with_var_nonce(nonce, aad, buf, truncate) + } + + fn encrypt_within_with_var_nonce>( + &self, + nonce: &[u8], + aad: &[u8], + buf: &mut B, + extend: impl FnOnce(&mut B, usize), + ) -> Result<()> { + let tag_len = Self::TagSize::USIZE; + let pt_len = buf.as_mut().len(); + let ct_len = pt_len.checked_add(tag_len).ok_or(Error)?; + + extend(buf, ct_len); + assert_eq!(buf.as_mut().len(), ct_len); + + let (msg, tag_dst) = match Self::TAG_POSITION { + TagPosition::Postfix => buf.as_mut().split_at_mut(pt_len), + TagPosition::Prefix => { + buf.as_mut().copy_within(..pt_len, tag_len); + let (tag_dst, ct_dst) = buf.as_mut().split_at_mut(tag_len); + (ct_dst, tag_dst) + } + }; + + let buf = InOutBuf::from(msg); + let tag = self.encrypt_inout_with_var_nonce_detached(nonce, aad, buf)?; + tag_dst.copy_from_slice(&tag); + + Ok(()) + } + + fn decrypt_within_with_var_nonce>( + &self, + nonce: &[u8], + aad: &[u8], + buf: &mut B, + truncate: impl FnOnce(&mut B, usize), + ) -> Result<()> { + let tag_len = Self::TagSize::USIZE; + let ct_len = buf.as_mut().len().checked_sub(tag_len).ok_or(Error)?; + + let (ct, tag) = match Self::TAG_POSITION { + TagPosition::Postfix => buf.as_mut().split_at_mut(ct_len), + TagPosition::Prefix => { + let (tag, ct) = buf.as_mut().split_at_mut(tag_len); + (ct, tag) + } + }; + + let tag = (&*tag).try_into().expect("`tag` has correct length"); + self.decrypt_inout_with_var_nonce_detached(nonce, aad, ct.into(), tag)?; + + if Self::TAG_POSITION == TagPosition::Prefix { + buf.as_mut().copy_within(tag_len.., 0); + } + + truncate(buf, ct_len); + assert_eq!(buf.as_mut().len(), ct_len); + + Ok(()) + } +} diff --git a/aead/tests/dummy.rs b/aead/tests/dummy.rs index e5cab9f4c..5a7787df5 100644 --- a/aead/tests/dummy.rs +++ b/aead/tests/dummy.rs @@ -187,12 +187,11 @@ impl AeadTagPosition for PostfixDummyAead { } #[test] -fn aead_core_dyn_compact() { - fn take_dyn_aead_core(_: &dyn AeadCore) {} - +fn dyn_compat() { let c1 = PrefixDummyAead::new(&[0u8; 8].into()); let c2 = PostfixDummyAead::new(&[0u8; 8].into()); + fn take_dyn_aead_core(_: &dyn AeadCore) {} take_dyn_aead_core(&c1); take_dyn_aead_core(&c2); From d5e9c1dc372f466eccf67ed7dc20bcd11aca5c97 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: Fri, 29 May 2026 19:35:41 +0300 Subject: [PATCH 10/20] rename `AeadTagPosition` to `AeadWithTag` --- aead/src/high_aead.rs | 4 ++-- aead/src/lib.rs | 2 +- aead/src/tag_pos.rs | 2 +- aead/tests/dummy.rs | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/aead/src/high_aead.rs b/aead/src/high_aead.rs index b951018a0..d61885422 100644 --- a/aead/src/high_aead.rs +++ b/aead/src/high_aead.rs @@ -1,4 +1,4 @@ -use crate::{AeadCore, AeadTagPosition, Result}; +use crate::{AeadCore, AeadWithTag, Result}; use alloc::vec::Vec; /// High-level functionality of Authenticated Encryption with Associated Data (AEAD) algorithms. @@ -56,7 +56,7 @@ pub trait Aead { fn decrypt(&self, payload: Payload<'_>) -> Result>; } -impl Aead for T { +impl Aead for T { #[allow(clippy::unwrap_in_result)] fn encrypt(&self, payload: Payload<'_>) -> Result> { let Payload { nonce, msg, aad } = payload; diff --git a/aead/src/lib.rs b/aead/src/lib.rs index 5dc8ffeac..98185c4e7 100644 --- a/aead/src/lib.rs +++ b/aead/src/lib.rs @@ -19,7 +19,7 @@ mod high_aead; pub use high_aead::{Aead, Payload}; mod tag_pos; -pub use tag_pos::{AeadTagPosition, TagPosition}; +pub use tag_pos::{AeadWithTag, TagPosition}; #[cfg(feature = "rand_core")] pub use common::{Generate, rand_core}; diff --git a/aead/src/tag_pos.rs b/aead/src/tag_pos.rs index 51727bd4a..f0cb413a2 100644 --- a/aead/src/tag_pos.rs +++ b/aead/src/tag_pos.rs @@ -13,7 +13,7 @@ pub enum TagPosition { /// Trait implemented for AEAD modes which specify tag position in ciphertext. #[allow(missing_docs, clippy::missing_errors_doc)] // TODO: fix -pub trait AeadTagPosition: AeadCore { +pub trait AeadWithTag: AeadCore { /// The AEAD tag position. const TAG_POSITION: TagPosition; diff --git a/aead/tests/dummy.rs b/aead/tests/dummy.rs index 5a7787df5..a9be359b3 100644 --- a/aead/tests/dummy.rs +++ b/aead/tests/dummy.rs @@ -7,8 +7,8 @@ #![allow(clippy::unwrap_used, reason = "tests")] use aead::{ - Aead, AeadCore, AeadTagPosition, Error, Key, KeyInit, KeySizeUser, Nonce, Result, Tag, - TagPosition, array::Array, consts::U8, + Aead, AeadCore, AeadWithTag, Error, Key, KeyInit, KeySizeUser, Nonce, Result, Tag, TagPosition, + array::Array, consts::U8, }; use core::fmt; use inout::InOutBuf; @@ -141,7 +141,7 @@ impl AeadCore for PrefixDummyAead { } } -impl AeadTagPosition for PrefixDummyAead { +impl AeadWithTag for PrefixDummyAead { const TAG_POSITION: TagPosition = TagPosition::Prefix; } @@ -182,7 +182,7 @@ impl AeadCore for PostfixDummyAead { } } -impl AeadTagPosition for PostfixDummyAead { +impl AeadWithTag for PostfixDummyAead { const TAG_POSITION: TagPosition = TagPosition::Postfix; } From 4cb6442e6ff0e7f1805df2a11eef4df186448d2d 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: Fri, 29 May 2026 19:59:48 +0300 Subject: [PATCH 11/20] Use macros to implement `AeadWithTag` methods --- aead/src/tag_pos.rs | 246 +++++++++++++++++++++++++++----------------- 1 file changed, 149 insertions(+), 97 deletions(-) diff --git a/aead/src/tag_pos.rs b/aead/src/tag_pos.rs index f0cb413a2..4e31c63c0 100644 --- a/aead/src/tag_pos.rs +++ b/aead/src/tag_pos.rs @@ -11,86 +11,168 @@ pub enum TagPosition { Prefix, } +macro_rules! encrypt_into { + ($plaintext:ident, $allocate:ident, $encrypt:expr) => {{ + 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); + + let (ct_dst, tag_dst) = match Self::TAG_POSITION { + TagPosition::Postfix => ct_tag.as_mut().split_at_mut($plaintext.len()), + TagPosition::Prefix => { + let (tag_dst, ct_dst) = ct_tag.as_mut().split_at_mut(tag_len); + (ct_dst, tag_dst) + } + }; + + let buf = + InOutBuf::new($plaintext, ct_dst).expect("`msg` and `ct_dst` have the same length"); + let tag = $encrypt(buf)?; + tag_dst.copy_from_slice(&tag); + + Ok(ct_tag) + }}; +} + +macro_rules! decrypt_into { + ($ciphertext:ident, $allocate:ident, $decrypt:expr) => {{ + let tag_len = Self::TagSize::USIZE; + let pt_len = $ciphertext.len().checked_sub(tag_len).ok_or(Error)?; + + let (ct, tag) = match Self::TAG_POSITION { + TagPosition::Postfix => $ciphertext.split_at(pt_len), + TagPosition::Prefix => { + let (tag, ct) = $ciphertext.split_at(tag_len); + (ct, tag) + } + }; + + let mut pt_dst = $allocate(pt_len); + assert_eq!(pt_dst.as_mut().len(), pt_len); + + 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 have the same length"); + $decrypt(buf, tag)?; + + Ok(pt_dst) + }}; +} + +macro_rules! encrypt_within { + ($buf:ident, $extend:ident, $encrypt:expr) => {{ + let tag_len = Self::TagSize::USIZE; + let pt_len = $buf.as_mut().len(); + let ct_len = pt_len.checked_add(tag_len).ok_or(Error)?; + + $extend($buf, ct_len); + let buf = $buf.as_mut(); + assert_eq!(buf.len(), ct_len); + + let (pt_dst, tag_dst) = match Self::TAG_POSITION { + TagPosition::Postfix => buf.split_at_mut(pt_len), + TagPosition::Prefix => { + buf.copy_within(..pt_len, tag_len); + let (tag_dst, ct_dst) = $buf.as_mut().split_at_mut(tag_len); + (ct_dst, tag_dst) + } + }; + + let tag = $encrypt(InOutBuf::from(pt_dst))?; + tag_dst.copy_from_slice(&tag); + + Ok(()) + }}; +} + +macro_rules! decrypt_within { + ($buf:ident, $truncate:ident, $decrypt:expr) => {{ + let tag_len = Self::TagSize::USIZE; + let ct_len = $buf.as_mut().len().checked_sub(tag_len).ok_or(Error)?; + + let (ct, tag) = match Self::TAG_POSITION { + TagPosition::Postfix => $buf.as_mut().split_at_mut(ct_len), + TagPosition::Prefix => { + let (tag, ct) = $buf.as_mut().split_at_mut(tag_len); + (ct, tag) + } + }; + + let tag = (&*tag).try_into().expect("`tag` has correct length"); + $decrypt(ct.into(), tag)?; + + if Self::TAG_POSITION == TagPosition::Prefix { + $buf.as_mut().copy_within(tag_len.., 0); + } + + $truncate($buf, ct_len); + assert_eq!($buf.as_mut().len(), ct_len); + + Ok(()) + }}; +} + /// Trait implemented for AEAD modes which specify tag position in ciphertext. #[allow(missing_docs, clippy::missing_errors_doc)] // TODO: fix pub trait AeadWithTag: AeadCore { /// The AEAD tag position. const TAG_POSITION: TagPosition; + #[inline] fn encrypt_into>( &self, nonce: &Nonce, aad: &[u8], - msg: &[u8], + plaintext: &[u8], allocate: impl FnOnce(usize) -> B, ) -> Result { - self.encrypt_into_with_var_nonce(nonce, aad, msg, allocate) + encrypt_into!(plaintext, allocate, |buf| { + self.encrypt_inout_detached(nonce, aad, buf) + }) } - fn decrypt_into>( + #[inline] + fn encrypt_into_with_var_nonce>( &self, - nonce: &Nonce, + nonce: &[u8], aad: &[u8], - msg: &[u8], + plaintext: &[u8], allocate: impl FnOnce(usize) -> B, ) -> Result { - self.decrypt_into_with_var_nonce(nonce, aad, msg, allocate) + encrypt_into!(plaintext, allocate, |buf| { + self.encrypt_inout_with_var_nonce_detached(nonce, aad, buf) + }) } - fn encrypt_into_with_var_nonce>( + #[inline] + fn decrypt_into>( &self, - nonce: &[u8], + nonce: &Nonce, aad: &[u8], - msg: &[u8], + ciphertext: &[u8], allocate: impl FnOnce(usize) -> B, ) -> Result { - let tag_len = Self::TagSize::USIZE; - let ct_tag_len = msg.len().checked_add(tag_len).ok_or(Error)?; - let mut ct_tag = allocate(ct_tag_len); - assert_eq!(ct_tag.as_mut().len(), ct_tag_len); - - let (ct_dst, tag_dst) = match Self::TAG_POSITION { - TagPosition::Postfix => ct_tag.as_mut().split_at_mut(msg.len()), - TagPosition::Prefix => { - let (tag_dst, ct_dst) = ct_tag.as_mut().split_at_mut(tag_len); - (ct_dst, tag_dst) - } - }; - - let buf = InOutBuf::new(msg, ct_dst).expect("`msg` and `ct_dst` have the same length"); - let tag = self.encrypt_inout_with_var_nonce_detached(nonce, aad, buf)?; - tag_dst.copy_from_slice(&tag); - - Ok(ct_tag) + decrypt_into!(ciphertext, allocate, |buf, tag| { + self.decrypt_inout_detached(nonce, aad, buf, tag) + }) } + #[inline] fn decrypt_into_with_var_nonce>( &self, nonce: &[u8], aad: &[u8], - msg: &[u8], + ciphertext: &[u8], allocate: impl FnOnce(usize) -> B, ) -> Result { - let tag_len = Self::TagSize::USIZE; - let ct_len = msg.len().checked_sub(tag_len).ok_or(Error)?; - - let (ct, tag) = match Self::TAG_POSITION { - TagPosition::Postfix => msg.split_at(ct_len), - TagPosition::Prefix => { - let (tag, ct) = msg.split_at(tag_len); - (ct, tag) - } - }; - - let tag = tag.try_into().expect("`tag` has correct length"); - let mut pt_dst = allocate(ct_len); - let buf = InOutBuf::new(ct, pt_dst.as_mut()) - .expect("`ct` and `pt_dst` should have the same length"); - self.decrypt_inout_with_var_nonce_detached(nonce, aad, buf, tag)?; - - Ok(pt_dst) + decrypt_into!(ciphertext, allocate, |buf, tag| { + self.decrypt_inout_with_var_nonce_detached(nonce, aad, buf, tag) + }) } + #[inline] fn encrypt_within>( &self, nonce: &Nonce, @@ -98,49 +180,38 @@ pub trait AeadWithTag: AeadCore { buf: &mut B, extend: impl FnOnce(&mut B, usize), ) -> Result<()> { - self.encrypt_within_with_var_nonce(nonce, aad, buf, extend) + encrypt_within!(buf, extend, |buf| { + self.encrypt_inout_detached(nonce, aad, buf) + }) } - fn decrypt_within>( + #[inline] + fn encrypt_within_with_var_nonce>( &self, - nonce: &Nonce, + nonce: &[u8], aad: &[u8], buf: &mut B, - truncate: impl FnOnce(&mut B, usize), + extend: impl FnOnce(&mut B, usize), ) -> Result<()> { - self.decrypt_within_with_var_nonce(nonce, aad, buf, truncate) + encrypt_within!(buf, extend, |buf| { + self.encrypt_inout_with_var_nonce_detached(nonce, aad, buf) + }) } - fn encrypt_within_with_var_nonce>( + #[inline] + fn decrypt_within>( &self, - nonce: &[u8], + nonce: &Nonce, aad: &[u8], buf: &mut B, - extend: impl FnOnce(&mut B, usize), + truncate: impl FnOnce(&mut B, usize), ) -> Result<()> { - let tag_len = Self::TagSize::USIZE; - let pt_len = buf.as_mut().len(); - let ct_len = pt_len.checked_add(tag_len).ok_or(Error)?; - - extend(buf, ct_len); - assert_eq!(buf.as_mut().len(), ct_len); - - let (msg, tag_dst) = match Self::TAG_POSITION { - TagPosition::Postfix => buf.as_mut().split_at_mut(pt_len), - TagPosition::Prefix => { - buf.as_mut().copy_within(..pt_len, tag_len); - let (tag_dst, ct_dst) = buf.as_mut().split_at_mut(tag_len); - (ct_dst, tag_dst) - } - }; - - let buf = InOutBuf::from(msg); - let tag = self.encrypt_inout_with_var_nonce_detached(nonce, aad, buf)?; - tag_dst.copy_from_slice(&tag); - - Ok(()) + decrypt_within!(buf, truncate, |buf, tag| { + self.decrypt_inout_detached(nonce, aad, buf, tag) + }) } + #[inline] fn decrypt_within_with_var_nonce>( &self, nonce: &[u8], @@ -148,27 +219,8 @@ pub trait AeadWithTag: AeadCore { buf: &mut B, truncate: impl FnOnce(&mut B, usize), ) -> Result<()> { - let tag_len = Self::TagSize::USIZE; - let ct_len = buf.as_mut().len().checked_sub(tag_len).ok_or(Error)?; - - let (ct, tag) = match Self::TAG_POSITION { - TagPosition::Postfix => buf.as_mut().split_at_mut(ct_len), - TagPosition::Prefix => { - let (tag, ct) = buf.as_mut().split_at_mut(tag_len); - (ct, tag) - } - }; - - let tag = (&*tag).try_into().expect("`tag` has correct length"); - self.decrypt_inout_with_var_nonce_detached(nonce, aad, ct.into(), tag)?; - - if Self::TAG_POSITION == TagPosition::Prefix { - buf.as_mut().copy_within(tag_len.., 0); - } - - truncate(buf, ct_len); - assert_eq!(buf.as_mut().len(), ct_len); - - Ok(()) + decrypt_within!(buf, truncate, |buf, tag| { + self.decrypt_inout_with_var_nonce_detached(nonce, aad, buf, tag) + }) } } From 82f3ee270aa42e155cc62f044dcdb4321f7ae7d2 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: Mon, 1 Jun 2026 15:42:29 +0300 Subject: [PATCH 12/20] Move `TAG_POSITION` to `AeadCore` --- aead/src/{high_aead.rs => aead.rs} | 12 +- aead/src/dev.rs | 23 +-- aead/src/lib.rs | 131 ++++++++++++++++- aead/src/tag_pos.rs | 226 ----------------------------- aead/src/utils.rs | 125 ++++++++++++++++ aead/tests/dummy.rs | 26 +--- 6 files changed, 276 insertions(+), 267 deletions(-) rename aead/src/{high_aead.rs => aead.rs} (91%) delete mode 100644 aead/src/tag_pos.rs create mode 100644 aead/src/utils.rs diff --git a/aead/src/high_aead.rs b/aead/src/aead.rs similarity index 91% rename from aead/src/high_aead.rs rename to aead/src/aead.rs index d61885422..da6c0f8bd 100644 --- a/aead/src/high_aead.rs +++ b/aead/src/aead.rs @@ -1,4 +1,4 @@ -use crate::{AeadCore, AeadWithTag, Result}; +use crate::{AeadCore, Result}; use alloc::vec::Vec; /// High-level functionality of Authenticated Encryption with Associated Data (AEAD) algorithms. @@ -29,7 +29,7 @@ pub trait Aead { /// /// # Errors /// AEAD algorithm implementations may return an error if the plaintext or AAD are too long. - fn encrypt(&self, payload: Payload<'_>) -> Result>; + fn encrypt_into_vec(&self, payload: Payload<'_>) -> Result>; /// Decrypt the given ciphertext slice, and return the resulting plaintext /// as a vector of bytes. @@ -53,18 +53,18 @@ pub trait Aead { /// - if the `ciphertext` is inauthentic (i.e. tag verification failure) /// - if the `ciphertext` is too long /// - if the `aad` is too long - fn decrypt(&self, payload: Payload<'_>) -> Result>; + fn decrypt_into_vec(&self, payload: Payload<'_>) -> Result>; } -impl Aead for T { +impl Aead for T { #[allow(clippy::unwrap_in_result)] - fn encrypt(&self, payload: Payload<'_>) -> Result> { + fn encrypt_into_vec(&self, payload: Payload<'_>) -> Result> { let Payload { nonce, msg, aad } = payload; self.encrypt_into_with_var_nonce(nonce, aad, msg, |n| alloc::vec![0u8; n]) } #[allow(clippy::unwrap_in_result)] - fn decrypt(&self, payload: Payload<'_>) -> Result> { + fn decrypt_into_vec(&self, payload: Payload<'_>) -> Result> { let Payload { nonce, msg, aad } = payload; self.decrypt_into_with_var_nonce(nonce, aad, msg, |n| alloc::vec![0u8; n]) } diff --git a/aead/src/dev.rs b/aead/src/dev.rs index fd33ef53f..4dbd0555c 100644 --- a/aead/src/dev.rs +++ b/aead/src/dev.rs @@ -1,9 +1,5 @@ //! 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, Payload}; pub use blobby; use common::KeyInit; @@ -24,7 +20,10 @@ pub struct TestVector { } /// Run AEAD test for the provided passing test vector -#[allow(clippy::cast_possible_truncation)] +/// +/// # Errors +/// - If the cipher has failed initialzation with the provided key. +/// - If the AEAD mode has failed to pass the test vector. pub fn pass_test( &TestVector { key, @@ -34,10 +33,10 @@ pub fn pass_test( ciphertext, }: &TestVector, ) -> Result<(), &'static str> { - 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(Payload { + .encrypt_into_vec(Payload { nonce, aad, msg: plaintext, @@ -48,7 +47,7 @@ pub fn pass_test( } let res = cipher - .decrypt(Payload { + .decrypt_into_vec(Payload { nonce, aad, msg: ciphertext, @@ -62,6 +61,10 @@ pub fn pass_test( } /// Run AEAD test for the provided failing test vector +/// +/// # Errors +/// - If the cipher has failed initialzation with the provided key. +/// - If the cipher has passed the test vector. pub fn fail_test( &TestVector { key, @@ -71,9 +74,9 @@ pub fn fail_test( .. }: &TestVector, ) -> Result<(), &'static str> { - 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(Payload { + let res = cipher.decrypt_into_vec(Payload { nonce, aad, msg: ciphertext, diff --git a/aead/src/lib.rs b/aead/src/lib.rs index 98185c4e7..f9eb8ef54 100644 --- a/aead/src/lib.rs +++ b/aead/src/lib.rs @@ -14,12 +14,11 @@ extern crate alloc; pub mod dev; #[cfg(feature = "alloc")] -mod high_aead; +mod aead; #[cfg(feature = "alloc")] -pub use high_aead::{Aead, Payload}; +pub use aead::{Aead, Payload}; -mod tag_pos; -pub use tag_pos::{AeadWithTag, TagPosition}; +mod utils; #[cfg(feature = "rand_core")] pub use common::{Generate, rand_core}; @@ -28,6 +27,7 @@ pub use inout; pub use common::{ self, Key, KeyInit, KeySizeUser, array::{self, typenum::consts}, + typenum::Unsigned, }; pub use inout::InOutBuf; @@ -87,13 +87,19 @@ pub type Nonce = Array::NonceSize>; pub type Tag = Array::TagSize>; /// Low-level functionality of Authenticated Encryption with Associated Data (AEAD) algorithms. +#[allow(missing_docs, clippy::missing_errors_doc)] // TODO: fix pub trait AeadCore { - /// The length of a nonce in bytes. + /// The recommended nonce length in bytes. type NonceSize: ArraySize; - /// The length of a tag in bytes. + /// The recommended tag length in bytes. type TagSize: ArraySize; + /// The recommended AEAD tag position in resulting ciphertext. + /// + /// If tag position is not explicitly specified, we use postfix tags by default. + const TAG_POSITION: TagPosition; + /// Encrypt the data in the provided [`InOutBuf`], returning the authentication tag. /// /// # Errors @@ -231,6 +237,119 @@ pub trait AeadCore { let nonce = nonce.try_into().map_err(|_| Error)?; self.decrypt_inout_detached(nonce, associated_data, buffer.into(), tag) } + + #[inline] + fn encrypt_into>( + &self, + nonce: &Nonce, + aad: &[u8], + plaintext: &[u8], + allocate: impl FnOnce(usize) -> B, + ) -> Result { + utils::encrypt_into::(plaintext, allocate, |buf| { + self.encrypt_inout_detached(nonce, aad, buf) + }) + } + + #[inline] + fn encrypt_into_with_var_nonce>( + &self, + nonce: &[u8], + aad: &[u8], + plaintext: &[u8], + allocate: impl FnOnce(usize) -> B, + ) -> Result { + utils::encrypt_into::(plaintext, allocate, |buf| { + self.encrypt_inout_with_var_nonce_detached(nonce, aad, buf) + }) + } + + #[inline] + fn decrypt_into>( + &self, + nonce: &Nonce, + aad: &[u8], + ciphertext: &[u8], + allocate: impl FnOnce(usize) -> B, + ) -> Result { + utils::decrypt_into::(ciphertext, allocate, |buf, tag| { + self.decrypt_inout_detached(nonce, aad, buf, tag) + }) + } + + #[inline] + fn decrypt_into_with_var_nonce>( + &self, + nonce: &[u8], + aad: &[u8], + ciphertext: &[u8], + allocate: impl FnOnce(usize) -> B, + ) -> Result { + utils::decrypt_into::(ciphertext, allocate, |buf, tag| { + self.decrypt_inout_with_var_nonce_detached(nonce, aad, buf, tag) + }) + } + + #[inline] + fn encrypt_within>( + &self, + nonce: &Nonce, + aad: &[u8], + buf: &mut B, + extend: impl FnOnce(&mut B, usize), + ) -> Result<()> { + utils::encrypt_within::(buf, extend, |buf| { + self.encrypt_inout_detached(nonce, aad, buf) + }) + } + + #[inline] + fn encrypt_within_with_var_nonce>( + &self, + nonce: &[u8], + aad: &[u8], + buf: &mut B, + extend: impl FnOnce(&mut B, usize), + ) -> Result<()> { + utils::encrypt_within::(buf, extend, |buf| { + self.encrypt_inout_with_var_nonce_detached(nonce, aad, buf) + }) + } + + #[inline] + fn decrypt_within>( + &self, + nonce: &Nonce, + aad: &[u8], + buf: &mut B, + truncate: impl FnOnce(&mut B, usize), + ) -> Result<()> { + utils::decrypt_within::(buf, truncate, |buf, tag| { + self.decrypt_inout_detached(nonce, aad, buf, tag) + }) + } + + #[inline] + fn decrypt_within_with_var_nonce>( + &self, + nonce: &[u8], + aad: &[u8], + buf: &mut B, + truncate: impl FnOnce(&mut B, usize), + ) -> Result<()> { + utils::decrypt_within::(buf, truncate, |buf, tag| { + self.decrypt_inout_with_var_nonce_detached(nonce, aad, buf, tag) + }) + } +} + +/// 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, } /// Error type. diff --git a/aead/src/tag_pos.rs b/aead/src/tag_pos.rs deleted file mode 100644 index 4e31c63c0..000000000 --- a/aead/src/tag_pos.rs +++ /dev/null @@ -1,226 +0,0 @@ -use crate::{AeadCore, Error, Nonce, Result}; -use common::typenum::Unsigned; -use inout::InOutBuf; - -/// 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, -} - -macro_rules! encrypt_into { - ($plaintext:ident, $allocate:ident, $encrypt:expr) => {{ - 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); - - let (ct_dst, tag_dst) = match Self::TAG_POSITION { - TagPosition::Postfix => ct_tag.as_mut().split_at_mut($plaintext.len()), - TagPosition::Prefix => { - let (tag_dst, ct_dst) = ct_tag.as_mut().split_at_mut(tag_len); - (ct_dst, tag_dst) - } - }; - - let buf = - InOutBuf::new($plaintext, ct_dst).expect("`msg` and `ct_dst` have the same length"); - let tag = $encrypt(buf)?; - tag_dst.copy_from_slice(&tag); - - Ok(ct_tag) - }}; -} - -macro_rules! decrypt_into { - ($ciphertext:ident, $allocate:ident, $decrypt:expr) => {{ - let tag_len = Self::TagSize::USIZE; - let pt_len = $ciphertext.len().checked_sub(tag_len).ok_or(Error)?; - - let (ct, tag) = match Self::TAG_POSITION { - TagPosition::Postfix => $ciphertext.split_at(pt_len), - TagPosition::Prefix => { - let (tag, ct) = $ciphertext.split_at(tag_len); - (ct, tag) - } - }; - - let mut pt_dst = $allocate(pt_len); - assert_eq!(pt_dst.as_mut().len(), pt_len); - - 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 have the same length"); - $decrypt(buf, tag)?; - - Ok(pt_dst) - }}; -} - -macro_rules! encrypt_within { - ($buf:ident, $extend:ident, $encrypt:expr) => {{ - let tag_len = Self::TagSize::USIZE; - let pt_len = $buf.as_mut().len(); - let ct_len = pt_len.checked_add(tag_len).ok_or(Error)?; - - $extend($buf, ct_len); - let buf = $buf.as_mut(); - assert_eq!(buf.len(), ct_len); - - let (pt_dst, tag_dst) = match Self::TAG_POSITION { - TagPosition::Postfix => buf.split_at_mut(pt_len), - TagPosition::Prefix => { - buf.copy_within(..pt_len, tag_len); - let (tag_dst, ct_dst) = $buf.as_mut().split_at_mut(tag_len); - (ct_dst, tag_dst) - } - }; - - let tag = $encrypt(InOutBuf::from(pt_dst))?; - tag_dst.copy_from_slice(&tag); - - Ok(()) - }}; -} - -macro_rules! decrypt_within { - ($buf:ident, $truncate:ident, $decrypt:expr) => {{ - let tag_len = Self::TagSize::USIZE; - let ct_len = $buf.as_mut().len().checked_sub(tag_len).ok_or(Error)?; - - let (ct, tag) = match Self::TAG_POSITION { - TagPosition::Postfix => $buf.as_mut().split_at_mut(ct_len), - TagPosition::Prefix => { - let (tag, ct) = $buf.as_mut().split_at_mut(tag_len); - (ct, tag) - } - }; - - let tag = (&*tag).try_into().expect("`tag` has correct length"); - $decrypt(ct.into(), tag)?; - - if Self::TAG_POSITION == TagPosition::Prefix { - $buf.as_mut().copy_within(tag_len.., 0); - } - - $truncate($buf, ct_len); - assert_eq!($buf.as_mut().len(), ct_len); - - Ok(()) - }}; -} - -/// Trait implemented for AEAD modes which specify tag position in ciphertext. -#[allow(missing_docs, clippy::missing_errors_doc)] // TODO: fix -pub trait AeadWithTag: AeadCore { - /// The AEAD tag position. - const TAG_POSITION: TagPosition; - - #[inline] - fn encrypt_into>( - &self, - nonce: &Nonce, - aad: &[u8], - plaintext: &[u8], - allocate: impl FnOnce(usize) -> B, - ) -> Result { - encrypt_into!(plaintext, allocate, |buf| { - self.encrypt_inout_detached(nonce, aad, buf) - }) - } - - #[inline] - fn encrypt_into_with_var_nonce>( - &self, - nonce: &[u8], - aad: &[u8], - plaintext: &[u8], - allocate: impl FnOnce(usize) -> B, - ) -> Result { - encrypt_into!(plaintext, allocate, |buf| { - self.encrypt_inout_with_var_nonce_detached(nonce, aad, buf) - }) - } - - #[inline] - fn decrypt_into>( - &self, - nonce: &Nonce, - aad: &[u8], - ciphertext: &[u8], - allocate: impl FnOnce(usize) -> B, - ) -> Result { - decrypt_into!(ciphertext, allocate, |buf, tag| { - self.decrypt_inout_detached(nonce, aad, buf, tag) - }) - } - - #[inline] - fn decrypt_into_with_var_nonce>( - &self, - nonce: &[u8], - aad: &[u8], - ciphertext: &[u8], - allocate: impl FnOnce(usize) -> B, - ) -> Result { - decrypt_into!(ciphertext, allocate, |buf, tag| { - self.decrypt_inout_with_var_nonce_detached(nonce, aad, buf, tag) - }) - } - - #[inline] - fn encrypt_within>( - &self, - nonce: &Nonce, - aad: &[u8], - buf: &mut B, - extend: impl FnOnce(&mut B, usize), - ) -> Result<()> { - encrypt_within!(buf, extend, |buf| { - self.encrypt_inout_detached(nonce, aad, buf) - }) - } - - #[inline] - fn encrypt_within_with_var_nonce>( - &self, - nonce: &[u8], - aad: &[u8], - buf: &mut B, - extend: impl FnOnce(&mut B, usize), - ) -> Result<()> { - encrypt_within!(buf, extend, |buf| { - self.encrypt_inout_with_var_nonce_detached(nonce, aad, buf) - }) - } - - #[inline] - fn decrypt_within>( - &self, - nonce: &Nonce, - aad: &[u8], - buf: &mut B, - truncate: impl FnOnce(&mut B, usize), - ) -> Result<()> { - decrypt_within!(buf, truncate, |buf, tag| { - self.decrypt_inout_detached(nonce, aad, buf, tag) - }) - } - - #[inline] - fn decrypt_within_with_var_nonce>( - &self, - nonce: &[u8], - aad: &[u8], - buf: &mut B, - truncate: impl FnOnce(&mut B, usize), - ) -> Result<()> { - decrypt_within!(buf, truncate, |buf, tag| { - self.decrypt_inout_with_var_nonce_detached(nonce, aad, buf, tag) - }) - } -} diff --git a/aead/src/utils.rs b/aead/src/utils.rs new file mode 100644 index 000000000..cf6062636 --- /dev/null +++ b/aead/src/utils.rs @@ -0,0 +1,125 @@ +use crate::{ + AeadCore, Error, Result, Tag, + TagPosition::{Postfix, Prefix}, +}; +use common::typenum::Unsigned; +use inout::InOutBuf; + +#[inline(always)] +#[allow(clippy::unwrap_in_result)] +pub(crate) fn encrypt_into>( + plaintext: &[u8], + allocate: impl FnOnce(usize) -> B, + encrypt: impl FnOnce(InOutBuf<'_, '_, u8>) -> Result>, +) -> Result { + let tag_len = C::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); + + let (ct_dst, tag_dst) = match C::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) + } + }; + + let buf = InOutBuf::new(plaintext, ct_dst) + .expect("`plaintext` and `ct_dst` always have the same length"); + let tag = encrypt(buf)?; + tag_dst.copy_from_slice(&tag); + + Ok(ct_tag) +} + +#[inline(always)] +#[allow(clippy::unwrap_in_result)] +pub(crate) fn decrypt_into>( + ciphertext: &[u8], + allocate: impl FnOnce(usize) -> B, + decrypt: impl FnOnce(InOutBuf<'_, '_, u8>, &Tag) -> Result<()>, +) -> Result { + let tag_len = C::TagSize::USIZE; + let pt_len = ciphertext.len().checked_sub(tag_len).ok_or(Error)?; + + let (ct, tag) = match C::TAG_POSITION { + Postfix => ciphertext.split_at(pt_len), + Prefix => { + let (tag, ct) = ciphertext.split_at(tag_len); + (ct, tag) + } + }; + + let mut pt_dst = allocate(pt_len); + assert_eq!(pt_dst.as_mut().len(), pt_len); + + 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"); + decrypt(buf, tag)?; + + Ok(pt_dst) +} + +#[inline(always)] +#[allow(clippy::unwrap_in_result)] +pub(crate) fn encrypt_within>( + buf: &mut B, + extend: impl FnOnce(&mut B, usize), + encrypt: impl FnOnce(InOutBuf<'_, '_, u8>) -> Result>, +) -> Result<()> { + let tag_len = C::TagSize::USIZE; + let pt_len = buf.as_mut().len(); + let ct_len = pt_len.checked_add(tag_len).ok_or(Error)?; + + extend(buf, ct_len); + let buf = buf.as_mut(); + assert_eq!(buf.len(), ct_len); + + let (pt_dst, tag_dst) = match C::TAG_POSITION { + Postfix => buf.split_at_mut(pt_len), + Prefix => { + buf.copy_within(..pt_len, tag_len); + let (tag_dst, ct_dst) = buf.as_mut().split_at_mut(tag_len); + (ct_dst, tag_dst) + } + }; + + let tag = encrypt(InOutBuf::from(pt_dst))?; + tag_dst.copy_from_slice(&tag); + + Ok(()) +} + +#[inline(always)] +#[allow(clippy::unwrap_in_result)] +pub(crate) fn decrypt_within>( + buf: &mut B, + truncate: impl FnOnce(&mut B, usize), + decrypt: impl FnOnce(InOutBuf<'_, '_, u8>, &Tag) -> Result<()>, +) -> Result<()> { + let tag_len = C::TagSize::USIZE; + let ct_len = buf.as_mut().len().checked_sub(tag_len).ok_or(Error)?; + + let (ct, tag) = match C::TAG_POSITION { + Postfix => buf.as_mut().split_at_mut(ct_len), + Prefix => { + let (tag, ct) = buf.as_mut().split_at_mut(tag_len); + (ct, tag) + } + }; + + let tag = (&*tag).try_into().expect("`tag` has correct length"); + decrypt(ct.into(), tag)?; + + if C::TAG_POSITION == Prefix { + buf.as_mut().copy_within(tag_len.., 0); + } + + truncate(buf, ct_len); + assert_eq!(buf.as_mut().len(), ct_len); + + Ok(()) +} diff --git a/aead/tests/dummy.rs b/aead/tests/dummy.rs index a9be359b3..1368549b0 100644 --- a/aead/tests/dummy.rs +++ b/aead/tests/dummy.rs @@ -7,7 +7,7 @@ #![allow(clippy::unwrap_used, reason = "tests")] use aead::{ - Aead, AeadCore, AeadWithTag, Error, Key, KeyInit, KeySizeUser, Nonce, Result, Tag, TagPosition, + Aead, AeadCore, Error, Key, KeyInit, KeySizeUser, Nonce, Result, Tag, TagPosition, array::Array, consts::U8, }; use core::fmt; @@ -120,6 +120,7 @@ impl KeyInit for PrefixDummyAead { impl AeadCore for PrefixDummyAead { type NonceSize = U8; type TagSize = U8; + const TAG_POSITION: TagPosition = TagPosition::Prefix; fn encrypt_inout_detached( &self, @@ -141,10 +142,6 @@ impl AeadCore for PrefixDummyAead { } } -impl AeadWithTag for PrefixDummyAead { - const TAG_POSITION: TagPosition = TagPosition::Prefix; -} - #[derive(Debug)] pub struct PostfixDummyAead(DummyAead); @@ -161,6 +158,7 @@ impl KeyInit for PostfixDummyAead { impl AeadCore for PostfixDummyAead { type NonceSize = U8; type TagSize = U8; + const TAG_POSITION: TagPosition = TagPosition::Postfix; fn encrypt_inout_detached( &self, @@ -182,26 +180,16 @@ impl AeadCore for PostfixDummyAead { } } -impl AeadWithTag for PostfixDummyAead { - const TAG_POSITION: TagPosition = TagPosition::Postfix; -} - +#[cfg(feature = "alloc")] #[test] fn dyn_compat() { let c1 = PrefixDummyAead::new(&[0u8; 8].into()); let c2 = PostfixDummyAead::new(&[0u8; 8].into()); - fn take_dyn_aead_core(_: &dyn AeadCore) {} - take_dyn_aead_core(&c1); - take_dyn_aead_core(&c2); + fn take_dyn_aead(_: &dyn Aead) {} - #[cfg(feature = "alloc")] - { - fn take_dyn_aead(_: &dyn Aead) {} - - take_dyn_aead(&c1); - take_dyn_aead(&c2); - } + take_dyn_aead(&c1); + take_dyn_aead(&c2); } #[cfg(feature = "dev")] From d5e76b7d5e948949a43ad97540ef3e8ee319403a 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: Mon, 1 Jun 2026 16:32:36 +0300 Subject: [PATCH 13/20] Tweak `Aead` --- aead/src/aead.rs | 95 ++++++++++++++--------------------------------- aead/src/dev.rs | 21 +++-------- aead/src/lib.rs | 2 +- aead/src/utils.rs | 21 +++++++---- 4 files changed, 47 insertions(+), 92 deletions(-) diff --git a/aead/src/aead.rs b/aead/src/aead.rs index da6c0f8bd..29302d3e7 100644 --- a/aead/src/aead.rs +++ b/aead/src/aead.rs @@ -6,93 +6,54 @@ pub trait Aead { /// 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 and nonce: 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`: - /// - /// ```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_into_vec(&self, payload: Payload<'_>) -> Result>; + 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. /// - /// See notes on [`Aead::encrypt()`] about allowable message payloads and - /// Associated Additional Data (AAD). + /// # 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. /// - /// If you have no AAD, you can call this as follows: + /// On success, `buf` will contain the resulting ciphertext, + /// while on error it will be left intact. /// - /// ```nobuild - /// let ciphertext = b"..."; - /// let plaintext = cipher.decrypt(nonce, ciphertext)?; - /// ``` + /// # Errors + /// AEAD algorithm implementations may return an error if the plaintext or AAD are too long. + fn encrypt_within_vec(&self, nonce: &[u8], aad: &[u8], buf: &mut Vec) -> Result<()>; + + /// Decrypt ciphertext in `buf` truncating it as necessary. /// - /// 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. + /// 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 - fn decrypt_into_vec(&self, payload: Payload<'_>) -> Result>; + fn decrypt_within_vec(&self, nonce: &[u8], aad: &[u8], buf: &mut Vec) -> Result<()>; } impl Aead for T { - #[allow(clippy::unwrap_in_result)] - fn encrypt_into_vec(&self, payload: Payload<'_>) -> Result> { - let Payload { nonce, msg, aad } = payload; - self.encrypt_into_with_var_nonce(nonce, aad, msg, |n| alloc::vec![0u8; n]) + fn encrypt_into_vec(&self, nonce: &[u8], aad: &[u8], plaintext: &[u8]) -> Result> { + self.encrypt_into_with_var_nonce(nonce, aad, plaintext, |n| alloc::vec![0u8; n]) } - #[allow(clippy::unwrap_in_result)] - fn decrypt_into_vec(&self, payload: Payload<'_>) -> Result> { - let Payload { nonce, msg, aad } = payload; - self.decrypt_into_with_var_nonce(nonce, aad, msg, |n| alloc::vec![0u8; n]) + fn decrypt_into_vec(&self, nonce: &[u8], aad: &[u8], ciphertext: &[u8]) -> Result> { + self.decrypt_into_with_var_nonce(nonce, aad, ciphertext, |n| alloc::vec![0u8; n]) } -} -/// AEAD payloads (nonce + message + AAD). -/// -/// Combination of a nonce, 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 an empty slice. -/// -/// This type is used to emulate named function arguments to guard against potential bugs -/// caused by the use of the same type (`&[u8]`) by all payload parts. -#[derive(Debug)] -pub struct Payload<'a> { - /// Nonce used for encryption/decryption. - /// - /// See [`Nonce`][crate::Nonce] for additional information. - pub nonce: &'a [u8], - - /// Message to be encrypted/decrypted - pub msg: &'a [u8], + fn encrypt_within_vec(&self, nonce: &[u8], aad: &[u8], buf: &mut Vec) -> Result<()> { + self.encrypt_within_with_var_nonce(nonce, aad, buf, |buf, len| buf.resize(len, 0)) + } - /// 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: &'a [u8], + fn decrypt_within_vec(&self, nonce: &[u8], aad: &[u8], buf: &mut Vec) -> Result<()> { + self.decrypt_within_with_var_nonce(nonce, aad, buf, Vec::truncate) + } } diff --git a/aead/src/dev.rs b/aead/src/dev.rs index 4dbd0555c..f221fdf45 100644 --- a/aead/src/dev.rs +++ b/aead/src/dev.rs @@ -1,7 +1,8 @@ //! Development-related functionality -use crate::{Aead, Payload}; pub use blobby; + +use crate::Aead; use common::KeyInit; /// AEAD test vector @@ -36,22 +37,14 @@ pub fn pass_test( let cipher: C = KeyInit::new_from_slice(key).map_err(|_| "failed to initialize the cipher")?; let res = cipher - .encrypt_into_vec(Payload { - nonce, - 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_into_vec(Payload { - nonce, - 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"); @@ -76,11 +69,7 @@ pub fn fail_test( ) -> Result<(), &'static str> { let cipher: C = KeyInit::new_from_slice(key).map_err(|_| "failed to initialize the cipher")?; - let res = cipher.decrypt_into_vec(Payload { - nonce, - aad, - msg: ciphertext, - }); + let res = cipher.decrypt_into_vec(nonce, aad, ciphertext); if res.is_ok() { Err("decryption must return error") } else { diff --git a/aead/src/lib.rs b/aead/src/lib.rs index f9eb8ef54..5a16d6654 100644 --- a/aead/src/lib.rs +++ b/aead/src/lib.rs @@ -16,7 +16,7 @@ pub mod dev; #[cfg(feature = "alloc")] mod aead; #[cfg(feature = "alloc")] -pub use aead::{Aead, Payload}; +pub use aead::Aead; mod utils; diff --git a/aead/src/utils.rs b/aead/src/utils.rs index cf6062636..799617f8e 100644 --- a/aead/src/utils.rs +++ b/aead/src/utils.rs @@ -5,6 +5,7 @@ use crate::{ use common::typenum::Unsigned; use inout::InOutBuf; +/// Allocate a buffer for ciphertext and encrypt `plaintext` into it. #[inline(always)] #[allow(clippy::unwrap_in_result)] pub(crate) fn encrypt_into>( @@ -34,6 +35,7 @@ pub(crate) fn encrypt_into>( Ok(ct_tag) } +/// Allocate a buffer for plaintext and decrypt `ciphertext` into it. #[inline(always)] #[allow(clippy::unwrap_in_result)] pub(crate) fn decrypt_into>( @@ -63,6 +65,7 @@ pub(crate) fn decrypt_into>( Ok(pt_dst) } +/// Encrypt plaintext inside the buffer extending it as necessary. #[inline(always)] #[allow(clippy::unwrap_in_result)] pub(crate) fn encrypt_within>( @@ -74,25 +77,25 @@ pub(crate) fn encrypt_within>( let pt_len = buf.as_mut().len(); let ct_len = pt_len.checked_add(tag_len).ok_or(Error)?; + let tag = encrypt(InOutBuf::from(buf.as_mut()))?; + extend(buf, ct_len); let buf = buf.as_mut(); assert_eq!(buf.len(), ct_len); - - let (pt_dst, tag_dst) = match C::TAG_POSITION { - Postfix => buf.split_at_mut(pt_len), + let tag_dst = match C::TAG_POSITION { + Postfix => &mut buf[pt_len..], Prefix => { buf.copy_within(..pt_len, tag_len); - let (tag_dst, ct_dst) = buf.as_mut().split_at_mut(tag_len); - (ct_dst, tag_dst) + &mut buf[..tag_len] } }; - let tag = encrypt(InOutBuf::from(pt_dst))?; tag_dst.copy_from_slice(&tag); Ok(()) } +/// Decrypt ciphertext inside the buffer truncating it as necessary. #[inline(always)] #[allow(clippy::unwrap_in_result)] pub(crate) fn decrypt_within>( @@ -111,8 +114,10 @@ pub(crate) fn decrypt_within>( } }; - let tag = (&*tag).try_into().expect("`tag` has correct length"); - decrypt(ct.into(), tag)?; + let tag: &mut Tag = tag.try_into().expect("`tag` has correct length"); + // Note that on failure the `ct` part should be zeroized by `decrypt`, + // so here we only need to zeroize `tag`. + decrypt(ct.into(), tag).inspect_err(|_| tag.fill(0))?; if C::TAG_POSITION == Prefix { buf.as_mut().copy_within(tag_len.., 0); From 01702010bec43f2d622a8d42d26f1472e6d5db10 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: Mon, 1 Jun 2026 16:34:38 +0300 Subject: [PATCH 14/20] fix typos --- aead/src/dev.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aead/src/dev.rs b/aead/src/dev.rs index f221fdf45..d591a0ca1 100644 --- a/aead/src/dev.rs +++ b/aead/src/dev.rs @@ -23,7 +23,7 @@ pub struct TestVector { /// Run AEAD test for the provided passing test vector /// /// # Errors -/// - If the cipher has failed initialzation with the provided key. +/// - 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 { @@ -56,7 +56,7 @@ pub fn pass_test( /// Run AEAD test for the provided failing test vector /// /// # Errors -/// - If the cipher has failed initialzation with the provided key. +/// - If the cipher has failed initialization with the provided key. /// - If the cipher has passed the test vector. pub fn fail_test( &TestVector { From 1d2b049ac1986b95be77f482affc22cfd204ad31 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: Mon, 1 Jun 2026 17:13:37 +0300 Subject: [PATCH 15/20] tweak test macros --- aead/src/dev.rs | 6 ++++++ ...postfix_fail.blb => dummy_aead_postfix_fail.blb} | Bin ...postfix_pass.blb => dummy_aead_postfix_pass.blb} | Bin .../{prefix_fail.blb => dummy_aead_prefix_fail.blb} | Bin .../{prefix_pass.blb => dummy_aead_prefix_pass.blb} | Bin aead/tests/dummy.rs | 8 ++++---- 6 files changed, 10 insertions(+), 4 deletions(-) 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/aead/src/dev.rs b/aead/src/dev.rs index d591a0ca1..76df5f5ed 100644 --- a/aead/src/dev.rs +++ b/aead/src/dev.rs @@ -80,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() { @@ -111,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/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 1368549b0..689f5052f 100644 --- a/aead/tests/dummy.rs +++ b/aead/tests/dummy.rs @@ -196,8 +196,8 @@ fn dyn_compat() { 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 ea1ea54eea82fd456da872860875f81aec2d68b4 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: Mon, 1 Jun 2026 17:19:46 +0300 Subject: [PATCH 16/20] Add blanket impls for `Aead::de/encrypt_within_vec` --- aead/src/aead.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/aead/src/aead.rs b/aead/src/aead.rs index 29302d3e7..2ab1a1b49 100644 --- a/aead/src/aead.rs +++ b/aead/src/aead.rs @@ -26,7 +26,11 @@ pub trait Aead { /// /// # Errors /// AEAD algorithm implementations may return an error if the plaintext or AAD are too long. - fn encrypt_within_vec(&self, nonce: &[u8], aad: &[u8], buf: &mut Vec) -> Result<()>; + 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. /// @@ -37,7 +41,19 @@ pub trait Aead { /// - if the `ciphertext` is inauthentic (i.e. tag verification failure) /// - if the `ciphertext` is too long /// - if the `aad` is too long - fn decrypt_within_vec(&self, nonce: &[u8], aad: &[u8], buf: &mut Vec) -> Result<()>; + 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 { From 20828261e7d321bfd6e8c974d974207d36f989cf 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: Mon, 1 Jun 2026 17:22:07 +0300 Subject: [PATCH 17/20] tweak attributes --- aead/src/aead.rs | 6 ++++++ aead/src/lib.rs | 6 ++++++ aead/src/utils.rs | 5 +---- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/aead/src/aead.rs b/aead/src/aead.rs index 2ab1a1b49..fd8943a32 100644 --- a/aead/src/aead.rs +++ b/aead/src/aead.rs @@ -26,6 +26,7 @@ pub trait Aead { /// /// # 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; @@ -41,6 +42,7 @@ pub trait Aead { /// - 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 { @@ -57,18 +59,22 @@ pub trait Aead { } impl Aead for T { + #[inline] fn encrypt_into_vec(&self, nonce: &[u8], aad: &[u8], plaintext: &[u8]) -> Result> { self.encrypt_into_with_var_nonce(nonce, aad, plaintext, |n| alloc::vec![0u8; n]) } + #[inline] fn decrypt_into_vec(&self, nonce: &[u8], aad: &[u8], ciphertext: &[u8]) -> Result> { self.decrypt_into_with_var_nonce(nonce, aad, ciphertext, |n| alloc::vec![0u8; n]) } + #[inline] fn encrypt_within_vec(&self, nonce: &[u8], aad: &[u8], buf: &mut Vec) -> Result<()> { self.encrypt_within_with_var_nonce(nonce, aad, buf, |buf, len| buf.resize(len, 0)) } + #[inline] fn decrypt_within_vec(&self, nonce: &[u8], aad: &[u8], buf: &mut Vec) -> Result<()> { self.decrypt_within_with_var_nonce(nonce, aad, buf, Vec::truncate) } diff --git a/aead/src/lib.rs b/aead/src/lib.rs index 5a16d6654..74c603739 100644 --- a/aead/src/lib.rs +++ b/aead/src/lib.rs @@ -138,6 +138,7 @@ pub trait AeadCore { /// # Errors /// AEAD algorithm implementations may return an error if the plaintext or AAD are too long /// or an invalid nonce is used. + #[inline] fn encrypt_inout_with_var_nonce_detached( &self, nonce: &[u8], @@ -156,6 +157,7 @@ pub trait AeadCore { /// - 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_inout_with_var_nonce_detached( &self, nonce: &[u8], @@ -171,6 +173,7 @@ pub trait AeadCore { /// /// # Errors /// AEAD algorithm implementations may return an error if the plaintext or AAD are too long. + #[inline] fn encrypt_detached( &self, nonce: &Nonce, @@ -188,6 +191,7 @@ pub trait AeadCore { /// - 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_detached( &self, nonce: &Nonce, @@ -209,6 +213,7 @@ pub trait AeadCore { /// # 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 encrypt_with_var_nonce_detached( &self, nonce: &[u8], @@ -227,6 +232,7 @@ pub trait AeadCore { /// - 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_with_var_nonce_detached( &self, nonce: &[u8], diff --git a/aead/src/utils.rs b/aead/src/utils.rs index 799617f8e..67108b6ec 100644 --- a/aead/src/utils.rs +++ b/aead/src/utils.rs @@ -1,3 +1,4 @@ +#![allow(clippy::unwrap_in_result)] use crate::{ AeadCore, Error, Result, Tag, TagPosition::{Postfix, Prefix}, @@ -7,7 +8,6 @@ use inout::InOutBuf; /// Allocate a buffer for ciphertext and encrypt `plaintext` into it. #[inline(always)] -#[allow(clippy::unwrap_in_result)] pub(crate) fn encrypt_into>( plaintext: &[u8], allocate: impl FnOnce(usize) -> B, @@ -37,7 +37,6 @@ pub(crate) fn encrypt_into>( /// Allocate a buffer for plaintext and decrypt `ciphertext` into it. #[inline(always)] -#[allow(clippy::unwrap_in_result)] pub(crate) fn decrypt_into>( ciphertext: &[u8], allocate: impl FnOnce(usize) -> B, @@ -67,7 +66,6 @@ pub(crate) fn decrypt_into>( /// Encrypt plaintext inside the buffer extending it as necessary. #[inline(always)] -#[allow(clippy::unwrap_in_result)] pub(crate) fn encrypt_within>( buf: &mut B, extend: impl FnOnce(&mut B, usize), @@ -97,7 +95,6 @@ pub(crate) fn encrypt_within>( /// Decrypt ciphertext inside the buffer truncating it as necessary. #[inline(always)] -#[allow(clippy::unwrap_in_result)] pub(crate) fn decrypt_within>( buf: &mut B, truncate: impl FnOnce(&mut B, usize), From 4caeffcc411eff78d48243d36c08a0611c2f5cba 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: Mon, 1 Jun 2026 17:49:51 +0300 Subject: [PATCH 18/20] Introduce `VariableAead` trait --- aead/src/aead.rs | 4 +- aead/src/lib.rs | 139 +------------------------------------------ aead/src/variable.rs | 139 +++++++++++++++++++++++++++++++++++++++++++ aead/tests/dummy.rs | 11 +++- 4 files changed, 152 insertions(+), 141 deletions(-) create mode 100644 aead/src/variable.rs diff --git a/aead/src/aead.rs b/aead/src/aead.rs index fd8943a32..c4f9281f7 100644 --- a/aead/src/aead.rs +++ b/aead/src/aead.rs @@ -1,4 +1,4 @@ -use crate::{AeadCore, Result}; +use crate::{Result, VariablAead}; use alloc::vec::Vec; /// High-level functionality of Authenticated Encryption with Associated Data (AEAD) algorithms. @@ -58,7 +58,7 @@ pub trait Aead { } } -impl Aead for T { +impl Aead for T { #[inline] fn encrypt_into_vec(&self, nonce: &[u8], aad: &[u8], plaintext: &[u8]) -> Result> { self.encrypt_into_with_var_nonce(nonce, aad, plaintext, |n| alloc::vec![0u8; n]) diff --git a/aead/src/lib.rs b/aead/src/lib.rs index 74c603739..b6531f8b7 100644 --- a/aead/src/lib.rs +++ b/aead/src/lib.rs @@ -19,6 +19,9 @@ mod aead; pub use aead::Aead; mod utils; +mod variable; + +pub use variable::VariablAead; #[cfg(feature = "rand_core")] pub use common::{Generate, rand_core}; @@ -127,48 +130,6 @@ pub trait AeadCore { tag: &Tag, ) -> Result<()>; - /// Encrypt the data in the provided [`InOutBuf`] with variable size nonce, - /// returning the authentication tag. - /// - /// # Warning - /// Some algorithms support very short nonces. Users should exercise extreme caution - /// while using this method since incorrect handling of nonces may defeat security - /// provided by the algorithm. - /// - /// # Errors - /// AEAD algorithm implementations may return an error if the plaintext or AAD are too long - /// or an invalid nonce is used. - #[inline] - fn encrypt_inout_with_var_nonce_detached( - &self, - nonce: &[u8], - associated_data: &[u8], - buffer: InOutBuf<'_, '_, u8>, - ) -> Result> { - let nonce = nonce.try_into().map_err(|_| Error)?; - self.encrypt_inout_detached(nonce, associated_data, buffer) - } - - /// Decrypt the data in the provided [`InOutBuf`] with variable size nonce, - /// 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 decrypt_inout_with_var_nonce_detached( - &self, - nonce: &[u8], - associated_data: &[u8], - buffer: InOutBuf<'_, '_, u8>, - tag: &Tag, - ) -> Result<()> { - let nonce = nonce.try_into().map_err(|_| Error)?; - self.decrypt_inout_detached(nonce, associated_data, buffer, tag) - } - /// Encrypt the data in-place in the provided buffer, returning the authentication tag. /// /// # Errors @@ -202,48 +163,6 @@ pub trait AeadCore { self.decrypt_inout_detached(nonce, associated_data, buffer.into(), tag) } - /// Encrypt the data in-place in the provided buffer with variable size nonce, - /// returning the authentication tag. - /// - /// # Warning - /// Some algorithms support very short nonces. Users should exercise extreme caution - /// while using this method since incorrect handling of nonces may defeat security - /// provided by the algorithm. - /// - /// # 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 encrypt_with_var_nonce_detached( - &self, - nonce: &[u8], - associated_data: &[u8], - buf: &mut [u8], - ) -> Result> { - let nonce = nonce.try_into().map_err(|_| Error)?; - self.encrypt_inout_detached(nonce, associated_data, buf.into()) - } - - /// 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) - /// - if the `ciphertext` is too long - /// - if the `aad` is too long - #[inline] - fn decrypt_with_var_nonce_detached( - &self, - nonce: &[u8], - associated_data: &[u8], - buffer: &mut [u8], - tag: &Tag, - ) -> Result<()> { - let nonce = nonce.try_into().map_err(|_| Error)?; - self.decrypt_inout_detached(nonce, associated_data, buffer.into(), tag) - } - #[inline] fn encrypt_into>( &self, @@ -257,19 +176,6 @@ pub trait AeadCore { }) } - #[inline] - fn encrypt_into_with_var_nonce>( - &self, - nonce: &[u8], - aad: &[u8], - plaintext: &[u8], - allocate: impl FnOnce(usize) -> B, - ) -> Result { - utils::encrypt_into::(plaintext, allocate, |buf| { - self.encrypt_inout_with_var_nonce_detached(nonce, aad, buf) - }) - } - #[inline] fn decrypt_into>( &self, @@ -283,19 +189,6 @@ pub trait AeadCore { }) } - #[inline] - fn decrypt_into_with_var_nonce>( - &self, - nonce: &[u8], - aad: &[u8], - ciphertext: &[u8], - allocate: impl FnOnce(usize) -> B, - ) -> Result { - utils::decrypt_into::(ciphertext, allocate, |buf, tag| { - self.decrypt_inout_with_var_nonce_detached(nonce, aad, buf, tag) - }) - } - #[inline] fn encrypt_within>( &self, @@ -309,19 +202,6 @@ pub trait AeadCore { }) } - #[inline] - fn encrypt_within_with_var_nonce>( - &self, - nonce: &[u8], - aad: &[u8], - buf: &mut B, - extend: impl FnOnce(&mut B, usize), - ) -> Result<()> { - utils::encrypt_within::(buf, extend, |buf| { - self.encrypt_inout_with_var_nonce_detached(nonce, aad, buf) - }) - } - #[inline] fn decrypt_within>( &self, @@ -334,19 +214,6 @@ pub trait AeadCore { self.decrypt_inout_detached(nonce, aad, buf, tag) }) } - - #[inline] - fn decrypt_within_with_var_nonce>( - &self, - nonce: &[u8], - aad: &[u8], - buf: &mut B, - truncate: impl FnOnce(&mut B, usize), - ) -> Result<()> { - utils::decrypt_within::(buf, truncate, |buf, tag| { - self.decrypt_inout_with_var_nonce_detached(nonce, aad, buf, tag) - }) - } } /// Enum which specifies tag position used by an AEAD algorithm. diff --git a/aead/src/variable.rs b/aead/src/variable.rs new file mode 100644 index 000000000..c863e3bc9 --- /dev/null +++ b/aead/src/variable.rs @@ -0,0 +1,139 @@ +use crate::{AeadCore, Error, Result, Tag, utils}; +use inout::InOutBuf; + +/// Functionality of Authenticated Encryption with Associated Data (AEAD) algorithms +/// with variable nonce size support. +/// +///
+/// Some algorithms support very short nonces. Users should exercise extreme caution +/// while using this trait since incorrect handling of nonces may defeat security +/// provided by the algorithm. +///
+#[allow(missing_docs, clippy::missing_errors_doc)] // TODO: fix +pub trait VariablAead: AeadCore { + /// Encrypt the data in the provided [`InOutBuf`] with variable size nonce, + /// returning the authentication tag. + /// + /// # Errors + /// AEAD algorithm implementations may return an error if the plaintext or AAD are too long + /// or an invalid nonce is used. + #[inline] + fn encrypt_inout_with_var_nonce_detached( + &self, + nonce: &[u8], + associated_data: &[u8], + buffer: InOutBuf<'_, '_, u8>, + ) -> Result> { + let nonce = nonce.try_into().map_err(|_| Error)?; + self.encrypt_inout_detached(nonce, associated_data, buffer) + } + + /// Decrypt the data in the provided [`InOutBuf`] with variable size nonce, + /// 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 decrypt_inout_with_var_nonce_detached( + &self, + nonce: &[u8], + associated_data: &[u8], + buffer: InOutBuf<'_, '_, u8>, + tag: &Tag, + ) -> Result<()> { + let nonce = nonce.try_into().map_err(|_| Error)?; + self.decrypt_inout_detached(nonce, associated_data, buffer, tag) + } + + /// Encrypt the data in-place in the provided buffer with variable size nonce, + /// 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 encrypt_with_var_nonce_detached( + &self, + nonce: &[u8], + associated_data: &[u8], + buf: &mut [u8], + ) -> Result> { + let nonce = nonce.try_into().map_err(|_| Error)?; + self.encrypt_inout_detached(nonce, associated_data, buf.into()) + } + + /// 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) + /// - if the `ciphertext` is too long + /// - if the `aad` is too long + #[inline] + fn decrypt_with_var_nonce_detached( + &self, + nonce: &[u8], + associated_data: &[u8], + buffer: &mut [u8], + tag: &Tag, + ) -> Result<()> { + let nonce = nonce.try_into().map_err(|_| Error)?; + self.decrypt_inout_detached(nonce, associated_data, buffer.into(), tag) + } + + #[inline] + fn encrypt_into_with_var_nonce>( + &self, + nonce: &[u8], + aad: &[u8], + plaintext: &[u8], + allocate: impl FnOnce(usize) -> B, + ) -> Result { + utils::encrypt_into::(plaintext, allocate, |buf| { + self.encrypt_inout_with_var_nonce_detached(nonce, aad, buf) + }) + } + + #[inline] + fn decrypt_into_with_var_nonce>( + &self, + nonce: &[u8], + aad: &[u8], + ciphertext: &[u8], + allocate: impl FnOnce(usize) -> B, + ) -> Result { + utils::decrypt_into::(ciphertext, allocate, |buf, tag| { + self.decrypt_inout_with_var_nonce_detached(nonce, aad, buf, tag) + }) + } + + #[inline] + fn encrypt_within_with_var_nonce>( + &self, + nonce: &[u8], + aad: &[u8], + buf: &mut B, + extend: impl FnOnce(&mut B, usize), + ) -> Result<()> { + utils::encrypt_within::(buf, extend, |buf| { + self.encrypt_inout_with_var_nonce_detached(nonce, aad, buf) + }) + } + + #[inline] + fn decrypt_within_with_var_nonce>( + &self, + nonce: &[u8], + aad: &[u8], + buf: &mut B, + truncate: impl FnOnce(&mut B, usize), + ) -> Result<()> { + utils::decrypt_within::(buf, truncate, |buf, tag| { + self.decrypt_inout_with_var_nonce_detached(nonce, aad, buf, tag) + }) + } +} diff --git a/aead/tests/dummy.rs b/aead/tests/dummy.rs index 689f5052f..784f9385d 100644 --- a/aead/tests/dummy.rs +++ b/aead/tests/dummy.rs @@ -7,7 +7,7 @@ #![allow(clippy::unwrap_used, reason = "tests")] use aead::{ - Aead, AeadCore, Error, Key, KeyInit, KeySizeUser, Nonce, Result, Tag, TagPosition, + Aead, AeadCore, Error, Key, KeyInit, KeySizeUser, Nonce, Result, Tag, TagPosition, VariablAead, array::Array, consts::U8, }; use core::fmt; @@ -142,6 +142,8 @@ impl AeadCore for PrefixDummyAead { } } +impl VariablAead for PrefixDummyAead {} + #[derive(Debug)] pub struct PostfixDummyAead(DummyAead); @@ -180,11 +182,14 @@ impl AeadCore for PostfixDummyAead { } } +impl VariablAead for PostfixDummyAead {} + #[cfg(feature = "alloc")] #[test] fn dyn_compat() { - let c1 = PrefixDummyAead::new(&[0u8; 8].into()); - let c2 = PostfixDummyAead::new(&[0u8; 8].into()); + let key = &[0u8; 8].into(); + let c1 = PrefixDummyAead::new(key); + let c2 = PostfixDummyAead::new(key); fn take_dyn_aead(_: &dyn Aead) {} From a0e60293c554c84d0b016a6ab1fe1797b7a001ec 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: Mon, 1 Jun 2026 17:51:10 +0300 Subject: [PATCH 19/20] fix typo --- aead/src/aead.rs | 4 ++-- aead/src/lib.rs | 2 +- aead/src/variable.rs | 2 +- aead/tests/dummy.rs | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/aead/src/aead.rs b/aead/src/aead.rs index c4f9281f7..f7d262187 100644 --- a/aead/src/aead.rs +++ b/aead/src/aead.rs @@ -1,4 +1,4 @@ -use crate::{Result, VariablAead}; +use crate::{Result, VariableAead}; use alloc::vec::Vec; /// High-level functionality of Authenticated Encryption with Associated Data (AEAD) algorithms. @@ -58,7 +58,7 @@ pub trait Aead { } } -impl Aead for T { +impl Aead for T { #[inline] fn encrypt_into_vec(&self, nonce: &[u8], aad: &[u8], plaintext: &[u8]) -> Result> { self.encrypt_into_with_var_nonce(nonce, aad, plaintext, |n| alloc::vec![0u8; n]) diff --git a/aead/src/lib.rs b/aead/src/lib.rs index b6531f8b7..607f3a269 100644 --- a/aead/src/lib.rs +++ b/aead/src/lib.rs @@ -21,7 +21,7 @@ pub use aead::Aead; mod utils; mod variable; -pub use variable::VariablAead; +pub use variable::VariableAead; #[cfg(feature = "rand_core")] pub use common::{Generate, rand_core}; diff --git a/aead/src/variable.rs b/aead/src/variable.rs index c863e3bc9..740078623 100644 --- a/aead/src/variable.rs +++ b/aead/src/variable.rs @@ -10,7 +10,7 @@ use inout::InOutBuf; /// provided by the algorithm. /// #[allow(missing_docs, clippy::missing_errors_doc)] // TODO: fix -pub trait VariablAead: AeadCore { +pub trait VariableAead: AeadCore { /// Encrypt the data in the provided [`InOutBuf`] with variable size nonce, /// returning the authentication tag. /// diff --git a/aead/tests/dummy.rs b/aead/tests/dummy.rs index 784f9385d..d9c28a1a6 100644 --- a/aead/tests/dummy.rs +++ b/aead/tests/dummy.rs @@ -7,8 +7,8 @@ #![allow(clippy::unwrap_used, reason = "tests")] use aead::{ - Aead, AeadCore, Error, Key, KeyInit, KeySizeUser, Nonce, Result, Tag, TagPosition, VariablAead, - 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; @@ -142,7 +142,7 @@ impl AeadCore for PrefixDummyAead { } } -impl VariablAead for PrefixDummyAead {} +impl VariableAead for PrefixDummyAead {} #[derive(Debug)] pub struct PostfixDummyAead(DummyAead); @@ -182,7 +182,7 @@ impl AeadCore for PostfixDummyAead { } } -impl VariablAead for PostfixDummyAead {} +impl VariableAead for PostfixDummyAead {} #[cfg(feature = "alloc")] #[test] From 908b1888bd5954387bc9ebe70ff42606fe266ca6 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: Mon, 1 Jun 2026 18:08:50 +0300 Subject: [PATCH 20/20] add variable tag support --- aead/src/aead.rs | 8 +++---- aead/src/variable.rs | 50 +++++++++++++++++++++++++++----------------- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/aead/src/aead.rs b/aead/src/aead.rs index f7d262187..6c8341973 100644 --- a/aead/src/aead.rs +++ b/aead/src/aead.rs @@ -61,21 +61,21 @@ pub trait Aead { impl Aead for T { #[inline] fn encrypt_into_vec(&self, nonce: &[u8], aad: &[u8], plaintext: &[u8]) -> Result> { - self.encrypt_into_with_var_nonce(nonce, aad, plaintext, |n| alloc::vec![0u8; n]) + self.variable_encrypt_into(nonce, aad, plaintext, |n| alloc::vec![0u8; n]) } #[inline] fn decrypt_into_vec(&self, nonce: &[u8], aad: &[u8], ciphertext: &[u8]) -> Result> { - self.decrypt_into_with_var_nonce(nonce, aad, ciphertext, |n| alloc::vec![0u8; n]) + self.variable_decrypt_into(nonce, aad, ciphertext, |n| alloc::vec![0u8; n]) } #[inline] fn encrypt_within_vec(&self, nonce: &[u8], aad: &[u8], buf: &mut Vec) -> Result<()> { - self.encrypt_within_with_var_nonce(nonce, aad, buf, |buf, len| buf.resize(len, 0)) + self.variable_encrypt_within(nonce, aad, buf, |buf, len| buf.resize(len, 0)) } #[inline] fn decrypt_within_vec(&self, nonce: &[u8], aad: &[u8], buf: &mut Vec) -> Result<()> { - self.decrypt_within_with_var_nonce(nonce, aad, buf, Vec::truncate) + self.variable_decrypt_within(nonce, aad, buf, Vec::truncate) } } diff --git a/aead/src/variable.rs b/aead/src/variable.rs index 740078623..1bc1dc810 100644 --- a/aead/src/variable.rs +++ b/aead/src/variable.rs @@ -2,7 +2,7 @@ use crate::{AeadCore, Error, Result, Tag, utils}; use inout::InOutBuf; /// Functionality of Authenticated Encryption with Associated Data (AEAD) algorithms -/// with variable nonce size support. +/// with variable nonce and tag size support. /// ///
/// Some algorithms support very short nonces. Users should exercise extreme caution @@ -18,14 +18,17 @@ pub trait VariableAead: AeadCore { /// AEAD algorithm implementations may return an error if the plaintext or AAD are too long /// or an invalid nonce is used. #[inline] - fn encrypt_inout_with_var_nonce_detached( + fn variable_encrypt_inout_detached( &self, nonce: &[u8], associated_data: &[u8], buffer: InOutBuf<'_, '_, u8>, - ) -> Result> { + tag_dst: &mut [u8], + ) -> Result<()> { let nonce = nonce.try_into().map_err(|_| Error)?; - self.encrypt_inout_detached(nonce, associated_data, buffer) + let tag_dst: &mut Tag = tag_dst.try_into().map_err(|_| Error)?; + *tag_dst = self.encrypt_inout_detached(nonce, associated_data, buffer)?; + Ok(()) } /// Decrypt the data in the provided [`InOutBuf`] with variable size nonce, @@ -37,14 +40,15 @@ pub trait VariableAead: AeadCore { /// - if the `ciphertext` is too long /// - if the `aad` is too long #[inline] - fn decrypt_inout_with_var_nonce_detached( + fn variable_decrypt_inout_detached( &self, nonce: &[u8], associated_data: &[u8], buffer: InOutBuf<'_, '_, u8>, - tag: &Tag, + tag: &[u8], ) -> Result<()> { let nonce = nonce.try_into().map_err(|_| Error)?; + let tag = tag.try_into().map_err(|_| Error)?; self.decrypt_inout_detached(nonce, associated_data, buffer, tag) } @@ -55,14 +59,17 @@ pub trait VariableAead: AeadCore { /// 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 encrypt_with_var_nonce_detached( + fn variable_encrypt_detached( &self, nonce: &[u8], associated_data: &[u8], buf: &mut [u8], - ) -> Result> { + tag_dst: &mut [u8], + ) -> Result<()> { let nonce = nonce.try_into().map_err(|_| Error)?; - self.encrypt_inout_detached(nonce, associated_data, buf.into()) + let tag_dst: &mut Tag = tag_dst.try_into().map_err(|_| Error)?; + *tag_dst = self.encrypt_inout_detached(nonce, associated_data, buf.into())?; + Ok(()) } /// Decrypt the data in-place in the provided buffer, returning an error in the event the @@ -74,19 +81,20 @@ pub trait VariableAead: AeadCore { /// - if the `ciphertext` is too long /// - if the `aad` is too long #[inline] - fn decrypt_with_var_nonce_detached( + fn variable_decrypt_detached( &self, nonce: &[u8], associated_data: &[u8], buffer: &mut [u8], - tag: &Tag, + tag: &[u8], ) -> Result<()> { let nonce = nonce.try_into().map_err(|_| Error)?; + let tag = tag.try_into().map_err(|_| Error)?; self.decrypt_inout_detached(nonce, associated_data, buffer.into(), tag) } #[inline] - fn encrypt_into_with_var_nonce>( + fn variable_encrypt_into>( &self, nonce: &[u8], aad: &[u8], @@ -94,12 +102,14 @@ pub trait VariableAead: AeadCore { allocate: impl FnOnce(usize) -> B, ) -> Result { utils::encrypt_into::(plaintext, allocate, |buf| { - self.encrypt_inout_with_var_nonce_detached(nonce, aad, buf) + let mut tag = Tag::::default(); + self.variable_encrypt_inout_detached(nonce, aad, buf, &mut tag)?; + Ok(tag) }) } #[inline] - fn decrypt_into_with_var_nonce>( + fn variable_decrypt_into>( &self, nonce: &[u8], aad: &[u8], @@ -107,12 +117,12 @@ pub trait VariableAead: AeadCore { allocate: impl FnOnce(usize) -> B, ) -> Result { utils::decrypt_into::(ciphertext, allocate, |buf, tag| { - self.decrypt_inout_with_var_nonce_detached(nonce, aad, buf, tag) + self.variable_decrypt_inout_detached(nonce, aad, buf, tag) }) } #[inline] - fn encrypt_within_with_var_nonce>( + fn variable_encrypt_within>( &self, nonce: &[u8], aad: &[u8], @@ -120,12 +130,14 @@ pub trait VariableAead: AeadCore { extend: impl FnOnce(&mut B, usize), ) -> Result<()> { utils::encrypt_within::(buf, extend, |buf| { - self.encrypt_inout_with_var_nonce_detached(nonce, aad, buf) + let mut tag = Tag::::default(); + self.variable_encrypt_inout_detached(nonce, aad, buf, &mut tag)?; + Ok(tag) }) } #[inline] - fn decrypt_within_with_var_nonce>( + fn variable_decrypt_within>( &self, nonce: &[u8], aad: &[u8], @@ -133,7 +145,7 @@ pub trait VariableAead: AeadCore { truncate: impl FnOnce(&mut B, usize), ) -> Result<()> { utils::decrypt_within::(buf, truncate, |buf, tag| { - self.decrypt_inout_with_var_nonce_detached(nonce, aad, buf, tag) + self.variable_decrypt_inout_detached(nonce, aad, buf, tag) }) } }