Skip to content

Commit

Permalink
aead: rework traits
Browse files Browse the repository at this point in the history
  • Loading branch information
newpavlov committed Nov 18, 2024
1 parent 8bb3381 commit 0a483d1
Show file tree
Hide file tree
Showing 6 changed files with 624 additions and 363 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions aead/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ rust-version = "1.81"

[dependencies]
crypto-common = "0.2.0-rc.0"
inout = "0.2.0-rc.1"

# optional dependencies
arrayvec = { version = "0.7", optional = true, default-features = false }
Expand Down
38 changes: 36 additions & 2 deletions aead/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,43 @@ able to execute [chosen-ciphertext attacks]. The resulting security property,
[ciphertext indistinguishability], is considered a basic requirement for
modern cryptographic implementations.

See [RustCrypto/AEADs] for cipher implementations which use this trait.
See [RustCrypto/AEADs] for cipher implementations which implement traits from
this crate.

[Documentation][docs-link]
## Nonces: ⚠️ Security Warning ⚠️

AEAD algorithms accept a parameter to encryption/decryption called
a "nonce" which must be unique every time encryption is performed and
never repeated for the same key. The nonce is often prepended to the
ciphertext. The nonce used to produce a given ciphertext must be passed
to the decryption function in order for it to decrypt correctly.

AEAD algorithms often fail catastrophically if nonces are ever repeated
for the same key (with SIV modes being a "misuse-resistent" exception).

Nonces don't necessarily have to be random, but it is one strategy
which is often used in practice.

Using random nonces runs the risk of repeating them unless the nonce
size is particularly large, e.g. 192-bit extended nonces used by the
`XChaCha20Poly1305` and `XSalsa20Poly1305` constructions.

[NIST SP 800-38D] recommends the following for 128-bit nonces:

> The total number of invocations of the authenticated encryption
> function shall not exceed 2^32, including all IV lengths and all
> instances of the authenticated encryption function with the given key.
Following this guideline, only 4,294,967,296 messages with random
nonces can be encrypted under a given key. While this bound is high,
it's possible to encounter in practice, and systems which might
reach it should consider alternatives to purely random nonces, like
a counter or a combination of a random nonce + counter.

See the [`aead-stream`] crate for a ready-made implementation of the latter.

[NIST SP 800-38D]: https://csrc.nist.gov/publications/detail/sp/800-38d/final
[`aead-stream`]: https://docs.rs/aead-stream

## Minimum Supported Rust Version

Expand Down
201 changes: 201 additions & 0 deletions aead/src/dyn_aead.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
use inout::{InOutBuf, InOutBufReserved};

use crate::{Aead, Buffer, Error, Result};

#[cfg(feature = "alloc")]
use alloc::vec::Vec;

mod sealed {
pub trait Sealed {}
}

/// Object-safe variant of the [`Aead`] trait.
///
/// This trait is implemented automaticlly for all types which implement the [`Aead`] trait.
pub trait DynAead: sealed::Sealed {
fn postfix_encrypt_inout<'out>(
&self,
nonce: &[u8],
associated_data: &[u8],
buffer: InOutBufReserved<'_, 'out, u8>,
) -> Result<&'out mut [u8]>;

fn postfix_decrypt_inout<'out>(
&self,
nonce: &[u8],
associated_data: &[u8],
buffer: InOutBuf<'_, 'out, u8>,
) -> Result<&'out mut [u8]>;

fn postfix_encrypt_inplace<'out>(
&self,
nonce: &[u8],
associated_data: &[u8],
buffer: &'out mut [u8],
plaintext_len: usize,
) -> Result<&'out mut [u8]>;

fn postfix_decrypt_inplace<'out>(
&self,
nonce: &[u8],
associated_data: &[u8],
buffer: &'out mut [u8],
) -> Result<&'out mut [u8]>;

fn postfix_encrypt_to_buf<'out>(
&self,
nonce: &[u8],
associated_data: &[u8],
plaintext: &[u8],
buffer: &'out mut [u8],
) -> Result<&'out mut [u8]>;

fn postfix_decrypt_to_buf<'out>(
&self,
nonce: &[u8],
associated_data: &[u8],
ciphertext: &[u8],
buffer: &'out mut [u8],
) -> Result<&'out mut [u8]>;

fn encrypt_to_buffer2(
&self,
nonce: &[u8],
associated_data: &[u8],
plaintext: &[u8],
buffer: &mut dyn Buffer,
) -> Result<()>;

fn decrypt_to_buffer2(
&self,
nonce: &[u8],
associated_data: &[u8],
ciphertext: &[u8],
buffer: &mut dyn Buffer,
) -> Result<()>;

#[cfg(feature = "alloc")]
fn encrypt_to_vec(
&self,
nonce: &[u8],
associated_data: &[u8],
plaintext: &[u8],
) -> Result<Vec<u8>>;

#[cfg(feature = "alloc")]
fn decrypt_to_vec(
&self,
nonce: &[u8],
associated_data: &[u8],
ciphertext: &[u8],
) -> Result<Vec<u8>>;
}

impl<T: Aead> sealed::Sealed for T {}

impl<T: Aead> DynAead for T {
fn postfix_encrypt_inout<'out>(
&self,
nonce: &[u8],
associated_data: &[u8],
buffer: InOutBufReserved<'_, 'out, u8>,
) -> Result<&'out mut [u8]> {
let nonce = nonce.try_into().map_err(|_| Error)?;
Aead::postfix_encrypt_inout(self, nonce, associated_data, buffer)
}

fn postfix_decrypt_inout<'out>(
&self,
nonce: &[u8],
associated_data: &[u8],
buffer: InOutBuf<'_, 'out, u8>,
) -> Result<&'out mut [u8]> {
let nonce = nonce.try_into().map_err(|_| Error)?;
Aead::postfix_decrypt_inout(self, nonce, associated_data, buffer)
}

fn postfix_encrypt_inplace<'out>(
&self,
nonce: &[u8],
associated_data: &[u8],
buffer: &'out mut [u8],
plaintext_len: usize,
) -> Result<&'out mut [u8]> {
let nonce = nonce.try_into().map_err(|_| Error)?;
Aead::postfix_encrypt_inplace(self, nonce, associated_data, buffer, plaintext_len)
}

fn postfix_decrypt_inplace<'out>(
&self,
nonce: &[u8],
associated_data: &[u8],
buffer: &'out mut [u8],
) -> Result<&'out mut [u8]> {
let nonce = nonce.try_into().map_err(|_| Error)?;
Aead::postfix_decrypt_inplace(self, nonce, associated_data, buffer)
}

fn postfix_encrypt_to_buf<'out>(
&self,
nonce: &[u8],
associated_data: &[u8],
plaintext: &[u8],
buffer: &'out mut [u8],
) -> Result<&'out mut [u8]> {
let nonce = nonce.try_into().map_err(|_| Error)?;
Aead::postfix_encrypt_to_buf(self, nonce, associated_data, plaintext, buffer)
}

fn postfix_decrypt_to_buf<'out>(
&self,
nonce: &[u8],
associated_data: &[u8],
ciphertext: &[u8],
buffer: &'out mut [u8],
) -> Result<&'out mut [u8]> {
let nonce = nonce.try_into().map_err(|_| Error)?;
Aead::postfix_decrypt_to_buf(self, nonce, associated_data, ciphertext, buffer)
}

fn encrypt_to_buffer2(
&self,
nonce: &[u8],
aad: &[u8],
msg: &[u8],
buffer: &mut dyn Buffer,
) -> Result<()> {
let nonce = nonce.try_into().map_err(|_| Error)?;
let payload = crate::Payload { aad, msg };
Aead::encrypt_to_buffer(self, nonce, payload, buffer)
}

fn decrypt_to_buffer2(
&self,
nonce: &[u8],
aad: &[u8],
msg: &[u8],
buffer: &mut dyn Buffer,
) -> Result<()> {
let nonce = nonce.try_into().map_err(|_| Error)?;
let payload = crate::Payload { aad, msg };
Aead::decrypt_to_buffer(self, nonce, payload, buffer)
}

#[cfg(feature = "alloc")]
fn encrypt_to_vec(&self, nonce: &[u8], aad: &[u8], msg: &[u8]) -> Result<Vec<u8>> {
let nonce = nonce.try_into().map_err(|_| Error)?;
let payload = crate::Payload { aad, msg };
Aead::encrypt_to_vec(self, nonce, payload)
}

#[cfg(feature = "alloc")]
fn decrypt_to_vec(&self, nonce: &[u8], aad: &[u8], msg: &[u8]) -> Result<Vec<u8>> {
let nonce = nonce.try_into().map_err(|_| Error)?;
let payload = crate::Payload { aad, msg };
Aead::decrypt_to_vec(self, nonce, payload)
}
}

// Ensure that `DynAead` is an object-safe trait
#[allow(dead_code)]
fn foo(_: &dyn DynAead) {}
Loading

0 comments on commit 0a483d1

Please sign in to comment.