From 90dfc7666561a363eaa15f4317bbeb463405f58c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Fri, 15 Nov 2024 21:45:59 +0300 Subject: [PATCH] aead: trait API rework --- Cargo.lock | 1 + aead/Cargo.toml | 1 + aead/src/lib.rs | 650 ++++++++++++++++++++++-------------------------- 3 files changed, 297 insertions(+), 355 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 136d93a91..03e744fe1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,6 +21,7 @@ dependencies = [ "bytes", "crypto-common 0.2.0-rc.1", "heapless", + "inout 0.2.0-rc.2", ] [[package]] diff --git a/aead/Cargo.toml b/aead/Cargo.toml index 9cfaa2ff9..952375860 100644 --- a/aead/Cargo.toml +++ b/aead/Cargo.toml @@ -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 } diff --git a/aead/src/lib.rs b/aead/src/lib.rs index 2a3560c21..4ac637585 100644 --- a/aead/src/lib.rs +++ b/aead/src/lib.rs @@ -5,10 +5,10 @@ html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg" )] -#![forbid(unsafe_code)] +// #![forbid(unsafe_code)] #![warn( clippy::unwrap_used, - missing_docs, + // missing_docs, rust_2018_idioms, missing_debug_implementations )] @@ -19,38 +19,37 @@ extern crate alloc; #[cfg(feature = "dev")] pub mod dev; -pub mod stream; +// pub mod stream; pub use crypto_common::{ array::{self, typenum::consts}, Key, KeyInit, KeySizeUser, }; -#[cfg(feature = "arrayvec")] -pub use arrayvec; -#[cfg(feature = "bytes")] -pub use bytes; #[cfg(feature = "getrandom")] pub use crypto_common::rand_core::OsRng; -#[cfg(feature = "heapless")] -pub use heapless; #[cfg(feature = "rand_core")] pub use crypto_common::rand_core; +use inout::{InOutBuf, InOutBufReserved}; use core::fmt; use crypto_common::array::{typenum::Unsigned, Array, ArraySize}; #[cfg(feature = "alloc")] -use alloc::vec::Vec; -#[cfg(feature = "bytes")] -use bytes::BytesMut; +use alloc::{vec, vec::Vec}; #[cfg(feature = "getrandom")] use crypto_common::getrandom; #[cfg(feature = "rand_core")] use rand_core::CryptoRngCore; -/// Error type. +/// Nonce: single-use value for ensuring ciphertexts are unique +pub type Nonce = Array::NonceSize>; + +/// Tag: authentication code which ensures ciphertexts are authentic +pub type Tag = Array::TagSize>; + +/// Authenticated Encryption with Associated Data (AEAD) error type. /// /// This type is deliberately opaque as to avoid potential side-channel /// leakage (e.g. padding oracle). @@ -68,11 +67,13 @@ impl fmt::Display for Error { impl core::error::Error for Error {} -/// Nonce: single-use value for ensuring ciphertexts are unique -pub type Nonce = Array::NonceSize>; +/// Marker type which indicates that AEAD uses postfix tags. +#[derive(Debug)] +pub struct PostfixTag; -/// Tag: authentication code which ensures ciphertexts are authentic -pub type Tag = Array::TagSize>; +/// Marker type which indicates that AEAD uses prefix tags. +#[derive(Debug)] +pub struct PrefixTag; /// Authenticated Encryption with Associated Data (AEAD) algorithm core trait. /// @@ -85,9 +86,10 @@ pub trait AeadCore { /// The maximum length of the tag. type TagSize: ArraySize; - /// The upper bound amount of additional space required to support a - /// ciphertext vs. a plaintext. - type CiphertextOverhead: ArraySize + Unsigned; + /// Constant which defines whether AEAD specification appends or prepends tags. + /// + /// If the specification does not explicitly specify tag kind, we default to postfix tags. + const IS_POSTFIX: bool = true; /// Generate a random nonce for this AEAD algorithm. /// @@ -152,319 +154,347 @@ pub trait AeadCore { } } -/// Authenticated Encryption with Associated Data (AEAD) algorithm. +/// Authenticated Encryption with Associated Data (AEAD) algorithm core encryption trait. /// -/// This trait is intended for use with stateless AEAD algorithms. The -/// [`AeadMut`] trait provides a stateful interface. -#[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. - fn encrypt<'msg, 'aad>( +/// Provides a "detached tag" style API. +pub trait AeadCoreEncrypt: AeadCore { + /// Encrypt the [`InOutBuf`] data, returning the authentication tag. + fn detached_encrypt_inout( &self, nonce: &Nonce, - plaintext: impl Into>, - ) -> Result>; + associated_data: &[u8], + buffer: InOutBuf<'_, '_, u8>, + ) -> 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: - /// - /// ```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>( + /// Encrypt the data in-place, returning the authentication tag. + #[inline] + fn detached_encrypt( &self, nonce: &Nonce, - ciphertext: impl Into>, - ) -> Result>; -} - -/// Stateful Authenticated Encryption with Associated Data algorithm. -#[cfg(feature = "alloc")] -pub trait AeadMut: AeadCore { - /// 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>; - - /// 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>; -} - -/// 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) => {{ - 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::::try_from(&*tag).expect("tag length mismatch"); - - $aead.decrypt_in_place_detached($nonce, $aad, msg, &tag)?; - $buffer.truncate(tag_pos); - Ok(()) - }}; -} + associated_data: &[u8], + buffer: &mut [u8], + ) -> Result> { + self.detached_encrypt_inout(nonce, associated_data, buffer.into()) + } -/// In-place stateless 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( + /// Encrypt the data buffer-to-buffer, returning the authentication tag. + #[inline] + fn detached_encrypt_b2b( &self, nonce: &Nonce, associated_data: &[u8], - buffer: &mut dyn Buffer, - ) -> Result<()> { - let tag = self.encrypt_in_place_detached(nonce, associated_data, buffer.as_mut())?; - buffer.extend_from_slice(tag.as_slice())?; - Ok(()) + src: &[u8], + dst: &mut [u8], + ) -> Result> { + let buf = InOutBuf::new(src, dst).map_err(|_| Error)?; + self.detached_encrypt_inout(nonce, associated_data, buf) } +} - /// Encrypt the data in-place, returning the authentication tag - fn encrypt_in_place_detached( +/// Authenticated Encryption with Associated Data (AEAD) algorithm core decryption trait. +/// +/// Provides a "detached tag" style API. +pub trait AeadCoreDecrypt: AeadCore { + /// Decrypt the [`InOutBuf`] data, returning an error in the event the provided + /// authentication tag does not match the given ciphertext. + fn detached_decrypt_inout( &self, nonce: &Nonce, associated_data: &[u8], - buffer: &mut [u8], - ) -> Result>; + buffer: InOutBuf<'_, '_, u8>, + tag: &Tag, + ) -> 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( + /// Encrypt the data in-place, returning an error in the event the provided + /// authentication tag does not match the given ciphertext. + #[inline] + fn detached_decrypt( &self, nonce: &Nonce, associated_data: &[u8], - buffer: &mut dyn Buffer, + buffer: &mut [u8], + tag: &Tag, ) -> Result<()> { - impl_decrypt_in_place!(self, nonce, associated_data, buffer) + self.detached_decrypt_inout(nonce, associated_data, buffer.into(), tag) } - /// 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) - fn decrypt_in_place_detached( + /// Encrypt the data buffer-to-buffer, returning an error in the event the provided + /// authentication tag does not match the given ciphertext. + #[inline] + fn detached_decrypt_b2b( &self, nonce: &Nonce, associated_data: &[u8], - buffer: &mut [u8], + src: &[u8], + dst: &mut [u8], tag: &Tag, - ) -> Result<()>; + ) -> Result<()> { + let buf = InOutBuf::new(src, dst).map_err(|_| Error)?; + self.detached_decrypt_inout(nonce, associated_data, buf, tag) + } } -/// In-place stateful AEAD trait. -/// -/// This trait is both object safe and has no dependencies on `alloc` or `std`. -pub trait AeadMutInPlace: 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( - &mut self, - nonce: &Nonce, - associated_data: &[u8], - buffer: &mut impl Buffer, - ) -> Result<()> { - let tag = self.encrypt_in_place_detached(nonce, associated_data, buffer.as_mut())?; - buffer.extend_from_slice(tag.as_slice())?; - Ok(()) +// TODO: move to `inout` +fn split_reserved<'a>( + buf: &'a mut InOutBufReserved<'_, '_, u8>, +) -> (InOutBuf<'a, 'a, u8>, &'a mut [u8]) { + let in_len = buf.get_in_len(); + let out_len = buf.get_out_len(); + let in_ptr = buf.get_in().as_ptr(); + let out_ptr = buf.get_out().as_mut_ptr(); + unsafe { + let body = InOutBuf::from_raw(in_ptr, out_ptr, in_len); + let tail = core::slice::from_raw_parts_mut(out_ptr.add(in_len), out_len - in_len); + (body, tail) } +} - /// 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>; +fn into_out_buf<'out>(buf: InOutBuf<'_, 'out, u8>) -> &'out mut [u8] { + let out_len = buf.len(); + let (_, out_ptr) = buf.into_raw(); + unsafe { core::slice::from_raw_parts_mut(out_ptr, out_len) } +} - /// 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( - &mut self, +fn into_out_buf2<'out>(buf: InOutBufReserved<'_, 'out, u8>) -> &'out mut [u8] { + let out_len = buf.get_out_len(); + let (_, out_ptr) = buf.into_raw(); + unsafe { core::slice::from_raw_parts_mut(out_ptr, out_len) } +} + +pub trait AeadPostfixEncrypt: AeadCoreEncrypt { + #[inline] + fn postfix_encrypt_inout<'out>( + &self, nonce: &Nonce, associated_data: &[u8], - buffer: &mut impl Buffer, - ) -> Result<()> { - impl_decrypt_in_place!(self, nonce, associated_data, buffer) + mut buffer: InOutBufReserved<'_, 'out, u8>, + ) -> Result<&'out [u8]> { + let (msg, tail) = split_reserved(&mut buffer); + let tag_len = Self::TagSize::USIZE; + let tag_dst = tail.get_mut(..tag_len).ok_or(Error)?; + let res_len = msg.len() + tag_len; + let tag = self.detached_encrypt_inout(nonce, associated_data, msg)?; + tag_dst.copy_from_slice(&tag); + + let out_buf = into_out_buf2(buffer); + Ok(&out_buf[..res_len]) } - /// 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, + #[inline] + fn postfix_encrypt<'a>( + &self, nonce: &Nonce, associated_data: &[u8], - buffer: &mut [u8], - tag: &Tag, - ) -> Result<()>; -} + buffer: &'a mut [u8], + plaintext_len: usize, + ) -> Result<&'a [u8]> { + let tag_len = Self::TagSize::USIZE; + let res_len = plaintext_len + tag_len; + let buf = buffer.get_mut(..res_len).ok_or(Error)?; + let (msg, tag_dst) = buf.split_at_mut(plaintext_len); + let tag = self.detached_encrypt_inout(nonce, associated_data, msg.into())?; + tag_dst.copy_from_slice(&tag); + Ok(buf) + } -#[cfg(feature = "alloc")] -impl Aead for Alg { - fn encrypt<'msg, 'aad>( + #[inline] + fn postfix_encrypt_b2b<'a>( &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) + associated_data: &[u8], + plaintext: &[u8], + buffer: &'a mut [u8], + ) -> Result<&'a [u8]> { + let tag_len = Self::TagSize::USIZE; + let res_len = plaintext.len() + tag_len; + let buf = buffer.get_mut(..res_len).ok_or(Error)?; + let (msg_dst, tag_dst) = buf.split_at_mut(plaintext.len()); + let inout_buf = InOutBuf::new(plaintext, msg_dst).expect("ct_dst has correct length"); + let tag = self.detached_encrypt_inout(nonce, associated_data, inout_buf)?; + tag_dst.copy_from_slice(&tag); + Ok(buf) } - fn decrypt<'msg, 'aad>( + #[cfg(feature = "alloc")] + #[inline] + fn postfix_encrypt_vec( &self, nonce: &Nonce, - ciphertext: impl Into>, + associated_data: &[u8], + plaintext: &[u8], ) -> Result> { - let payload = ciphertext.into(); - let mut buffer = Vec::from(payload.msg); - self.decrypt_in_place(nonce, payload.aad, &mut buffer)?; - Ok(buffer) + let tag_len = Self::TagSize::USIZE; + let pt_len = plaintext.len(); + let mut buf = vec![0u8; pt_len + tag_len]; + let (ct_dst, tag_dst) = buf.split_at_mut(pt_len); + let inout_buf = InOutBuf::new(plaintext, ct_dst).expect("ct_dst has correct length"); + let tag = self.detached_encrypt_inout(nonce, associated_data, inout_buf)?; + tag_dst.copy_from_slice(&tag); + Ok(buf) } } -#[cfg(feature = "alloc")] -impl AeadMut for Alg { - fn encrypt<'msg, 'aad>( - &mut 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) - } +impl AeadPostfixEncrypt for T {} - fn decrypt<'msg, 'aad>( - &mut self, +pub trait AeadPostfixDecrypt: AeadCoreDecrypt { + /// Decrypt the [`InOutBuf`] data, returning an error in the event the provided + /// authentication tag does not match the given ciphertext. + #[inline] + fn postfix_decrypt_inout<'out>( + &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) + associated_data: &[u8], + buffer: InOutBuf<'_, 'out, u8>, + ) -> Result<&'out mut [u8]> { + let tag_len = Self::TagSize::USIZE; + let ct_len = buffer.len().checked_sub(tag_len).ok_or(Error)?; + let (mut buf, tag) = buffer.split_at(ct_len); + let tag = tag.get_in().try_into().expect("tag has correct length"); + self.detached_decrypt_inout(nonce, associated_data, buf.reborrow(), tag)?; + Ok(into_out_buf(buf)) } -} -impl AeadMutInPlace for Alg { - fn encrypt_in_place( - &mut self, + /// Encrypt the data in-place, returning an error in the event the provided + /// authentication tag does not match the given ciphertext. + #[inline] + fn postfix_decrypt<'out>( + &self, nonce: &Nonce, associated_data: &[u8], - buffer: &mut impl Buffer, - ) -> Result<()> { - ::encrypt_in_place(self, nonce, associated_data, buffer) + buffer: &'out mut [u8], + ) -> Result<&'out mut [u8]> { + let tag_len = Self::TagSize::USIZE; + let ct_len = buffer.len().checked_sub(tag_len).ok_or(Error)?; + let (buf, tag) = buffer.split_at_mut(ct_len); + let tag = (&*tag).try_into().expect("tag has correct length"); + self.detached_decrypt_inout(nonce, associated_data, buf.into(), tag)?; + Ok(buf) } - fn encrypt_in_place_detached( - &mut self, + /// Encrypt the data buffer-to-buffer, returning an error in the event the provided + /// authentication tag does not match the given ciphertext. + #[inline] + fn postfix_decrypt_b2b<'out>( + &self, nonce: &Nonce, associated_data: &[u8], - buffer: &mut [u8], - ) -> Result> { - ::encrypt_in_place_detached(self, nonce, associated_data, buffer) + ciphertext: &[u8], + buffer: &'out mut [u8], + ) -> Result<&'out mut [u8]> { + let tag_len = Self::TagSize::USIZE; + let pt_len = ciphertext.len().checked_sub(tag_len).ok_or(Error)?; + let pt_dst = buffer.get_mut(..pt_len).ok_or(Error)?; + let (ct, tag) = ciphertext.split_at(pt_len); + let tag = tag.try_into().expect("tag has correct length"); + let buf = InOutBuf::new(ct, pt_dst).expect("buffers have the same length"); + self.detached_decrypt_inout(nonce, associated_data, buf, tag)?; + Ok(pt_dst) } - fn decrypt_in_place( - &mut self, + #[cfg(feature = "alloc")] + #[inline] + fn postfix_decrypt_vec( + &self, nonce: &Nonce, associated_data: &[u8], - buffer: &mut impl Buffer, + buffer: &mut Vec, ) -> Result<()> { - ::decrypt_in_place(self, nonce, associated_data, buffer) + let pt = self.postfix_decrypt(nonce, associated_data, buffer)?; + let pt_len = pt.len(); + buffer.truncate(pt_len); + Ok(()) } +} - fn decrypt_in_place_detached( - &mut self, +impl AeadPostfixDecrypt for T {} + +/// Authenticated Encryption with Associated Data (AEAD) algorithm. +/// +/// This trait is intended for use with stateless AEAD algorithms. The +/// [`AeadMut`] trait provides a stateful interface. +#[cfg(feature = "alloc")] +pub trait Aead: AeadCoreEncrypt + AeadCoreDecrypt { + /// 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. + fn encrypt<'msg, 'aad>( + &self, nonce: &Nonce, - associated_data: &[u8], - buffer: &mut [u8], - tag: &Tag, - ) -> Result<()> { - ::decrypt_in_place_detached(self, nonce, associated_data, buffer, tag) + pt_payload: impl Into>, + ) -> Result> { + let Payload { msg: pt, aad } = pt_payload.into(); + let tag_len = Self::TagSize::USIZE; + let mut res = vec![0u8; pt.len() + tag_len]; + let (ct_dst, tag_dst) = if Self::IS_POSTFIX { + res.split_at_mut(pt.len()) + } else { + res.split_at_mut(tag_len) + }; + let tag = self.detached_encrypt_b2b(nonce, aad, pt, ct_dst)?; + tag_dst.copy_from_slice(&tag); + Ok(res) + } + + /// 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, + ct_payload: impl Into>, + ) -> Result> { + let Payload { msg: ct_tag, aad } = ct_payload.into(); + let tag_len = Self::TagSize::USIZE; + let pt_len = ct_tag.len().checked_sub(tag_len).ok_or(Error)?; + let mut pt_dst = vec![0u8; pt_len]; + let (ct, tag) = if Self::IS_POSTFIX { + ct_tag.split_at(pt_len) + } else { + ct_tag.split_at(tag_len) + }; + let tag = tag.try_into().expect("tag has correct length"); + self.detached_decrypt_b2b(nonce, aad, ct, &mut pt_dst, tag)?; + Ok(pt_dst) } } +#[cfg(feature = "alloc")] +impl Aead for T {} + /// AEAD payloads (message + AAD). /// /// Combination of a message (plaintext or ciphertext) and @@ -488,98 +518,8 @@ pub struct Payload<'msg, 'aad> { #[cfg(feature = "alloc")] impl<'msg> From<&'msg [u8]> for Payload<'msg, '_> { + #[inline] fn from(msg: &'msg [u8]) -> Self { Self { msg, aad: b"" } } } - -/// In-place encryption/decryption byte buffers. -/// -/// 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 - 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) - } - - fn extend_from_slice(&mut self, other: &[u8]) -> Result<()> { - BytesMut::extend_from_slice(self, other); - Ok(()) - } - - fn truncate(&mut self, len: usize) { - BytesMut::truncate(self, len); - } -} - -#[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); - } -} - -#[cfg(feature = "heapless")] -impl Buffer for heapless::Vec { - fn extend_from_slice(&mut self, other: &[u8]) -> Result<()> { - heapless::Vec::extend_from_slice(self, other).map_err(|_| Error) - } - - fn truncate(&mut self, len: usize) { - 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; -}