1#![no_std]
2#![doc = include_str!("../README.md")]
3#![doc(
4 html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
5 html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg"
6)]
7#![forbid(unsafe_code)]
8#![cfg_attr(docsrs, feature(doc_cfg))]
9#![warn(missing_debug_implementations, missing_docs, rust_2018_idioms)]
10
11#![cfg_attr(feature = "getrandom", doc = "```")]
16#![cfg_attr(not(feature = "getrandom"), doc = "```ignore")]
17#![cfg_attr(all(feature = "getrandom", feature = "arrayvec"), doc = "```")]
53#![cfg_attr(
54 not(all(feature = "getrandom", feature = "arrayvec")),
55 doc = "```ignore"
56)]
57extern crate alloc;
86
87pub use aead::{self, AeadCore, AeadInOut, Error, Key, KeyInit, KeySizeUser, TagPosition};
88use aead::inout::InOutBuf;
89use core::ops::Add;
90use eme2::cipher::{
91 array::Array,
92 consts::{U16, U24, U32},
93 BlockCipherDecrypt, BlockCipherEncrypt, BlockSizeUser,
94};
95
96#[cfg(feature = "aes")]
97pub use aes;
98
99use blake3::Hasher;
100use eme2::cipher::InnerIvInit;
101use eme2::Eme2;
102use subtle::ConstantTimeEq;
103
104#[cfg(feature = "zeroize")]
105use zeroize::{Zeroize, ZeroizeOnDrop};
106
107const SIV_CONTEXT: &str = "AES-EME2-BLAKE3-SIV-01";
108
109pub type Nonce<NonceSize = U16> = Array<u8, NonceSize>;
111
112pub type Tag<TagSize = U24> = Array<u8, TagSize>;
114
115#[cfg(feature = "aes")]
117pub type Aes128Eme2Blake3 = Eme2Blake3<aes::Aes128>;
118
119#[cfg(feature = "aes")]
121pub type Aes256Eme2Blake3 = Eme2Blake3<aes::Aes256>;
122
123#[cfg(feature = "zeroize")]
125pub trait ZeroizeMarker: zeroize::ZeroizeOnDrop {}
126#[cfg(feature = "zeroize")]
127impl<T: zeroize::ZeroizeOnDrop> ZeroizeMarker for T {}
128
129#[cfg(not(feature = "zeroize"))]
131pub trait ZeroizeMarker {}
132#[cfg(not(feature = "zeroize"))]
133impl<T> ZeroizeMarker for T {}
134
135#[derive(Clone)]
139#[cfg_attr(feature = "zeroize", derive(ZeroizeOnDrop))]
140pub struct Eme2Blake3<C>
141where
142 C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt + Clone + ZeroizeMarker,
143{
144 cipher: C,
145 base_hasher: Hasher,
146}
147
148impl<C> core::fmt::Debug for Eme2Blake3<C>
149where
150 C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt + Clone + ZeroizeMarker,
151{
152 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
153 f.debug_struct("Eme2Blake3").finish_non_exhaustive()
154 }
155}
156
157impl<C> KeySizeUser for Eme2Blake3<C>
158where
159 C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt + KeySizeUser + Clone + ZeroizeMarker,
160 C::KeySize: Add<U32>,
161 <C::KeySize as Add<U32>>::Output: eme2::cipher::array::ArraySize,
162{
163 type KeySize = <C::KeySize as Add<U32>>::Output;
164}
165
166impl<C> KeyInit for Eme2Blake3<C>
167where
168 C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt + KeySizeUser + KeyInit + Clone + ZeroizeMarker,
169 C::KeySize: Add<U32>,
170 <C::KeySize as Add<U32>>::Output: eme2::cipher::array::ArraySize,
171{
172 fn new(key: &Key<Self>) -> Self {
173 use eme2::cipher::typenum::Unsigned;
174 let enc_size = C::KeySize::USIZE;
175 let (enc_slice, mac_slice) = key.split_at(enc_size);
176 let cipher = C::new_from_slice(enc_slice).unwrap();
177 let mut base_hasher = Hasher::new_keyed(mac_slice.try_into().unwrap());
178 base_hasher.update(SIV_CONTEXT.as_bytes());
179
180 Self { cipher, base_hasher }
181 }
182}
183
184impl<C> AeadCore for Eme2Blake3<C>
185where
186 C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt + Clone + ZeroizeMarker,
187{
188 type NonceSize = U16;
189 type TagSize = U24;
190 const TAG_POSITION: TagPosition = TagPosition::Prefix;
191}
192
193impl<C> AeadInOut for Eme2Blake3<C>
194where
195 C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt + Clone + ZeroizeMarker,
196{
197 fn encrypt_inout_detached(
198 &self,
199 nonce: &aead::Nonce<Self>,
200 associated_data: &[u8],
201 mut buffer: InOutBuf<'_, '_, u8>,
202 ) -> Result<aead::Tag<Self>, Error> {
203 let plaintext_len = buffer.len();
204 if plaintext_len < 16 {
205 return Err(Error);
206 }
207
208 let in_data = buffer.get_in();
209 let mut temp = alloc::vec::Vec::from(in_data);
210
211 let tag_bytes = self.compute_siv(nonce, associated_data, &temp);
212
213 let tweak: [u8; 16] = tag_bytes[..16].try_into().unwrap();
214 let eme2_cipher = Eme2::inner_iv_init(self.cipher.clone(), &tweak.into());
215 eme2_cipher.encrypt(&mut temp).map_err(|_| Error)?;
216
217 buffer.get_out().copy_from_slice(&temp);
218
219 #[cfg(feature = "zeroize")]
220 temp.zeroize();
221
222 Ok(tag_bytes.into())
223 }
224
225 fn decrypt_inout_detached(
226 &self,
227 nonce: &aead::Nonce<Self>,
228 associated_data: &[u8],
229 mut buffer: InOutBuf<'_, '_, u8>,
230 tag: &aead::Tag<Self>,
231 ) -> Result<(), Error> {
232 if buffer.len() < 16 {
233 return Err(Error);
234 }
235
236 let in_data = buffer.get_in();
237 let mut temp = alloc::vec::Vec::from(in_data);
238
239 let tweak: [u8; 16] = tag[..16].try_into().unwrap();
240 let eme2_cipher = Eme2::inner_iv_init(self.cipher.clone(), &tweak.into());
241 eme2_cipher.decrypt(&mut temp).map_err(|_| Error)?;
242
243 let expected_tag = self.compute_siv(nonce, associated_data, &temp);
244
245 if expected_tag.ct_eq(tag.as_slice()).unwrap_u8() == 0 {
246 #[cfg(feature = "zeroize")]
247 temp.zeroize();
248 return Err(Error);
249 }
250
251 buffer.get_out().copy_from_slice(&temp);
252
253 #[cfg(feature = "zeroize")]
254 temp.zeroize();
255
256 Ok(())
257 }
258}
259
260impl<C> Eme2Blake3<C>
261where
262 C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt + Clone + ZeroizeMarker,
263{
264 fn compute_siv(&self, nonce: &[u8], ad: &[u8], payload: &[u8]) -> [u8; 24] {
265 let mut hasher = self.base_hasher.clone();
266 hasher.update(nonce);
267 hasher.update(&(ad.len() as u64).to_le_bytes());
268 hasher.update(&(payload.len() as u64).to_le_bytes());
269 hasher.update(ad);
270 hasher.update(payload);
271
272 let mut tag = [0u8; 24];
273 hasher.finalize_xof().fill(&mut tag);
274 tag
275 }
276}