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: diff --git a/Cargo.lock b/Cargo.lock index d5112ed9b..3f616e10b 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,17 +17,11 @@ 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" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base16ct" @@ -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" @@ -369,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" @@ -556,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", diff --git a/aead/Cargo.toml b/aead/Cargo.toml index c3937a96a..7e6c21716 100644 --- a/aead/Cargo.toml +++ b/aead/Cargo.toml @@ -10,25 +10,19 @@ 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"] +getrandom = ["common/getrandom", "rand_core"] rand_core = ["common/rand_core"] [lints] diff --git a/aead/src/aead.rs b/aead/src/aead.rs new file mode 100644 index 000000000..6c8341973 --- /dev/null +++ b/aead/src/aead.rs @@ -0,0 +1,81 @@ +use crate::{Result, VariableAead}; +use alloc::vec::Vec; + +/// High-level functionality of Authenticated Encryption with Associated Data (AEAD) algorithms. +pub trait Aead { + /// Encrypt the given plaintext payload, and return the resulting + /// ciphertext as a vector of bytes. + /// + /// # Errors + /// AEAD algorithm implementations may return an error if the plaintext or AAD are too long. + 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. + /// + /// # 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. + /// + /// On success, `buf` will contain the resulting ciphertext, + /// while on error it will be left intact. + /// + /// # 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; + Ok(()) + } + + /// Decrypt ciphertext in `buf` truncating it as necessary. + /// + /// 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 + #[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 { + Ok(pt) => { + *buf = pt; + Ok(()) + } + Err(err) => { + buf.fill(0); + Err(err) + } + } + } +} + +impl Aead for T { + #[inline] + fn encrypt_into_vec(&self, nonce: &[u8], aad: &[u8], plaintext: &[u8]) -> Result> { + 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.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.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.variable_decrypt_within(nonce, aad, buf, Vec::truncate) + } +} diff --git a/aead/src/dev.rs b/aead/src/dev.rs index 903c95c61..76df5f5ed 100644 --- a/aead/src/dev.rs +++ b/aead/src/dev.rs @@ -1,13 +1,8 @@ //! 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, AeadInOut, Payload, Tag, TagPosition, array::typenum::Unsigned, inout::InOutBuf, -}; pub use blobby; + +use crate::Aead; use common::KeyInit; /// AEAD test vector @@ -26,8 +21,11 @@ pub struct TestVector { } /// Run AEAD test for the provided passing test vector -#[allow(clippy::cast_possible_truncation)] -pub fn pass_test( +/// +/// # Errors +/// - 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 { key, nonce, @@ -36,76 +34,31 @@ 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 cipher: C = KeyInit::new_from_slice(key).map_err(|_| "failed to initialize the cipher")?; let res = cipher - .encrypt( - nonce, - Payload { - 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( - nonce, - Payload { - 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"); } - 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(()) } /// Run AEAD test for the provided failing test vector -pub fn fail_test( +/// +/// # Errors +/// - If the cipher has failed initialization with the provided key. +/// - If the cipher has passed the test vector. +pub fn fail_test( &TestVector { key, nonce, @@ -114,16 +67,9 @@ 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 cipher: C = KeyInit::new_from_slice(key).map_err(|_| "failed to initialize the cipher")?; - let res = cipher.decrypt( - nonce, - Payload { - aad, - msg: ciphertext, - }, - ); + let res = cipher.decrypt_into_vec(nonce, aad, ciphertext); if res.is_ok() { Err("decryption must return error") } else { @@ -134,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() { @@ -165,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/src/lib.rs b/aead/src/lib.rs index bc69494e7..607f3a269 100644 --- a/aead/src/lib.rs +++ b/aead/src/lib.rs @@ -13,45 +13,29 @@ extern crate alloc; #[cfg(feature = "dev")] pub mod dev; -pub use common::{ - self, Key, KeyInit, KeySizeUser, - array::{self, typenum::consts}, -}; +#[cfg(feature = "alloc")] +mod aead; +#[cfg(feature = "alloc")] +pub use aead::Aead; + +mod utils; +mod variable; + +pub use variable::VariableAead; -#[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}, + typenum::Unsigned, +}; +pub use inout::InOutBuf; -impl core::error::Error for Error {} +use array::{Array, ArraySize}; +use core::fmt; /// Nonce: single-use value for ensuring ciphertexts are unique. /// @@ -105,119 +89,20 @@ 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. +#[allow(missing_docs, clippy::missing_errors_doc)] // TODO: fix pub trait AeadCore { - /// The length of a nonce. + /// The recommended nonce length in bytes. type NonceSize: ArraySize; - /// The maximum length of the tag. + /// The recommended tag length 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: + /// The recommended AEAD tag position in resulting ciphertext. /// - /// ```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) - } -} + /// If tag position is not explicitly specified, we use postfix tags by default. + const TAG_POSITION: TagPosition; -/// 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,260 +130,115 @@ 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. + #[inline] + 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( + /// - if the `ciphertext` is too long + /// - if the `aad` is too long + #[inline] + fn decrypt_detached( &self, nonce: &Nonce, associated_data: &[u8], buffer: &mut [u8], tag: &Tag, - ) -> Result<()>; -} + ) -> Result<()> { + self.decrypt_inout_detached(nonce, associated_data, buffer.into(), tag) + } -#[allow(deprecated)] -impl AeadInPlace for T { - fn encrypt_in_place( + #[inline] + fn encrypt_into>( &self, nonce: &Nonce, - associated_data: &[u8], - buffer: &mut dyn Buffer, - ) -> Result<()> { - ::encrypt_in_place(self, nonce, associated_data, buffer) + aad: &[u8], + plaintext: &[u8], + allocate: impl FnOnce(usize) -> B, + ) -> Result { + utils::encrypt_into::(plaintext, allocate, |buf| { + self.encrypt_inout_detached(nonce, aad, buf) + }) } - fn encrypt_in_place_detached( + #[inline] + fn decrypt_into>( &self, nonce: &Nonce, - associated_data: &[u8], - buffer: &mut [u8], - ) -> Result> { - self.encrypt_inout_detached(nonce, associated_data, buffer.into()) + 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) + }) } - fn decrypt_in_place( + #[inline] + fn encrypt_within>( &self, nonce: &Nonce, - associated_data: &[u8], - buffer: &mut dyn Buffer, + aad: &[u8], + buf: &mut B, + extend: impl FnOnce(&mut B, usize), ) -> Result<()> { - ::decrypt_in_place(self, nonce, associated_data, buffer) + utils::encrypt_within::(buf, extend, |buf| { + self.encrypt_inout_detached(nonce, aad, buf) + }) } - fn decrypt_in_place_detached( + #[inline] + fn decrypt_within>( &self, nonce: &Nonce, - associated_data: &[u8], - buffer: &mut [u8], - tag: &Tag, + aad: &[u8], + buf: &mut B, + truncate: impl FnOnce(&mut B, usize), ) -> Result<()> { - self.decrypt_inout_detached(nonce, associated_data, buffer.into(), tag) + utils::decrypt_within::(buf, truncate, |buf, tag| { + self.decrypt_inout_detached(nonce, aad, buf, tag) + }) } } -/// 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"" } - } +/// 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, } -/// 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/src/utils.rs b/aead/src/utils.rs new file mode 100644 index 000000000..67108b6ec --- /dev/null +++ b/aead/src/utils.rs @@ -0,0 +1,127 @@ +#![allow(clippy::unwrap_in_result)] +use crate::{ + AeadCore, Error, Result, Tag, + TagPosition::{Postfix, Prefix}, +}; +use common::typenum::Unsigned; +use inout::InOutBuf; + +/// Allocate a buffer for ciphertext and encrypt `plaintext` into it. +#[inline(always)] +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) +} + +/// Allocate a buffer for plaintext and decrypt `ciphertext` into it. +#[inline(always)] +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) +} + +/// Encrypt plaintext inside the buffer extending it as necessary. +#[inline(always)] +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)?; + + let tag = encrypt(InOutBuf::from(buf.as_mut()))?; + + extend(buf, ct_len); + let buf = buf.as_mut(); + assert_eq!(buf.len(), ct_len); + let tag_dst = match C::TAG_POSITION { + Postfix => &mut buf[pt_len..], + Prefix => { + buf.copy_within(..pt_len, tag_len); + &mut buf[..tag_len] + } + }; + + tag_dst.copy_from_slice(&tag); + + Ok(()) +} + +/// Decrypt ciphertext inside the buffer truncating it as necessary. +#[inline(always)] +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: &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); + } + + truncate(buf, ct_len); + assert_eq!(buf.as_mut().len(), ct_len); + + Ok(()) +} diff --git a/aead/src/variable.rs b/aead/src/variable.rs new file mode 100644 index 000000000..1bc1dc810 --- /dev/null +++ b/aead/src/variable.rs @@ -0,0 +1,151 @@ +use crate::{AeadCore, Error, Result, Tag, utils}; +use inout::InOutBuf; + +/// Functionality of Authenticated Encryption with Associated Data (AEAD) algorithms +/// with variable nonce and tag 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 VariableAead: 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 variable_encrypt_inout_detached( + &self, + nonce: &[u8], + associated_data: &[u8], + buffer: InOutBuf<'_, '_, u8>, + tag_dst: &mut [u8], + ) -> Result<()> { + let nonce = nonce.try_into().map_err(|_| Error)?; + 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, + /// 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 variable_decrypt_inout_detached( + &self, + nonce: &[u8], + associated_data: &[u8], + buffer: InOutBuf<'_, '_, u8>, + 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) + } + + /// 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 variable_encrypt_detached( + &self, + nonce: &[u8], + associated_data: &[u8], + buf: &mut [u8], + tag_dst: &mut [u8], + ) -> Result<()> { + let nonce = nonce.try_into().map_err(|_| Error)?; + 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 + /// 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 variable_decrypt_detached( + &self, + nonce: &[u8], + associated_data: &[u8], + buffer: &mut [u8], + 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 variable_encrypt_into>( + &self, + nonce: &[u8], + aad: &[u8], + plaintext: &[u8], + allocate: impl FnOnce(usize) -> B, + ) -> Result { + utils::encrypt_into::(plaintext, allocate, |buf| { + let mut tag = Tag::::default(); + self.variable_encrypt_inout_detached(nonce, aad, buf, &mut tag)?; + Ok(tag) + }) + } + + #[inline] + fn variable_decrypt_into>( + &self, + nonce: &[u8], + aad: &[u8], + ciphertext: &[u8], + allocate: impl FnOnce(usize) -> B, + ) -> Result { + utils::decrypt_into::(ciphertext, allocate, |buf, tag| { + self.variable_decrypt_inout_detached(nonce, aad, buf, tag) + }) + } + + #[inline] + fn variable_encrypt_within>( + &self, + nonce: &[u8], + aad: &[u8], + buf: &mut B, + extend: impl FnOnce(&mut B, usize), + ) -> Result<()> { + utils::encrypt_within::(buf, extend, |buf| { + let mut tag = Tag::::default(); + self.variable_encrypt_inout_detached(nonce, aad, buf, &mut tag)?; + Ok(tag) + }) + } + + #[inline] + fn variable_decrypt_within>( + &self, + nonce: &[u8], + aad: &[u8], + buf: &mut B, + truncate: impl FnOnce(&mut B, usize), + ) -> Result<()> { + utils::decrypt_within::(buf, truncate, |buf, tag| { + self.variable_decrypt_inout_detached(nonce, aad, buf, tag) + }) + } +} 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 11cf14545..d9c28a1a6 100644 --- a/aead/tests/dummy.rs +++ b/aead/tests/dummy.rs @@ -7,8 +7,8 @@ #![allow(clippy::unwrap_used, reason = "tests")] use aead::{ - AeadCore, AeadInOut, Error, Key, KeyInit, KeySizeUser, Nonce, Result, Tag, TagPosition, - 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; @@ -121,9 +121,7 @@ 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 +142,8 @@ impl AeadInOut for PrefixDummyAead { } } +impl VariableAead for PrefixDummyAead {} + #[derive(Debug)] pub struct PostfixDummyAead(DummyAead); @@ -161,9 +161,7 @@ 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 VariableAead for PostfixDummyAead {} + +#[cfg(feature = "alloc")] +#[test] +fn dyn_compat() { + let key = &[0u8; 8].into(); + let c1 = PrefixDummyAead::new(key); + let c2 = PostfixDummyAead::new(key); + + fn take_dyn_aead(_: &dyn Aead) {} + + take_dyn_aead(&c1); + take_dyn_aead(&c2); +} + #[cfg(feature = "dev")] 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); }