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
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
@@ -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"]
215 changes: 106 additions & 109 deletions aead/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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};

@@ -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
@@ -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 {
@@ -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>,
@@ -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
@@ -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
@@ -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"" }
@@ -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
@@ -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};

@@ -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,
{
@@ -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,
{
@@ -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,
@@ -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,