Skip to main content

hardware_rust_crypto/aes_gcm/
mod.rs

1//! Candidate AES-256-GCM API for the hardware-only `RustCrypto` fork.
2//!
3//! The implementation uses vendored hardware-only AES and GHASH paths on
4//! supported `x86_64` and `aarch64` targets, with no software AES fallback
5//! compiled into the reusable key state. See the repository `NOTICE` file for
6//! upstream attribution of the vendored backends.
7//!
8//! # Constant-time notes
9//!
10//! All secret-dependent computation happens in the hardware AES and
11//! carryless-multiply backends (see the `aes` and `ghash` module docs) or in
12//! straight-line XOR/copy loops whose trip counts derive from public
13//! lengths. Control flow in this module branches only on public values:
14//! input and buffer lengths, CPU feature availability, and the accept/reject
15//! outcome of the tag check - which is itself computed in constant time via
16//! `subtle` and is public by definition (the caller learns it from the
17//! `Result`). Nonces, counter blocks, and lengths are not secret in GCM;
18//! keystream and tag material never feed a branch condition or memory index.
19//! Data-independent instruction timing is taken as a hardware-vendor
20//! guarantee; see `docs/design.md` for the full constant-time basis.
21
22#![allow(unsafe_code)]
23
24mod aes;
25mod fork;
26mod ghash;
27mod nonce;
28mod siv;
29
30/// Re-exported only under `ct-verify` so the constant-time verifier can reach
31/// the `mulx` wrapper and its non-vacuity control as public symbols; not part of
32/// the shipped API.
33#[cfg(feature = "ct-verify")]
34pub use ghash::{ct_verify_leaky_control, ct_verify_mulx};
35
36/// Re-exported only under `saw-verify` so SAW can reach the field-multiply
37/// wrappers as public symbols; not part of the shipped API.
38#[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
56/// AES-256 key length in bytes.
57pub const KEY_SIZE: usize = 32;
58/// GCM nonce length in bytes.
59pub const NONCE_SIZE: usize = 12;
60/// GCM authentication tag length in bytes.
61pub 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/// Opaque key-state layout reported to callers before they allocate storage.
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub struct KeyStateLayout {
71    /// Required storage size in bytes.
72    pub size: usize,
73    /// Required storage alignment in bytes.
74    pub align: usize,
75}
76
77/// Error type for the candidate AES-256-GCM API.
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79#[non_exhaustive]
80pub enum Error {
81    /// The provided key is not exactly 32 bytes.
82    InvalidKeyLength,
83    /// The provided nonce is not exactly 12 bytes.
84    InvalidNonceLength,
85    /// Encryption failed.
86    Encrypt,
87    /// Decryption or authentication failed.
88    Decrypt,
89    /// The nonce-appended ciphertext layout is too short to contain tag plus nonce.
90    CiphertextTooShort,
91    /// Required AES/GHASH hardware support is unavailable.
92    UnsupportedCpu,
93    /// Caller-provided key-state storage is too small.
94    KeyStateStorageTooSmall,
95    /// Caller-provided key-state storage does not satisfy alignment.
96    KeyStateStorageMisaligned,
97    /// Input is too large for AES-GCM's counter or GHASH length limits.
98    InputTooLarge,
99    /// Caller-provided output buffer is too small for the plaintext.
100    OutputTooSmall,
101    /// The OS entropy source failed while generating a nonce.
102    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        // SAFETY: dst points to valid writable KeyState storage supplied by the
140        // caller. The field pointer stays within that allocation.
141        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        // SAFETY: aes_ptr was initialized successfully above and remains live.
146        unsafe { (&*aes_ptr).encrypt_block(&mut hash_subkey) };
147        // SAFETY: dst points to valid writable KeyState storage supplied by the
148        // caller. The field pointer stays within that allocation.
149        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            // Unreachable: hardware_available() covered GHASH features above.
152            // Defensively wipe the AES state already expanded in caller
153            // storage before reporting failure.
154            hash_subkey.zeroize();
155            // SAFETY: aes_ptr was initialized above and is not used again.
156            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    /// Fused encrypt-and-authenticate: each ciphertext chunk feeds GHASH as
288    /// soon as it is produced, so the message is traversed once.
289    ///
290    /// Constant time with respect to secrets: chunking and tail handling
291    /// branch only on the public message length; the keystream is combined
292    /// by straight-line XOR.
293    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        // The bulk region's AES keystream and GHASH run in one fused,
308        // software-pipelined loop so the AES and carryless-multiply pipelines
309        // overlap; the sub-batch tail is handled serially.
310        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    /// In-place encrypt-and-authenticate. `data` enters as plaintext and exits
337    /// as ciphertext, so allocating APIs can avoid zero-filling an output Vec
338    /// before immediately overwriting it.
339    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    /// Single-block in-place CTR for sub-batch tails.
382    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        // The comparison itself is constant time (subtle); branching on its
445        // result is sound because accept/reject is public output - the
446        // caller observes it through the Result either way. On rejection, wipe
447        // the transient plaintext written before authentication completed.
448        if !constant_time_eq(&expected, tag) {
449            out.zeroize();
450            return Err(Error::Decrypt);
451        }
452
453        Ok(ciphertext.len())
454    }
455
456    /// Fused authenticate-and-decrypt: bulk ciphertext chunks feed GHASH while
457    /// their CTR plaintext is written to the caller's buffer. The caller must
458    /// zeroize `plaintext` if the returned tag does not match the supplied tag.
459    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        // Decrypt writes plaintext before the final tag check. This matches
474        // high-performance AEAD APIs such as ring's in-place open path; the
475        // public wrapper wipes the written range on authentication failure.
476        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    /// In-place authenticate-and-decrypt. `data` enters as ciphertext and
503    /// exits as plaintext. The caller must zeroize `data` if the returned tag
504    /// does not match the supplied tag.
505    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
537/// Caller-owned uninitialized storage for AES-256-GCM key state.
538pub struct UninitKeyStateSlot<'a> {
539    storage: &'a mut [u8],
540}
541
542impl<'a> UninitKeyStateSlot<'a> {
543    /// Validates caller-provided storage for key-state initialization.
544    ///
545    /// # Errors
546    ///
547    /// Returns [`Error::KeyStateStorageTooSmall`] or
548    /// [`Error::KeyStateStorageMisaligned`] before any key material is touched.
549    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    // The raw pointer is taken once and the `&mut` slice ends here, so the
581    // handle never aliases a live mutable reference.
582    // SAFETY: UninitKeyStateSlot validated that storage has non-zero size,
583    // sufficient length, and KeyState alignment, so the pointer is non-null.
584    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
591/// Opaque initialized key-equivalent state in caller-owned storage.
592///
593/// The handle stores a raw pointer into the caller's storage instead of the
594/// caller's `&mut` slice itself, so reads through the handle never coexist
595/// with a live mutable reference under strict aliasing models.
596pub 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    // Compile-time proof that KeyState stays thread-safe; the unsafe Send/Sync
604    // impls below rely on it.
605    assert_send_sync::<KeyState>();
606};
607
608// SAFETY: OpaqueKeyState exclusively owns the KeyState in the caller storage
609// for 'a (the storage `&mut` borrow is consumed on construction). KeyState is
610// Send + Sync (asserted above), there is no interior mutability, and all
611// access outside drop is read-only, so moving the handle across threads is
612// sound.
613unsafe impl Send for OpaqueKeyState<'_> {}
614// SAFETY: see the Send impl; a shared OpaqueKeyState only permits shared reads
615// of a Sync pointee.
616unsafe impl Sync for OpaqueKeyState<'_> {}
617
618impl OpaqueKeyState<'_> {
619    fn state_ptr(&self) -> *mut KeyState {
620        // UninitKeyStateSlot validated KeyState alignment before this handle
621        // could be constructed.
622        #[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        // KeyState's field Drop impls (Aes256, GHashKey) exist only to wipe
631        // their own bytes and release no heap or handle resources, so they
632        // are deliberately not run here: one volatile wipe of the whole
633        // storage supersedes them and avoids a redundant second pass. If
634        // KeyState ever gains a resource-owning field, this must go back to
635        // drop_in_place before the wipe.
636        // SAFETY: the caller storage remains borrowed for 'a, holds at least
637        // `size` bytes, and is KeyState-aligned (validated at slot
638        // construction).
639        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
649/// AES-256-GCM instance backed by caller-owned key-state storage.
650pub struct HardwareAes256GcmIn<'a> {
651    state: OpaqueKeyState<'a>,
652    /// Lazily initialized on the first encrypting default API call. Not part
653    /// of the caller-placed key state, so it does not affect
654    /// `key_state_layout`.
655    nonce_gen: Option<nonce::NonceGen>,
656}
657
658impl<'a> HardwareAes256GcmIn<'a> {
659    /// Initializes reusable AES-256-GCM state directly in caller-owned storage.
660    ///
661    /// # Errors
662    ///
663    /// Returns storage validation errors, [`Error::InvalidKeyLength`], or
664    /// [`Error::UnsupportedCpu`].
665    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    /// Encrypts `plaintext` and returns `ciphertext || tag || nonce`.
677    ///
678    /// # Errors
679    ///
680    /// Returns [`Error::OsEntropy`] if nonce generation fails, plus
681    /// [`Error::InputTooLarge`] or [`Error::Encrypt`].
682    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    /// Encrypts `plaintext` into a caller-provided buffer as
688    /// `ciphertext || tag || nonce` and returns the written length.
689    ///
690    /// No heap allocation is performed.
691    ///
692    /// # Errors
693    ///
694    /// Returns [`Error::OsEntropy`] if nonce generation fails,
695    /// [`Error::OutputTooSmall`] if `out` is shorter than
696    /// `plaintext.len() + TAG_SIZE + NONCE_SIZE`, plus [`Error::InputTooLarge`]
697    /// or [`Error::Encrypt`].
698    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    /// Encrypts `plaintext` into a caller-provided buffer as
721    /// `ciphertext || tag` and returns the written length.
722    ///
723    /// No heap allocation is performed.
724    ///
725    /// # Errors
726    ///
727    /// Returns [`Error::OutputTooSmall`] if `out` is shorter than
728    /// `plaintext.len() + TAG_SIZE`, plus the same errors as
729    /// [`Self::encrypt_with_nonce`].
730    #[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    /// Decrypts `ciphertext || tag || nonce` and returns plaintext.
743    ///
744    /// # Errors
745    ///
746    /// Returns [`Error::CiphertextTooShort`], [`Error::InputTooLarge`], or
747    /// [`Error::Decrypt`].
748    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    /// Decrypts `ciphertext || tag || nonce` into a caller-provided buffer and
753    /// returns the plaintext length.
754    ///
755    /// Decrypts into `out` before the final tag comparison so CTR and GHASH can
756    /// run in one fused pass. If authentication fails, the plaintext-length
757    /// prefix of `out` is zeroized before returning [`Error::Decrypt`].
758    ///
759    /// # Errors
760    ///
761    /// Returns [`Error::CiphertextTooShort`], [`Error::OutputTooSmall`],
762    /// [`Error::InputTooLarge`], or [`Error::Decrypt`].
763    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    /// Decrypts `ciphertext || tag` into a caller-provided buffer and returns
785    /// the plaintext length.
786    ///
787    /// Decrypts into `out` before the final tag comparison so CTR and GHASH can
788    /// run in one fused pass. If authentication fails, the plaintext-length
789    /// prefix of `out` is zeroized before returning [`Error::Decrypt`].
790    ///
791    /// # Errors
792    ///
793    /// Returns [`Error::OutputTooSmall`] if `out` is shorter than the
794    /// plaintext, plus the same errors as [`Self::decrypt_with_nonce`].
795    #[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    /// Encrypts and appends the nonce: `ciphertext || tag || nonce`.
809    ///
810    /// # Errors
811    ///
812    /// Returns the same errors as [`Self::encrypt_with_nonce`].
813    #[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    /// Encrypts the plaintext already in `in_out` in place, then appends the
820    /// tag and nonce so the final layout is `ciphertext || tag || nonce`.
821    ///
822    /// If `in_out` has capacity for `plaintext.len() + TAG_SIZE + NONCE_SIZE`,
823    /// this performs no heap allocation. The buffer is zeroized before
824    /// returning [`Error::Encrypt`] if encryption fails after mutating it.
825    ///
826    /// # Errors
827    ///
828    /// Returns the same errors as [`Self::encrypt_with_nonce`].
829    #[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    /// Encrypts into a caller-provided buffer with the nonce appended and
841    /// returns the written length. No heap allocation is performed.
842    ///
843    /// # Errors
844    ///
845    /// Returns [`Error::OutputTooSmall`] if `out` is shorter than
846    /// `plaintext.len() + TAG_SIZE + NONCE_SIZE`, plus the same errors as
847    /// [`Self::encrypt_with_nonce`].
848    #[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    /// Decrypts the nonce-appended layout: `ciphertext || tag || nonce`.
860    ///
861    /// # Errors
862    ///
863    /// Returns [`Error::CiphertextTooShort`] if the input cannot contain a tag
864    /// and nonce. Returns [`Error::InvalidNonceLength`],
865    /// [`Error::InputTooLarge`], or [`Error::Decrypt`].
866    #[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    /// Decrypts the nonce-appended layout into a caller-provided buffer and
872    /// returns the plaintext length.
873    ///
874    /// # Errors
875    ///
876    /// Returns the same errors as [`Self::decrypt_nonce_appended`] plus
877    /// [`Error::OutputTooSmall`].
878    #[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    /// Encrypts `plaintext` under a library-generated unique nonce, returning
884    /// the nonce alongside `ciphertext || tag`.
885    ///
886    /// The nonce is drawn from a per-instance sequence (96-bit OS-seeded salt
887    /// plus a 64-bit counter, re-salted on fork; see [`crate`]); callers do
888    /// not manage nonce uniqueness. The returned nonce must be retained to
889    /// decrypt.
890    ///
891    /// # Errors
892    ///
893    /// Returns [`Error::OsEntropy`] if seeding the nonce sequence fails, plus
894    /// the same errors as [`Self::encrypt`].
895    #[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    /// Encrypts `plaintext` under a library-generated unique nonce and returns
907    /// the self-framed `ciphertext || tag || nonce` layout (empty AAD).
908    ///
909    /// # Errors
910    ///
911    /// Returns the same errors as [`Self::encrypt_with_generated_nonce`].
912    #[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        // SAFETY: OpaqueKeyState owns a live initialized KeyState until drop.
927        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        // SAFETY: MaybeUninit<KeyState> is non-null and aligned for KeyState.
955        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
964/// Allocation-free owned reusable AES-256-GCM key state.
965///
966/// This stores the same opaque key-equivalent bytes reported by
967/// [`HardwareAes256Gcm::key_state_layout`] inline in the value, avoiding the
968/// heap allocation used by [`HardwareAes256Gcm`] while still wiping the key
969/// state on drop. The nonce generator is held outside the inline key state.
970pub struct HardwareAes256GcmKeyState {
971    storage: AlignedKeyStateStorage,
972    nonce_gen: Option<nonce::NonceGen>,
973}
974
975impl HardwareAes256GcmKeyState {
976    /// Creates reusable AES-256-GCM state from a raw 32-byte key without heap
977    /// allocation.
978    ///
979    /// # Errors
980    ///
981    /// Returns [`Error::InvalidKeyLength`] if `key` is not exactly 32 bytes,
982    /// or [`Error::UnsupportedCpu`] if required AES-GCM hardware is absent.
983    #[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    /// Returns the current size of the reusable inline key state.
994    #[must_use]
995    pub const fn state_size() -> usize {
996        std::mem::size_of::<KeyState>()
997    }
998
999    /// Encrypts `plaintext` and returns `ciphertext || tag || nonce`.
1000    ///
1001    /// # Errors
1002    ///
1003    /// Returns [`Error::OsEntropy`] if nonce generation fails,
1004    /// [`Error::InputTooLarge`] if `plaintext` or `aad` exceed the AES-GCM
1005    /// limits, or [`Error::Encrypt`] if the backend rejects encryption.
1006    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    /// Encrypts `plaintext` into a caller-provided buffer as
1012    /// `ciphertext || tag || nonce` and returns the written length.
1013    ///
1014    /// No heap allocation is performed.
1015    ///
1016    /// # Errors
1017    ///
1018    /// Returns [`Error::OsEntropy`] if nonce generation fails,
1019    /// [`Error::OutputTooSmall`] if `out` is shorter than
1020    /// `plaintext.len() + TAG_SIZE + NONCE_SIZE`, [`Error::InputTooLarge`] if
1021    /// `plaintext` or `aad` exceed the AES-GCM limits, or [`Error::Encrypt`]
1022    /// if the backend rejects encryption.
1023    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    /// Decrypts `ciphertext || tag || nonce` and returns plaintext.
1035    ///
1036    /// # Errors
1037    ///
1038    /// Returns [`Error::CiphertextTooShort`], [`Error::InputTooLarge`], or
1039    /// [`Error::Decrypt`] if authentication fails.
1040    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    /// Decrypts `ciphertext || tag || nonce` into a caller-provided buffer and
1045    /// returns the plaintext length.
1046    ///
1047    /// Decrypts into `out` before the final tag comparison so CTR and GHASH can
1048    /// run in one fused pass. If authentication fails, the plaintext-length
1049    /// prefix of `out` is zeroized before returning [`Error::Decrypt`].
1050    ///
1051    /// # Errors
1052    ///
1053    /// Returns [`Error::CiphertextTooShort`], [`Error::OutputTooSmall`],
1054    /// [`Error::InputTooLarge`], or [`Error::Decrypt`] if authentication fails.
1055    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    /// Encrypts and appends the nonce: `ciphertext || tag || nonce`.
1066    ///
1067    /// # Errors
1068    ///
1069    /// Returns [`Error::InvalidNonceLength`], [`Error::InputTooLarge`], or
1070    /// [`Error::Encrypt`].
1071    #[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    /// Encrypts the plaintext already in `in_out` in place, then appends the
1079    /// tag and nonce so the final layout is `ciphertext || tag || nonce`.
1080    ///
1081    /// If `in_out` has capacity for `plaintext.len() + TAG_SIZE + NONCE_SIZE`,
1082    /// this performs no heap allocation. The buffer is zeroized before
1083    /// returning [`Error::Encrypt`] if encryption fails after mutating it.
1084    ///
1085    /// # Errors
1086    ///
1087    /// Returns the same errors as [`Self::encrypt_nonce_appended`].
1088    #[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    /// Decrypts the nonce-appended layout: `ciphertext || tag || nonce`.
1101    ///
1102    /// # Errors
1103    ///
1104    /// Returns [`Error::CiphertextTooShort`] if the input cannot contain a tag
1105    /// and nonce. Returns [`Error::InvalidNonceLength`],
1106    /// [`Error::InputTooLarge`], or [`Error::Decrypt`].
1107    #[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        // SAFETY: HardwareAes256GcmKeyState::new initializes storage before
1123        // constructing Self, and the storage is never mutated except during
1124        // Drop after all shared borrows have ended.
1125        unsafe { &*self.storage.state_ptr() }
1126    }
1127}
1128
1129impl Drop for HardwareAes256GcmKeyState {
1130    fn drop(&mut self) {
1131        let size = Self::state_size();
1132        // KeyState's field Drop impls exist only to wipe their own bytes and
1133        // release no heap or handle resources, so one volatile wipe of the
1134        // inline storage supersedes those per-field wipes. If KeyState ever
1135        // gains a resource-owning field, this must run drop_in_place first.
1136        // SAFETY: storage is inline in self, writable for `size` bytes, and
1137        // aligned for KeyState by AlignedKeyStateStorage.
1138        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
1149/// Owned reusable hardware-only AES-256-GCM key state.
1150pub struct HardwareAes256Gcm {
1151    state: Box<KeyState>,
1152    /// Lazily initialized on the first encrypting default API call. Held
1153    /// outside `KeyState`, so it does not affect `state_size`/the boxed
1154    /// key-state footprint.
1155    nonce_gen: Option<nonce::NonceGen>,
1156}
1157
1158impl HardwareAes256Gcm {
1159    /// Creates reusable AES-256-GCM state from a raw 32-byte key.
1160    ///
1161    /// # Errors
1162    ///
1163    /// Returns [`Error::InvalidKeyLength`] if `key` is not exactly 32 bytes.
1164    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        // SAFETY: Box::new_uninit returns a non-null allocation for KeyState.
1170        let ptr = unsafe { NonNull::new_unchecked(state.as_mut_ptr()) };
1171        init_key_state_at(key, ptr)?;
1172        // SAFETY: KeyState::init_in_place initialized the allocation on success.
1173        let state = unsafe { state.assume_init() };
1174        Ok(Self {
1175            state,
1176            nonce_gen: None,
1177        })
1178    }
1179
1180    /// Returns whether all required AES-GCM hardware features are available.
1181    #[must_use]
1182    pub fn hardware_available() -> bool {
1183        aes::hardware_available() && ghash::hardware_available()
1184    }
1185
1186    /// Returns the current size of the reusable key state.
1187    ///
1188    /// This is a benchmark/validation hook for the compact hardware-only state.
1189    #[must_use]
1190    pub const fn state_size() -> usize {
1191        std::mem::size_of::<KeyState>()
1192    }
1193
1194    /// Returns the current opaque key-state layout.
1195    ///
1196    /// Reports the exact layout required for caller-provided opaque storage.
1197    #[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    /// Encrypts `plaintext` and returns `ciphertext || tag || nonce`.
1206    ///
1207    /// # Errors
1208    ///
1209    /// Returns [`Error::OsEntropy`] if nonce generation fails,
1210    /// [`Error::InputTooLarge`] if `plaintext` or `aad` exceed the AES-GCM
1211    /// limits, or [`Error::Encrypt`] if the backend rejects encryption.
1212    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    /// Encrypts `plaintext` into a caller-provided buffer as
1218    /// `ciphertext || tag || nonce` and returns the written length.
1219    ///
1220    /// No heap allocation is performed.
1221    ///
1222    /// # Errors
1223    ///
1224    /// Returns [`Error::OsEntropy`] if nonce generation fails,
1225    /// [`Error::OutputTooSmall`] if `out` is shorter than
1226    /// `plaintext.len() + TAG_SIZE + NONCE_SIZE`, [`Error::InputTooLarge`] if
1227    /// `plaintext` or `aad` exceed the AES-GCM limits, or [`Error::Encrypt`]
1228    /// if the backend rejects encryption.
1229    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    /// Encrypts `plaintext` into a caller-provided buffer as
1251    /// `ciphertext || tag` and returns the written length.
1252    ///
1253    /// No heap allocation is performed.
1254    ///
1255    /// # Errors
1256    ///
1257    /// Returns [`Error::OutputTooSmall`] if `out` is shorter than
1258    /// `plaintext.len() + TAG_SIZE`, plus the same errors as
1259    /// [`Self::encrypt_with_nonce`].
1260    #[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    /// Decrypts `ciphertext || tag || nonce` and returns plaintext.
1273    ///
1274    /// # Errors
1275    ///
1276    /// Returns [`Error::CiphertextTooShort`], [`Error::InputTooLarge`], or
1277    /// [`Error::Decrypt`] if authentication fails.
1278    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    /// Decrypts `ciphertext || tag || nonce` into a caller-provided buffer and
1283    /// returns the plaintext length.
1284    ///
1285    /// Decrypts into `out` before the final tag comparison so CTR and GHASH can
1286    /// run in one fused pass. If authentication fails, the plaintext-length
1287    /// prefix of `out` is zeroized before returning [`Error::Decrypt`].
1288    ///
1289    /// # Errors
1290    ///
1291    /// Returns [`Error::CiphertextTooShort`], [`Error::OutputTooSmall`],
1292    /// [`Error::InputTooLarge`], or [`Error::Decrypt`] if authentication fails.
1293    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    /// Decrypts `ciphertext || tag` into a caller-provided buffer and returns
1315    /// the plaintext length.
1316    ///
1317    /// Decrypts into `out` before the final tag comparison so CTR and GHASH can
1318    /// run in one fused pass. If authentication fails, the plaintext-length
1319    /// prefix of `out` is zeroized before returning [`Error::Decrypt`].
1320    ///
1321    /// # Errors
1322    ///
1323    /// Returns [`Error::OutputTooSmall`] if `out` is shorter than the
1324    /// plaintext, plus the same errors as [`Self::decrypt_with_nonce`].
1325    #[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    /// Encrypts and appends the nonce: `ciphertext || tag || nonce`.
1338    ///
1339    /// # Errors
1340    ///
1341    /// Returns the same errors as [`Self::encrypt_with_nonce`].
1342    #[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    /// Encrypts the plaintext already in `in_out` in place, then appends the
1349    /// tag and nonce so the final layout is `ciphertext || tag || nonce`.
1350    ///
1351    /// If `in_out` has capacity for `plaintext.len() + TAG_SIZE + NONCE_SIZE`,
1352    /// this performs no heap allocation. The buffer is zeroized before
1353    /// returning [`Error::Encrypt`] if encryption fails after mutating it.
1354    ///
1355    /// # Errors
1356    ///
1357    /// Returns the same errors as [`Self::encrypt_with_nonce`].
1358    #[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    /// Encrypts into a caller-provided buffer with the nonce appended and
1369    /// returns the written length. No heap allocation is performed.
1370    ///
1371    /// # Errors
1372    ///
1373    /// Returns [`Error::OutputTooSmall`] if `out` is shorter than
1374    /// `plaintext.len() + TAG_SIZE + NONCE_SIZE`, plus the same errors as
1375    /// [`Self::encrypt_with_nonce`].
1376    #[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    /// Decrypts the nonce-appended layout: `ciphertext || tag || nonce`.
1388    ///
1389    /// # Errors
1390    ///
1391    /// Returns [`Error::CiphertextTooShort`] if the input cannot contain a tag
1392    /// and nonce. Returns [`Error::InvalidNonceLength`],
1393    /// [`Error::InputTooLarge`], or [`Error::Decrypt`] for malformed
1394    /// nonce/authentication failures.
1395    #[doc(hidden)]
1396    pub fn decrypt_nonce_appended(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
1397        self.state.decrypt_envelope(&[], data)
1398    }
1399
1400    /// Decrypts the nonce-appended layout into a caller-provided buffer and
1401    /// returns the plaintext length.
1402    ///
1403    /// # Errors
1404    ///
1405    /// Returns the same errors as [`Self::decrypt_nonce_appended`] plus
1406    /// [`Error::OutputTooSmall`].
1407    #[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    /// Encrypts `plaintext` under a library-generated unique nonce, returning
1413    /// the nonce alongside `ciphertext || tag`.
1414    ///
1415    /// The nonce is drawn from a per-instance sequence (96-bit OS-seeded salt
1416    /// plus a 64-bit counter, re-salted on fork; see [`crate`]); callers do
1417    /// not manage nonce uniqueness. The returned nonce must be retained to
1418    /// decrypt.
1419    ///
1420    /// # Errors
1421    ///
1422    /// Returns [`Error::OsEntropy`] if seeding the nonce sequence fails, plus
1423    /// the same errors as [`Self::encrypt`].
1424    #[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    /// Encrypts `plaintext` under a library-generated unique nonce and returns
1436    /// the self-framed `ciphertext || tag || nonce` layout (empty AAD).
1437    ///
1438    /// # Errors
1439    ///
1440    /// Returns the same errors as [`Self::encrypt_with_generated_nonce`].
1441    #[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    // SAFETY: the TAG_SIZE + NONCE_SIZE spare bytes immediately after the
1478    // original length were initialized above, and capacity was ensured by the
1479    // caller before encryption mutated the buffer.
1480    unsafe { out.set_len(start + TAG_SIZE + NONCE_SIZE) };
1481}
1482
1483/// Volatile wipe of caller-placed key-state storage using the widest stores
1484/// the pointer alignment allows.
1485///
1486/// # Safety
1487///
1488/// `bytes..bytes + len` must be writable.
1489#[allow(clippy::cast_ptr_alignment)] // Wide stores are alignment-checked at runtime.
1490unsafe 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            // SAFETY: in bounds per the caller contract and aligned per the
1495            // check above.
1496            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        // SAFETY: in bounds per the caller contract.
1502        unsafe { ptr::write_volatile(bytes.add(offset), 0) };
1503    }
1504    core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
1505}
1506
1507/// Shared no-alloc nonce-appended encryption: `ciphertext || tag || nonce`.
1508#[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    // encrypt_to validated the nonce length, so this copy is exactly
1524    // NONCE_SIZE bytes.
1525    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
1550/// Operates on the public nonce-derived counter block; constant time anyway
1551/// (`wrapping_add` carries without branching).
1552pub(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/// Stable-symbol `extern "C"` wrapper so the SAW LLVM-bitcode verifier can prove
1560/// the *compiled* `increment_counter` matches its Cryptol spec (`proofs/saw/`).
1561/// Build-time only; `saw-verify` is never a shipped feature.
1562#[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    // SAFETY: SAW calls this with a valid, writable 16-byte buffer.
1567    let c = unsafe { &mut *(counter.cast::<[u8; 16]>()) };
1568    increment_counter(c);
1569}
1570
1571/// Stable-symbol `extern "C"` wrapper around `j0` for SAW (`proofs/saw/`).
1572/// Build-time only; never shipped.
1573#[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    // SAFETY: SAW calls this with a readable 12-byte `nonce` and writable 16-byte `out`.
1578    let n = unsafe { &*(nonce.cast::<[u8; NONCE_SIZE]>()) };
1579    let block = j0(n);
1580    // SAFETY: `out` is a writable 16-byte buffer.
1581    unsafe { core::ptr::copy_nonoverlapping(block.as_ptr(), out, 16) };
1582}
1583
1584fn constant_time_eq(expected: &[u8; TAG_SIZE], actual: &[u8]) -> bool {
1585    // subtle's slice impl compares lengths first (length is public) and then
1586    // the contents in constant time behind optimization barriers, so the
1587    // compiler cannot reintroduce an early-exit byte comparison. This is the
1588    // one place in the crate where secret-derived values are compared.
1589    expected.as_slice().ct_eq(actual).into()
1590}
1591
1592/// Non-inlined wrapper so the constant-time verifier can disassemble the tag
1593/// comparison as a named symbol and confirm it compiles branch-free, with the
1594/// loop length fixed to `TAG_SIZE` (see `proofs/constant-time/verify.sh`).
1595/// Build-time only; `ct-verify` is never a shipped feature.
1596#[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    /// Anchors the Z3 model in `proofs/prove_composition.py` to the real compiled
1620    /// code: `increment_counter` is the SP 800-38D `inc_32` (big-endian counter in
1621    /// the trailing four bytes, wrapping mod 2^32, leading 96 bits preserved).
1622    #[test]
1623    fn increment_counter_is_be32_inc_leaving_high_96_bits() {
1624        // Wrap across the 32-bit boundary; the leading 12 bytes must not change.
1625        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        // Plain carry into the next byte, big-endian.
1641        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        // J0 for a 96-bit nonce is IV || 0^31 || 1 (SP 800-38D).
1647        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        // Same key + same plaintext must not reuse a nonce.
1858        assert_ne!(nonce_a, nonce_b, "generated nonces must be unique");
1859        // The returned nonce decrypts its ciphertext.
1860        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    /// Exercises *every* `HardwareAes256GcmIn` explicit-buffer / nonce-appended
1901    /// method with a verified round trip (and byte-exact cross-validation against
1902    /// `HardwareAes256Gcm` for the explicit-nonce paths). Without this, the thin
1903    /// delegations to the underlying key state are called but their output is
1904    /// never checked, so a wrong (or stubbed) wrapper survives - a gap surfaced
1905    /// by mutation testing (see docs/mutation-testing.md).
1906    #[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        // Independent reference for byte-exact cross-validation.
1918        let reference = HardwareAes256Gcm::new(&KEY).expect("reference key");
1919
1920        // --- explicit nonce: byte-identical to the reference, and round-trips ---
1921        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        // --- nonce-appended (empty AAD), explicit nonce ---
1944        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        // In-place encrypt: from an under-capacity Vec (forces the reserve path)
1962        // and from an exact-capacity Vec; both must round-trip.
1963        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        // --- generated nonce: round-trips, and successive outputs differ ----
1973        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    /// The owned-key-state `encrypt_to` must write a real envelope that decrypts
1990    /// back (a delegation previously not output-verified; mutation testing).
1991    #[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    /// `validate_gcm_lengths` must reject an input that exceeds *any one* of the
2002    /// length limits (not only all of them) - exercised with length *values*, so
2003    /// no allocation is needed. Pins the `||` chain against a `&&` regression
2004    /// (mutation testing) and complements the Kani harness, which `cargo test`
2005    /// does not run.
2006    #[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        // Over the GCM counter limit on the data (AAD in range).
2011        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        // Over the GHASH 64-bit length-field limit on the AAD (data in range).
2015        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        // Exactly at the GCM data limit is accepted.
2019        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        // The nonce generator lives on the handle, not the placed key state.
2027        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        // SAFETY: key lives in this stack frame and ManuallyDrop keeps the
2106        // backing bytes valid after its Drop impl runs (the value is logically
2107        // dropped but not freed). The storage pointer is taken *after* the drop
2108        // so its provenance is not invalidated by the `&mut key` the drop uses -
2109        // reading the just-wiped bytes through a freshly-derived pointer.
2110        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/// Kani bounded-model-checking harnesses for the intrinsic-free GCM logic.
2231///
2232/// Unlike the Z3 proofs in `proofs/`, which reason about a faithful *model* of
2233/// the code, Kani (CBMC) verifies the **actual compiled Rust** symbolically over
2234/// all inputs (bounded where noted) - so these are extraction-grade proofs of
2235/// the counter increment, the length validation, and the nonce parser. Run with
2236/// `cargo kani` (see docs/assurance.md). Compiled only under `cfg(kani)`.
2237#[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    /// The compiled `increment_counter` is exactly SP 800-38D `inc_32`: the
2245    /// trailing 32 bits increment big-endian (wrapping), the leading 96 bits are
2246    /// untouched. Verified over all 2^128 counter blocks.
2247    #[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    /// `j0` builds `IV || 0^31 || 1` for any 96-bit nonce.
2258    #[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    /// `validate_gcm_lengths` never panics and accepts exactly the lengths within
2267    /// both the GCM counter limit and the GHASH 64-bit length field.
2268    #[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    /// `nonce_from_slice` never panics and returns Ok exactly when the slice is
2281    /// the 12-byte nonce length. Bounded to lengths 0..=24.
2282    #[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    /// The authentication decision is functionally exact: for equal-length
2295    /// inputs `constant_time_eq` is bytewise equality. (Constant-*time*-ness is
2296    /// covered separately by the dudect harnesses; this proves *correctness* -
2297    /// that the masking/folding never accepts a wrong tag or rejects a right one.)
2298    /// Verified over all tag-pairs.
2299    ///
2300    /// Only the equal-length case is model-checked: callers always pass a
2301    /// `TAG_SIZE` slice (`split_ciphertext_tag`/the decrypt split are proven to
2302    /// hand over exactly `TAG_SIZE` bytes), and `subtle`'s variable-length slice
2303    /// loop does not bound under CBMC.
2304    #[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}