Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aead: add AeadCore::TAG_POSITION and move AeadInPlace methods to AeadInOut #1798

Merged
merged 11 commits into from
Mar 21, 2025
Merged
4 changes: 2 additions & 2 deletions aead/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ such as AES-GCM as ChaCha20Poly1305, which provide a high-level API

[dependencies]
crypto-common = { version = "0.2.0-rc.1", path = "../crypto-common" }
inout = "0.2.0-rc.4"

# optional dependencies
arrayvec = { version = "0.7", optional = true, default-features = false }
blobby = { version = "0.4.0-pre.0", optional = true }
bytes = { version = "1", optional = true, default-features = false }
heapless = { version = "0.8", optional = true, default-features = false }
inout = { version = "0.2.0-rc.4", optional = true, default-features = false }

[features]
default = ["inout", "rand_core"]
default = ["rand_core"]
alloc = []
dev = ["blobby"]
os_rng = ["crypto-common/os_rng", "rand_core"]
Expand Down
222 changes: 110 additions & 112 deletions aead/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,18 @@ pub use bytes;
pub use crypto_common::rand_core;
#[cfg(feature = "heapless")]
pub use heapless;
#[cfg(feature = "inout")]
pub use inout;

use core::fmt;
use crypto_common::array::{Array, ArraySize};
use crypto_common::array::{Array, ArraySize, typenum::Unsigned};
use inout::InOutBuf;

#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "bytes")]
use bytes::BytesMut;
#[cfg(any(feature = "alloc", feature = "inout"))]
use crypto_common::array::typenum::Unsigned;
#[cfg(feature = "os_rng")]
use crypto_common::rand_core::{OsError, OsRng, TryRngCore};
#[cfg(feature = "inout")]
use inout::InOutBuf;
#[cfg(feature = "rand_core")]
use rand_core::{CryptoRng, TryCryptoRng};

Expand Down Expand Up @@ -77,17 +73,26 @@ pub type Nonce<A> = Array<u8, <A as AeadCore>::NonceSize>;
/// Tag: authentication code which ensures ciphertexts are authentic
pub type Tag<A> = Array<u8, <A as AeadCore>::TagSize>;

/// Authenticated Encryption with Associated Data (AEAD) algorithm core trait.
///
/// Defines nonce, tag, and overhead sizes that are consumed by various other
/// `Aead*` traits.
/// 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.
pub trait AeadCore {
/// The length of a nonce.
type NonceSize: ArraySize;

/// The maximum length of the tag.
type TagSize: ArraySize;

/// The AEAD tag position.
const TAG_POSITION: TagPosition;

/// Generate a random nonce for this AEAD algorithm.
///
/// AEAD algorithms accept a parameter to encryption/decryption called
Expand Down Expand Up @@ -155,66 +160,30 @@ pub trait AeadCore {
}
}

/// 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.
fn encrypt<'msg, 'aad>(
/// In-place 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.
fn encrypt_inout_detached(
&self,
nonce: &Nonce<Self>,
plaintext: impl Into<Payload<'msg, 'aad>>,
) -> Result<Vec<u8>>;
associated_data: &[u8],
buffer: InOutBuf<'_, '_, u8>,
) -> Result<Tag<Self>>;

/// 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.
fn decrypt<'msg, 'aad>(
/// Decrypt the data in the provided [`InOutBuf`], returning an error in the event the
/// provided authentication tag is invalid for the given ciphertext (i.e. ciphertext
/// is modified/unauthentic)
fn decrypt_inout_detached(
&self,
nonce: &Nonce<Self>,
ciphertext: impl Into<Payload<'msg, 'aad>>,
) -> Result<Vec<u8>>;
associated_data: &[u8],
buffer: InOutBuf<'_, '_, u8>,
tag: &Tag<Self>,
) -> Result<()>;
}

/// In-place AEAD trait.
///
/// This trait is both object safe and has no dependencies on `alloc` or `std`.
pub trait AeadInPlace: AeadCore {
/// Authenticated Encryption with Associated Data (AEAD) algorithm.
pub trait Aead: AeadCore {
/// Encrypt the given buffer containing a plaintext message in-place.
///
/// The buffer must have sufficient capacity to store the ciphertext
Expand Down Expand Up @@ -242,39 +211,31 @@ pub trait AeadInPlace: AeadCore {
associated_data: &[u8],
buffer: &mut dyn Buffer,
) -> Result<()>;
}

/// In-place AEAD trait which handles the authentication tag as a return value/separate parameter.
#[cfg(feature = "inout")]
pub trait AeadInOut: AeadCore {
/// Encrypt the data in the provided [`InOutBuf`], returning the authentication tag.
fn encrypt_inout_detached(
&self,
nonce: &Nonce<Self>,
associated_data: &[u8],
buffer: InOutBuf<'_, '_, u8>,
) -> Result<Tag<Self>>;

/// Decrypt the data in the provided [`InOutBuf`], returning an error in the event the
/// provided authentication tag is invalid for the given ciphertext (i.e. ciphertext
/// is modified/unauthentic)
fn decrypt_inout_detached(
&self,
nonce: &Nonce<Self>,
associated_data: &[u8],
buffer: InOutBuf<'_, '_, u8>,
tag: &Tag<Self>,
) -> Result<()>;
}

/// Marker trait for AEAD algorithms which append the authentication tag to the end of the
/// ciphertext message.
///
/// This is the common convention for AEAD algorithms.
pub trait PostfixTagged {}

#[cfg(feature = "alloc")]
impl<Alg: AeadInPlace> Aead for Alg {
/// 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.
#[cfg(feature = "alloc")]
fn encrypt<'msg, 'aad>(
&self,
nonce: &Nonce<Self>,
Expand All @@ -287,6 +248,24 @@ impl<Alg: AeadInPlace> Aead for Alg {
Ok(buffer)
}

/// 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.
#[cfg(feature = "alloc")]
fn decrypt<'msg, 'aad>(
&self,
nonce: &Nonce<Self>,
Expand All @@ -299,16 +278,30 @@ impl<Alg: AeadInPlace> Aead for Alg {
}
}

#[cfg(feature = "inout")]
impl<T: AeadInOut + PostfixTagged> AeadInPlace for T {
impl<T: AeadInOut> Aead for T {
fn encrypt_in_place(
&self,
nonce: &Nonce<Self>,
associated_data: &[u8],
buffer: &mut dyn Buffer,
) -> Result<()> {
let tag = self.encrypt_inout_detached(nonce, associated_data, buffer.as_mut().into())?;
buffer.extend_from_slice(tag.as_slice())?;
match Self::TAG_POSITION {
TagPosition::Prefix => {
let msg_len = buffer.len();
buffer.extend_from_slice(&Tag::<Self>::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(())
}

Expand All @@ -318,16 +311,23 @@ impl<T: AeadInOut + PostfixTagged> AeadInPlace for T {
associated_data: &[u8],
buffer: &mut dyn Buffer,
) -> Result<()> {
let tag_pos = buffer
.len()
.checked_sub(Self::TagSize::to_usize())
.ok_or(Error)?;

let (msg, tag) = buffer.as_mut().split_at_mut(tag_pos);
let tag = Tag::<Self>::try_from(&*tag).expect("tag length mismatch");

self.decrypt_inout_detached(nonce, associated_data, msg.into(), &tag)?;
buffer.truncate(tag_pos);
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 (msg, tag) = buffer.as_mut().split_at_mut(tag_size);
let tag = Tag::<Self>::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::<Self>::try_from(&*tag).expect("tag length mismatch");
self.decrypt_inout_detached(nonce, associated_data, msg.into(), &tag)?;
}
}
buffer.truncate(tagless_len);
Ok(())
}
}
Expand All @@ -340,7 +340,6 @@ impl<T: AeadInOut + PostfixTagged> AeadInPlace for T {
///
/// 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.
#[cfg(feature = "alloc")]
#[derive(Debug)]
pub struct Payload<'msg, 'aad> {
/// Message to be encrypted/decrypted
Expand All @@ -353,7 +352,6 @@ pub struct Payload<'msg, 'aad> {
pub aad: &'aad [u8],
}

#[cfg(feature = "alloc")]
impl<'msg> From<&'msg [u8]> for Payload<'msg, '_> {
fn from(msg: &'msg [u8]) -> Self {
Self { msg, aad: b"" }
Expand Down Expand Up @@ -442,5 +440,5 @@ mod tests {

/// Ensure that `AeadInPlace` is object-safe
#[allow(dead_code)]
type DynAeadInPlace<N, T> = dyn AeadInPlace<NonceSize = N, TagSize = T>;
type DynAeadInPlace<N, T> = dyn Aead<NonceSize = N, TagSize = T>;
}
10 changes: 5 additions & 5 deletions aead/src/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

#![allow(clippy::upper_case_acronyms)]

use crate::{AeadCore, AeadInPlace, Buffer, Error, Key, KeyInit, Result};
use crate::{Aead, AeadCore, Buffer, Error, Key, KeyInit, Result};
use core::ops::{AddAssign, Sub};
use crypto_common::array::{Array, ArraySize};

Expand All @@ -24,7 +24,7 @@ pub type NonceSize<A, S> =
/// Create a new STREAM from the provided AEAD.
pub trait NewStream<A>: StreamPrimitive<A>
where
A: AeadInPlace,
A: Aead,
A::NonceSize: Sub<Self::NonceOverhead>,
NonceSize<A, Self>: ArraySize,
{
Expand All @@ -49,7 +49,7 @@ where
/// Deliberately immutable and stateless to permit parallel operation.
pub trait StreamPrimitive<A>
where
A: AeadInPlace,
A: Aead,
A::NonceSize: Sub<Self::NonceOverhead>,
NonceSize<A, Self>: ArraySize,
{
Expand Down Expand Up @@ -157,7 +157,7 @@ macro_rules! impl_stream_object {
#[derive(Debug)]
pub struct $name<A, S>
where
A: AeadInPlace,
A: Aead,
S: StreamPrimitive<A>,
A::NonceSize: Sub<<S as StreamPrimitive<A>>::NonceOverhead>,
NonceSize<A, S>: ArraySize,
Expand All @@ -171,7 +171,7 @@ macro_rules! impl_stream_object {

impl<A, S> $name<A, S>
where
A: AeadInPlace,
A: Aead,
S: StreamPrimitive<A>,
A::NonceSize: Sub<<S as StreamPrimitive<A>>::NonceOverhead>,
NonceSize<A, S>: ArraySize,
Expand Down