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;
28
29use core::{
30    marker::PhantomData,
31    mem::MaybeUninit,
32    ptr::{self, NonNull},
33};
34use subtle::ConstantTimeEq as _;
35use zeroize::Zeroize as _;
36
37/// AES-256 key length in bytes.
38pub const KEY_SIZE: usize = 32;
39/// GCM nonce length in bytes.
40pub const NONCE_SIZE: usize = 12;
41/// GCM authentication tag length in bytes.
42pub const TAG_SIZE: usize = 16;
43const AES_BLOCK_SIZE: usize = 16;
44const PAR_BLOCKS: usize = aes::PAR_BLOCKS;
45const PAR_BYTES: usize = PAR_BLOCKS * AES_BLOCK_SIZE;
46const MAX_GCM_DATA_LEN: u64 = ((u32::MAX as u64) - 1) * AES_BLOCK_SIZE as u64;
47const MAX_GHASH_INPUT_LEN: u64 = u64::MAX / 8;
48
49/// Opaque key-state layout reported to callers before they allocate storage.
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub struct KeyStateLayout {
52    /// Required storage size in bytes.
53    pub size: usize,
54    /// Required storage alignment in bytes.
55    pub align: usize,
56}
57
58/// Error type for the candidate AES-256-GCM API.
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60#[non_exhaustive]
61pub enum Error {
62    /// The provided key is not exactly 32 bytes.
63    InvalidKeyLength,
64    /// The provided nonce is not exactly 12 bytes.
65    InvalidNonceLength,
66    /// Encryption failed.
67    Encrypt,
68    /// Decryption or authentication failed.
69    Decrypt,
70    /// The nonce-appended ciphertext layout is too short to contain tag plus nonce.
71    CiphertextTooShort,
72    /// Required AES/GHASH hardware support is unavailable.
73    UnsupportedCpu,
74    /// Caller-provided key-state storage is too small.
75    KeyStateStorageTooSmall,
76    /// Caller-provided key-state storage does not satisfy alignment.
77    KeyStateStorageMisaligned,
78    /// Input is too large for AES-GCM's counter or GHASH length limits.
79    InputTooLarge,
80    /// Caller-provided output buffer is too small for the plaintext.
81    OutputTooSmall,
82    /// The OS entropy source failed while generating a nonce.
83    OsEntropy,
84}
85
86impl std::fmt::Display for Error {
87    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88        match self {
89            Self::InvalidKeyLength => f.write_str("invalid AES-256-GCM key length"),
90            Self::InvalidNonceLength => f.write_str("invalid AES-GCM nonce length"),
91            Self::Encrypt => f.write_str("AES-256-GCM encryption failed"),
92            Self::Decrypt => f.write_str("AES-256-GCM authentication failed"),
93            Self::CiphertextTooShort => f.write_str("ciphertext too short"),
94            Self::UnsupportedCpu => {
95                f.write_str("required AES-256-GCM hardware support is unavailable")
96            }
97            Self::KeyStateStorageTooSmall => f.write_str("key-state storage too small"),
98            Self::KeyStateStorageMisaligned => f.write_str("key-state storage misaligned"),
99            Self::InputTooLarge => f.write_str("AES-256-GCM input too large"),
100            Self::OutputTooSmall => f.write_str("plaintext output buffer too small"),
101            Self::OsEntropy => f.write_str("OS entropy source failed during nonce generation"),
102        }
103    }
104}
105
106impl std::error::Error for Error {}
107
108struct KeyState {
109    aes: aes::Aes256,
110    ghash: ghash::GHashKey,
111}
112
113impl KeyState {
114    fn init_in_place(dst: NonNull<Self>, key: &[u8; KEY_SIZE]) -> Result<(), Error> {
115        if !HardwareAes256Gcm::hardware_available() {
116            return Err(Error::UnsupportedCpu);
117        }
118
119        let dst = dst.as_ptr();
120        // SAFETY: dst points to valid writable KeyState storage supplied by the
121        // caller. The field pointer stays within that allocation.
122        let aes_ptr = unsafe { ptr::addr_of_mut!((*dst).aes) };
123        aes::Aes256::init_in_place(aes_ptr, key).ok_or(Error::UnsupportedCpu)?;
124
125        let mut hash_subkey = [0_u8; TAG_SIZE];
126        // SAFETY: aes_ptr was initialized successfully above and remains live.
127        unsafe { (&*aes_ptr).encrypt_block(&mut hash_subkey) };
128        // SAFETY: dst points to valid writable KeyState storage supplied by the
129        // caller. The field pointer stays within that allocation.
130        let ghash_ptr = unsafe { ptr::addr_of_mut!((*dst).ghash) };
131        if ghash::GHashKey::init_in_place(ghash_ptr, &mut hash_subkey).is_none() {
132            // Unreachable: hardware_available() covered GHASH features above.
133            // Defensively wipe the AES state already expanded in caller
134            // storage before reporting failure.
135            hash_subkey.zeroize();
136            // SAFETY: aes_ptr was initialized above and is not used again.
137            unsafe { ptr::drop_in_place(aes_ptr) };
138            return Err(Error::UnsupportedCpu);
139        }
140        Ok(())
141    }
142
143    fn encrypt(&self, nonce: &[u8], aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
144        let total = plaintext
145            .len()
146            .checked_add(TAG_SIZE)
147            .ok_or(Error::InputTooLarge)?;
148        let mut out = vec![0_u8; total];
149        self.encrypt_to(nonce, aad, plaintext, &mut out)?;
150        Ok(out)
151    }
152
153    fn encrypt_nonce_appended(&self, nonce: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
154        let nonce = nonce_from_slice(nonce)?;
155        validate_gcm_lengths(0, plaintext.len())?;
156        let total = plaintext
157            .len()
158            .checked_add(TAG_SIZE + NONCE_SIZE)
159            .ok_or(Error::InputTooLarge)?;
160        let mut out = Vec::with_capacity(total);
161        out.extend_from_slice(plaintext);
162        let Some(tag) = self.seal_in_place(&nonce, &[], &mut out) else {
163            out.zeroize();
164            return Err(Error::Encrypt);
165        };
166        append_tag_nonce(&mut out, &tag, &nonce);
167        Ok(out)
168    }
169
170    fn encrypt_nonce_appended_in_place(
171        &self,
172        nonce: &[u8],
173        in_out: &mut Vec<u8>,
174    ) -> Result<(), Error> {
175        let nonce = nonce_from_slice(nonce)?;
176        validate_gcm_lengths(0, in_out.len())?;
177        let total = in_out
178            .len()
179            .checked_add(TAG_SIZE + NONCE_SIZE)
180            .ok_or(Error::InputTooLarge)?;
181        if in_out.capacity() < total {
182            in_out.reserve_exact(total - in_out.len());
183        }
184        let Some(tag) = self.seal_in_place(&nonce, &[], in_out.as_mut_slice()) else {
185            in_out.zeroize();
186            return Err(Error::Encrypt);
187        };
188        append_tag_nonce(in_out, &tag, &nonce);
189        Ok(())
190    }
191
192    fn encrypt_to(
193        &self,
194        nonce: &[u8],
195        aad: &[u8],
196        plaintext: &[u8],
197        out: &mut [u8],
198    ) -> Result<usize, Error> {
199        let nonce = nonce_from_slice(nonce)?;
200        validate_gcm_lengths(aad.len(), plaintext.len())?;
201        let total = plaintext
202            .len()
203            .checked_add(TAG_SIZE)
204            .ok_or(Error::InputTooLarge)?;
205        if out.len() < total {
206            return Err(Error::OutputTooSmall);
207        }
208
209        let (ciphertext, rest) = out.split_at_mut(plaintext.len());
210        let tag = self
211            .seal(&nonce, aad, plaintext, ciphertext)
212            .ok_or(Error::Encrypt)?;
213        rest[..TAG_SIZE].copy_from_slice(&tag);
214        Ok(total)
215    }
216
217    /// Fused encrypt-and-authenticate: each ciphertext chunk feeds GHASH as
218    /// soon as it is produced, so the message is traversed once.
219    ///
220    /// Constant time with respect to secrets: chunking and tail handling
221    /// branch only on the public message length; the keystream is combined
222    /// by straight-line XOR.
223    fn seal(
224        &self,
225        nonce: &[u8; NONCE_SIZE],
226        aad: &[u8],
227        plaintext: &[u8],
228        ciphertext: &mut [u8],
229    ) -> Option<[u8; TAG_SIZE]> {
230        debug_assert_eq!(plaintext.len(), ciphertext.len());
231        let mut ghasher = ghash::Ghasher::new(&self.ghash)?;
232        ghasher.absorb_padded(aad);
233
234        let mut counter = j0(nonce);
235        increment_counter(&mut counter);
236
237        // The bulk region's AES keystream and GHASH run in one fused,
238        // software-pipelined loop so the AES and carryless-multiply pipelines
239        // overlap; the sub-batch tail is handled serially.
240        let bulk = (plaintext.len() / PAR_BYTES) * PAR_BYTES;
241        let (plaintext_bulk, plaintext_tail) = plaintext.split_at(bulk);
242        let (ciphertext_bulk, ciphertext_tail) = ciphertext.split_at_mut(bulk);
243        if !plaintext_bulk.is_empty() {
244            ghasher.seal_bulk(
245                self.aes.round_keys(),
246                &mut counter,
247                plaintext_bulk,
248                ciphertext_bulk,
249            );
250        }
251        if !plaintext_tail.is_empty() {
252            ciphertext_tail.copy_from_slice(plaintext_tail);
253            self.apply_ctr_serial(&mut counter, ciphertext_tail);
254            ghasher.absorb_padded(ciphertext_tail);
255        }
256
257        let mut tag = ghasher.finalize(aad.len(), plaintext.len())?;
258        let mut mask = j0(nonce);
259        self.aes.encrypt_block(&mut mask);
260        for (tag_byte, mask_byte) in tag.iter_mut().zip(mask.iter()) {
261            *tag_byte ^= mask_byte;
262        }
263        Some(tag)
264    }
265
266    /// In-place encrypt-and-authenticate. `data` enters as plaintext and exits
267    /// as ciphertext, so allocating APIs can avoid zero-filling an output Vec
268    /// before immediately overwriting it.
269    fn seal_in_place(
270        &self,
271        nonce: &[u8; NONCE_SIZE],
272        aad: &[u8],
273        data: &mut [u8],
274    ) -> Option<[u8; TAG_SIZE]> {
275        let mut ghasher = ghash::Ghasher::new(&self.ghash)?;
276        ghasher.absorb_padded(aad);
277
278        let mut counter = j0(nonce);
279        increment_counter(&mut counter);
280
281        let bulk = (data.len() / PAR_BYTES) * PAR_BYTES;
282        let (data_bulk, data_tail) = data.split_at_mut(bulk);
283        if !data_bulk.is_empty() {
284            ghasher.seal_in_place_bulk(self.aes.round_keys(), &mut counter, data_bulk);
285        }
286        if !data_tail.is_empty() {
287            let full = (data_tail.len() / AES_BLOCK_SIZE) * AES_BLOCK_SIZE;
288            let (data_tail_blocks, data_partial) = data_tail.split_at_mut(full);
289            if !data_tail_blocks.is_empty() {
290                ghasher.seal_in_place_tail_blocks(
291                    self.aes.round_keys(),
292                    &mut counter,
293                    data_tail_blocks,
294                );
295            }
296            if !data_partial.is_empty() {
297                self.apply_ctr_serial(&mut counter, data_partial);
298                ghasher.absorb_padded(data_partial);
299            }
300        }
301
302        let mut tag = ghasher.finalize(aad.len(), data.len())?;
303        let mut mask = j0(nonce);
304        self.aes.encrypt_block(&mut mask);
305        for (tag_byte, mask_byte) in tag.iter_mut().zip(mask.iter()) {
306            *tag_byte ^= mask_byte;
307        }
308        Some(tag)
309    }
310
311    /// Single-block in-place CTR for sub-batch tails.
312    fn apply_ctr_serial(&self, counter: &mut [u8; AES_BLOCK_SIZE], data: &mut [u8]) {
313        let mut keystream = [0_u8; AES_BLOCK_SIZE];
314        for block in data.chunks_mut(AES_BLOCK_SIZE) {
315            keystream.copy_from_slice(counter);
316            self.aes.encrypt_block(&mut keystream);
317            for (byte, key_byte) in block.iter_mut().zip(keystream.iter()) {
318                *byte ^= key_byte;
319            }
320            increment_counter(counter);
321        }
322    }
323
324    fn decrypt(
325        &self,
326        nonce: &[u8],
327        aad: &[u8],
328        ciphertext_and_tag: &[u8],
329    ) -> Result<Vec<u8>, Error> {
330        let nonce = nonce_from_slice(nonce)?;
331        if ciphertext_and_tag.len() < TAG_SIZE {
332            return Err(Error::Decrypt);
333        }
334
335        let tag_pos = ciphertext_and_tag.len() - TAG_SIZE;
336        let (ciphertext, tag) = ciphertext_and_tag.split_at(tag_pos);
337        validate_gcm_lengths(aad.len(), ciphertext.len())?;
338        let mut out = Vec::with_capacity(ciphertext.len());
339        out.extend_from_slice(ciphertext);
340        let Some(expected) = self.open_in_place(&nonce, aad, &mut out) else {
341            out.zeroize();
342            return Err(Error::Decrypt);
343        };
344        if !constant_time_eq(&expected, tag) {
345            out.zeroize();
346            return Err(Error::Decrypt);
347        }
348        Ok(out)
349    }
350
351    fn decrypt_to(
352        &self,
353        nonce: &[u8],
354        aad: &[u8],
355        ciphertext_and_tag: &[u8],
356        out: &mut [u8],
357    ) -> Result<usize, Error> {
358        let nonce = nonce_from_slice(nonce)?;
359        if ciphertext_and_tag.len() < TAG_SIZE {
360            return Err(Error::Decrypt);
361        }
362
363        let tag_pos = ciphertext_and_tag.len() - TAG_SIZE;
364        let (ciphertext, tag) = ciphertext_and_tag.split_at(tag_pos);
365        validate_gcm_lengths(aad.len(), ciphertext.len())?;
366        if out.len() < ciphertext.len() {
367            return Err(Error::OutputTooSmall);
368        }
369        let out = &mut out[..ciphertext.len()];
370        let Some(expected) = self.open(&nonce, aad, ciphertext, out) else {
371            out.zeroize();
372            return Err(Error::Decrypt);
373        };
374        // The comparison itself is constant time (subtle); branching on its
375        // result is sound because accept/reject is public output - the
376        // caller observes it through the Result either way. On rejection, wipe
377        // the transient plaintext written before authentication completed.
378        if !constant_time_eq(&expected, tag) {
379            out.zeroize();
380            return Err(Error::Decrypt);
381        }
382
383        Ok(ciphertext.len())
384    }
385
386    /// Fused authenticate-and-decrypt: bulk ciphertext chunks feed GHASH while
387    /// their CTR plaintext is written to the caller's buffer. The caller must
388    /// zeroize `plaintext` if the returned tag does not match the supplied tag.
389    fn open(
390        &self,
391        nonce: &[u8; NONCE_SIZE],
392        aad: &[u8],
393        ciphertext: &[u8],
394        plaintext: &mut [u8],
395    ) -> Option<[u8; TAG_SIZE]> {
396        debug_assert_eq!(ciphertext.len(), plaintext.len());
397        let mut ghasher = ghash::Ghasher::new(&self.ghash)?;
398        ghasher.absorb_padded(aad);
399
400        let mut counter = j0(nonce);
401        increment_counter(&mut counter);
402
403        // Decrypt writes plaintext before the final tag check. This matches
404        // high-performance AEAD APIs such as ring's in-place open path; the
405        // public wrapper wipes the written range on authentication failure.
406        let bulk = (ciphertext.len() / PAR_BYTES) * PAR_BYTES;
407        let (ciphertext_bulk, ciphertext_tail) = ciphertext.split_at(bulk);
408        let (plaintext_bulk, plaintext_tail) = plaintext.split_at_mut(bulk);
409        if !ciphertext_bulk.is_empty() {
410            ghasher.open_bulk(
411                self.aes.round_keys(),
412                &mut counter,
413                ciphertext_bulk,
414                plaintext_bulk,
415            );
416        }
417        if !ciphertext_tail.is_empty() {
418            ghasher.absorb_padded(ciphertext_tail);
419            plaintext_tail.copy_from_slice(ciphertext_tail);
420            self.apply_ctr_serial(&mut counter, plaintext_tail);
421        }
422
423        let mut tag = ghasher.finalize(aad.len(), ciphertext.len())?;
424        let mut mask = j0(nonce);
425        self.aes.encrypt_block(&mut mask);
426        for (tag_byte, mask_byte) in tag.iter_mut().zip(mask.iter()) {
427            *tag_byte ^= mask_byte;
428        }
429        Some(tag)
430    }
431
432    /// In-place authenticate-and-decrypt. `data` enters as ciphertext and
433    /// exits as plaintext. The caller must zeroize `data` if the returned tag
434    /// does not match the supplied tag.
435    fn open_in_place(
436        &self,
437        nonce: &[u8; NONCE_SIZE],
438        aad: &[u8],
439        data: &mut [u8],
440    ) -> Option<[u8; TAG_SIZE]> {
441        let mut ghasher = ghash::Ghasher::new(&self.ghash)?;
442        ghasher.absorb_padded(aad);
443
444        let mut counter = j0(nonce);
445        increment_counter(&mut counter);
446
447        let bulk = (data.len() / PAR_BYTES) * PAR_BYTES;
448        let (data_bulk, data_tail) = data.split_at_mut(bulk);
449        if !data_bulk.is_empty() {
450            ghasher.open_in_place_bulk(self.aes.round_keys(), &mut counter, data_bulk);
451        }
452        if !data_tail.is_empty() {
453            ghasher.absorb_padded(data_tail);
454            self.apply_ctr_serial(&mut counter, data_tail);
455        }
456
457        let mut tag = ghasher.finalize(aad.len(), data.len())?;
458        let mut mask = j0(nonce);
459        self.aes.encrypt_block(&mut mask);
460        for (tag_byte, mask_byte) in tag.iter_mut().zip(mask.iter()) {
461            *tag_byte ^= mask_byte;
462        }
463        Some(tag)
464    }
465}
466
467/// Caller-owned uninitialized storage for AES-256-GCM key state.
468pub struct UninitKeyStateSlot<'a> {
469    storage: &'a mut [u8],
470}
471
472impl<'a> UninitKeyStateSlot<'a> {
473    /// Validates caller-provided storage for key-state initialization.
474    ///
475    /// # Errors
476    ///
477    /// Returns [`Error::KeyStateStorageTooSmall`] or
478    /// [`Error::KeyStateStorageMisaligned`] before any key material is touched.
479    pub fn new(storage: &'a mut [u8]) -> Result<Self, Error> {
480        let layout = HardwareAes256Gcm::key_state_layout();
481        if storage.len() < layout.size {
482            return Err(Error::KeyStateStorageTooSmall);
483        }
484        if !storage.as_ptr().addr().is_multiple_of(layout.align) {
485            return Err(Error::KeyStateStorageMisaligned);
486        }
487        Ok(Self { storage })
488    }
489}
490
491impl std::fmt::Debug for UninitKeyStateSlot<'_> {
492    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
493        f.debug_struct("UninitKeyStateSlot")
494            .field("len", &self.storage.len())
495            .finish_non_exhaustive()
496    }
497}
498
499#[inline]
500fn init_key_state_at(key: &[u8], state: NonNull<KeyState>) -> Result<(), Error> {
501    if key.len() != KEY_SIZE {
502        return Err(Error::InvalidKeyLength);
503    }
504    let key: &[u8; KEY_SIZE] = key.try_into().map_err(|_| Error::InvalidKeyLength)?;
505    KeyState::init_in_place(state, key)
506}
507
508fn init_key_state_in_slot(key: &[u8], slot: UninitKeyStateSlot<'_>) -> Result<NonNull<u8>, Error> {
509    let UninitKeyStateSlot { storage } = slot;
510    // The raw pointer is taken once and the `&mut` slice ends here, so the
511    // handle never aliases a live mutable reference.
512    // SAFETY: UninitKeyStateSlot validated that storage has non-zero size,
513    // sufficient length, and KeyState alignment, so the pointer is non-null.
514    let storage = unsafe { NonNull::new_unchecked(storage.as_mut_ptr()) };
515    #[allow(clippy::cast_ptr_alignment)]
516    let state = storage.cast::<KeyState>();
517    init_key_state_at(key, state)?;
518    Ok(storage)
519}
520
521/// Opaque initialized key-equivalent state in caller-owned storage.
522///
523/// The handle stores a raw pointer into the caller's storage instead of the
524/// caller's `&mut` slice itself, so reads through the handle never coexist
525/// with a live mutable reference under strict aliasing models.
526pub struct OpaqueKeyState<'a> {
527    storage: NonNull<u8>,
528    _marker: PhantomData<&'a mut [u8]>,
529}
530
531const _: () = {
532    const fn assert_send_sync<T: Send + Sync>() {}
533    // Compile-time proof that KeyState stays thread-safe; the unsafe Send/Sync
534    // impls below rely on it.
535    assert_send_sync::<KeyState>();
536};
537
538// SAFETY: OpaqueKeyState exclusively owns the KeyState in the caller storage
539// for 'a (the storage `&mut` borrow is consumed on construction). KeyState is
540// Send + Sync (asserted above), there is no interior mutability, and all
541// access outside drop is read-only, so moving the handle across threads is
542// sound.
543unsafe impl Send for OpaqueKeyState<'_> {}
544// SAFETY: see the Send impl; a shared OpaqueKeyState only permits shared reads
545// of a Sync pointee.
546unsafe impl Sync for OpaqueKeyState<'_> {}
547
548impl OpaqueKeyState<'_> {
549    fn state_ptr(&self) -> *mut KeyState {
550        // UninitKeyStateSlot validated KeyState alignment before this handle
551        // could be constructed.
552        #[allow(clippy::cast_ptr_alignment)]
553        self.storage.as_ptr().cast::<KeyState>()
554    }
555}
556
557impl Drop for OpaqueKeyState<'_> {
558    fn drop(&mut self) {
559        let size = HardwareAes256Gcm::key_state_layout().size;
560        // KeyState's field Drop impls (Aes256, GHashKey) exist only to wipe
561        // their own bytes and release no heap or handle resources, so they
562        // are deliberately not run here: one volatile wipe of the whole
563        // storage supersedes them and avoids a redundant second pass. If
564        // KeyState ever gains a resource-owning field, this must go back to
565        // drop_in_place before the wipe.
566        // SAFETY: the caller storage remains borrowed for 'a, holds at least
567        // `size` bytes, and is KeyState-aligned (validated at slot
568        // construction).
569        unsafe { volatile_wipe(self.storage.as_ptr(), size) };
570    }
571}
572
573impl std::fmt::Debug for OpaqueKeyState<'_> {
574    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
575        f.debug_struct("OpaqueKeyState").finish_non_exhaustive()
576    }
577}
578
579/// AES-256-GCM instance backed by caller-owned key-state storage.
580pub struct HardwareAes256GcmIn<'a> {
581    state: OpaqueKeyState<'a>,
582    /// Lazily initialized on first use of a generated-nonce method, so
583    /// caller-supplied-nonce users never draw OS entropy. Not part of the
584    /// caller-placed key state, so it does not affect `key_state_layout`.
585    nonce_gen: Option<nonce::NonceGen>,
586}
587
588impl<'a> HardwareAes256GcmIn<'a> {
589    /// Initializes reusable AES-256-GCM state directly in caller-owned storage.
590    ///
591    /// # Errors
592    ///
593    /// Returns storage validation errors, [`Error::InvalidKeyLength`], or
594    /// [`Error::UnsupportedCpu`].
595    pub fn new_in(key: &[u8], slot: UninitKeyStateSlot<'a>) -> Result<Self, Error> {
596        let storage = init_key_state_in_slot(key, slot)?;
597        Ok(Self {
598            state: OpaqueKeyState {
599                storage,
600                _marker: PhantomData,
601            },
602            nonce_gen: None,
603        })
604    }
605
606    /// Encrypts `plaintext` and returns `ciphertext || tag`.
607    ///
608    /// # Errors
609    ///
610    /// Returns [`Error::InvalidNonceLength`], [`Error::InputTooLarge`], or
611    /// [`Error::Encrypt`].
612    pub fn encrypt(&self, nonce: &[u8], aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
613        self.state_ref().encrypt(nonce, aad, plaintext)
614    }
615
616    /// Encrypts `plaintext` into a caller-provided buffer as
617    /// `ciphertext || tag` and returns the written length.
618    ///
619    /// No heap allocation is performed.
620    ///
621    /// # Errors
622    ///
623    /// Returns [`Error::OutputTooSmall`] if `out` is shorter than
624    /// `plaintext.len() + TAG_SIZE`, plus the same errors as [`Self::encrypt`].
625    pub fn encrypt_to(
626        &self,
627        nonce: &[u8],
628        aad: &[u8],
629        plaintext: &[u8],
630        out: &mut [u8],
631    ) -> Result<usize, Error> {
632        self.state_ref().encrypt_to(nonce, aad, plaintext, out)
633    }
634
635    /// Decrypts `ciphertext || tag` and returns plaintext.
636    ///
637    /// # Errors
638    ///
639    /// Returns [`Error::InvalidNonceLength`], [`Error::InputTooLarge`], or
640    /// [`Error::Decrypt`].
641    pub fn decrypt(
642        &self,
643        nonce: &[u8],
644        aad: &[u8],
645        ciphertext_and_tag: &[u8],
646    ) -> Result<Vec<u8>, Error> {
647        self.state_ref().decrypt(nonce, aad, ciphertext_and_tag)
648    }
649
650    /// Decrypts `ciphertext || tag` into a caller-provided buffer and returns
651    /// the plaintext length.
652    ///
653    /// Decrypts into `out` before the final tag comparison so CTR and GHASH can
654    /// run in one fused pass. If authentication fails, the plaintext-length
655    /// prefix of `out` is zeroized before returning [`Error::Decrypt`].
656    ///
657    /// # Errors
658    ///
659    /// Returns [`Error::OutputTooSmall`] if `out` is shorter than the
660    /// plaintext, plus the same errors as [`Self::decrypt`].
661    pub fn decrypt_to(
662        &self,
663        nonce: &[u8],
664        aad: &[u8],
665        ciphertext_and_tag: &[u8],
666        out: &mut [u8],
667    ) -> Result<usize, Error> {
668        self.state_ref()
669            .decrypt_to(nonce, aad, ciphertext_and_tag, out)
670    }
671
672    /// Encrypts and appends the nonce: `ciphertext || tag || nonce`.
673    ///
674    /// # Errors
675    ///
676    /// Returns the same errors as [`Self::encrypt`].
677    pub fn encrypt_nonce_appended(&self, nonce: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
678        self.state_ref().encrypt_nonce_appended(nonce, plaintext)
679    }
680
681    /// Encrypts the plaintext already in `in_out` in place, then appends the
682    /// tag and nonce so the final layout is `ciphertext || tag || nonce`.
683    ///
684    /// If `in_out` has capacity for `plaintext.len() + TAG_SIZE + NONCE_SIZE`,
685    /// this performs no heap allocation. The buffer is zeroized before
686    /// returning [`Error::Encrypt`] if encryption fails after mutating it.
687    ///
688    /// # Errors
689    ///
690    /// Returns the same errors as [`Self::encrypt`].
691    pub fn encrypt_nonce_appended_in_place(
692        &self,
693        nonce: &[u8],
694        in_out: &mut Vec<u8>,
695    ) -> Result<(), Error> {
696        self.state_ref()
697            .encrypt_nonce_appended_in_place(nonce, in_out)
698    }
699
700    /// Encrypts into a caller-provided buffer with the nonce appended and
701    /// returns the written length. No heap allocation is performed.
702    ///
703    /// # Errors
704    ///
705    /// Returns [`Error::OutputTooSmall`] if `out` is shorter than
706    /// `plaintext.len() + TAG_SIZE + NONCE_SIZE`, plus the same errors as
707    /// [`Self::encrypt`].
708    pub fn encrypt_nonce_appended_to(
709        &self,
710        nonce: &[u8],
711        plaintext: &[u8],
712        out: &mut [u8],
713    ) -> Result<usize, Error> {
714        encrypt_nonce_appended_to(self.state_ref(), nonce, plaintext, out)
715    }
716
717    /// Decrypts the nonce-appended layout: `ciphertext || tag || nonce`.
718    ///
719    /// # Errors
720    ///
721    /// Returns [`Error::CiphertextTooShort`] if the input cannot contain a tag
722    /// and nonce. Returns [`Error::InvalidNonceLength`],
723    /// [`Error::InputTooLarge`], or [`Error::Decrypt`].
724    pub fn decrypt_nonce_appended(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
725        if data.len() < TAG_SIZE + NONCE_SIZE {
726            return Err(Error::CiphertextTooShort);
727        }
728        let nonce_pos = data.len() - NONCE_SIZE;
729        let (ciphertext_and_tag, nonce) = data.split_at(nonce_pos);
730        self.decrypt(nonce, &[], ciphertext_and_tag)
731    }
732
733    /// Decrypts the nonce-appended layout into a caller-provided buffer and
734    /// returns the plaintext length.
735    ///
736    /// # Errors
737    ///
738    /// Returns the same errors as [`Self::decrypt_nonce_appended`] plus
739    /// [`Error::OutputTooSmall`].
740    pub fn decrypt_nonce_appended_to(&self, data: &[u8], out: &mut [u8]) -> Result<usize, Error> {
741        if data.len() < TAG_SIZE + NONCE_SIZE {
742            return Err(Error::CiphertextTooShort);
743        }
744        let nonce_pos = data.len() - NONCE_SIZE;
745        let (ciphertext_and_tag, nonce) = data.split_at(nonce_pos);
746        self.decrypt_to(nonce, &[], ciphertext_and_tag, out)
747    }
748
749    /// Encrypts `plaintext` under a library-generated unique nonce, returning
750    /// the nonce alongside `ciphertext || tag`.
751    ///
752    /// The nonce is drawn from a per-instance sequence (96-bit OS-seeded salt
753    /// plus a 64-bit counter, re-salted on fork; see [`crate`]); callers do
754    /// not manage nonce uniqueness. The returned nonce must be retained to
755    /// decrypt.
756    ///
757    /// # Errors
758    ///
759    /// Returns [`Error::OsEntropy`] if seeding the nonce sequence fails, plus
760    /// the same errors as [`Self::encrypt`].
761    pub fn encrypt_with_generated_nonce(
762        &mut self,
763        aad: &[u8],
764        plaintext: &[u8],
765    ) -> Result<([u8; NONCE_SIZE], Vec<u8>), Error> {
766        let nonce = self.next_nonce()?;
767        let ciphertext = self.state_ref().encrypt(&nonce, aad, plaintext)?;
768        Ok((nonce, ciphertext))
769    }
770
771    /// Encrypts `plaintext` under a library-generated unique nonce and returns
772    /// the self-framed `ciphertext || tag || nonce` layout (empty AAD).
773    ///
774    /// # Errors
775    ///
776    /// Returns the same errors as [`Self::encrypt_with_generated_nonce`].
777    pub fn encrypt_nonce_appended_generated(&mut self, plaintext: &[u8]) -> Result<Vec<u8>, Error> {
778        let nonce = self.next_nonce()?;
779        self.state_ref().encrypt_nonce_appended(&nonce, plaintext)
780    }
781
782    fn next_nonce(&mut self) -> Result<[u8; NONCE_SIZE], Error> {
783        match self.nonce_gen {
784            Some(ref mut g) => g.next(),
785            None => self.nonce_gen.insert(nonce::NonceGen::new()?).next(),
786        }
787    }
788
789    fn state_ref(&self) -> &KeyState {
790        // SAFETY: OpaqueKeyState owns a live initialized KeyState until drop.
791        unsafe { &*self.state.state_ptr() }
792    }
793}
794
795impl std::fmt::Debug for HardwareAes256GcmIn<'_> {
796    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
797        f.debug_struct("HardwareAes256GcmIn")
798            .finish_non_exhaustive()
799    }
800}
801
802#[repr(transparent)]
803struct AlignedKeyStateStorage(MaybeUninit<KeyState>);
804
805impl AlignedKeyStateStorage {
806    #[inline]
807    const fn uninit() -> Self {
808        Self(MaybeUninit::uninit())
809    }
810
811    #[inline]
812    fn state_ptr(&self) -> *const KeyState {
813        self.0.as_ptr()
814    }
815
816    #[inline]
817    fn state_ptr_mut(&mut self) -> NonNull<KeyState> {
818        // SAFETY: MaybeUninit<KeyState> is non-null and aligned for KeyState.
819        unsafe { NonNull::new_unchecked(self.0.as_mut_ptr()) }
820    }
821
822    #[inline]
823    fn bytes_ptr_mut(&mut self) -> *mut u8 {
824        self.0.as_mut_ptr().cast::<u8>()
825    }
826}
827
828/// Allocation-free owned reusable AES-256-GCM key state.
829///
830/// This stores the same opaque key-equivalent bytes reported by
831/// [`HardwareAes256Gcm::key_state_layout`] inline in the value, avoiding the
832/// heap allocation used by [`HardwareAes256Gcm`] while still wiping the key
833/// state on drop.
834#[repr(transparent)]
835pub struct HardwareAes256GcmKeyState {
836    storage: AlignedKeyStateStorage,
837}
838
839impl HardwareAes256GcmKeyState {
840    /// Creates reusable AES-256-GCM state from a raw 32-byte key without heap
841    /// allocation.
842    ///
843    /// # Errors
844    ///
845    /// Returns [`Error::InvalidKeyLength`] if `key` is not exactly 32 bytes,
846    /// or [`Error::UnsupportedCpu`] if required AES-GCM hardware is absent.
847    #[inline]
848    pub fn new(key: &[u8]) -> Result<Self, Error> {
849        let mut storage = AlignedKeyStateStorage::uninit();
850        init_key_state_at(key, storage.state_ptr_mut())?;
851        Ok(Self { storage })
852    }
853
854    /// Returns the current size of the reusable inline key state.
855    #[must_use]
856    pub const fn state_size() -> usize {
857        std::mem::size_of::<KeyState>()
858    }
859
860    /// Encrypts and appends the nonce: `ciphertext || tag || nonce`.
861    ///
862    /// # Errors
863    ///
864    /// Returns [`Error::InvalidNonceLength`], [`Error::InputTooLarge`], or
865    /// [`Error::Encrypt`].
866    #[inline]
867    pub fn encrypt_nonce_appended(&self, nonce: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
868        self.state_ref().encrypt_nonce_appended(nonce, plaintext)
869    }
870
871    /// Encrypts the plaintext already in `in_out` in place, then appends the
872    /// tag and nonce so the final layout is `ciphertext || tag || nonce`.
873    ///
874    /// If `in_out` has capacity for `plaintext.len() + TAG_SIZE + NONCE_SIZE`,
875    /// this performs no heap allocation. The buffer is zeroized before
876    /// returning [`Error::Encrypt`] if encryption fails after mutating it.
877    ///
878    /// # Errors
879    ///
880    /// Returns the same errors as [`Self::encrypt_nonce_appended`].
881    #[inline]
882    pub fn encrypt_nonce_appended_in_place(
883        &self,
884        nonce: &[u8],
885        in_out: &mut Vec<u8>,
886    ) -> Result<(), Error> {
887        self.state_ref()
888            .encrypt_nonce_appended_in_place(nonce, in_out)
889    }
890
891    /// Decrypts the nonce-appended layout: `ciphertext || tag || nonce`.
892    ///
893    /// # Errors
894    ///
895    /// Returns [`Error::CiphertextTooShort`] if the input cannot contain a tag
896    /// and nonce. Returns [`Error::InvalidNonceLength`],
897    /// [`Error::InputTooLarge`], or [`Error::Decrypt`].
898    #[inline]
899    pub fn decrypt_nonce_appended(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
900        if data.len() < TAG_SIZE + NONCE_SIZE {
901            return Err(Error::CiphertextTooShort);
902        }
903        let nonce_pos = data.len() - NONCE_SIZE;
904        let (ciphertext_and_tag, nonce) = data.split_at(nonce_pos);
905        self.state_ref().decrypt(nonce, &[], ciphertext_and_tag)
906    }
907
908    fn state_ref(&self) -> &KeyState {
909        // SAFETY: HardwareAes256GcmKeyState::new initializes storage before
910        // constructing Self, and the storage is never mutated except during
911        // Drop after all shared borrows have ended.
912        unsafe { &*self.storage.state_ptr() }
913    }
914}
915
916impl Drop for HardwareAes256GcmKeyState {
917    fn drop(&mut self) {
918        let size = Self::state_size();
919        // KeyState's field Drop impls exist only to wipe their own bytes and
920        // release no heap or handle resources, so one volatile wipe of the
921        // inline storage supersedes those per-field wipes. If KeyState ever
922        // gains a resource-owning field, this must run drop_in_place first.
923        // SAFETY: storage is inline in self, writable for `size` bytes, and
924        // aligned for KeyState by AlignedKeyStateStorage.
925        unsafe { volatile_wipe(self.storage.bytes_ptr_mut(), size) };
926    }
927}
928
929impl std::fmt::Debug for HardwareAes256GcmKeyState {
930    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
931        f.debug_struct("HardwareAes256GcmKeyState")
932            .finish_non_exhaustive()
933    }
934}
935
936/// Owned reusable hardware-only AES-256-GCM key state.
937pub struct HardwareAes256Gcm {
938    state: Box<KeyState>,
939    /// Lazily initialized on first use of a generated-nonce method. Held
940    /// outside `KeyState`, so it does not affect `state_size`/the boxed
941    /// key-state footprint.
942    nonce_gen: Option<nonce::NonceGen>,
943}
944
945impl HardwareAes256Gcm {
946    /// Creates reusable AES-256-GCM state from a raw 32-byte key.
947    ///
948    /// # Errors
949    ///
950    /// Returns [`Error::InvalidKeyLength`] if `key` is not exactly 32 bytes.
951    pub fn new(key: &[u8]) -> Result<Self, Error> {
952        if key.len() != KEY_SIZE {
953            return Err(Error::InvalidKeyLength);
954        }
955        let mut state = Box::<KeyState>::new_uninit();
956        // SAFETY: Box::new_uninit returns a non-null allocation for KeyState.
957        let ptr = unsafe { NonNull::new_unchecked(state.as_mut_ptr()) };
958        init_key_state_at(key, ptr)?;
959        // SAFETY: KeyState::init_in_place initialized the allocation on success.
960        let state = unsafe { state.assume_init() };
961        Ok(Self {
962            state,
963            nonce_gen: None,
964        })
965    }
966
967    /// Returns whether all required AES-GCM hardware features are available.
968    #[must_use]
969    pub fn hardware_available() -> bool {
970        aes::hardware_available() && ghash::hardware_available()
971    }
972
973    /// Returns the current size of the reusable key state.
974    ///
975    /// This is a benchmark/validation hook for the compact hardware-only state.
976    #[must_use]
977    pub const fn state_size() -> usize {
978        std::mem::size_of::<KeyState>()
979    }
980
981    /// Returns the current opaque key-state layout.
982    ///
983    /// Reports the exact layout required for caller-provided opaque storage.
984    #[must_use]
985    pub const fn key_state_layout() -> KeyStateLayout {
986        KeyStateLayout {
987            size: std::mem::size_of::<KeyState>(),
988            align: std::mem::align_of::<KeyState>(),
989        }
990    }
991
992    /// Encrypts `plaintext` and returns `ciphertext || tag`.
993    ///
994    /// # Errors
995    ///
996    /// Returns [`Error::InvalidNonceLength`] if `nonce` is not exactly 12 bytes.
997    /// Returns [`Error::InputTooLarge`] if `plaintext` or `aad` exceed the
998    /// AES-GCM limits. Returns [`Error::Encrypt`] if the backend rejects
999    /// encryption.
1000    pub fn encrypt(&self, nonce: &[u8], aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
1001        self.state.encrypt(nonce, aad, plaintext)
1002    }
1003
1004    /// Encrypts `plaintext` into a caller-provided buffer as
1005    /// `ciphertext || tag` and returns the written length.
1006    ///
1007    /// No heap allocation is performed.
1008    ///
1009    /// # Errors
1010    ///
1011    /// Returns [`Error::OutputTooSmall`] if `out` is shorter than
1012    /// `plaintext.len() + TAG_SIZE`, plus the same errors as [`Self::encrypt`].
1013    pub fn encrypt_to(
1014        &self,
1015        nonce: &[u8],
1016        aad: &[u8],
1017        plaintext: &[u8],
1018        out: &mut [u8],
1019    ) -> Result<usize, Error> {
1020        self.state.encrypt_to(nonce, aad, plaintext, out)
1021    }
1022
1023    /// Decrypts `ciphertext || tag` and returns plaintext.
1024    ///
1025    /// # Errors
1026    ///
1027    /// Returns [`Error::InvalidNonceLength`] if `nonce` is not exactly 12 bytes.
1028    /// Returns [`Error::InputTooLarge`] if the ciphertext or `aad` exceed the
1029    /// AES-GCM limits. Returns [`Error::Decrypt`] if authentication fails.
1030    pub fn decrypt(
1031        &self,
1032        nonce: &[u8],
1033        aad: &[u8],
1034        ciphertext_and_tag: &[u8],
1035    ) -> Result<Vec<u8>, Error> {
1036        self.state.decrypt(nonce, aad, ciphertext_and_tag)
1037    }
1038
1039    /// Decrypts `ciphertext || tag` into a caller-provided buffer and returns
1040    /// the plaintext length.
1041    ///
1042    /// Decrypts into `out` before the final tag comparison so CTR and GHASH can
1043    /// run in one fused pass. If authentication fails, the plaintext-length
1044    /// prefix of `out` is zeroized before returning [`Error::Decrypt`].
1045    ///
1046    /// # Errors
1047    ///
1048    /// Returns [`Error::OutputTooSmall`] if `out` is shorter than the
1049    /// plaintext, plus the same errors as [`Self::decrypt`].
1050    pub fn decrypt_to(
1051        &self,
1052        nonce: &[u8],
1053        aad: &[u8],
1054        ciphertext_and_tag: &[u8],
1055        out: &mut [u8],
1056    ) -> Result<usize, Error> {
1057        self.state.decrypt_to(nonce, aad, ciphertext_and_tag, out)
1058    }
1059
1060    /// Encrypts and appends the nonce: `ciphertext || tag || nonce`.
1061    ///
1062    /// # Errors
1063    ///
1064    /// Returns the same errors as [`Self::encrypt`].
1065    pub fn encrypt_nonce_appended(&self, nonce: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
1066        self.state.encrypt_nonce_appended(nonce, plaintext)
1067    }
1068
1069    /// Encrypts the plaintext already in `in_out` in place, then appends the
1070    /// tag and nonce so the final layout is `ciphertext || tag || nonce`.
1071    ///
1072    /// If `in_out` has capacity for `plaintext.len() + TAG_SIZE + NONCE_SIZE`,
1073    /// this performs no heap allocation. The buffer is zeroized before
1074    /// returning [`Error::Encrypt`] if encryption fails after mutating it.
1075    ///
1076    /// # Errors
1077    ///
1078    /// Returns the same errors as [`Self::encrypt`].
1079    pub fn encrypt_nonce_appended_in_place(
1080        &self,
1081        nonce: &[u8],
1082        in_out: &mut Vec<u8>,
1083    ) -> Result<(), Error> {
1084        self.state.encrypt_nonce_appended_in_place(nonce, in_out)
1085    }
1086
1087    /// Encrypts into a caller-provided buffer with the nonce appended and
1088    /// returns the written length. No heap allocation is performed.
1089    ///
1090    /// # Errors
1091    ///
1092    /// Returns [`Error::OutputTooSmall`] if `out` is shorter than
1093    /// `plaintext.len() + TAG_SIZE + NONCE_SIZE`, plus the same errors as
1094    /// [`Self::encrypt`].
1095    pub fn encrypt_nonce_appended_to(
1096        &self,
1097        nonce: &[u8],
1098        plaintext: &[u8],
1099        out: &mut [u8],
1100    ) -> Result<usize, Error> {
1101        encrypt_nonce_appended_to(&self.state, nonce, plaintext, out)
1102    }
1103
1104    /// Decrypts the nonce-appended layout: `ciphertext || tag || nonce`.
1105    ///
1106    /// # Errors
1107    ///
1108    /// Returns [`Error::CiphertextTooShort`] if the input cannot contain a tag
1109    /// and nonce. Returns [`Error::InvalidNonceLength`],
1110    /// [`Error::InputTooLarge`], or [`Error::Decrypt`] for malformed
1111    /// nonce/authentication failures.
1112    pub fn decrypt_nonce_appended(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
1113        if data.len() < TAG_SIZE + NONCE_SIZE {
1114            return Err(Error::CiphertextTooShort);
1115        }
1116        let nonce_pos = data.len() - NONCE_SIZE;
1117        let (ciphertext_and_tag, nonce) = data.split_at(nonce_pos);
1118        self.decrypt(nonce, &[], ciphertext_and_tag)
1119    }
1120
1121    /// Decrypts the nonce-appended layout into a caller-provided buffer and
1122    /// returns the plaintext length.
1123    ///
1124    /// # Errors
1125    ///
1126    /// Returns the same errors as [`Self::decrypt_nonce_appended`] plus
1127    /// [`Error::OutputTooSmall`].
1128    pub fn decrypt_nonce_appended_to(&self, data: &[u8], out: &mut [u8]) -> Result<usize, Error> {
1129        if data.len() < TAG_SIZE + NONCE_SIZE {
1130            return Err(Error::CiphertextTooShort);
1131        }
1132        let nonce_pos = data.len() - NONCE_SIZE;
1133        let (ciphertext_and_tag, nonce) = data.split_at(nonce_pos);
1134        self.decrypt_to(nonce, &[], ciphertext_and_tag, out)
1135    }
1136
1137    /// Encrypts `plaintext` under a library-generated unique nonce, returning
1138    /// the nonce alongside `ciphertext || tag`.
1139    ///
1140    /// The nonce is drawn from a per-instance sequence (96-bit OS-seeded salt
1141    /// plus a 64-bit counter, re-salted on fork; see [`crate`]); callers do
1142    /// not manage nonce uniqueness. The returned nonce must be retained to
1143    /// decrypt.
1144    ///
1145    /// # Errors
1146    ///
1147    /// Returns [`Error::OsEntropy`] if seeding the nonce sequence fails, plus
1148    /// the same errors as [`Self::encrypt`].
1149    pub fn encrypt_with_generated_nonce(
1150        &mut self,
1151        aad: &[u8],
1152        plaintext: &[u8],
1153    ) -> Result<([u8; NONCE_SIZE], Vec<u8>), Error> {
1154        let nonce = self.next_nonce()?;
1155        let ciphertext = self.state.encrypt(&nonce, aad, plaintext)?;
1156        Ok((nonce, ciphertext))
1157    }
1158
1159    /// Encrypts `plaintext` under a library-generated unique nonce and returns
1160    /// the self-framed `ciphertext || tag || nonce` layout (empty AAD).
1161    ///
1162    /// # Errors
1163    ///
1164    /// Returns the same errors as [`Self::encrypt_with_generated_nonce`].
1165    pub fn encrypt_nonce_appended_generated(&mut self, plaintext: &[u8]) -> Result<Vec<u8>, Error> {
1166        let nonce = self.next_nonce()?;
1167        self.state.encrypt_nonce_appended(&nonce, plaintext)
1168    }
1169
1170    fn next_nonce(&mut self) -> Result<[u8; NONCE_SIZE], Error> {
1171        match self.nonce_gen {
1172            Some(ref mut g) => g.next(),
1173            None => self.nonce_gen.insert(nonce::NonceGen::new()?).next(),
1174        }
1175    }
1176}
1177
1178impl std::fmt::Debug for HardwareAes256Gcm {
1179    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1180        f.debug_struct("HardwareAes256Gcm").finish_non_exhaustive()
1181    }
1182}
1183
1184fn nonce_from_slice(nonce: &[u8]) -> Result<[u8; NONCE_SIZE], Error> {
1185    nonce.try_into().map_err(|_| Error::InvalidNonceLength)
1186}
1187
1188fn append_tag_nonce(out: &mut Vec<u8>, tag: &[u8; TAG_SIZE], nonce: &[u8; NONCE_SIZE]) {
1189    debug_assert!(out.capacity() >= out.len() + TAG_SIZE + NONCE_SIZE);
1190
1191    let start = out.len();
1192    let trailer = &mut out.spare_capacity_mut()[..TAG_SIZE + NONCE_SIZE];
1193    for (slot, byte) in trailer[..TAG_SIZE].iter_mut().zip(tag) {
1194        slot.write(*byte);
1195    }
1196    for (slot, byte) in trailer[TAG_SIZE..].iter_mut().zip(nonce) {
1197        slot.write(*byte);
1198    }
1199
1200    // SAFETY: the TAG_SIZE + NONCE_SIZE spare bytes immediately after the
1201    // original length were initialized above, and capacity was ensured by the
1202    // caller before encryption mutated the buffer.
1203    unsafe { out.set_len(start + TAG_SIZE + NONCE_SIZE) };
1204}
1205
1206/// Volatile wipe of caller-placed key-state storage using the widest stores
1207/// the pointer alignment allows.
1208///
1209/// # Safety
1210///
1211/// `bytes..bytes + len` must be writable.
1212#[allow(clippy::cast_ptr_alignment)] // Wide stores are alignment-checked at runtime.
1213unsafe fn volatile_wipe(bytes: *mut u8, len: usize) {
1214    let mut offset = 0_usize;
1215    if bytes.addr().is_multiple_of(core::mem::align_of::<u128>()) {
1216        while offset + core::mem::size_of::<u128>() <= len {
1217            // SAFETY: in bounds per the caller contract and aligned per the
1218            // check above.
1219            unsafe { ptr::write_volatile(bytes.add(offset).cast::<u128>(), 0) };
1220            offset += core::mem::size_of::<u128>();
1221        }
1222    }
1223    for offset in offset..len {
1224        // SAFETY: in bounds per the caller contract.
1225        unsafe { ptr::write_volatile(bytes.add(offset), 0) };
1226    }
1227    core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
1228}
1229
1230/// Shared no-alloc nonce-appended encryption: `ciphertext || tag || nonce`.
1231fn encrypt_nonce_appended_to(
1232    state: &KeyState,
1233    nonce: &[u8],
1234    plaintext: &[u8],
1235    out: &mut [u8],
1236) -> Result<usize, Error> {
1237    let total = plaintext
1238        .len()
1239        .checked_add(TAG_SIZE + NONCE_SIZE)
1240        .ok_or(Error::InputTooLarge)?;
1241    if out.len() < total {
1242        return Err(Error::OutputTooSmall);
1243    }
1244    let written = state.encrypt_to(nonce, &[], plaintext, out)?;
1245    // encrypt_to validated the nonce length, so this copy is exactly
1246    // NONCE_SIZE bytes.
1247    out[written..total].copy_from_slice(nonce);
1248    Ok(total)
1249}
1250
1251fn validate_gcm_lengths(aad_len: usize, data_len: usize) -> Result<(), Error> {
1252    if len_exceeds_u64_limit(data_len, MAX_GCM_DATA_LEN)
1253        || len_exceeds_u64_limit(aad_len, MAX_GHASH_INPUT_LEN)
1254        || len_exceeds_u64_limit(data_len, MAX_GHASH_INPUT_LEN)
1255    {
1256        return Err(Error::InputTooLarge);
1257    }
1258    Ok(())
1259}
1260
1261fn len_exceeds_u64_limit(len: usize, limit: u64) -> bool {
1262    u64::try_from(len).map_or(true, |len| len > limit)
1263}
1264
1265fn j0(nonce: &[u8; NONCE_SIZE]) -> [u8; 16] {
1266    let mut out = [0_u8; 16];
1267    out[..NONCE_SIZE].copy_from_slice(nonce);
1268    out[15] = 1;
1269    out
1270}
1271
1272/// Operates on the public nonce-derived counter block; constant time anyway
1273/// (`wrapping_add` carries without branching).
1274pub(crate) fn increment_counter(counter: &mut [u8; 16]) {
1275    let mut low_bytes = [0_u8; 4];
1276    low_bytes.copy_from_slice(&counter[12..]);
1277    let low = u32::from_be_bytes(low_bytes).wrapping_add(1);
1278    counter[12..].copy_from_slice(&low.to_be_bytes());
1279}
1280
1281fn constant_time_eq(expected: &[u8; TAG_SIZE], actual: &[u8]) -> bool {
1282    // subtle's slice impl compares lengths first (length is public) and then
1283    // the contents in constant time behind optimization barriers, so the
1284    // compiler cannot reintroduce an early-exit byte comparison. This is the
1285    // one place in the crate where secret-derived values are compared.
1286    expected.as_slice().ct_eq(actual).into()
1287}
1288
1289#[cfg(test)]
1290mod tests {
1291    #![allow(clippy::expect_used, clippy::panic)]
1292
1293    use super::{
1294        Error, HardwareAes256Gcm, HardwareAes256GcmIn, HardwareAes256GcmKeyState,
1295        UninitKeyStateSlot, NONCE_SIZE,
1296    };
1297    use core::mem::ManuallyDrop;
1298
1299    #[repr(align(64))]
1300    struct AlignedStorage<const N: usize>([u8; N]);
1301
1302    #[test]
1303    fn rejects_wrong_key_length() {
1304        let Err(err) = HardwareAes256Gcm::new(&[0_u8; 31]) else {
1305            panic!("31-byte key should be rejected");
1306        };
1307        assert_eq!(err, Error::InvalidKeyLength);
1308    }
1309
1310    #[test]
1311    fn caller_placed_key_state_rejects_wrong_key_length() {
1312        let layout = HardwareAes256Gcm::key_state_layout();
1313        let mut storage = AlignedStorage::<512>([0_u8; 512]);
1314        let slot = UninitKeyStateSlot::new(&mut storage.0[..layout.size]).expect("valid slot");
1315        let Err(err) = HardwareAes256GcmIn::new_in(&[0_u8; 31], slot) else {
1316            panic!("31-byte key should be rejected");
1317        };
1318        assert_eq!(err, Error::InvalidKeyLength);
1319    }
1320
1321    #[test]
1322    fn inline_owned_key_state_rejects_wrong_key_length() {
1323        let Err(err) = HardwareAes256GcmKeyState::new(&[0_u8; 31]) else {
1324            panic!("31-byte key should be rejected");
1325        };
1326        assert_eq!(err, Error::InvalidKeyLength);
1327    }
1328
1329    #[test]
1330    fn rejects_ciphertext_shorter_than_tag() {
1331        let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1332        let Err(err) = key.decrypt(&[0_u8; NONCE_SIZE], &[], &[0_u8; super::TAG_SIZE - 1]) else {
1333            panic!("input shorter than the tag should be rejected");
1334        };
1335        assert_eq!(err, Error::Decrypt);
1336    }
1337
1338    #[test]
1339    fn rejects_nonce_appended_shorter_than_tag_and_nonce() {
1340        let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1341        let short = [0_u8; super::TAG_SIZE + NONCE_SIZE - 1];
1342        let Err(err) = key.decrypt_nonce_appended(&short) else {
1343            panic!("input shorter than tag plus nonce should be rejected");
1344        };
1345        assert_eq!(err, Error::CiphertextTooShort);
1346
1347        let mut out = [0_u8; 64];
1348        let Err(err) = key.decrypt_nonce_appended_to(&short, &mut out) else {
1349            panic!("input shorter than tag plus nonce should be rejected");
1350        };
1351        assert_eq!(err, Error::CiphertextTooShort);
1352    }
1353
1354    #[test]
1355    fn decrypt_to_writes_plaintext_into_caller_buffer() {
1356        let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1357        let nonce = [9_u8; NONCE_SIZE];
1358        let ciphertext = key
1359            .encrypt(&nonce, b"aad", b"plaintext")
1360            .expect("encryption should succeed");
1361
1362        let mut exact = [0_u8; 9];
1363        let written = key
1364            .decrypt_to(&nonce, b"aad", &ciphertext, &mut exact)
1365            .expect("decryption into an exact-size buffer should succeed");
1366        assert_eq!(written, 9);
1367        assert_eq!(&exact, b"plaintext");
1368
1369        let mut oversized = [0xff_u8; 32];
1370        let written = key
1371            .decrypt_to(&nonce, b"aad", &ciphertext, &mut oversized)
1372            .expect("decryption into a larger buffer should succeed");
1373        assert_eq!(&oversized[..written], b"plaintext");
1374
1375        let mut short = [0_u8; 8];
1376        let Err(err) = key.decrypt_to(&nonce, b"aad", &ciphertext, &mut short) else {
1377            panic!("short output buffer should be rejected");
1378        };
1379        assert_eq!(err, Error::OutputTooSmall);
1380        assert_eq!(short, [0_u8; 8], "no plaintext may leak into short buffers");
1381
1382        let mut tampered = ciphertext;
1383        *tampered.last_mut().expect("tag byte") ^= 1;
1384        let mut invalid_out = [0xff_u8; 9];
1385        let Err(err) = key.decrypt_to(&nonce, b"aad", &tampered, &mut invalid_out) else {
1386            panic!("invalid authentication tag should be rejected");
1387        };
1388        assert_eq!(err, Error::Decrypt);
1389        assert_eq!(
1390            invalid_out, [0_u8; 9],
1391            "transient plaintext must be wiped on authentication failure"
1392        );
1393    }
1394
1395    #[test]
1396    fn encrypt_to_matches_allocating_encrypt() {
1397        let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1398        let nonce = [9_u8; NONCE_SIZE];
1399        let plaintext = [0x5a_u8; 200];
1400        let expected = key
1401            .encrypt(&nonce, b"aad", &plaintext)
1402            .expect("encryption should succeed");
1403
1404        let mut exact = [0_u8; 200 + super::TAG_SIZE];
1405        let written = key
1406            .encrypt_to(&nonce, b"aad", &plaintext, &mut exact)
1407            .expect("encryption into an exact-size buffer should succeed");
1408        assert_eq!(written, exact.len());
1409        assert_eq!(exact.as_slice(), expected.as_slice());
1410
1411        let mut oversized = [0_u8; 256];
1412        let written = key
1413            .encrypt_to(&nonce, b"aad", &plaintext, &mut oversized)
1414            .expect("encryption into a larger buffer should succeed");
1415        assert_eq!(&oversized[..written], expected.as_slice());
1416
1417        let mut short = [0_u8; 200 + super::TAG_SIZE - 1];
1418        let Err(err) = key.encrypt_to(&nonce, b"aad", &plaintext, &mut short) else {
1419            panic!("short output buffer should be rejected");
1420        };
1421        assert_eq!(err, Error::OutputTooSmall);
1422        assert!(
1423            short.iter().all(|byte| *byte == 0),
1424            "no ciphertext may be written into short buffers"
1425        );
1426    }
1427
1428    #[test]
1429    fn encrypt_nonce_appended_to_matches_allocating_variant() {
1430        let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1431        let nonce = [9_u8; NONCE_SIZE];
1432        let expected = key
1433            .encrypt_nonce_appended(&nonce, b"plaintext")
1434            .expect("encryption should succeed");
1435
1436        let mut out = [0_u8; 9 + super::TAG_SIZE + NONCE_SIZE];
1437        let written = key
1438            .encrypt_nonce_appended_to(&nonce, b"plaintext", &mut out)
1439            .expect("encryption should succeed");
1440        assert_eq!(written, out.len());
1441        assert_eq!(out.as_slice(), expected.as_slice());
1442
1443        let mut short = [0_u8; 9 + super::TAG_SIZE + NONCE_SIZE - 1];
1444        let Err(err) = key.encrypt_nonce_appended_to(&nonce, b"plaintext", &mut short) else {
1445            panic!("short output buffer should be rejected");
1446        };
1447        assert_eq!(err, Error::OutputTooSmall);
1448    }
1449
1450    #[test]
1451    fn encrypt_nonce_appended_in_place_uses_existing_vec_capacity() {
1452        let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1453        let nonce = [9_u8; NONCE_SIZE];
1454        let expected = key
1455            .encrypt_nonce_appended(&nonce, b"plaintext")
1456            .expect("encryption should succeed");
1457
1458        let mut in_out = Vec::with_capacity(b"plaintext".len() + super::TAG_SIZE + NONCE_SIZE);
1459        in_out.extend_from_slice(b"plaintext");
1460        let ptr_before = in_out.as_ptr();
1461        key.encrypt_nonce_appended_in_place(&nonce, &mut in_out)
1462            .expect("in-place encryption should succeed");
1463
1464        assert_eq!(in_out, expected);
1465        assert_eq!(
1466            in_out.as_ptr(),
1467            ptr_before,
1468            "pre-reserved in-place encryption should not reallocate"
1469        );
1470        assert_eq!(
1471            key.decrypt_nonce_appended(&in_out)
1472                .expect("decryption should succeed"),
1473            b"plaintext"
1474        );
1475    }
1476
1477    #[test]
1478    fn decrypt_nonce_appended_to_round_trips() {
1479        let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1480        let nonce = [9_u8; NONCE_SIZE];
1481        let data = key
1482            .encrypt_nonce_appended(&nonce, b"plaintext")
1483            .expect("encryption should succeed");
1484
1485        let mut out = [0_u8; 32];
1486        let written = key
1487            .decrypt_nonce_appended_to(&data, &mut out)
1488            .expect("decryption should succeed");
1489        assert_eq!(&out[..written], b"plaintext");
1490    }
1491
1492    #[test]
1493    fn generated_nonce_round_trips_and_is_unique() {
1494        let mut key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1495
1496        let (nonce_a, ct_a) = key
1497            .encrypt_with_generated_nonce(b"aad", b"plaintext")
1498            .expect("encryption should succeed");
1499        let (nonce_b, _ct_b) = key
1500            .encrypt_with_generated_nonce(b"aad", b"plaintext")
1501            .expect("encryption should succeed");
1502
1503        // Same key + same plaintext must not reuse a nonce.
1504        assert_ne!(nonce_a, nonce_b, "generated nonces must be unique");
1505        // The returned nonce decrypts its ciphertext.
1506        assert_eq!(
1507            key.decrypt(&nonce_a, b"aad", &ct_a)
1508                .expect("decryption should succeed"),
1509            b"plaintext"
1510        );
1511    }
1512
1513    #[test]
1514    fn generated_nonce_appended_layout_round_trips() {
1515        let mut key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1516        let blob = key
1517            .encrypt_nonce_appended_generated(b"plaintext")
1518            .expect("encryption should succeed");
1519        assert_eq!(
1520            blob.len(),
1521            b"plaintext".len() + super::TAG_SIZE + NONCE_SIZE
1522        );
1523        assert_eq!(
1524            key.decrypt_nonce_appended(&blob)
1525                .expect("decryption should succeed"),
1526            b"plaintext"
1527        );
1528    }
1529
1530    #[test]
1531    fn caller_placed_generated_nonce_round_trips() {
1532        let layout = HardwareAes256Gcm::key_state_layout();
1533        let mut storage = AlignedStorage::<512>([0_u8; 512]);
1534        let slot = UninitKeyStateSlot::new(&mut storage.0[..layout.size]).expect("valid slot");
1535        let mut key = HardwareAes256GcmIn::new_in(&[7_u8; 32], slot).expect("valid test key");
1536        let (nonce, ct) = key
1537            .encrypt_with_generated_nonce(&[], b"plaintext")
1538            .expect("encryption should succeed");
1539        assert_eq!(
1540            key.decrypt(&nonce, &[], &ct)
1541                .expect("decryption should succeed"),
1542            b"plaintext"
1543        );
1544    }
1545
1546    #[test]
1547    fn generated_nonce_does_not_change_key_state_size() {
1548        // The nonce generator lives on the handle, not the placed key state.
1549        assert_eq!(HardwareAes256Gcm::key_state_layout().size, 368);
1550        assert_eq!(HardwareAes256Gcm::state_size(), 368);
1551    }
1552
1553    #[test]
1554    fn inline_owned_key_state_layout_matches_reported_layout() {
1555        let layout = HardwareAes256Gcm::key_state_layout();
1556        assert_eq!(
1557            std::mem::size_of::<HardwareAes256GcmKeyState>(),
1558            layout.size
1559        );
1560        assert_eq!(
1561            std::mem::align_of::<HardwareAes256GcmKeyState>(),
1562            layout.align
1563        );
1564        assert_eq!(HardwareAes256GcmKeyState::state_size(), layout.size);
1565    }
1566
1567    #[test]
1568    fn inline_owned_key_state_round_trips_and_uses_existing_vec_capacity() {
1569        let key = HardwareAes256GcmKeyState::new(&[7_u8; 32]).expect("valid test key");
1570        let nonce = [9_u8; NONCE_SIZE];
1571        let expected = key
1572            .encrypt_nonce_appended(&nonce, b"plaintext")
1573            .expect("encryption should succeed");
1574        assert_eq!(
1575            key.decrypt_nonce_appended(&expected)
1576                .expect("decryption should succeed"),
1577            b"plaintext"
1578        );
1579
1580        let mut in_out = Vec::with_capacity(b"plaintext".len() + super::TAG_SIZE + NONCE_SIZE);
1581        in_out.extend_from_slice(b"plaintext");
1582        let ptr_before = in_out.as_ptr();
1583        key.encrypt_nonce_appended_in_place(&nonce, &mut in_out)
1584            .expect("in-place encryption should succeed");
1585        assert_eq!(in_out, expected);
1586        assert_eq!(
1587            in_out.as_ptr(),
1588            ptr_before,
1589            "pre-reserved in-place encryption should not reallocate"
1590        );
1591    }
1592
1593    #[test]
1594    fn inline_owned_key_state_wipes_storage_on_drop() {
1595        let layout = HardwareAes256Gcm::key_state_layout();
1596        let mut key =
1597            ManuallyDrop::new(HardwareAes256GcmKeyState::new(&[7_u8; 32]).expect("valid test key"));
1598        let storage = key.storage.bytes_ptr_mut();
1599
1600        // SAFETY: key lives in this stack frame. ManuallyDrop lets us run its
1601        // Drop impl while retaining access to the backing bytes to verify the
1602        // wipe before the stack slot is reused.
1603        unsafe {
1604            ManuallyDrop::drop(&mut key);
1605            let bytes = std::slice::from_raw_parts(storage.cast_const(), layout.size);
1606            assert!(bytes.iter().all(|byte| *byte == 0));
1607        }
1608    }
1609
1610    #[test]
1611    fn caller_placed_key_state_is_usable_across_threads() {
1612        let layout = HardwareAes256Gcm::key_state_layout();
1613        let mut storage = AlignedStorage::<512>([0_u8; 512]);
1614        let slot = UninitKeyStateSlot::new(&mut storage.0[..layout.size]).expect("valid slot");
1615        let key = HardwareAes256GcmIn::new_in(&[7_u8; 32], slot).expect("valid test key");
1616        let nonce = [9_u8; NONCE_SIZE];
1617        let ciphertext = key
1618            .encrypt(&nonce, &[], b"plaintext")
1619            .expect("encryption should succeed");
1620
1621        std::thread::scope(|scope| {
1622            for _ in 0..4 {
1623                scope.spawn(|| {
1624                    assert_eq!(
1625                        key.decrypt(&nonce, &[], &ciphertext)
1626                            .expect("shared decryption should succeed"),
1627                        b"plaintext"
1628                    );
1629                });
1630            }
1631        });
1632    }
1633
1634    #[test]
1635    fn rejects_wrong_nonce_length() {
1636        let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1637        let Err(err) = key.encrypt(&[0_u8; NONCE_SIZE - 1], &[], b"data") else {
1638            panic!("short nonce should be rejected");
1639        };
1640        assert_eq!(err, Error::InvalidNonceLength);
1641    }
1642
1643    #[test]
1644    #[cfg(target_pointer_width = "64")]
1645    fn rejects_inputs_that_exceed_gcm_counter_limit() {
1646        let max_data_len =
1647            usize::try_from(super::MAX_GCM_DATA_LEN).expect("64-bit usize holds GCM data limit");
1648        assert_eq!(
1649            super::validate_gcm_lengths(0, max_data_len + 1),
1650            Err(Error::InputTooLarge)
1651        );
1652    }
1653
1654    #[test]
1655    #[cfg(target_pointer_width = "64")]
1656    fn accepts_inputs_at_gcm_counter_limit() {
1657        let max_data_len =
1658            usize::try_from(super::MAX_GCM_DATA_LEN).expect("64-bit usize holds GCM data limit");
1659        assert_eq!(super::validate_gcm_lengths(0, max_data_len), Ok(()));
1660    }
1661
1662    #[test]
1663    fn nonce_appended_round_trips() {
1664        let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1665        let nonce = [9_u8; NONCE_SIZE];
1666        let ciphertext = key
1667            .encrypt_nonce_appended(&nonce, b"plaintext")
1668            .expect("encryption should succeed");
1669        assert_eq!(
1670            key.decrypt_nonce_appended(&ciphertext)
1671                .expect("decryption should succeed"),
1672            b"plaintext"
1673        );
1674    }
1675
1676    #[test]
1677    fn reports_nonzero_key_state_layout() {
1678        let layout = HardwareAes256Gcm::key_state_layout();
1679        assert!(layout.size >= super::KEY_SIZE);
1680        assert!(layout.align.is_power_of_two());
1681        assert!(layout.size <= 384);
1682        assert_eq!(layout.size, HardwareAes256Gcm::state_size());
1683    }
1684
1685    #[test]
1686    fn caller_placed_key_state_round_trips_and_wipes_storage() {
1687        let layout = HardwareAes256Gcm::key_state_layout();
1688        let mut storage = AlignedStorage::<512>([0xa5; 512]);
1689        let nonce = [9_u8; NONCE_SIZE];
1690
1691        {
1692            let slot = UninitKeyStateSlot::new(&mut storage.0[..layout.size]).expect("valid slot");
1693            let key = HardwareAes256GcmIn::new_in(&[7_u8; 32], slot).expect("valid test key");
1694            let ciphertext = key
1695                .encrypt_nonce_appended(&nonce, b"plaintext")
1696                .expect("encryption should succeed");
1697            assert_eq!(
1698                key.decrypt_nonce_appended(&ciphertext)
1699                    .expect("decryption should succeed"),
1700                b"plaintext"
1701            );
1702        }
1703
1704        assert!(storage.0[..layout.size].iter().all(|byte| *byte == 0));
1705    }
1706
1707    #[test]
1708    fn caller_placed_key_state_validates_storage_before_key() {
1709        let layout = HardwareAes256Gcm::key_state_layout();
1710        let mut short = AlignedStorage::<512>([0_u8; 512]);
1711        let err = UninitKeyStateSlot::new(&mut short.0[..layout.size - 1])
1712            .expect_err("short storage should fail");
1713        assert_eq!(err, Error::KeyStateStorageTooSmall);
1714
1715        let mut misaligned = AlignedStorage::<512>([0_u8; 512]);
1716        let start = 1;
1717        let end = start + layout.size;
1718        let err = UninitKeyStateSlot::new(&mut misaligned.0[start..end])
1719            .expect_err("misaligned storage should fail");
1720        assert_eq!(err, Error::KeyStateStorageMisaligned);
1721    }
1722}