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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
215 changes: 106 additions & 109 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,6 +160,94 @@ pub trait AeadCore {
}
}

/// 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.
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<()>;

/// 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.
///
/// Returns an error if the buffer has insufficient capacity to store the
/// resulting ciphertext message.
fn encrypt_in_place(
&self,
nonce: &Nonce<Self>,
associated_data: &[u8],
buffer: &mut dyn Buffer,
) -> Result<()> {
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(())
}

/// 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.
fn decrypt_in_place(
&self,
nonce: &Nonce<Self>,
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::<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(())
}
}

/// Authenticated Encryption with Associated Data (AEAD) algorithm.
#[cfg(feature = "alloc")]
pub trait Aead: AeadCore {
Expand Down Expand Up @@ -211,70 +304,8 @@ pub trait Aead: AeadCore {
) -> Result<Vec<u8>>;
}

/// In-place AEAD trait.
///
/// This trait is both object safe and has no dependencies on `alloc` or `std`.
pub trait AeadInPlace: AeadCore {
/// 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.
///
/// Returns an error if the buffer has insufficient capacity to store the
/// resulting ciphertext message.
fn encrypt_in_place(
&self,
nonce: &Nonce<Self>,
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.
///
/// The buffer will be truncated to the length of the original plaintext
/// message upon success.
fn decrypt_in_place(
&self,
nonce: &Nonce<Self>,
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 {
impl<T: AeadInOut> Aead for T {
fn encrypt<'msg, 'aad>(
&self,
nonce: &Nonce<Self>,
Expand All @@ -299,39 +330,6 @@ impl<Alg: AeadInPlace> Aead for Alg {
}
}

#[cfg(feature = "inout")]
impl<T: AeadInOut + PostfixTagged> AeadInPlace 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())?;
Ok(())
}

fn decrypt_in_place(
&self,
nonce: &Nonce<Self>,
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);
Ok(())
}
}

/// AEAD payloads (message + AAD).
///
/// Combination of a message (plaintext or ciphertext) and
Expand All @@ -340,7 +338,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 +350,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 @@ -436,11 +432,12 @@ impl<const N: usize> Buffer for heapless::Vec<u8, N> {
}
}

#[cfg(feature = "alloc")]
#[cfg(test)]
mod tests {
use super::*;

/// 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::{AeadCore, AeadInOut, 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: AeadInOut,
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: AeadInOut,
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: AeadInOut,
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: AeadInOut,
S: StreamPrimitive<A>,
A::NonceSize: Sub<<S as StreamPrimitive<A>>::NonceOverhead>,
NonceSize<A, S>: ArraySize,
Expand Down