1#![allow(unsafe_code)]
23
24mod aes;
25mod fork;
26mod ghash;
27mod nonce;
28mod siv;
29
30#[cfg(feature = "ct-verify")]
34pub use ghash::{ct_verify_leaky_control, ct_verify_mulx};
35
36#[cfg(feature = "saw-verify")]
39pub use ghash::{
40 saw_field_mul, saw_field_mul_commutes, saw_field_mul_left_linear, saw_field_mul_right_linear,
41};
42
43pub use siv::{
44 HardwareAes256GcmSiv, HardwareAes256GcmSivIn, HardwareAes256GcmSivKeyState,
45 SivUninitKeyStateSlot,
46};
47
48use core::{
49 marker::PhantomData,
50 mem::MaybeUninit,
51 ptr::{self, NonNull},
52};
53use subtle::ConstantTimeEq as _;
54use zeroize::Zeroize as _;
55
56pub const KEY_SIZE: usize = 32;
58pub const NONCE_SIZE: usize = 12;
60pub const TAG_SIZE: usize = 16;
62const AES_BLOCK_SIZE: usize = 16;
63const PAR_BLOCKS: usize = aes::PAR_BLOCKS;
64const PAR_BYTES: usize = PAR_BLOCKS * AES_BLOCK_SIZE;
65const MAX_GCM_DATA_LEN: u64 = ((u32::MAX as u64) - 1) * AES_BLOCK_SIZE as u64;
66const MAX_GHASH_INPUT_LEN: u64 = u64::MAX / 8;
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub struct KeyStateLayout {
71 pub size: usize,
73 pub align: usize,
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79#[non_exhaustive]
80pub enum Error {
81 InvalidKeyLength,
83 InvalidNonceLength,
85 Encrypt,
87 Decrypt,
89 CiphertextTooShort,
91 UnsupportedCpu,
93 KeyStateStorageTooSmall,
95 KeyStateStorageMisaligned,
97 InputTooLarge,
99 OutputTooSmall,
101 OsEntropy,
103}
104
105impl std::fmt::Display for Error {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 match self {
108 Self::InvalidKeyLength => f.write_str("invalid AES-256-GCM key length"),
109 Self::InvalidNonceLength => f.write_str("invalid AES-GCM nonce length"),
110 Self::Encrypt => f.write_str("AES-256-GCM encryption failed"),
111 Self::Decrypt => f.write_str("AES-256-GCM authentication failed"),
112 Self::CiphertextTooShort => f.write_str("ciphertext too short"),
113 Self::UnsupportedCpu => {
114 f.write_str("required AES-256-GCM hardware support is unavailable")
115 }
116 Self::KeyStateStorageTooSmall => f.write_str("key-state storage too small"),
117 Self::KeyStateStorageMisaligned => f.write_str("key-state storage misaligned"),
118 Self::InputTooLarge => f.write_str("AES-256-GCM input too large"),
119 Self::OutputTooSmall => f.write_str("plaintext output buffer too small"),
120 Self::OsEntropy => f.write_str("OS entropy source failed during nonce generation"),
121 }
122 }
123}
124
125impl std::error::Error for Error {}
126
127struct KeyState {
128 aes: aes::Aes256,
129 ghash: ghash::GHashKey,
130}
131
132impl KeyState {
133 fn init_in_place(dst: NonNull<Self>, key: &[u8; KEY_SIZE]) -> Result<(), Error> {
134 if !HardwareAes256Gcm::hardware_available() {
135 return Err(Error::UnsupportedCpu);
136 }
137
138 let dst = dst.as_ptr();
139 let aes_ptr = unsafe { ptr::addr_of_mut!((*dst).aes) };
142 aes::Aes256::init_in_place(aes_ptr, key).ok_or(Error::UnsupportedCpu)?;
143
144 let mut hash_subkey = [0_u8; TAG_SIZE];
145 unsafe { (&*aes_ptr).encrypt_block(&mut hash_subkey) };
147 let ghash_ptr = unsafe { ptr::addr_of_mut!((*dst).ghash) };
150 if ghash::GHashKey::init_in_place(ghash_ptr, &mut hash_subkey).is_none() {
151 hash_subkey.zeroize();
155 unsafe { ptr::drop_in_place(aes_ptr) };
157 return Err(Error::UnsupportedCpu);
158 }
159 Ok(())
160 }
161
162 fn encrypt(&self, nonce: &[u8], aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
163 let total = plaintext
164 .len()
165 .checked_add(TAG_SIZE)
166 .ok_or(Error::InputTooLarge)?;
167 let mut out = vec![0_u8; total];
168 self.encrypt_to(nonce, aad, plaintext, &mut out)?;
169 Ok(out)
170 }
171
172 fn encrypt_nonce_appended(&self, nonce: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
173 self.encrypt_envelope(nonce, &[], plaintext)
174 }
175
176 fn encrypt_envelope(
177 &self,
178 nonce: &[u8],
179 aad: &[u8],
180 plaintext: &[u8],
181 ) -> Result<Vec<u8>, Error> {
182 let nonce = nonce_from_slice(nonce)?;
183 validate_gcm_lengths(aad.len(), plaintext.len())?;
184 let total = plaintext
185 .len()
186 .checked_add(TAG_SIZE + NONCE_SIZE)
187 .ok_or(Error::InputTooLarge)?;
188 let mut out = Vec::with_capacity(total);
189 out.extend_from_slice(plaintext);
190 let Some(tag) = self.seal_in_place(&nonce, aad, &mut out) else {
191 out.zeroize();
192 return Err(Error::Encrypt);
193 };
194 append_tag_nonce(&mut out, &tag, &nonce);
195 Ok(out)
196 }
197
198 fn encrypt_envelope_to(
199 &self,
200 nonce: &[u8],
201 aad: &[u8],
202 plaintext: &[u8],
203 out: &mut [u8],
204 ) -> Result<usize, Error> {
205 let nonce = nonce_from_slice(nonce)?;
206 validate_gcm_lengths(aad.len(), plaintext.len())?;
207 let total = plaintext
208 .len()
209 .checked_add(TAG_SIZE + NONCE_SIZE)
210 .ok_or(Error::InputTooLarge)?;
211 if out.len() < total {
212 return Err(Error::OutputTooSmall);
213 }
214
215 let ciphertext_tag_len = plaintext.len() + TAG_SIZE;
216 self.encrypt_to(&nonce, aad, plaintext, &mut out[..ciphertext_tag_len])?;
217 out[ciphertext_tag_len..total].copy_from_slice(&nonce);
218 Ok(total)
219 }
220
221 #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
222 fn encrypt_nonce_appended_in_place(
223 &self,
224 nonce: &[u8],
225 in_out: &mut Vec<u8>,
226 ) -> Result<(), Error> {
227 let nonce = nonce_from_slice(nonce)?;
228 validate_gcm_lengths(0, in_out.len())?;
229 let total = in_out
230 .len()
231 .checked_add(TAG_SIZE + NONCE_SIZE)
232 .ok_or(Error::InputTooLarge)?;
233 if in_out.capacity() < total {
234 in_out.reserve_exact(total - in_out.len());
235 }
236 let Some(tag) = self.seal_in_place(&nonce, &[], in_out.as_mut_slice()) else {
237 in_out.zeroize();
238 return Err(Error::Encrypt);
239 };
240 append_tag_nonce(in_out, &tag, &nonce);
241 Ok(())
242 }
243
244 fn decrypt_envelope(&self, aad: &[u8], data: &[u8]) -> Result<Vec<u8>, Error> {
245 if data.len() < TAG_SIZE + NONCE_SIZE {
246 return Err(Error::CiphertextTooShort);
247 }
248 let nonce_pos = data.len() - NONCE_SIZE;
249 let (ciphertext_and_tag, nonce) = data.split_at(nonce_pos);
250 self.decrypt(nonce, aad, ciphertext_and_tag)
251 }
252
253 fn decrypt_envelope_to(&self, aad: &[u8], data: &[u8], out: &mut [u8]) -> Result<usize, Error> {
254 if data.len() < TAG_SIZE + NONCE_SIZE {
255 return Err(Error::CiphertextTooShort);
256 }
257 let nonce_pos = data.len() - NONCE_SIZE;
258 let (ciphertext_and_tag, nonce) = data.split_at(nonce_pos);
259 self.decrypt_to(nonce, aad, ciphertext_and_tag, out)
260 }
261
262 fn encrypt_to(
263 &self,
264 nonce: &[u8],
265 aad: &[u8],
266 plaintext: &[u8],
267 out: &mut [u8],
268 ) -> Result<usize, Error> {
269 let nonce = nonce_from_slice(nonce)?;
270 validate_gcm_lengths(aad.len(), plaintext.len())?;
271 let total = plaintext
272 .len()
273 .checked_add(TAG_SIZE)
274 .ok_or(Error::InputTooLarge)?;
275 if out.len() < total {
276 return Err(Error::OutputTooSmall);
277 }
278
279 let (ciphertext, rest) = out.split_at_mut(plaintext.len());
280 let tag = self
281 .seal(&nonce, aad, plaintext, ciphertext)
282 .ok_or(Error::Encrypt)?;
283 rest[..TAG_SIZE].copy_from_slice(&tag);
284 Ok(total)
285 }
286
287 fn seal(
294 &self,
295 nonce: &[u8; NONCE_SIZE],
296 aad: &[u8],
297 plaintext: &[u8],
298 ciphertext: &mut [u8],
299 ) -> Option<[u8; TAG_SIZE]> {
300 debug_assert_eq!(plaintext.len(), ciphertext.len());
301 let mut ghasher = ghash::Ghasher::new(&self.ghash)?;
302 ghasher.absorb_padded(aad);
303
304 let mut counter = j0(nonce);
305 increment_counter(&mut counter);
306
307 let bulk = (plaintext.len() / PAR_BYTES) * PAR_BYTES;
311 let (plaintext_bulk, plaintext_tail) = plaintext.split_at(bulk);
312 let (ciphertext_bulk, ciphertext_tail) = ciphertext.split_at_mut(bulk);
313 if !plaintext_bulk.is_empty() {
314 ghasher.seal_bulk(
315 self.aes.round_keys(),
316 &mut counter,
317 plaintext_bulk,
318 ciphertext_bulk,
319 );
320 }
321 if !plaintext_tail.is_empty() {
322 ciphertext_tail.copy_from_slice(plaintext_tail);
323 self.apply_ctr_serial(&mut counter, ciphertext_tail);
324 ghasher.absorb_padded(ciphertext_tail);
325 }
326
327 let mut tag = ghasher.finalize(aad.len(), plaintext.len())?;
328 let mut mask = j0(nonce);
329 self.aes.encrypt_block(&mut mask);
330 for (tag_byte, mask_byte) in tag.iter_mut().zip(mask.iter()) {
331 *tag_byte ^= mask_byte;
332 }
333 Some(tag)
334 }
335
336 fn seal_in_place(
340 &self,
341 nonce: &[u8; NONCE_SIZE],
342 aad: &[u8],
343 data: &mut [u8],
344 ) -> Option<[u8; TAG_SIZE]> {
345 let mut ghasher = ghash::Ghasher::new(&self.ghash)?;
346 ghasher.absorb_padded(aad);
347
348 let mut counter = j0(nonce);
349 increment_counter(&mut counter);
350
351 let bulk = (data.len() / PAR_BYTES) * PAR_BYTES;
352 let (data_bulk, data_tail) = data.split_at_mut(bulk);
353 if !data_bulk.is_empty() {
354 ghasher.seal_in_place_bulk(self.aes.round_keys(), &mut counter, data_bulk);
355 }
356 if !data_tail.is_empty() {
357 let full = (data_tail.len() / AES_BLOCK_SIZE) * AES_BLOCK_SIZE;
358 let (data_tail_blocks, data_partial) = data_tail.split_at_mut(full);
359 if !data_tail_blocks.is_empty() {
360 ghasher.seal_in_place_tail_blocks(
361 self.aes.round_keys(),
362 &mut counter,
363 data_tail_blocks,
364 );
365 }
366 if !data_partial.is_empty() {
367 self.apply_ctr_serial(&mut counter, data_partial);
368 ghasher.absorb_padded(data_partial);
369 }
370 }
371
372 let mut tag = ghasher.finalize(aad.len(), data.len())?;
373 let mut mask = j0(nonce);
374 self.aes.encrypt_block(&mut mask);
375 for (tag_byte, mask_byte) in tag.iter_mut().zip(mask.iter()) {
376 *tag_byte ^= mask_byte;
377 }
378 Some(tag)
379 }
380
381 fn apply_ctr_serial(&self, counter: &mut [u8; AES_BLOCK_SIZE], data: &mut [u8]) {
383 let mut keystream = [0_u8; AES_BLOCK_SIZE];
384 for block in data.chunks_mut(AES_BLOCK_SIZE) {
385 keystream.copy_from_slice(counter);
386 self.aes.encrypt_block(&mut keystream);
387 for (byte, key_byte) in block.iter_mut().zip(keystream.iter()) {
388 *byte ^= key_byte;
389 }
390 increment_counter(counter);
391 }
392 }
393
394 fn decrypt(
395 &self,
396 nonce: &[u8],
397 aad: &[u8],
398 ciphertext_and_tag: &[u8],
399 ) -> Result<Vec<u8>, Error> {
400 let nonce = nonce_from_slice(nonce)?;
401 if ciphertext_and_tag.len() < TAG_SIZE {
402 return Err(Error::Decrypt);
403 }
404
405 let tag_pos = ciphertext_and_tag.len() - TAG_SIZE;
406 let (ciphertext, tag) = ciphertext_and_tag.split_at(tag_pos);
407 validate_gcm_lengths(aad.len(), ciphertext.len())?;
408 let mut out = Vec::with_capacity(ciphertext.len());
409 out.extend_from_slice(ciphertext);
410 let Some(expected) = self.open_in_place(&nonce, aad, &mut out) else {
411 out.zeroize();
412 return Err(Error::Decrypt);
413 };
414 if !constant_time_eq(&expected, tag) {
415 out.zeroize();
416 return Err(Error::Decrypt);
417 }
418 Ok(out)
419 }
420
421 fn decrypt_to(
422 &self,
423 nonce: &[u8],
424 aad: &[u8],
425 ciphertext_and_tag: &[u8],
426 out: &mut [u8],
427 ) -> Result<usize, Error> {
428 let nonce = nonce_from_slice(nonce)?;
429 if ciphertext_and_tag.len() < TAG_SIZE {
430 return Err(Error::Decrypt);
431 }
432
433 let tag_pos = ciphertext_and_tag.len() - TAG_SIZE;
434 let (ciphertext, tag) = ciphertext_and_tag.split_at(tag_pos);
435 validate_gcm_lengths(aad.len(), ciphertext.len())?;
436 if out.len() < ciphertext.len() {
437 return Err(Error::OutputTooSmall);
438 }
439 let out = &mut out[..ciphertext.len()];
440 let Some(expected) = self.open(&nonce, aad, ciphertext, out) else {
441 out.zeroize();
442 return Err(Error::Decrypt);
443 };
444 if !constant_time_eq(&expected, tag) {
449 out.zeroize();
450 return Err(Error::Decrypt);
451 }
452
453 Ok(ciphertext.len())
454 }
455
456 fn open(
460 &self,
461 nonce: &[u8; NONCE_SIZE],
462 aad: &[u8],
463 ciphertext: &[u8],
464 plaintext: &mut [u8],
465 ) -> Option<[u8; TAG_SIZE]> {
466 debug_assert_eq!(ciphertext.len(), plaintext.len());
467 let mut ghasher = ghash::Ghasher::new(&self.ghash)?;
468 ghasher.absorb_padded(aad);
469
470 let mut counter = j0(nonce);
471 increment_counter(&mut counter);
472
473 let bulk = (ciphertext.len() / PAR_BYTES) * PAR_BYTES;
477 let (ciphertext_bulk, ciphertext_tail) = ciphertext.split_at(bulk);
478 let (plaintext_bulk, plaintext_tail) = plaintext.split_at_mut(bulk);
479 if !ciphertext_bulk.is_empty() {
480 ghasher.open_bulk(
481 self.aes.round_keys(),
482 &mut counter,
483 ciphertext_bulk,
484 plaintext_bulk,
485 );
486 }
487 if !ciphertext_tail.is_empty() {
488 ghasher.absorb_padded(ciphertext_tail);
489 plaintext_tail.copy_from_slice(ciphertext_tail);
490 self.apply_ctr_serial(&mut counter, plaintext_tail);
491 }
492
493 let mut tag = ghasher.finalize(aad.len(), ciphertext.len())?;
494 let mut mask = j0(nonce);
495 self.aes.encrypt_block(&mut mask);
496 for (tag_byte, mask_byte) in tag.iter_mut().zip(mask.iter()) {
497 *tag_byte ^= mask_byte;
498 }
499 Some(tag)
500 }
501
502 fn open_in_place(
506 &self,
507 nonce: &[u8; NONCE_SIZE],
508 aad: &[u8],
509 data: &mut [u8],
510 ) -> Option<[u8; TAG_SIZE]> {
511 let mut ghasher = ghash::Ghasher::new(&self.ghash)?;
512 ghasher.absorb_padded(aad);
513
514 let mut counter = j0(nonce);
515 increment_counter(&mut counter);
516
517 let bulk = (data.len() / PAR_BYTES) * PAR_BYTES;
518 let (data_bulk, data_tail) = data.split_at_mut(bulk);
519 if !data_bulk.is_empty() {
520 ghasher.open_in_place_bulk(self.aes.round_keys(), &mut counter, data_bulk);
521 }
522 if !data_tail.is_empty() {
523 ghasher.absorb_padded(data_tail);
524 self.apply_ctr_serial(&mut counter, data_tail);
525 }
526
527 let mut tag = ghasher.finalize(aad.len(), data.len())?;
528 let mut mask = j0(nonce);
529 self.aes.encrypt_block(&mut mask);
530 for (tag_byte, mask_byte) in tag.iter_mut().zip(mask.iter()) {
531 *tag_byte ^= mask_byte;
532 }
533 Some(tag)
534 }
535}
536
537pub struct UninitKeyStateSlot<'a> {
539 storage: &'a mut [u8],
540}
541
542impl<'a> UninitKeyStateSlot<'a> {
543 pub fn new(storage: &'a mut [u8]) -> Result<Self, Error> {
550 let layout = HardwareAes256Gcm::key_state_layout();
551 if storage.len() < layout.size {
552 return Err(Error::KeyStateStorageTooSmall);
553 }
554 if !storage.as_ptr().addr().is_multiple_of(layout.align) {
555 return Err(Error::KeyStateStorageMisaligned);
556 }
557 Ok(Self { storage })
558 }
559}
560
561impl std::fmt::Debug for UninitKeyStateSlot<'_> {
562 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
563 f.debug_struct("UninitKeyStateSlot")
564 .field("len", &self.storage.len())
565 .finish_non_exhaustive()
566 }
567}
568
569#[inline]
570fn init_key_state_at(key: &[u8], state: NonNull<KeyState>) -> Result<(), Error> {
571 if key.len() != KEY_SIZE {
572 return Err(Error::InvalidKeyLength);
573 }
574 let key: &[u8; KEY_SIZE] = key.try_into().map_err(|_| Error::InvalidKeyLength)?;
575 KeyState::init_in_place(state, key)
576}
577
578fn init_key_state_in_slot(key: &[u8], slot: UninitKeyStateSlot<'_>) -> Result<NonNull<u8>, Error> {
579 let UninitKeyStateSlot { storage } = slot;
580 let storage = unsafe { NonNull::new_unchecked(storage.as_mut_ptr()) };
585 #[allow(clippy::cast_ptr_alignment)]
586 let state = storage.cast::<KeyState>();
587 init_key_state_at(key, state)?;
588 Ok(storage)
589}
590
591pub struct OpaqueKeyState<'a> {
597 storage: NonNull<u8>,
598 _marker: PhantomData<&'a mut [u8]>,
599}
600
601const _: () = {
602 const fn assert_send_sync<T: Send + Sync>() {}
603 assert_send_sync::<KeyState>();
606};
607
608unsafe impl Send for OpaqueKeyState<'_> {}
614unsafe impl Sync for OpaqueKeyState<'_> {}
617
618impl OpaqueKeyState<'_> {
619 fn state_ptr(&self) -> *mut KeyState {
620 #[allow(clippy::cast_ptr_alignment)]
623 self.storage.as_ptr().cast::<KeyState>()
624 }
625}
626
627impl Drop for OpaqueKeyState<'_> {
628 fn drop(&mut self) {
629 let size = HardwareAes256Gcm::key_state_layout().size;
630 unsafe { volatile_wipe(self.storage.as_ptr(), size) };
640 }
641}
642
643impl std::fmt::Debug for OpaqueKeyState<'_> {
644 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
645 f.debug_struct("OpaqueKeyState").finish_non_exhaustive()
646 }
647}
648
649pub struct HardwareAes256GcmIn<'a> {
651 state: OpaqueKeyState<'a>,
652 nonce_gen: Option<nonce::NonceGen>,
656}
657
658impl<'a> HardwareAes256GcmIn<'a> {
659 pub fn new_in(key: &[u8], slot: UninitKeyStateSlot<'a>) -> Result<Self, Error> {
666 let storage = init_key_state_in_slot(key, slot)?;
667 Ok(Self {
668 state: OpaqueKeyState {
669 storage,
670 _marker: PhantomData,
671 },
672 nonce_gen: None,
673 })
674 }
675
676 pub fn encrypt(&mut self, aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
683 let nonce = self.next_nonce()?;
684 self.state_ref().encrypt_envelope(&nonce, aad, plaintext)
685 }
686
687 pub fn encrypt_to(
699 &mut self,
700 aad: &[u8],
701 plaintext: &[u8],
702 out: &mut [u8],
703 ) -> Result<usize, Error> {
704 let nonce = self.next_nonce()?;
705 self.state_ref()
706 .encrypt_envelope_to(&nonce, aad, plaintext, out)
707 }
708
709 #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
710 #[doc(hidden)]
711 pub fn encrypt_with_nonce(
712 &self,
713 nonce: &[u8],
714 aad: &[u8],
715 plaintext: &[u8],
716 ) -> Result<Vec<u8>, Error> {
717 self.state_ref().encrypt(nonce, aad, plaintext)
718 }
719
720 #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
731 #[doc(hidden)]
732 pub fn encrypt_with_nonce_to(
733 &self,
734 nonce: &[u8],
735 aad: &[u8],
736 plaintext: &[u8],
737 out: &mut [u8],
738 ) -> Result<usize, Error> {
739 self.state_ref().encrypt_to(nonce, aad, plaintext, out)
740 }
741
742 pub fn decrypt(&self, aad: &[u8], ciphertext_tag_nonce: &[u8]) -> Result<Vec<u8>, Error> {
749 self.state_ref().decrypt_envelope(aad, ciphertext_tag_nonce)
750 }
751
752 pub fn decrypt_to(
764 &self,
765 aad: &[u8],
766 ciphertext_tag_nonce: &[u8],
767 out: &mut [u8],
768 ) -> Result<usize, Error> {
769 self.state_ref()
770 .decrypt_envelope_to(aad, ciphertext_tag_nonce, out)
771 }
772
773 #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
774 #[doc(hidden)]
775 pub fn decrypt_with_nonce(
776 &self,
777 nonce: &[u8],
778 aad: &[u8],
779 ciphertext_and_tag: &[u8],
780 ) -> Result<Vec<u8>, Error> {
781 self.state_ref().decrypt(nonce, aad, ciphertext_and_tag)
782 }
783
784 #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
796 #[doc(hidden)]
797 pub fn decrypt_with_nonce_to(
798 &self,
799 nonce: &[u8],
800 aad: &[u8],
801 ciphertext_and_tag: &[u8],
802 out: &mut [u8],
803 ) -> Result<usize, Error> {
804 self.state_ref()
805 .decrypt_to(nonce, aad, ciphertext_and_tag, out)
806 }
807
808 #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
814 #[doc(hidden)]
815 pub fn encrypt_nonce_appended(&self, nonce: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
816 self.state_ref().encrypt_nonce_appended(nonce, plaintext)
817 }
818
819 #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
830 #[doc(hidden)]
831 pub fn encrypt_nonce_appended_in_place(
832 &self,
833 nonce: &[u8],
834 in_out: &mut Vec<u8>,
835 ) -> Result<(), Error> {
836 self.state_ref()
837 .encrypt_nonce_appended_in_place(nonce, in_out)
838 }
839
840 #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
849 #[doc(hidden)]
850 pub fn encrypt_nonce_appended_to(
851 &self,
852 nonce: &[u8],
853 plaintext: &[u8],
854 out: &mut [u8],
855 ) -> Result<usize, Error> {
856 encrypt_nonce_appended_to(self.state_ref(), nonce, plaintext, out)
857 }
858
859 #[doc(hidden)]
867 pub fn decrypt_nonce_appended(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
868 self.state_ref().decrypt_envelope(&[], data)
869 }
870
871 #[doc(hidden)]
879 pub fn decrypt_nonce_appended_to(&self, data: &[u8], out: &mut [u8]) -> Result<usize, Error> {
880 self.state_ref().decrypt_envelope_to(&[], data, out)
881 }
882
883 #[doc(hidden)]
896 pub fn encrypt_with_generated_nonce(
897 &mut self,
898 aad: &[u8],
899 plaintext: &[u8],
900 ) -> Result<([u8; NONCE_SIZE], Vec<u8>), Error> {
901 let nonce = self.next_nonce()?;
902 let ciphertext = self.state_ref().encrypt(&nonce, aad, plaintext)?;
903 Ok((nonce, ciphertext))
904 }
905
906 #[doc(hidden)]
913 pub fn encrypt_nonce_appended_generated(&mut self, plaintext: &[u8]) -> Result<Vec<u8>, Error> {
914 let nonce = self.next_nonce()?;
915 self.state_ref().encrypt_nonce_appended(&nonce, plaintext)
916 }
917
918 fn next_nonce(&mut self) -> Result<[u8; NONCE_SIZE], Error> {
919 match self.nonce_gen {
920 Some(ref mut g) => g.next(),
921 None => self.nonce_gen.insert(nonce::NonceGen::new()?).next(),
922 }
923 }
924
925 fn state_ref(&self) -> &KeyState {
926 unsafe { &*self.state.state_ptr() }
928 }
929}
930
931impl std::fmt::Debug for HardwareAes256GcmIn<'_> {
932 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
933 f.debug_struct("HardwareAes256GcmIn")
934 .finish_non_exhaustive()
935 }
936}
937
938#[repr(transparent)]
939struct AlignedKeyStateStorage(MaybeUninit<KeyState>);
940
941impl AlignedKeyStateStorage {
942 #[inline]
943 const fn uninit() -> Self {
944 Self(MaybeUninit::uninit())
945 }
946
947 #[inline]
948 fn state_ptr(&self) -> *const KeyState {
949 self.0.as_ptr()
950 }
951
952 #[inline]
953 fn state_ptr_mut(&mut self) -> NonNull<KeyState> {
954 unsafe { NonNull::new_unchecked(self.0.as_mut_ptr()) }
956 }
957
958 #[inline]
959 fn bytes_ptr_mut(&mut self) -> *mut u8 {
960 self.0.as_mut_ptr().cast::<u8>()
961 }
962}
963
964pub struct HardwareAes256GcmKeyState {
971 storage: AlignedKeyStateStorage,
972 nonce_gen: Option<nonce::NonceGen>,
973}
974
975impl HardwareAes256GcmKeyState {
976 #[inline]
984 pub fn new(key: &[u8]) -> Result<Self, Error> {
985 let mut storage = AlignedKeyStateStorage::uninit();
986 init_key_state_at(key, storage.state_ptr_mut())?;
987 Ok(Self {
988 storage,
989 nonce_gen: None,
990 })
991 }
992
993 #[must_use]
995 pub const fn state_size() -> usize {
996 std::mem::size_of::<KeyState>()
997 }
998
999 pub fn encrypt(&mut self, aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
1007 let nonce = self.next_nonce()?;
1008 self.state_ref().encrypt_envelope(&nonce, aad, plaintext)
1009 }
1010
1011 pub fn encrypt_to(
1024 &mut self,
1025 aad: &[u8],
1026 plaintext: &[u8],
1027 out: &mut [u8],
1028 ) -> Result<usize, Error> {
1029 let nonce = self.next_nonce()?;
1030 self.state_ref()
1031 .encrypt_envelope_to(&nonce, aad, plaintext, out)
1032 }
1033
1034 pub fn decrypt(&self, aad: &[u8], ciphertext_tag_nonce: &[u8]) -> Result<Vec<u8>, Error> {
1041 self.state_ref().decrypt_envelope(aad, ciphertext_tag_nonce)
1042 }
1043
1044 pub fn decrypt_to(
1056 &self,
1057 aad: &[u8],
1058 ciphertext_tag_nonce: &[u8],
1059 out: &mut [u8],
1060 ) -> Result<usize, Error> {
1061 self.state_ref()
1062 .decrypt_envelope_to(aad, ciphertext_tag_nonce, out)
1063 }
1064
1065 #[inline]
1072 #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1073 #[doc(hidden)]
1074 pub fn encrypt_nonce_appended(&self, nonce: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
1075 self.state_ref().encrypt_nonce_appended(nonce, plaintext)
1076 }
1077
1078 #[inline]
1089 #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1090 #[doc(hidden)]
1091 pub fn encrypt_nonce_appended_in_place(
1092 &self,
1093 nonce: &[u8],
1094 in_out: &mut Vec<u8>,
1095 ) -> Result<(), Error> {
1096 self.state_ref()
1097 .encrypt_nonce_appended_in_place(nonce, in_out)
1098 }
1099
1100 #[inline]
1108 #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1109 #[doc(hidden)]
1110 pub fn decrypt_nonce_appended(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
1111 self.state_ref().decrypt_envelope(&[], data)
1112 }
1113
1114 fn next_nonce(&mut self) -> Result<[u8; NONCE_SIZE], Error> {
1115 match self.nonce_gen {
1116 Some(ref mut g) => g.next(),
1117 None => self.nonce_gen.insert(nonce::NonceGen::new()?).next(),
1118 }
1119 }
1120
1121 fn state_ref(&self) -> &KeyState {
1122 unsafe { &*self.storage.state_ptr() }
1126 }
1127}
1128
1129impl Drop for HardwareAes256GcmKeyState {
1130 fn drop(&mut self) {
1131 let size = Self::state_size();
1132 unsafe { volatile_wipe(self.storage.bytes_ptr_mut(), size) };
1139 }
1140}
1141
1142impl std::fmt::Debug for HardwareAes256GcmKeyState {
1143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1144 f.debug_struct("HardwareAes256GcmKeyState")
1145 .finish_non_exhaustive()
1146 }
1147}
1148
1149pub struct HardwareAes256Gcm {
1151 state: Box<KeyState>,
1152 nonce_gen: Option<nonce::NonceGen>,
1156}
1157
1158impl HardwareAes256Gcm {
1159 pub fn new(key: &[u8]) -> Result<Self, Error> {
1165 if key.len() != KEY_SIZE {
1166 return Err(Error::InvalidKeyLength);
1167 }
1168 let mut state = Box::<KeyState>::new_uninit();
1169 let ptr = unsafe { NonNull::new_unchecked(state.as_mut_ptr()) };
1171 init_key_state_at(key, ptr)?;
1172 let state = unsafe { state.assume_init() };
1174 Ok(Self {
1175 state,
1176 nonce_gen: None,
1177 })
1178 }
1179
1180 #[must_use]
1182 pub fn hardware_available() -> bool {
1183 aes::hardware_available() && ghash::hardware_available()
1184 }
1185
1186 #[must_use]
1190 pub const fn state_size() -> usize {
1191 std::mem::size_of::<KeyState>()
1192 }
1193
1194 #[must_use]
1198 pub const fn key_state_layout() -> KeyStateLayout {
1199 KeyStateLayout {
1200 size: std::mem::size_of::<KeyState>(),
1201 align: std::mem::align_of::<KeyState>(),
1202 }
1203 }
1204
1205 pub fn encrypt(&mut self, aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
1213 let nonce = self.next_nonce()?;
1214 self.state.encrypt_envelope(&nonce, aad, plaintext)
1215 }
1216
1217 pub fn encrypt_to(
1230 &mut self,
1231 aad: &[u8],
1232 plaintext: &[u8],
1233 out: &mut [u8],
1234 ) -> Result<usize, Error> {
1235 let nonce = self.next_nonce()?;
1236 self.state.encrypt_envelope_to(&nonce, aad, plaintext, out)
1237 }
1238
1239 #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1240 #[doc(hidden)]
1241 pub fn encrypt_with_nonce(
1242 &self,
1243 nonce: &[u8],
1244 aad: &[u8],
1245 plaintext: &[u8],
1246 ) -> Result<Vec<u8>, Error> {
1247 self.state.encrypt(nonce, aad, plaintext)
1248 }
1249
1250 #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1261 #[doc(hidden)]
1262 pub fn encrypt_with_nonce_to(
1263 &self,
1264 nonce: &[u8],
1265 aad: &[u8],
1266 plaintext: &[u8],
1267 out: &mut [u8],
1268 ) -> Result<usize, Error> {
1269 self.state.encrypt_to(nonce, aad, plaintext, out)
1270 }
1271
1272 pub fn decrypt(&self, aad: &[u8], ciphertext_tag_nonce: &[u8]) -> Result<Vec<u8>, Error> {
1279 self.state.decrypt_envelope(aad, ciphertext_tag_nonce)
1280 }
1281
1282 pub fn decrypt_to(
1294 &self,
1295 aad: &[u8],
1296 ciphertext_tag_nonce: &[u8],
1297 out: &mut [u8],
1298 ) -> Result<usize, Error> {
1299 self.state
1300 .decrypt_envelope_to(aad, ciphertext_tag_nonce, out)
1301 }
1302
1303 #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1304 #[doc(hidden)]
1305 pub fn decrypt_with_nonce(
1306 &self,
1307 nonce: &[u8],
1308 aad: &[u8],
1309 ciphertext_and_tag: &[u8],
1310 ) -> Result<Vec<u8>, Error> {
1311 self.state.decrypt(nonce, aad, ciphertext_and_tag)
1312 }
1313
1314 #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1326 #[doc(hidden)]
1327 pub fn decrypt_with_nonce_to(
1328 &self,
1329 nonce: &[u8],
1330 aad: &[u8],
1331 ciphertext_and_tag: &[u8],
1332 out: &mut [u8],
1333 ) -> Result<usize, Error> {
1334 self.state.decrypt_to(nonce, aad, ciphertext_and_tag, out)
1335 }
1336
1337 #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1343 #[doc(hidden)]
1344 pub fn encrypt_nonce_appended(&self, nonce: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
1345 self.state.encrypt_nonce_appended(nonce, plaintext)
1346 }
1347
1348 #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1359 #[doc(hidden)]
1360 pub fn encrypt_nonce_appended_in_place(
1361 &self,
1362 nonce: &[u8],
1363 in_out: &mut Vec<u8>,
1364 ) -> Result<(), Error> {
1365 self.state.encrypt_nonce_appended_in_place(nonce, in_out)
1366 }
1367
1368 #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1377 #[doc(hidden)]
1378 pub fn encrypt_nonce_appended_to(
1379 &self,
1380 nonce: &[u8],
1381 plaintext: &[u8],
1382 out: &mut [u8],
1383 ) -> Result<usize, Error> {
1384 encrypt_nonce_appended_to(&self.state, nonce, plaintext, out)
1385 }
1386
1387 #[doc(hidden)]
1396 pub fn decrypt_nonce_appended(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
1397 self.state.decrypt_envelope(&[], data)
1398 }
1399
1400 #[doc(hidden)]
1408 pub fn decrypt_nonce_appended_to(&self, data: &[u8], out: &mut [u8]) -> Result<usize, Error> {
1409 self.state.decrypt_envelope_to(&[], data, out)
1410 }
1411
1412 #[doc(hidden)]
1425 pub fn encrypt_with_generated_nonce(
1426 &mut self,
1427 aad: &[u8],
1428 plaintext: &[u8],
1429 ) -> Result<([u8; NONCE_SIZE], Vec<u8>), Error> {
1430 let nonce = self.next_nonce()?;
1431 let ciphertext = self.state.encrypt(&nonce, aad, plaintext)?;
1432 Ok((nonce, ciphertext))
1433 }
1434
1435 #[doc(hidden)]
1442 pub fn encrypt_nonce_appended_generated(&mut self, plaintext: &[u8]) -> Result<Vec<u8>, Error> {
1443 let nonce = self.next_nonce()?;
1444 self.state.encrypt_nonce_appended(&nonce, plaintext)
1445 }
1446
1447 fn next_nonce(&mut self) -> Result<[u8; NONCE_SIZE], Error> {
1448 match self.nonce_gen {
1449 Some(ref mut g) => g.next(),
1450 None => self.nonce_gen.insert(nonce::NonceGen::new()?).next(),
1451 }
1452 }
1453}
1454
1455impl std::fmt::Debug for HardwareAes256Gcm {
1456 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1457 f.debug_struct("HardwareAes256Gcm").finish_non_exhaustive()
1458 }
1459}
1460
1461fn nonce_from_slice(nonce: &[u8]) -> Result<[u8; NONCE_SIZE], Error> {
1462 nonce.try_into().map_err(|_| Error::InvalidNonceLength)
1463}
1464
1465fn append_tag_nonce(out: &mut Vec<u8>, tag: &[u8; TAG_SIZE], nonce: &[u8; NONCE_SIZE]) {
1466 debug_assert!(out.capacity() >= out.len() + TAG_SIZE + NONCE_SIZE);
1467
1468 let start = out.len();
1469 let trailer = &mut out.spare_capacity_mut()[..TAG_SIZE + NONCE_SIZE];
1470 for (slot, byte) in trailer[..TAG_SIZE].iter_mut().zip(tag) {
1471 slot.write(*byte);
1472 }
1473 for (slot, byte) in trailer[TAG_SIZE..].iter_mut().zip(nonce) {
1474 slot.write(*byte);
1475 }
1476
1477 unsafe { out.set_len(start + TAG_SIZE + NONCE_SIZE) };
1481}
1482
1483#[allow(clippy::cast_ptr_alignment)] unsafe fn volatile_wipe(bytes: *mut u8, len: usize) {
1491 let mut offset = 0_usize;
1492 if bytes.addr().is_multiple_of(core::mem::align_of::<u128>()) {
1493 while offset + core::mem::size_of::<u128>() <= len {
1494 unsafe { ptr::write_volatile(bytes.add(offset).cast::<u128>(), 0) };
1497 offset += core::mem::size_of::<u128>();
1498 }
1499 }
1500 for offset in offset..len {
1501 unsafe { ptr::write_volatile(bytes.add(offset), 0) };
1503 }
1504 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
1505}
1506
1507#[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1509fn encrypt_nonce_appended_to(
1510 state: &KeyState,
1511 nonce: &[u8],
1512 plaintext: &[u8],
1513 out: &mut [u8],
1514) -> Result<usize, Error> {
1515 let total = plaintext
1516 .len()
1517 .checked_add(TAG_SIZE + NONCE_SIZE)
1518 .ok_or(Error::InputTooLarge)?;
1519 if out.len() < total {
1520 return Err(Error::OutputTooSmall);
1521 }
1522 let written = state.encrypt_to(nonce, &[], plaintext, out)?;
1523 out[written..total].copy_from_slice(nonce);
1526 Ok(total)
1527}
1528
1529fn validate_gcm_lengths(aad_len: usize, data_len: usize) -> Result<(), Error> {
1530 if len_exceeds_u64_limit(data_len, MAX_GCM_DATA_LEN)
1531 || len_exceeds_u64_limit(aad_len, MAX_GHASH_INPUT_LEN)
1532 || len_exceeds_u64_limit(data_len, MAX_GHASH_INPUT_LEN)
1533 {
1534 return Err(Error::InputTooLarge);
1535 }
1536 Ok(())
1537}
1538
1539fn len_exceeds_u64_limit(len: usize, limit: u64) -> bool {
1540 u64::try_from(len).map_or(true, |len| len > limit)
1541}
1542
1543fn j0(nonce: &[u8; NONCE_SIZE]) -> [u8; 16] {
1544 let mut out = [0_u8; 16];
1545 out[..NONCE_SIZE].copy_from_slice(nonce);
1546 out[15] = 1;
1547 out
1548}
1549
1550pub(crate) fn increment_counter(counter: &mut [u8; 16]) {
1553 let mut low_bytes = [0_u8; 4];
1554 low_bytes.copy_from_slice(&counter[12..]);
1555 let low = u32::from_be_bytes(low_bytes).wrapping_add(1);
1556 counter[12..].copy_from_slice(&low.to_be_bytes());
1557}
1558
1559#[cfg(feature = "saw-verify")]
1563#[no_mangle]
1564#[allow(clippy::not_unsafe_ptr_arg_deref)]
1565pub extern "C" fn saw_increment_counter(counter: *mut u8) {
1566 let c = unsafe { &mut *(counter.cast::<[u8; 16]>()) };
1568 increment_counter(c);
1569}
1570
1571#[cfg(feature = "saw-verify")]
1574#[no_mangle]
1575#[allow(clippy::not_unsafe_ptr_arg_deref)]
1576pub extern "C" fn saw_j0(nonce: *const u8, out: *mut u8) {
1577 let n = unsafe { &*(nonce.cast::<[u8; NONCE_SIZE]>()) };
1579 let block = j0(n);
1580 unsafe { core::ptr::copy_nonoverlapping(block.as_ptr(), out, 16) };
1582}
1583
1584fn constant_time_eq(expected: &[u8; TAG_SIZE], actual: &[u8]) -> bool {
1585 expected.as_slice().ct_eq(actual).into()
1590}
1591
1592#[cfg(feature = "ct-verify")]
1597#[inline(never)]
1598#[must_use]
1599pub fn ct_verify_constant_time_eq(expected: &[u8; TAG_SIZE], actual: &[u8; TAG_SIZE]) -> bool {
1600 constant_time_eq(
1601 core::hint::black_box(expected),
1602 core::hint::black_box(actual.as_slice()),
1603 )
1604}
1605
1606#[cfg(test)]
1607mod tests {
1608 #![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
1609
1610 use super::{
1611 increment_counter, j0, Error, HardwareAes256Gcm, HardwareAes256GcmIn,
1612 HardwareAes256GcmKeyState, UninitKeyStateSlot, NONCE_SIZE, TAG_SIZE,
1613 };
1614 use core::mem::ManuallyDrop;
1615
1616 #[repr(align(64))]
1617 struct AlignedStorage<const N: usize>([u8; N]);
1618
1619 #[test]
1623 fn increment_counter_is_be32_inc_leaving_high_96_bits() {
1624 let mut counter = [0_u8; 16];
1626 counter[..12].copy_from_slice(&[0xAA; 12]);
1627 counter[12..].copy_from_slice(&u32::MAX.to_be_bytes());
1628 increment_counter(&mut counter);
1629 assert_eq!(
1630 &counter[..12],
1631 &[0xAA; 12],
1632 "leading 96 bits must be preserved"
1633 );
1634 assert_eq!(
1635 &counter[12..],
1636 &0_u32.to_be_bytes(),
1637 "low 32 bits must wrap to 0"
1638 );
1639
1640 let mut counter = [0_u8; 16];
1642 counter[12..].copy_from_slice(&0x0000_00FF_u32.to_be_bytes());
1643 increment_counter(&mut counter);
1644 assert_eq!(&counter[12..], &0x0000_0100_u32.to_be_bytes());
1645
1646 let nonce = [0x11_u8; NONCE_SIZE];
1648 let block = j0(&nonce);
1649 assert_eq!(&block[..NONCE_SIZE], &nonce);
1650 assert_eq!(&block[NONCE_SIZE..], &[0, 0, 0, 1]);
1651 }
1652
1653 #[test]
1654 fn rejects_wrong_key_length() {
1655 let Err(err) = HardwareAes256Gcm::new(&[0_u8; 31]) else {
1656 panic!("31-byte key should be rejected");
1657 };
1658 assert_eq!(err, Error::InvalidKeyLength);
1659 }
1660
1661 #[test]
1662 fn caller_placed_key_state_rejects_wrong_key_length() {
1663 let layout = HardwareAes256Gcm::key_state_layout();
1664 let mut storage = AlignedStorage::<512>([0_u8; 512]);
1665 let slot = UninitKeyStateSlot::new(&mut storage.0[..layout.size]).expect("valid slot");
1666 let Err(err) = HardwareAes256GcmIn::new_in(&[0_u8; 31], slot) else {
1667 panic!("31-byte key should be rejected");
1668 };
1669 assert_eq!(err, Error::InvalidKeyLength);
1670 }
1671
1672 #[test]
1673 fn inline_owned_key_state_rejects_wrong_key_length() {
1674 let Err(err) = HardwareAes256GcmKeyState::new(&[0_u8; 31]) else {
1675 panic!("31-byte key should be rejected");
1676 };
1677 assert_eq!(err, Error::InvalidKeyLength);
1678 }
1679
1680 #[test]
1681 fn rejects_ciphertext_shorter_than_tag() {
1682 let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1683 let Err(err) =
1684 key.decrypt_with_nonce(&[0_u8; NONCE_SIZE], &[], &[0_u8; super::TAG_SIZE - 1])
1685 else {
1686 panic!("input shorter than the tag should be rejected");
1687 };
1688 assert_eq!(err, Error::Decrypt);
1689 }
1690
1691 #[test]
1692 fn rejects_nonce_appended_shorter_than_tag_and_nonce() {
1693 let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1694 let short = [0_u8; super::TAG_SIZE + NONCE_SIZE - 1];
1695 let Err(err) = key.decrypt_nonce_appended(&short) else {
1696 panic!("input shorter than tag plus nonce should be rejected");
1697 };
1698 assert_eq!(err, Error::CiphertextTooShort);
1699
1700 let mut out = [0_u8; 64];
1701 let Err(err) = key.decrypt_nonce_appended_to(&short, &mut out) else {
1702 panic!("input shorter than tag plus nonce should be rejected");
1703 };
1704 assert_eq!(err, Error::CiphertextTooShort);
1705 }
1706
1707 #[test]
1708 fn decrypt_to_writes_plaintext_into_caller_buffer() {
1709 let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1710 let nonce = [9_u8; NONCE_SIZE];
1711 let ciphertext = key
1712 .encrypt_with_nonce(&nonce, b"aad", b"plaintext")
1713 .expect("encryption should succeed");
1714
1715 let mut exact = [0_u8; 9];
1716 let written = key
1717 .decrypt_with_nonce_to(&nonce, b"aad", &ciphertext, &mut exact)
1718 .expect("decryption into an exact-size buffer should succeed");
1719 assert_eq!(written, 9);
1720 assert_eq!(&exact, b"plaintext");
1721
1722 let mut oversized = [0xff_u8; 32];
1723 let written = key
1724 .decrypt_with_nonce_to(&nonce, b"aad", &ciphertext, &mut oversized)
1725 .expect("decryption into a larger buffer should succeed");
1726 assert_eq!(&oversized[..written], b"plaintext");
1727
1728 let mut short = [0_u8; 8];
1729 let Err(err) = key.decrypt_with_nonce_to(&nonce, b"aad", &ciphertext, &mut short) else {
1730 panic!("short output buffer should be rejected");
1731 };
1732 assert_eq!(err, Error::OutputTooSmall);
1733 assert_eq!(short, [0_u8; 8], "no plaintext may leak into short buffers");
1734
1735 let mut tampered = ciphertext;
1736 *tampered.last_mut().expect("tag byte") ^= 1;
1737 let mut invalid_out = [0xff_u8; 9];
1738 let Err(err) = key.decrypt_with_nonce_to(&nonce, b"aad", &tampered, &mut invalid_out)
1739 else {
1740 panic!("invalid authentication tag should be rejected");
1741 };
1742 assert_eq!(err, Error::Decrypt);
1743 assert_eq!(
1744 invalid_out, [0_u8; 9],
1745 "transient plaintext must be wiped on authentication failure"
1746 );
1747 }
1748
1749 #[test]
1750 fn encrypt_to_matches_allocating_encrypt() {
1751 let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1752 let nonce = [9_u8; NONCE_SIZE];
1753 let plaintext = [0x5a_u8; 200];
1754 let expected = key
1755 .encrypt_with_nonce(&nonce, b"aad", &plaintext)
1756 .expect("encryption should succeed");
1757
1758 let mut exact = [0_u8; 200 + super::TAG_SIZE];
1759 let written = key
1760 .encrypt_with_nonce_to(&nonce, b"aad", &plaintext, &mut exact)
1761 .expect("encryption into an exact-size buffer should succeed");
1762 assert_eq!(written, exact.len());
1763 assert_eq!(exact.as_slice(), expected.as_slice());
1764
1765 let mut oversized = [0_u8; 256];
1766 let written = key
1767 .encrypt_with_nonce_to(&nonce, b"aad", &plaintext, &mut oversized)
1768 .expect("encryption into a larger buffer should succeed");
1769 assert_eq!(&oversized[..written], expected.as_slice());
1770
1771 let mut short = [0_u8; 200 + super::TAG_SIZE - 1];
1772 let Err(err) = key.encrypt_with_nonce_to(&nonce, b"aad", &plaintext, &mut short) else {
1773 panic!("short output buffer should be rejected");
1774 };
1775 assert_eq!(err, Error::OutputTooSmall);
1776 assert!(
1777 short.iter().all(|byte| *byte == 0),
1778 "no ciphertext may be written into short buffers"
1779 );
1780 }
1781
1782 #[test]
1783 fn encrypt_nonce_appended_to_matches_allocating_variant() {
1784 let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1785 let nonce = [9_u8; NONCE_SIZE];
1786 let expected = key
1787 .encrypt_nonce_appended(&nonce, b"plaintext")
1788 .expect("encryption should succeed");
1789
1790 let mut out = [0_u8; 9 + super::TAG_SIZE + NONCE_SIZE];
1791 let written = key
1792 .encrypt_nonce_appended_to(&nonce, b"plaintext", &mut out)
1793 .expect("encryption should succeed");
1794 assert_eq!(written, out.len());
1795 assert_eq!(out.as_slice(), expected.as_slice());
1796
1797 let mut short = [0_u8; 9 + super::TAG_SIZE + NONCE_SIZE - 1];
1798 let Err(err) = key.encrypt_nonce_appended_to(&nonce, b"plaintext", &mut short) else {
1799 panic!("short output buffer should be rejected");
1800 };
1801 assert_eq!(err, Error::OutputTooSmall);
1802 }
1803
1804 #[test]
1805 fn encrypt_nonce_appended_in_place_uses_existing_vec_capacity() {
1806 let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1807 let nonce = [9_u8; NONCE_SIZE];
1808 let expected = key
1809 .encrypt_nonce_appended(&nonce, b"plaintext")
1810 .expect("encryption should succeed");
1811
1812 let mut in_out = Vec::with_capacity(b"plaintext".len() + super::TAG_SIZE + NONCE_SIZE);
1813 in_out.extend_from_slice(b"plaintext");
1814 let ptr_before = in_out.as_ptr();
1815 key.encrypt_nonce_appended_in_place(&nonce, &mut in_out)
1816 .expect("in-place encryption should succeed");
1817
1818 assert_eq!(in_out, expected);
1819 assert_eq!(
1820 in_out.as_ptr(),
1821 ptr_before,
1822 "pre-reserved in-place encryption should not reallocate"
1823 );
1824 assert_eq!(
1825 key.decrypt_nonce_appended(&in_out)
1826 .expect("decryption should succeed"),
1827 b"plaintext"
1828 );
1829 }
1830
1831 #[test]
1832 fn decrypt_nonce_appended_to_round_trips() {
1833 let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1834 let nonce = [9_u8; NONCE_SIZE];
1835 let data = key
1836 .encrypt_nonce_appended(&nonce, b"plaintext")
1837 .expect("encryption should succeed");
1838
1839 let mut out = [0_u8; 32];
1840 let written = key
1841 .decrypt_nonce_appended_to(&data, &mut out)
1842 .expect("decryption should succeed");
1843 assert_eq!(&out[..written], b"plaintext");
1844 }
1845
1846 #[test]
1847 fn generated_nonce_round_trips_and_is_unique() {
1848 let mut key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1849
1850 let (nonce_a, ct_a) = key
1851 .encrypt_with_generated_nonce(b"aad", b"plaintext")
1852 .expect("encryption should succeed");
1853 let (nonce_b, _ct_b) = key
1854 .encrypt_with_generated_nonce(b"aad", b"plaintext")
1855 .expect("encryption should succeed");
1856
1857 assert_ne!(nonce_a, nonce_b, "generated nonces must be unique");
1859 assert_eq!(
1861 key.decrypt_with_nonce(&nonce_a, b"aad", &ct_a)
1862 .expect("decryption should succeed"),
1863 b"plaintext"
1864 );
1865 }
1866
1867 #[test]
1868 fn generated_nonce_appended_layout_round_trips() {
1869 let mut key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1870 let blob = key
1871 .encrypt_nonce_appended_generated(b"plaintext")
1872 .expect("encryption should succeed");
1873 assert_eq!(
1874 blob.len(),
1875 b"plaintext".len() + super::TAG_SIZE + NONCE_SIZE
1876 );
1877 assert_eq!(
1878 key.decrypt_nonce_appended(&blob)
1879 .expect("decryption should succeed"),
1880 b"plaintext"
1881 );
1882 }
1883
1884 #[test]
1885 fn caller_placed_generated_nonce_round_trips() {
1886 let layout = HardwareAes256Gcm::key_state_layout();
1887 let mut storage = AlignedStorage::<512>([0_u8; 512]);
1888 let slot = UninitKeyStateSlot::new(&mut storage.0[..layout.size]).expect("valid slot");
1889 let mut key = HardwareAes256GcmIn::new_in(&[7_u8; 32], slot).expect("valid test key");
1890 let (nonce, ct) = key
1891 .encrypt_with_generated_nonce(&[], b"plaintext")
1892 .expect("encryption should succeed");
1893 assert_eq!(
1894 key.decrypt_with_nonce(&nonce, &[], &ct)
1895 .expect("decryption should succeed"),
1896 b"plaintext"
1897 );
1898 }
1899
1900 #[test]
1907 fn caller_placed_in_buffer_methods_round_trip() {
1908 const KEY: [u8; 32] = [7_u8; 32];
1909 const NONCE: [u8; NONCE_SIZE] = [0x24; NONCE_SIZE];
1910 let pt = b"caller-placed in-buffer methods".to_vec();
1911 let aad = b"header".as_slice();
1912
1913 let layout = HardwareAes256Gcm::key_state_layout();
1914 let mut storage = AlignedStorage::<512>([0_u8; 512]);
1915 let slot = UninitKeyStateSlot::new(&mut storage.0[..layout.size]).expect("valid slot");
1916 let mut key = HardwareAes256GcmIn::new_in(&KEY, slot).expect("valid test key");
1917 let reference = HardwareAes256Gcm::new(&KEY).expect("reference key");
1919
1920 let ct = key
1922 .encrypt_with_nonce(&NONCE, aad, &pt)
1923 .expect("encrypt_with_nonce");
1924 assert_eq!(
1925 ct,
1926 reference.encrypt_with_nonce(&NONCE, aad, &pt).unwrap(),
1927 "encrypt_with_nonce diverged from the reference cipher"
1928 );
1929 assert_eq!(key.decrypt_with_nonce(&NONCE, aad, &ct).unwrap(), pt);
1930
1931 let mut buf = vec![0_u8; pt.len() + TAG_SIZE];
1932 let n = key
1933 .encrypt_with_nonce_to(&NONCE, aad, &pt, &mut buf)
1934 .expect("encrypt_with_nonce_to");
1935 assert_eq!(n, pt.len() + TAG_SIZE);
1936 assert_eq!(&buf[..n], &ct[..]);
1937 let mut out = vec![0_u8; pt.len()];
1938 let m = key
1939 .decrypt_with_nonce_to(&NONCE, aad, &buf[..n], &mut out)
1940 .expect("decrypt_with_nonce_to");
1941 assert_eq!(&out[..m], &pt[..]);
1942
1943 let env = key
1945 .encrypt_nonce_appended(&NONCE, &pt)
1946 .expect("encrypt_nonce_appended");
1947 assert_eq!(key.decrypt_nonce_appended(&env).unwrap(), pt);
1948
1949 let mut env_buf = vec![0_u8; pt.len() + TAG_SIZE + NONCE_SIZE];
1950 let n = key
1951 .encrypt_nonce_appended_to(&NONCE, &pt, &mut env_buf)
1952 .expect("encrypt_nonce_appended_to");
1953 assert_eq!(n, env_buf.len());
1954 assert_eq!(env_buf, env);
1955 let mut out = vec![0_u8; pt.len()];
1956 let m = key
1957 .decrypt_nonce_appended_to(&env_buf, &mut out)
1958 .expect("decrypt_nonce_appended_to");
1959 assert_eq!(&out[..m], &pt[..]);
1960
1961 for prealloc in [0_usize, pt.len() + TAG_SIZE + NONCE_SIZE] {
1964 let mut in_out = Vec::with_capacity(prealloc);
1965 in_out.extend_from_slice(&pt);
1966 key.encrypt_nonce_appended_in_place(&NONCE, &mut in_out)
1967 .expect("encrypt_nonce_appended_in_place");
1968 assert_eq!(in_out, env, "in-place layout must match");
1969 assert_eq!(key.decrypt_nonce_appended(&in_out).unwrap(), pt);
1970 }
1971
1972 let mut out = vec![0_u8; pt.len() + TAG_SIZE + NONCE_SIZE];
1974 let n = key.encrypt_to(aad, &pt, &mut out).expect("encrypt_to");
1975 assert_eq!(n, out.len());
1976 let mut dec = vec![0_u8; pt.len()];
1977 let m = key.decrypt_to(aad, &out, &mut dec).expect("decrypt_to");
1978 assert_eq!(&dec[..m], &pt[..]);
1979
1980 let g1 = key.encrypt_nonce_appended_generated(&pt).unwrap();
1981 let g2 = key.encrypt_nonce_appended_generated(&pt).unwrap();
1982 assert_ne!(g1, g2, "generated nonces must differ between calls");
1983 assert_eq!(key.decrypt_nonce_appended(&g1).unwrap(), pt);
1984 assert_eq!(key.decrypt_nonce_appended(&g2).unwrap(), pt);
1985
1986 drop(reference);
1987 }
1988
1989 #[test]
1992 fn owned_key_state_encrypt_to_round_trips() {
1993 let mut key = HardwareAes256GcmKeyState::new(&[9_u8; 32]).expect("valid key");
1994 let pt = b"owned key state encrypt_to path";
1995 let mut out = vec![0_u8; pt.len() + TAG_SIZE + NONCE_SIZE];
1996 let n = key.encrypt_to(b"aad", pt, &mut out).expect("encrypt_to");
1997 assert_eq!(n, out.len());
1998 assert_eq!(key.decrypt(b"aad", &out).expect("decrypt"), pt);
1999 }
2000
2001 #[test]
2007 fn gcm_length_validation_rejects_each_over_limit() {
2008 use super::{validate_gcm_lengths, MAX_GCM_DATA_LEN, MAX_GHASH_INPUT_LEN};
2009 assert!(validate_gcm_lengths(16, 16).is_ok());
2010 if let Ok(over) = usize::try_from(MAX_GCM_DATA_LEN + 1) {
2012 assert_eq!(validate_gcm_lengths(16, over), Err(Error::InputTooLarge));
2013 }
2014 if let Ok(over) = usize::try_from(MAX_GHASH_INPUT_LEN + 1) {
2016 assert_eq!(validate_gcm_lengths(over, 16), Err(Error::InputTooLarge));
2017 }
2018 if let Ok(lim) = usize::try_from(MAX_GCM_DATA_LEN) {
2020 assert!(validate_gcm_lengths(0, lim).is_ok());
2021 }
2022 }
2023
2024 #[test]
2025 fn generated_nonce_does_not_change_key_state_size() {
2026 assert_eq!(HardwareAes256Gcm::key_state_layout().size, 368);
2028 assert_eq!(HardwareAes256Gcm::state_size(), 368);
2029 }
2030
2031 #[test]
2032 fn inline_owned_key_state_layout_matches_reported_layout() {
2033 let layout = HardwareAes256Gcm::key_state_layout();
2034 assert!(std::mem::size_of::<HardwareAes256GcmKeyState>() >= layout.size);
2035 assert!(std::mem::align_of::<HardwareAes256GcmKeyState>() >= layout.align);
2036 assert_eq!(HardwareAes256GcmKeyState::state_size(), layout.size);
2037 }
2038
2039 #[test]
2040 fn inline_owned_key_state_default_envelope_round_trips() {
2041 let mut key = HardwareAes256GcmKeyState::new(&[7_u8; 32]).expect("valid test key");
2042 let first = key
2043 .encrypt(b"aad", b"plaintext")
2044 .expect("encryption should succeed");
2045 let second = key
2046 .encrypt(b"aad", b"plaintext")
2047 .expect("encryption should succeed");
2048
2049 assert_eq!(
2050 key.decrypt(b"aad", &first)
2051 .expect("decryption should succeed"),
2052 b"plaintext"
2053 );
2054 assert_eq!(
2055 key.decrypt(b"aad", &second)
2056 .expect("decryption should succeed"),
2057 b"plaintext"
2058 );
2059 assert_ne!(
2060 &first[first.len() - NONCE_SIZE..],
2061 &second[second.len() - NONCE_SIZE..],
2062 "generated nonces must be unique"
2063 );
2064
2065 let mut out = [0_u8; 9];
2066 let written = key
2067 .decrypt_to(b"aad", &first, &mut out)
2068 .expect("decryption into caller buffer should succeed");
2069 assert_eq!(written, out.len());
2070 assert_eq!(&out, b"plaintext");
2071 }
2072
2073 #[test]
2074 fn inline_owned_key_state_round_trips_and_uses_existing_vec_capacity() {
2075 let key = HardwareAes256GcmKeyState::new(&[7_u8; 32]).expect("valid test key");
2076 let nonce = [9_u8; NONCE_SIZE];
2077 let expected = key
2078 .encrypt_nonce_appended(&nonce, b"plaintext")
2079 .expect("encryption should succeed");
2080 assert_eq!(
2081 key.decrypt_nonce_appended(&expected)
2082 .expect("decryption should succeed"),
2083 b"plaintext"
2084 );
2085
2086 let mut in_out = Vec::with_capacity(b"plaintext".len() + super::TAG_SIZE + NONCE_SIZE);
2087 in_out.extend_from_slice(b"plaintext");
2088 let ptr_before = in_out.as_ptr();
2089 key.encrypt_nonce_appended_in_place(&nonce, &mut in_out)
2090 .expect("in-place encryption should succeed");
2091 assert_eq!(in_out, expected);
2092 assert_eq!(
2093 in_out.as_ptr(),
2094 ptr_before,
2095 "pre-reserved in-place encryption should not reallocate"
2096 );
2097 }
2098
2099 #[test]
2100 fn inline_owned_key_state_wipes_storage_on_drop() {
2101 let layout = HardwareAes256Gcm::key_state_layout();
2102 let mut key =
2103 ManuallyDrop::new(HardwareAes256GcmKeyState::new(&[7_u8; 32]).expect("valid test key"));
2104
2105 unsafe {
2111 ManuallyDrop::drop(&mut key);
2112 let storage = key.storage.bytes_ptr_mut();
2113 let bytes = std::slice::from_raw_parts(storage.cast_const(), layout.size);
2114 assert!(bytes.iter().all(|byte| *byte == 0));
2115 }
2116 }
2117
2118 #[test]
2119 fn caller_placed_key_state_is_usable_across_threads() {
2120 let layout = HardwareAes256Gcm::key_state_layout();
2121 let mut storage = AlignedStorage::<512>([0_u8; 512]);
2122 let slot = UninitKeyStateSlot::new(&mut storage.0[..layout.size]).expect("valid slot");
2123 let mut key = HardwareAes256GcmIn::new_in(&[7_u8; 32], slot).expect("valid test key");
2124 let ciphertext = key
2125 .encrypt(&[], b"plaintext")
2126 .expect("encryption should succeed");
2127
2128 std::thread::scope(|scope| {
2129 for _ in 0..4 {
2130 scope.spawn(|| {
2131 assert_eq!(
2132 key.decrypt(&[], &ciphertext)
2133 .expect("shared decryption should succeed"),
2134 b"plaintext"
2135 );
2136 });
2137 }
2138 });
2139 }
2140
2141 #[test]
2142 fn rejects_wrong_nonce_length() {
2143 let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
2144 let Err(err) = key.encrypt_with_nonce(&[0_u8; NONCE_SIZE - 1], &[], b"data") else {
2145 panic!("short nonce should be rejected");
2146 };
2147 assert_eq!(err, Error::InvalidNonceLength);
2148 }
2149
2150 #[test]
2151 #[cfg(target_pointer_width = "64")]
2152 fn rejects_inputs_that_exceed_gcm_counter_limit() {
2153 let max_data_len =
2154 usize::try_from(super::MAX_GCM_DATA_LEN).expect("64-bit usize holds GCM data limit");
2155 assert_eq!(
2156 super::validate_gcm_lengths(0, max_data_len + 1),
2157 Err(Error::InputTooLarge)
2158 );
2159 }
2160
2161 #[test]
2162 #[cfg(target_pointer_width = "64")]
2163 fn accepts_inputs_at_gcm_counter_limit() {
2164 let max_data_len =
2165 usize::try_from(super::MAX_GCM_DATA_LEN).expect("64-bit usize holds GCM data limit");
2166 assert_eq!(super::validate_gcm_lengths(0, max_data_len), Ok(()));
2167 }
2168
2169 #[test]
2170 fn nonce_appended_round_trips() {
2171 let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
2172 let nonce = [9_u8; NONCE_SIZE];
2173 let ciphertext = key
2174 .encrypt_nonce_appended(&nonce, b"plaintext")
2175 .expect("encryption should succeed");
2176 assert_eq!(
2177 key.decrypt_nonce_appended(&ciphertext)
2178 .expect("decryption should succeed"),
2179 b"plaintext"
2180 );
2181 }
2182
2183 #[test]
2184 fn reports_nonzero_key_state_layout() {
2185 let layout = HardwareAes256Gcm::key_state_layout();
2186 assert!(layout.size >= super::KEY_SIZE);
2187 assert!(layout.align.is_power_of_two());
2188 assert!(layout.size <= 384);
2189 assert_eq!(layout.size, HardwareAes256Gcm::state_size());
2190 }
2191
2192 #[test]
2193 fn caller_placed_key_state_round_trips_and_wipes_storage() {
2194 let layout = HardwareAes256Gcm::key_state_layout();
2195 let mut storage = AlignedStorage::<512>([0xa5; 512]);
2196
2197 {
2198 let slot = UninitKeyStateSlot::new(&mut storage.0[..layout.size]).expect("valid slot");
2199 let mut key = HardwareAes256GcmIn::new_in(&[7_u8; 32], slot).expect("valid test key");
2200 let ciphertext = key
2201 .encrypt(&[], b"plaintext")
2202 .expect("encryption should succeed");
2203 assert_eq!(
2204 key.decrypt(&[], &ciphertext)
2205 .expect("decryption should succeed"),
2206 b"plaintext"
2207 );
2208 }
2209
2210 assert!(storage.0[..layout.size].iter().all(|byte| *byte == 0));
2211 }
2212
2213 #[test]
2214 fn caller_placed_key_state_validates_storage_before_key() {
2215 let layout = HardwareAes256Gcm::key_state_layout();
2216 let mut short = AlignedStorage::<512>([0_u8; 512]);
2217 let err = UninitKeyStateSlot::new(&mut short.0[..layout.size - 1])
2218 .expect_err("short storage should fail");
2219 assert_eq!(err, Error::KeyStateStorageTooSmall);
2220
2221 let mut misaligned = AlignedStorage::<512>([0_u8; 512]);
2222 let start = 1;
2223 let end = start + layout.size;
2224 let err = UninitKeyStateSlot::new(&mut misaligned.0[start..end])
2225 .expect_err("misaligned storage should fail");
2226 assert_eq!(err, Error::KeyStateStorageMisaligned);
2227 }
2228}
2229
2230#[cfg(kani)]
2238mod kani_proofs {
2239 use super::{
2240 constant_time_eq, increment_counter, j0, nonce_from_slice, validate_gcm_lengths, Error,
2241 MAX_GCM_DATA_LEN, MAX_GHASH_INPUT_LEN, NONCE_SIZE, TAG_SIZE,
2242 };
2243
2244 #[kani::proof]
2248 fn increment_counter_is_be32_inc() {
2249 let mut counter: [u8; 16] = kani::any();
2250 let original = counter;
2251 increment_counter(&mut counter);
2252 assert!(counter[..12] == original[..12]);
2253 let low = u32::from_be_bytes([original[12], original[13], original[14], original[15]]);
2254 assert!(counter[12..] == low.wrapping_add(1).to_be_bytes());
2255 }
2256
2257 #[kani::proof]
2259 fn j0_layout() {
2260 let nonce: [u8; NONCE_SIZE] = kani::any();
2261 let block = j0(&nonce);
2262 assert!(block[..NONCE_SIZE] == nonce);
2263 assert!(block[NONCE_SIZE..] == [0, 0, 0, 1]);
2264 }
2265
2266 #[kani::proof]
2269 fn validate_gcm_lengths_matches_limits() {
2270 let aad_len: usize = kani::any();
2271 let data_len: usize = kani::any();
2272 let result = validate_gcm_lengths(aad_len, data_len);
2273 let aad = aad_len as u64;
2274 let data = data_len as u64;
2275 let in_range =
2276 data <= MAX_GCM_DATA_LEN && aad <= MAX_GHASH_INPUT_LEN && data <= MAX_GHASH_INPUT_LEN;
2277 assert!(result.is_ok() == in_range);
2278 }
2279
2280 #[kani::proof]
2283 fn nonce_from_slice_accepts_only_correct_length() {
2284 let len: usize = kani::any();
2285 kani::assume(len <= 24);
2286 let buf = [0_u8; 24];
2287 let result = nonce_from_slice(&buf[..len]);
2288 match result {
2289 Ok(n) => assert!(len == NONCE_SIZE && n.len() == NONCE_SIZE),
2290 Err(e) => assert!(len != NONCE_SIZE && matches!(e, Error::InvalidNonceLength)),
2291 }
2292 }
2293
2294 #[kani::proof]
2305 fn constant_time_eq_equals_bytewise_equality() {
2306 let expected: [u8; TAG_SIZE] = kani::any();
2307 let actual: [u8; TAG_SIZE] = kani::any();
2308 assert!(constant_time_eq(&expected, &actual) == (expected == actual));
2309 }
2310}