diff --git a/aead/src/lib.rs b/aead/src/lib.rs index e72170ba7..279937703 100644 --- a/aead/src/lib.rs +++ b/aead/src/lib.rs @@ -36,7 +36,10 @@ use generic_array::{typenum::Unsigned, ArrayLength, GenericArray}; #[cfg(feature = "alloc")] use alloc::vec::Vec; -/// Error type +/// 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; @@ -49,25 +52,6 @@ impl fmt::Display for Error { #[cfg(feature = "std")] impl std::error::Error for Error {} -/// Implement the `decrypt_in_place` method on `Aead` and `AeadMut`. -/// Uses a macro to gloss over `&self` vs `&mut self`. -/// -/// Assumes a postfix authentication tag. AEAD ciphers which do not use a -/// postfix authentication tag will need to define their own implementation. -macro_rules! impl_decrypt_in_place { - ($aead:expr, $nonce:expr, $aad:expr, $buffer:expr) => {{ - if $buffer.len() < Self::TagSize::to_usize() { - return Err(Error); - } - - let tag_pos = $buffer.len() - Self::TagSize::to_usize(); - let (msg, tag) = $buffer.as_mut().split_at_mut(tag_pos); - $aead.decrypt_in_place_detached($nonce, $aad, msg, Tag::from_slice(tag))?; - $buffer.truncate(tag_pos); - Ok(()) - }}; -} - /// Key for a [`NewAead`] algorithm // TODO(tarcieri): make this a struct and zeroize on drop? pub type Key = GenericArray::KeySize>; @@ -91,6 +75,8 @@ pub trait NewAead { /// /// This trait is intended for use with stateless AEAD algorithms. The /// [`AeadMut`] trait provides a stateful interface. +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub trait Aead { /// The length of a nonce. type NonceSize: ArrayLength; @@ -125,19 +111,105 @@ pub trait Aead { /// 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")] - #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] fn encrypt<'msg, 'aad>( &self, nonce: &Nonce, plaintext: impl Into>, - ) -> Result, Error> { - 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) - } + ) -> Result, Error>; + + /// 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>( + &self, + nonce: &Nonce, + ciphertext: impl Into>, + ) -> Result, Error>; +} + +/// Stateful Authenticated Encryption with Associated Data algorithm. +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +pub trait AeadMut { + /// The length of a nonce. + type NonceSize: ArrayLength; + + /// The maximum length of the nonce. + type TagSize: ArrayLength; + + /// The upper bound amount of additional space required to support a + /// ciphertext vs. a plaintext. + type CiphertextOverhead: ArrayLength + Unsigned; + + /// Encrypt the given plaintext slice, and return the resulting ciphertext + /// as a vector of bytes. + /// + /// See notes on [`Aead::encrypt()`] about allowable message payloads and + /// Associated Additional Data (AAD). + fn encrypt<'msg, 'aad>( + &mut self, + nonce: &Nonce, + plaintext: impl Into>, + ) -> Result, Error>; + + /// Decrypt the given ciphertext slice, and return the resulting plaintext + /// as a vector of bytes. + /// + /// See notes on [`Aead::encrypt()`] and [`Aead::decrypt()`] about allowable + /// message payloads and Associated Additional Data (AAD). + fn decrypt<'msg, 'aad>( + &mut self, + nonce: &Nonce, + ciphertext: impl Into>, + ) -> Result, Error>; +} + +/// Implement the `decrypt_in_place` method on [`AeadInPlace`] and +/// [`AeadMutInPlace]`, using a macro to gloss over the `&self` vs `&mut self`. +/// +/// Assumes a postfix authentication tag. AEAD ciphers which do not use a +/// postfix authentication tag will need to define their own implementation. +macro_rules! impl_decrypt_in_place { + ($aead:expr, $nonce:expr, $aad:expr, $buffer:expr) => {{ + if $buffer.len() < Self::TagSize::to_usize() { + return Err(Error); + } + + let tag_pos = $buffer.len() - Self::TagSize::to_usize(); + let (msg, tag) = $buffer.as_mut().split_at_mut(tag_pos); + $aead.decrypt_in_place_detached($nonce, $aad, msg, Tag::from_slice(tag))?; + $buffer.truncate(tag_pos); + Ok(()) + }}; +} + +/// In-place stateless AEAD trait. +/// +/// This trait is both object safe and has no dependencies on `alloc` or `std`. +pub trait AeadInPlace { + /// The length of a nonce. + type NonceSize: ArrayLength; + + /// The maximum length of the nonce. + type TagSize: ArrayLength; + + /// The upper bound amount of additional space required to support a + /// ciphertext vs. a plaintext. + type CiphertextOverhead: ArrayLength + Unsigned; /// Encrypt the given buffer containing a plaintext message in-place. /// @@ -152,7 +224,7 @@ pub trait Aead { &self, nonce: &Nonce, associated_data: &[u8], - buffer: &mut impl Buffer, + buffer: &mut dyn Buffer, ) -> Result<(), Error> { let tag = self.encrypt_in_place_detached(nonce, associated_data, buffer.as_mut())?; buffer.extend_from_slice(tag.as_slice())?; @@ -165,37 +237,7 @@ pub trait Aead { nonce: &Nonce, associated_data: &[u8], buffer: &mut [u8], - ) -> Result, Error>; - - /// 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")] - #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - fn decrypt<'msg, 'aad>( - &self, - nonce: &Nonce, - ciphertext: impl Into>, - ) -> Result, Error> { - let payload = ciphertext.into(); - let mut buffer = Vec::from(payload.msg); - self.decrypt_in_place(nonce, payload.aad, &mut buffer)?; - Ok(buffer) - } + ) -> Result, Error>; /// Decrypt the message in-place, returning an error in the event the /// provided authentication tag does not match the given ciphertext. @@ -206,7 +248,7 @@ pub trait Aead { &self, nonce: &Nonce, associated_data: &[u8], - buffer: &mut impl Buffer, + buffer: &mut dyn Buffer, ) -> Result<(), Error> { impl_decrypt_in_place!(self, nonce, associated_data, buffer) } @@ -219,12 +261,14 @@ pub trait Aead { nonce: &Nonce, associated_data: &[u8], buffer: &mut [u8], - tag: &Tag, + tag: &Tag, ) -> Result<(), Error>; } -/// Stateful Authenticated Encryption with Associated Data algorithm. -pub trait AeadMut { +/// In-place stateful AEAD trait. +/// +/// This trait is both object safe and has no dependencies on `alloc` or `std`. +pub trait AeadMutInPlace { /// The length of a nonce. type NonceSize: ArrayLength; @@ -235,25 +279,6 @@ pub trait AeadMut { /// ciphertext vs. a plaintext. type CiphertextOverhead: ArrayLength + Unsigned; - /// Encrypt the given plaintext slice, and return the resulting ciphertext - /// as a vector of bytes. - /// - /// See notes on [`Aead::encrypt()`] about allowable message payloads and - /// Associated Additional Data (AAD). - #[cfg(feature = "alloc")] - #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - fn encrypt<'msg, 'aad>( - &mut self, - nonce: &Nonce, - plaintext: impl Into>, - ) -> Result, Error> { - 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) - } - /// Encrypt the given buffer containing a plaintext message in-place. /// /// The buffer must have sufficient capacity to store the ciphertext @@ -280,25 +305,7 @@ pub trait AeadMut { nonce: &Nonce, associated_data: &[u8], buffer: &mut [u8], - ) -> Result, Error>; - - /// Decrypt the given ciphertext slice, and return the resulting plaintext - /// as a vector of bytes. - /// - /// See notes on [`Aead::encrypt()`] and [`Aead::decrypt()`] about allowable - /// message payloads and Associated Additional Data (AAD). - #[cfg(feature = "alloc")] - #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - fn decrypt<'msg, 'aad>( - &mut self, - nonce: &Nonce, - ciphertext: impl Into>, - ) -> Result, Error> { - let payload = ciphertext.into(); - let mut buffer = Vec::from(payload.msg); - self.decrypt_in_place(nonce, payload.aad, &mut buffer)?; - Ok(buffer) - } + ) -> Result, Error>; /// Decrypt the message in-place, returning an error in the event the /// provided authentication tag does not match the given ciphertext. @@ -322,81 +329,110 @@ pub trait AeadMut { nonce: &Nonce, associated_data: &[u8], buffer: &mut [u8], - tag: &Tag, + tag: &Tag, ) -> Result<(), Error>; } -/// A blanket implementation of the Stateful AEAD interface for Stateless -/// AEAD implementations. -impl AeadMut for Algo { - type NonceSize = Algo::NonceSize; - type TagSize = Algo::TagSize; - type CiphertextOverhead = Algo::CiphertextOverhead; +#[cfg(feature = "alloc")] +impl Aead for Alg { + type NonceSize = Alg::NonceSize; + type TagSize = Alg::TagSize; + type CiphertextOverhead = Alg::CiphertextOverhead; + + fn encrypt<'msg, 'aad>( + &self, + nonce: &Nonce, + plaintext: impl Into>, + ) -> Result, Error> { + 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, Error> { + let payload = ciphertext.into(); + let mut buffer = Vec::from(payload.msg); + self.decrypt_in_place(nonce, payload.aad, &mut buffer)?; + Ok(buffer) + } +} + +#[cfg(feature = "alloc")] +impl AeadMut for Alg { + type NonceSize = Alg::NonceSize; + type TagSize = Alg::TagSize; + type CiphertextOverhead = Alg::CiphertextOverhead; - /// Encrypt the given plaintext slice, and return the resulting ciphertext - /// as a vector of bytes. - #[cfg(feature = "alloc")] fn encrypt<'msg, 'aad>( &mut self, nonce: &Nonce, plaintext: impl Into>, ) -> Result, Error> { - ::encrypt(self, nonce, plaintext) + 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) } - /// Encrypt the given buffer containing a plaintext message in-place. + fn decrypt<'msg, 'aad>( + &mut self, + nonce: &Nonce, + ciphertext: impl Into>, + ) -> Result, Error> { + let payload = ciphertext.into(); + let mut buffer = Vec::from(payload.msg); + self.decrypt_in_place(nonce, payload.aad, &mut buffer)?; + Ok(buffer) + } +} + +impl AeadMutInPlace for Alg { + type NonceSize = Alg::NonceSize; + type TagSize = Alg::TagSize; + type CiphertextOverhead = Alg::CiphertextOverhead; + fn encrypt_in_place( &mut self, nonce: &Nonce, associated_data: &[u8], buffer: &mut impl Buffer, ) -> Result<(), Error> { - ::encrypt_in_place(self, nonce, associated_data, buffer) + ::encrypt_in_place(self, nonce, associated_data, buffer) } - /// Encrypt the data in-place, returning the authentication tag fn encrypt_in_place_detached( &mut self, nonce: &Nonce, associated_data: &[u8], buffer: &mut [u8], - ) -> Result, Error> { - ::encrypt_in_place_detached(self, nonce, associated_data, buffer) - } - - /// Decrypt the given ciphertext slice, and return the resulting plaintext - /// as a vector of bytes. - #[cfg(feature = "alloc")] - fn decrypt<'msg, 'aad>( - &mut self, - nonce: &Nonce, - ciphertext: impl Into>, - ) -> Result, Error> { - ::decrypt(self, nonce, ciphertext) + ) -> Result, Error> { + ::encrypt_in_place_detached(self, nonce, associated_data, buffer) } - /// Decrypt the message in-place, returning an error in the event the - /// provided authentication tag does not match the given ciphertext. fn decrypt_in_place( &mut self, nonce: &Nonce, associated_data: &[u8], buffer: &mut impl Buffer, ) -> Result<(), Error> { - ::decrypt_in_place(self, nonce, associated_data, buffer) + ::decrypt_in_place(self, nonce, associated_data, buffer) } - /// Decrypt the data in-place, returning an error in the event the provided - /// authentication tag does not match the given ciphertext (i.e. ciphertext - /// is modified/unauthentic) fn decrypt_in_place_detached( &mut self, nonce: &Nonce, associated_data: &[u8], buffer: &mut [u8], - tag: &Tag, + tag: &Tag, ) -> Result<(), Error> { - ::decrypt_in_place_detached(self, nonce, associated_data, buffer, tag) + ::decrypt_in_place_detached(self, nonce, associated_data, buffer, tag) } } @@ -406,6 +442,8 @@ impl AeadMut for Algo { /// /// 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")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub struct Payload<'msg, 'aad> { /// Message to be encrypted/decrypted pub msg: &'msg [u8], @@ -417,6 +455,7 @@ pub struct Payload<'msg, 'aad> { pub aad: &'aad [u8], } +#[cfg(feature = "alloc")] impl<'msg, 'aad> From<&'msg [u8]> for Payload<'msg, 'aad> { fn from(msg: &'msg [u8]) -> Self { Self { msg, aad: b"" } @@ -470,3 +509,18 @@ where heapless::Vec::truncate(self, len); } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Ensure that `AeadInPlace` is object-safe + #[allow(dead_code)] + type DynAeadInPlace = + dyn AeadInPlace; + + /// Ensure that `AeadMutInPlace` is object-safe + #[allow(dead_code)] + type DynAeadMutInPlace = + dyn AeadMutInPlace; +}