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    /// Detached seal: encrypt `plaintext` under a caller-supplied `nonce` and
457    /// `aad`, returning `(ciphertext, tag)` separately with no nonce appended.
458    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
459    fn seal_detached(
460        &self,
461        nonce: &[u8],
462        aad: &[u8],
463        plaintext: &[u8],
464    ) -> Result<(Vec<u8>, [u8; TAG_SIZE]), Error> {
465        let nonce = nonce_from_slice(nonce)?;
466        validate_gcm_lengths(aad.len(), plaintext.len())?;
467        let mut ciphertext = vec![0_u8; plaintext.len()];
468        let Some(tag) = self.seal(&nonce, aad, plaintext, &mut ciphertext) else {
469            ciphertext.zeroize();
470            return Err(Error::Encrypt);
471        };
472        Ok((ciphertext, tag))
473    }
474
475    /// In-place detached seal: `data` enters as plaintext and exits as
476    /// ciphertext; the authentication tag is returned. No nonce is appended.
477    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
478    fn seal_in_place_detached(
479        &self,
480        nonce: &[u8],
481        aad: &[u8],
482        data: &mut [u8],
483    ) -> Result<[u8; TAG_SIZE], Error> {
484        let nonce = nonce_from_slice(nonce)?;
485        validate_gcm_lengths(aad.len(), data.len())?;
486        let Some(tag) = self.seal_in_place(&nonce, aad, data) else {
487            data.zeroize();
488            return Err(Error::Encrypt);
489        };
490        Ok(tag)
491    }
492
493    /// Detached open: authenticate `ciphertext` against the supplied `tag`
494    /// (constant time) under `nonce`/`aad` and return the plaintext. The
495    /// transient plaintext is zeroized on authentication failure.
496    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
497    fn open_detached(
498        &self,
499        nonce: &[u8],
500        aad: &[u8],
501        ciphertext: &[u8],
502        tag: &[u8],
503    ) -> Result<Vec<u8>, Error> {
504        let nonce = nonce_from_slice(nonce)?;
505        if tag.len() != TAG_SIZE {
506            return Err(Error::Decrypt);
507        }
508        validate_gcm_lengths(aad.len(), ciphertext.len())?;
509        let mut out = vec![0_u8; ciphertext.len()];
510        let Some(expected) = self.open(&nonce, aad, ciphertext, &mut out) else {
511            out.zeroize();
512            return Err(Error::Decrypt);
513        };
514        if !constant_time_eq(&expected, tag) {
515            out.zeroize();
516            return Err(Error::Decrypt);
517        }
518        Ok(out)
519    }
520
521    /// In-place detached open: `data` enters as ciphertext and exits as
522    /// plaintext once authenticated against `tag` (constant time). On
523    /// authentication failure `data` is zeroized and `Err` returned.
524    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
525    fn open_in_place_detached(
526        &self,
527        nonce: &[u8],
528        aad: &[u8],
529        data: &mut [u8],
530        tag: &[u8],
531    ) -> Result<(), Error> {
532        let nonce = nonce_from_slice(nonce)?;
533        if tag.len() != TAG_SIZE {
534            return Err(Error::Decrypt);
535        }
536        validate_gcm_lengths(aad.len(), data.len())?;
537        let Some(expected) = self.open_in_place(&nonce, aad, data) else {
538            data.zeroize();
539            return Err(Error::Decrypt);
540        };
541        if !constant_time_eq(&expected, tag) {
542            data.zeroize();
543            return Err(Error::Decrypt);
544        }
545        Ok(())
546    }
547
548    /// Fused authenticate-and-decrypt: bulk ciphertext chunks feed GHASH while
549    /// their CTR plaintext is written to the caller's buffer. The caller must
550    /// zeroize `plaintext` if the returned tag does not match the supplied tag.
551    fn open(
552        &self,
553        nonce: &[u8; NONCE_SIZE],
554        aad: &[u8],
555        ciphertext: &[u8],
556        plaintext: &mut [u8],
557    ) -> Option<[u8; TAG_SIZE]> {
558        debug_assert_eq!(ciphertext.len(), plaintext.len());
559        let mut ghasher = ghash::Ghasher::new(&self.ghash)?;
560        ghasher.absorb_padded(aad);
561
562        let mut counter = j0(nonce);
563        increment_counter(&mut counter);
564
565        // Decrypt writes plaintext before the final tag check. This matches
566        // high-performance AEAD APIs such as ring's in-place open path; the
567        // public wrapper wipes the written range on authentication failure.
568        let bulk = (ciphertext.len() / PAR_BYTES) * PAR_BYTES;
569        let (ciphertext_bulk, ciphertext_tail) = ciphertext.split_at(bulk);
570        let (plaintext_bulk, plaintext_tail) = plaintext.split_at_mut(bulk);
571        if !ciphertext_bulk.is_empty() {
572            ghasher.open_bulk(
573                self.aes.round_keys(),
574                &mut counter,
575                ciphertext_bulk,
576                plaintext_bulk,
577            );
578        }
579        if !ciphertext_tail.is_empty() {
580            ghasher.absorb_padded(ciphertext_tail);
581            plaintext_tail.copy_from_slice(ciphertext_tail);
582            self.apply_ctr_serial(&mut counter, plaintext_tail);
583        }
584
585        let mut tag = ghasher.finalize(aad.len(), ciphertext.len())?;
586        let mut mask = j0(nonce);
587        self.aes.encrypt_block(&mut mask);
588        for (tag_byte, mask_byte) in tag.iter_mut().zip(mask.iter()) {
589            *tag_byte ^= mask_byte;
590        }
591        Some(tag)
592    }
593
594    /// In-place authenticate-and-decrypt. `data` enters as ciphertext and
595    /// exits as plaintext. The caller must zeroize `data` if the returned tag
596    /// does not match the supplied tag.
597    fn open_in_place(
598        &self,
599        nonce: &[u8; NONCE_SIZE],
600        aad: &[u8],
601        data: &mut [u8],
602    ) -> Option<[u8; TAG_SIZE]> {
603        let mut ghasher = ghash::Ghasher::new(&self.ghash)?;
604        ghasher.absorb_padded(aad);
605
606        let mut counter = j0(nonce);
607        increment_counter(&mut counter);
608
609        let bulk = (data.len() / PAR_BYTES) * PAR_BYTES;
610        let (data_bulk, data_tail) = data.split_at_mut(bulk);
611        if !data_bulk.is_empty() {
612            ghasher.open_in_place_bulk(self.aes.round_keys(), &mut counter, data_bulk);
613        }
614        if !data_tail.is_empty() {
615            ghasher.absorb_padded(data_tail);
616            self.apply_ctr_serial(&mut counter, data_tail);
617        }
618
619        let mut tag = ghasher.finalize(aad.len(), data.len())?;
620        let mut mask = j0(nonce);
621        self.aes.encrypt_block(&mut mask);
622        for (tag_byte, mask_byte) in tag.iter_mut().zip(mask.iter()) {
623            *tag_byte ^= mask_byte;
624        }
625        Some(tag)
626    }
627}
628
629/// Caller-owned uninitialized storage for AES-256-GCM key state.
630pub struct UninitKeyStateSlot<'a> {
631    storage: &'a mut [u8],
632}
633
634impl<'a> UninitKeyStateSlot<'a> {
635    /// Validates caller-provided storage for key-state initialization.
636    ///
637    /// # Errors
638    ///
639    /// Returns [`Error::KeyStateStorageTooSmall`] or
640    /// [`Error::KeyStateStorageMisaligned`] before any key material is touched.
641    pub fn new(storage: &'a mut [u8]) -> Result<Self, Error> {
642        let layout = HardwareAes256Gcm::key_state_layout();
643        if storage.len() < layout.size {
644            return Err(Error::KeyStateStorageTooSmall);
645        }
646        if !storage.as_ptr().addr().is_multiple_of(layout.align) {
647            return Err(Error::KeyStateStorageMisaligned);
648        }
649        Ok(Self { storage })
650    }
651}
652
653impl std::fmt::Debug for UninitKeyStateSlot<'_> {
654    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
655        f.debug_struct("UninitKeyStateSlot")
656            .field("len", &self.storage.len())
657            .finish_non_exhaustive()
658    }
659}
660
661#[inline]
662fn init_key_state_at(key: &[u8], state: NonNull<KeyState>) -> Result<(), Error> {
663    if key.len() != KEY_SIZE {
664        return Err(Error::InvalidKeyLength);
665    }
666    let key: &[u8; KEY_SIZE] = key.try_into().map_err(|_| Error::InvalidKeyLength)?;
667    KeyState::init_in_place(state, key)
668}
669
670fn init_key_state_in_slot(key: &[u8], slot: UninitKeyStateSlot<'_>) -> Result<NonNull<u8>, Error> {
671    let UninitKeyStateSlot { storage } = slot;
672    // The raw pointer is taken once and the `&mut` slice ends here, so the
673    // handle never aliases a live mutable reference.
674    // SAFETY: UninitKeyStateSlot validated that storage has non-zero size,
675    // sufficient length, and KeyState alignment, so the pointer is non-null.
676    let storage = unsafe { NonNull::new_unchecked(storage.as_mut_ptr()) };
677    #[allow(clippy::cast_ptr_alignment)]
678    let state = storage.cast::<KeyState>();
679    init_key_state_at(key, state)?;
680    Ok(storage)
681}
682
683/// Opaque initialized key-equivalent state in caller-owned storage.
684///
685/// The handle stores a raw pointer into the caller's storage instead of the
686/// caller's `&mut` slice itself, so reads through the handle never coexist
687/// with a live mutable reference under strict aliasing models.
688pub struct OpaqueKeyState<'a> {
689    storage: NonNull<u8>,
690    _marker: PhantomData<&'a mut [u8]>,
691}
692
693const _: () = {
694    const fn assert_send_sync<T: Send + Sync>() {}
695    // Compile-time proof that KeyState stays thread-safe; the unsafe Send/Sync
696    // impls below rely on it.
697    assert_send_sync::<KeyState>();
698};
699
700// SAFETY: OpaqueKeyState exclusively owns the KeyState in the caller storage
701// for 'a (the storage `&mut` borrow is consumed on construction). KeyState is
702// Send + Sync (asserted above), there is no interior mutability, and all
703// access outside drop is read-only, so moving the handle across threads is
704// sound.
705unsafe impl Send for OpaqueKeyState<'_> {}
706// SAFETY: see the Send impl; a shared OpaqueKeyState only permits shared reads
707// of a Sync pointee.
708unsafe impl Sync for OpaqueKeyState<'_> {}
709
710impl OpaqueKeyState<'_> {
711    fn state_ptr(&self) -> *mut KeyState {
712        // UninitKeyStateSlot validated KeyState alignment before this handle
713        // could be constructed.
714        #[allow(clippy::cast_ptr_alignment)]
715        self.storage.as_ptr().cast::<KeyState>()
716    }
717}
718
719impl Drop for OpaqueKeyState<'_> {
720    fn drop(&mut self) {
721        let size = HardwareAes256Gcm::key_state_layout().size;
722        // KeyState's field Drop impls (Aes256, GHashKey) exist only to wipe
723        // their own bytes and release no heap or handle resources, so they
724        // are deliberately not run here: one volatile wipe of the whole
725        // storage supersedes them and avoids a redundant second pass. If
726        // KeyState ever gains a resource-owning field, this must go back to
727        // drop_in_place before the wipe.
728        // SAFETY: the caller storage remains borrowed for 'a, holds at least
729        // `size` bytes, and is KeyState-aligned (validated at slot
730        // construction).
731        unsafe { volatile_wipe(self.storage.as_ptr(), size) };
732    }
733}
734
735impl std::fmt::Debug for OpaqueKeyState<'_> {
736    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
737        f.debug_struct("OpaqueKeyState").finish_non_exhaustive()
738    }
739}
740
741/// AES-256-GCM instance backed by caller-owned key-state storage.
742pub struct HardwareAes256GcmIn<'a> {
743    state: OpaqueKeyState<'a>,
744    /// Lazily initialized on the first encrypting default API call. Not part
745    /// of the caller-placed key state, so it does not affect
746    /// `key_state_layout`.
747    nonce_gen: Option<nonce::NonceGen>,
748}
749
750impl<'a> HardwareAes256GcmIn<'a> {
751    /// Initializes reusable AES-256-GCM state directly in caller-owned storage.
752    ///
753    /// # Errors
754    ///
755    /// Returns storage validation errors, [`Error::InvalidKeyLength`], or
756    /// [`Error::UnsupportedCpu`].
757    pub fn new_in(key: &[u8], slot: UninitKeyStateSlot<'a>) -> Result<Self, Error> {
758        let storage = init_key_state_in_slot(key, slot)?;
759        Ok(Self {
760            state: OpaqueKeyState {
761                storage,
762                _marker: PhantomData,
763            },
764            nonce_gen: None,
765        })
766    }
767
768    /// Encrypts `plaintext` and returns `ciphertext || tag || nonce`.
769    ///
770    /// # Errors
771    ///
772    /// Returns [`Error::OsEntropy`] if nonce generation fails, plus
773    /// [`Error::InputTooLarge`] or [`Error::Encrypt`].
774    pub fn encrypt(&mut self, aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
775        let nonce = self.next_nonce()?;
776        self.state_ref().encrypt_envelope(&nonce, aad, plaintext)
777    }
778
779    /// Encrypts `plaintext` into a caller-provided buffer as
780    /// `ciphertext || tag || nonce` and returns the written length.
781    ///
782    /// No heap allocation is performed.
783    ///
784    /// # Errors
785    ///
786    /// Returns [`Error::OsEntropy`] if nonce generation fails,
787    /// [`Error::OutputTooSmall`] if `out` is shorter than
788    /// `plaintext.len() + TAG_SIZE + NONCE_SIZE`, plus [`Error::InputTooLarge`]
789    /// or [`Error::Encrypt`].
790    pub fn encrypt_to(
791        &mut self,
792        aad: &[u8],
793        plaintext: &[u8],
794        out: &mut [u8],
795    ) -> Result<usize, Error> {
796        let nonce = self.next_nonce()?;
797        self.state_ref()
798            .encrypt_envelope_to(&nonce, aad, plaintext, out)
799    }
800
801    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
802    #[doc(hidden)]
803    pub fn encrypt_with_nonce(
804        &self,
805        nonce: &[u8],
806        aad: &[u8],
807        plaintext: &[u8],
808    ) -> Result<Vec<u8>, Error> {
809        self.state_ref().encrypt(nonce, aad, plaintext)
810    }
811
812    /// Encrypts `plaintext` into a caller-provided buffer as
813    /// `ciphertext || tag` and returns the written length.
814    ///
815    /// No heap allocation is performed.
816    ///
817    /// # Errors
818    ///
819    /// Returns [`Error::OutputTooSmall`] if `out` is shorter than
820    /// `plaintext.len() + TAG_SIZE`, plus the same errors as
821    /// [`Self::encrypt_with_nonce`].
822    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
823    #[doc(hidden)]
824    pub fn encrypt_with_nonce_to(
825        &self,
826        nonce: &[u8],
827        aad: &[u8],
828        plaintext: &[u8],
829        out: &mut [u8],
830    ) -> Result<usize, Error> {
831        self.state_ref().encrypt_to(nonce, aad, plaintext, out)
832    }
833
834    /// Decrypts `ciphertext || tag || nonce` and returns plaintext.
835    ///
836    /// # Errors
837    ///
838    /// Returns [`Error::CiphertextTooShort`], [`Error::InputTooLarge`], or
839    /// [`Error::Decrypt`].
840    pub fn decrypt(&self, aad: &[u8], ciphertext_tag_nonce: &[u8]) -> Result<Vec<u8>, Error> {
841        self.state_ref().decrypt_envelope(aad, ciphertext_tag_nonce)
842    }
843
844    /// Decrypts `ciphertext || tag || nonce` into a caller-provided buffer and
845    /// returns the plaintext length.
846    ///
847    /// Decrypts into `out` before the final tag comparison so CTR and GHASH can
848    /// run in one fused pass. If authentication fails, the plaintext-length
849    /// prefix of `out` is zeroized before returning [`Error::Decrypt`].
850    ///
851    /// # Errors
852    ///
853    /// Returns [`Error::CiphertextTooShort`], [`Error::OutputTooSmall`],
854    /// [`Error::InputTooLarge`], or [`Error::Decrypt`].
855    pub fn decrypt_to(
856        &self,
857        aad: &[u8],
858        ciphertext_tag_nonce: &[u8],
859        out: &mut [u8],
860    ) -> Result<usize, Error> {
861        self.state_ref()
862            .decrypt_envelope_to(aad, ciphertext_tag_nonce, out)
863    }
864
865    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
866    #[doc(hidden)]
867    pub fn decrypt_with_nonce(
868        &self,
869        nonce: &[u8],
870        aad: &[u8],
871        ciphertext_and_tag: &[u8],
872    ) -> Result<Vec<u8>, Error> {
873        self.state_ref().decrypt(nonce, aad, ciphertext_and_tag)
874    }
875
876    /// Decrypts `ciphertext || tag` into a caller-provided buffer and returns
877    /// the plaintext length.
878    ///
879    /// Decrypts into `out` before the final tag comparison so CTR and GHASH can
880    /// run in one fused pass. If authentication fails, the plaintext-length
881    /// prefix of `out` is zeroized before returning [`Error::Decrypt`].
882    ///
883    /// # Errors
884    ///
885    /// Returns [`Error::OutputTooSmall`] if `out` is shorter than the
886    /// plaintext, plus the same errors as [`Self::decrypt_with_nonce`].
887    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
888    #[doc(hidden)]
889    pub fn decrypt_with_nonce_to(
890        &self,
891        nonce: &[u8],
892        aad: &[u8],
893        ciphertext_and_tag: &[u8],
894        out: &mut [u8],
895    ) -> Result<usize, Error> {
896        self.state_ref()
897            .decrypt_to(nonce, aad, ciphertext_and_tag, out)
898    }
899
900    /// Encrypts and appends the nonce: `ciphertext || tag || nonce`.
901    ///
902    /// # Errors
903    ///
904    /// Returns the same errors as [`Self::encrypt_with_nonce`].
905    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
906    #[doc(hidden)]
907    pub fn encrypt_nonce_appended(&self, nonce: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
908        self.state_ref().encrypt_nonce_appended(nonce, plaintext)
909    }
910
911    /// Encrypts the plaintext already in `in_out` in place, then appends the
912    /// tag and nonce so the final layout is `ciphertext || tag || nonce`.
913    ///
914    /// If `in_out` has capacity for `plaintext.len() + TAG_SIZE + NONCE_SIZE`,
915    /// this performs no heap allocation. The buffer is zeroized before
916    /// returning [`Error::Encrypt`] if encryption fails after mutating it.
917    ///
918    /// # Errors
919    ///
920    /// Returns the same errors as [`Self::encrypt_with_nonce`].
921    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
922    #[doc(hidden)]
923    pub fn encrypt_nonce_appended_in_place(
924        &self,
925        nonce: &[u8],
926        in_out: &mut Vec<u8>,
927    ) -> Result<(), Error> {
928        self.state_ref()
929            .encrypt_nonce_appended_in_place(nonce, in_out)
930    }
931
932    /// Encrypts into a caller-provided buffer with the nonce appended and
933    /// returns the written length. No heap allocation is performed.
934    ///
935    /// # Errors
936    ///
937    /// Returns [`Error::OutputTooSmall`] if `out` is shorter than
938    /// `plaintext.len() + TAG_SIZE + NONCE_SIZE`, plus the same errors as
939    /// [`Self::encrypt_with_nonce`].
940    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
941    #[doc(hidden)]
942    pub fn encrypt_nonce_appended_to(
943        &self,
944        nonce: &[u8],
945        plaintext: &[u8],
946        out: &mut [u8],
947    ) -> Result<usize, Error> {
948        encrypt_nonce_appended_to(self.state_ref(), nonce, plaintext, out)
949    }
950
951    /// Decrypts the nonce-appended layout: `ciphertext || tag || nonce`.
952    ///
953    /// # Errors
954    ///
955    /// Returns [`Error::CiphertextTooShort`] if the input cannot contain a tag
956    /// and nonce. Returns [`Error::InvalidNonceLength`],
957    /// [`Error::InputTooLarge`], or [`Error::Decrypt`].
958    #[doc(hidden)]
959    pub fn decrypt_nonce_appended(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
960        self.state_ref().decrypt_envelope(&[], data)
961    }
962
963    /// Decrypts the nonce-appended layout into a caller-provided buffer and
964    /// returns the plaintext length.
965    ///
966    /// # Errors
967    ///
968    /// Returns the same errors as [`Self::decrypt_nonce_appended`] plus
969    /// [`Error::OutputTooSmall`].
970    #[doc(hidden)]
971    pub fn decrypt_nonce_appended_to(&self, data: &[u8], out: &mut [u8]) -> Result<usize, Error> {
972        self.state_ref().decrypt_envelope_to(&[], data, out)
973    }
974
975    /// Encrypts `plaintext` under a library-generated unique nonce, returning
976    /// the nonce alongside `ciphertext || tag`.
977    ///
978    /// The nonce is drawn from a per-instance sequence (96-bit OS-seeded salt
979    /// plus a 64-bit counter, re-salted on fork; see [`crate`]); callers do
980    /// not manage nonce uniqueness. The returned nonce must be retained to
981    /// decrypt.
982    ///
983    /// # Errors
984    ///
985    /// Returns [`Error::OsEntropy`] if seeding the nonce sequence fails, plus
986    /// the same errors as [`Self::encrypt`].
987    #[doc(hidden)]
988    pub fn encrypt_with_generated_nonce(
989        &mut self,
990        aad: &[u8],
991        plaintext: &[u8],
992    ) -> Result<([u8; NONCE_SIZE], Vec<u8>), Error> {
993        let nonce = self.next_nonce()?;
994        let ciphertext = self.state_ref().encrypt(&nonce, aad, plaintext)?;
995        Ok((nonce, ciphertext))
996    }
997
998    /// Encrypts `plaintext` under a library-generated unique nonce and returns
999    /// the self-framed `ciphertext || tag || nonce` layout (empty AAD).
1000    ///
1001    /// # Errors
1002    ///
1003    /// Returns the same errors as [`Self::encrypt_with_generated_nonce`].
1004    #[doc(hidden)]
1005    pub fn encrypt_nonce_appended_generated(&mut self, plaintext: &[u8]) -> Result<Vec<u8>, Error> {
1006        let nonce = self.next_nonce()?;
1007        self.state_ref().encrypt_nonce_appended(&nonce, plaintext)
1008    }
1009
1010    fn next_nonce(&mut self) -> Result<[u8; NONCE_SIZE], Error> {
1011        match self.nonce_gen {
1012            Some(ref mut g) => g.next(),
1013            None => self.nonce_gen.insert(nonce::NonceGen::new()?).next(),
1014        }
1015    }
1016
1017    fn state_ref(&self) -> &KeyState {
1018        // SAFETY: OpaqueKeyState owns a live initialized KeyState until drop.
1019        unsafe { &*self.state.state_ptr() }
1020    }
1021}
1022
1023impl std::fmt::Debug for HardwareAes256GcmIn<'_> {
1024    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1025        f.debug_struct("HardwareAes256GcmIn")
1026            .finish_non_exhaustive()
1027    }
1028}
1029
1030#[repr(transparent)]
1031struct AlignedKeyStateStorage(MaybeUninit<KeyState>);
1032
1033impl AlignedKeyStateStorage {
1034    #[inline]
1035    const fn uninit() -> Self {
1036        Self(MaybeUninit::uninit())
1037    }
1038
1039    #[inline]
1040    fn state_ptr(&self) -> *const KeyState {
1041        self.0.as_ptr()
1042    }
1043
1044    #[inline]
1045    fn state_ptr_mut(&mut self) -> NonNull<KeyState> {
1046        // SAFETY: MaybeUninit<KeyState> is non-null and aligned for KeyState.
1047        unsafe { NonNull::new_unchecked(self.0.as_mut_ptr()) }
1048    }
1049
1050    #[inline]
1051    fn bytes_ptr_mut(&mut self) -> *mut u8 {
1052        self.0.as_mut_ptr().cast::<u8>()
1053    }
1054}
1055
1056/// Allocation-free owned reusable AES-256-GCM key state.
1057///
1058/// This stores the same opaque key-equivalent bytes reported by
1059/// [`HardwareAes256Gcm::key_state_layout`] inline in the value, avoiding the
1060/// heap allocation used by [`HardwareAes256Gcm`] while still wiping the key
1061/// state on drop. The nonce generator is held outside the inline key state.
1062pub struct HardwareAes256GcmKeyState {
1063    storage: AlignedKeyStateStorage,
1064    nonce_gen: Option<nonce::NonceGen>,
1065}
1066
1067impl HardwareAes256GcmKeyState {
1068    /// Creates reusable AES-256-GCM state from a raw 32-byte key without heap
1069    /// allocation.
1070    ///
1071    /// # Errors
1072    ///
1073    /// Returns [`Error::InvalidKeyLength`] if `key` is not exactly 32 bytes,
1074    /// or [`Error::UnsupportedCpu`] if required AES-GCM hardware is absent.
1075    #[inline]
1076    pub fn new(key: &[u8]) -> Result<Self, Error> {
1077        let mut storage = AlignedKeyStateStorage::uninit();
1078        init_key_state_at(key, storage.state_ptr_mut())?;
1079        Ok(Self {
1080            storage,
1081            nonce_gen: None,
1082        })
1083    }
1084
1085    /// Returns the current size of the reusable inline key state.
1086    #[must_use]
1087    pub const fn state_size() -> usize {
1088        std::mem::size_of::<KeyState>()
1089    }
1090
1091    /// Encrypts `plaintext` and returns `ciphertext || tag || nonce`.
1092    ///
1093    /// # Errors
1094    ///
1095    /// Returns [`Error::OsEntropy`] if nonce generation fails,
1096    /// [`Error::InputTooLarge`] if `plaintext` or `aad` exceed the AES-GCM
1097    /// limits, or [`Error::Encrypt`] if the backend rejects encryption.
1098    pub fn encrypt(&mut self, aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
1099        let nonce = self.next_nonce()?;
1100        self.state_ref().encrypt_envelope(&nonce, aad, plaintext)
1101    }
1102
1103    /// Encrypts `plaintext` into a caller-provided buffer as
1104    /// `ciphertext || tag || nonce` and returns the written length.
1105    ///
1106    /// No heap allocation is performed.
1107    ///
1108    /// # Errors
1109    ///
1110    /// Returns [`Error::OsEntropy`] if nonce generation fails,
1111    /// [`Error::OutputTooSmall`] if `out` is shorter than
1112    /// `plaintext.len() + TAG_SIZE + NONCE_SIZE`, [`Error::InputTooLarge`] if
1113    /// `plaintext` or `aad` exceed the AES-GCM limits, or [`Error::Encrypt`]
1114    /// if the backend rejects encryption.
1115    pub fn encrypt_to(
1116        &mut self,
1117        aad: &[u8],
1118        plaintext: &[u8],
1119        out: &mut [u8],
1120    ) -> Result<usize, Error> {
1121        let nonce = self.next_nonce()?;
1122        self.state_ref()
1123            .encrypt_envelope_to(&nonce, aad, plaintext, out)
1124    }
1125
1126    /// Decrypts `ciphertext || tag || nonce` and returns plaintext.
1127    ///
1128    /// # Errors
1129    ///
1130    /// Returns [`Error::CiphertextTooShort`], [`Error::InputTooLarge`], or
1131    /// [`Error::Decrypt`] if authentication fails.
1132    pub fn decrypt(&self, aad: &[u8], ciphertext_tag_nonce: &[u8]) -> Result<Vec<u8>, Error> {
1133        self.state_ref().decrypt_envelope(aad, ciphertext_tag_nonce)
1134    }
1135
1136    /// Decrypts `ciphertext || tag || nonce` into a caller-provided buffer and
1137    /// returns the plaintext length.
1138    ///
1139    /// Decrypts into `out` before the final tag comparison so CTR and GHASH can
1140    /// run in one fused pass. If authentication fails, the plaintext-length
1141    /// prefix of `out` is zeroized before returning [`Error::Decrypt`].
1142    ///
1143    /// # Errors
1144    ///
1145    /// Returns [`Error::CiphertextTooShort`], [`Error::OutputTooSmall`],
1146    /// [`Error::InputTooLarge`], or [`Error::Decrypt`] if authentication fails.
1147    pub fn decrypt_to(
1148        &self,
1149        aad: &[u8],
1150        ciphertext_tag_nonce: &[u8],
1151        out: &mut [u8],
1152    ) -> Result<usize, Error> {
1153        self.state_ref()
1154            .decrypt_envelope_to(aad, ciphertext_tag_nonce, out)
1155    }
1156
1157    /// Encrypts and appends the nonce: `ciphertext || tag || nonce`.
1158    ///
1159    /// # Errors
1160    ///
1161    /// Returns [`Error::InvalidNonceLength`], [`Error::InputTooLarge`], or
1162    /// [`Error::Encrypt`].
1163    #[inline]
1164    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1165    #[doc(hidden)]
1166    pub fn encrypt_nonce_appended(&self, nonce: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
1167        self.state_ref().encrypt_nonce_appended(nonce, plaintext)
1168    }
1169
1170    /// Encrypts the plaintext already in `in_out` in place, then appends the
1171    /// tag and nonce so the final layout is `ciphertext || tag || nonce`.
1172    ///
1173    /// If `in_out` has capacity for `plaintext.len() + TAG_SIZE + NONCE_SIZE`,
1174    /// this performs no heap allocation. The buffer is zeroized before
1175    /// returning [`Error::Encrypt`] if encryption fails after mutating it.
1176    ///
1177    /// # Errors
1178    ///
1179    /// Returns the same errors as [`Self::encrypt_nonce_appended`].
1180    #[inline]
1181    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1182    #[doc(hidden)]
1183    pub fn encrypt_nonce_appended_in_place(
1184        &self,
1185        nonce: &[u8],
1186        in_out: &mut Vec<u8>,
1187    ) -> Result<(), Error> {
1188        self.state_ref()
1189            .encrypt_nonce_appended_in_place(nonce, in_out)
1190    }
1191
1192    /// Decrypts the nonce-appended layout: `ciphertext || tag || nonce`.
1193    ///
1194    /// # Errors
1195    ///
1196    /// Returns [`Error::CiphertextTooShort`] if the input cannot contain a tag
1197    /// and nonce. Returns [`Error::InvalidNonceLength`],
1198    /// [`Error::InputTooLarge`], or [`Error::Decrypt`].
1199    #[inline]
1200    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1201    #[doc(hidden)]
1202    pub fn decrypt_nonce_appended(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
1203        self.state_ref().decrypt_envelope(&[], data)
1204    }
1205
1206    /// Detached AES-256-GCM seal with a caller-supplied nonce and AAD.
1207    ///
1208    /// Returns `(ciphertext, tag)` separately and does **not** append the
1209    /// nonce — the caller owns nonce construction, AAD, and output framing.
1210    /// This is the protocol-neutral primitive a record layer (for example a
1211    /// rustls AEAD provider) or a standards known-answer test needs.
1212    ///
1213    /// The caller is responsible for nonce uniqueness under the key. For
1214    /// general use prefer [`Self::encrypt`], which generates a proven
1215    /// non-repeating nonce internally.
1216    ///
1217    /// # Errors
1218    ///
1219    /// Returns [`Error::InvalidNonceLength`], [`Error::InputTooLarge`], or
1220    /// [`Error::Encrypt`].
1221    #[inline]
1222    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1223    #[doc(hidden)]
1224    pub fn seal_detached(
1225        &self,
1226        nonce: &[u8],
1227        aad: &[u8],
1228        plaintext: &[u8],
1229    ) -> Result<(Vec<u8>, [u8; TAG_SIZE]), Error> {
1230        self.state_ref().seal_detached(nonce, aad, plaintext)
1231    }
1232
1233    /// In-place detached seal: `data` enters as plaintext and exits as
1234    /// ciphertext of equal length; the authentication tag is returned. No
1235    /// nonce is appended. See [`Self::seal_detached`] for semantics.
1236    ///
1237    /// # Errors
1238    ///
1239    /// Returns [`Error::InvalidNonceLength`], [`Error::InputTooLarge`], or
1240    /// [`Error::Encrypt`] (the buffer is zeroized before an `Encrypt` error).
1241    #[inline]
1242    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1243    #[doc(hidden)]
1244    pub fn seal_in_place_detached(
1245        &self,
1246        nonce: &[u8],
1247        aad: &[u8],
1248        data: &mut [u8],
1249    ) -> Result<[u8; TAG_SIZE], Error> {
1250        self.state_ref().seal_in_place_detached(nonce, aad, data)
1251    }
1252
1253    /// Detached open: authenticate `ciphertext` against `tag` (constant time)
1254    /// under the caller-supplied `nonce`/`aad` and return the plaintext.
1255    ///
1256    /// # Errors
1257    ///
1258    /// Returns [`Error::InvalidNonceLength`], [`Error::InputTooLarge`], or
1259    /// [`Error::Decrypt`] if authentication fails (the transient plaintext is
1260    /// zeroized first).
1261    #[inline]
1262    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1263    #[doc(hidden)]
1264    pub fn open_detached(
1265        &self,
1266        nonce: &[u8],
1267        aad: &[u8],
1268        ciphertext: &[u8],
1269        tag: &[u8],
1270    ) -> Result<Vec<u8>, Error> {
1271        self.state_ref().open_detached(nonce, aad, ciphertext, tag)
1272    }
1273
1274    /// In-place detached open: `data` enters as ciphertext and exits as
1275    /// plaintext once authenticated against `tag` (constant time). On
1276    /// authentication failure `data` is zeroized and `Err` is returned.
1277    ///
1278    /// # Errors
1279    ///
1280    /// Returns [`Error::InvalidNonceLength`], [`Error::InputTooLarge`], or
1281    /// [`Error::Decrypt`] if authentication fails.
1282    #[inline]
1283    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1284    #[doc(hidden)]
1285    pub fn open_in_place_detached(
1286        &self,
1287        nonce: &[u8],
1288        aad: &[u8],
1289        data: &mut [u8],
1290        tag: &[u8],
1291    ) -> Result<(), Error> {
1292        self.state_ref()
1293            .open_in_place_detached(nonce, aad, data, tag)
1294    }
1295
1296    fn next_nonce(&mut self) -> Result<[u8; NONCE_SIZE], Error> {
1297        match self.nonce_gen {
1298            Some(ref mut g) => g.next(),
1299            None => self.nonce_gen.insert(nonce::NonceGen::new()?).next(),
1300        }
1301    }
1302
1303    fn state_ref(&self) -> &KeyState {
1304        // SAFETY: HardwareAes256GcmKeyState::new initializes storage before
1305        // constructing Self, and the storage is never mutated except during
1306        // Drop after all shared borrows have ended.
1307        unsafe { &*self.storage.state_ptr() }
1308    }
1309}
1310
1311impl Drop for HardwareAes256GcmKeyState {
1312    fn drop(&mut self) {
1313        let size = Self::state_size();
1314        // KeyState's field Drop impls exist only to wipe their own bytes and
1315        // release no heap or handle resources, so one volatile wipe of the
1316        // inline storage supersedes those per-field wipes. If KeyState ever
1317        // gains a resource-owning field, this must run drop_in_place first.
1318        // SAFETY: storage is inline in self, writable for `size` bytes, and
1319        // aligned for KeyState by AlignedKeyStateStorage.
1320        unsafe { volatile_wipe(self.storage.bytes_ptr_mut(), size) };
1321    }
1322}
1323
1324impl std::fmt::Debug for HardwareAes256GcmKeyState {
1325    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1326        f.debug_struct("HardwareAes256GcmKeyState")
1327            .finish_non_exhaustive()
1328    }
1329}
1330
1331/// Owned reusable hardware-only AES-256-GCM key state.
1332pub struct HardwareAes256Gcm {
1333    state: Box<KeyState>,
1334    /// Lazily initialized on the first encrypting default API call. Held
1335    /// outside `KeyState`, so it does not affect `state_size`/the boxed
1336    /// key-state footprint.
1337    nonce_gen: Option<nonce::NonceGen>,
1338}
1339
1340impl HardwareAes256Gcm {
1341    /// Creates reusable AES-256-GCM state from a raw 32-byte key.
1342    ///
1343    /// # Errors
1344    ///
1345    /// Returns [`Error::InvalidKeyLength`] if `key` is not exactly 32 bytes.
1346    pub fn new(key: &[u8]) -> Result<Self, Error> {
1347        if key.len() != KEY_SIZE {
1348            return Err(Error::InvalidKeyLength);
1349        }
1350        let mut state = Box::<KeyState>::new_uninit();
1351        // SAFETY: Box::new_uninit returns a non-null allocation for KeyState.
1352        let ptr = unsafe { NonNull::new_unchecked(state.as_mut_ptr()) };
1353        init_key_state_at(key, ptr)?;
1354        // SAFETY: KeyState::init_in_place initialized the allocation on success.
1355        let state = unsafe { state.assume_init() };
1356        Ok(Self {
1357            state,
1358            nonce_gen: None,
1359        })
1360    }
1361
1362    /// Returns whether all required AES-GCM hardware features are available.
1363    #[must_use]
1364    pub fn hardware_available() -> bool {
1365        aes::hardware_available() && ghash::hardware_available()
1366    }
1367
1368    /// Returns the current size of the reusable key state.
1369    ///
1370    /// This is a benchmark/validation hook for the compact hardware-only state.
1371    #[must_use]
1372    pub const fn state_size() -> usize {
1373        std::mem::size_of::<KeyState>()
1374    }
1375
1376    /// Returns the current opaque key-state layout.
1377    ///
1378    /// Reports the exact layout required for caller-provided opaque storage.
1379    #[must_use]
1380    pub const fn key_state_layout() -> KeyStateLayout {
1381        KeyStateLayout {
1382            size: std::mem::size_of::<KeyState>(),
1383            align: std::mem::align_of::<KeyState>(),
1384        }
1385    }
1386
1387    /// Encrypts `plaintext` and returns `ciphertext || tag || nonce`.
1388    ///
1389    /// # Errors
1390    ///
1391    /// Returns [`Error::OsEntropy`] if nonce generation fails,
1392    /// [`Error::InputTooLarge`] if `plaintext` or `aad` exceed the AES-GCM
1393    /// limits, or [`Error::Encrypt`] if the backend rejects encryption.
1394    pub fn encrypt(&mut self, aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
1395        let nonce = self.next_nonce()?;
1396        self.state.encrypt_envelope(&nonce, aad, plaintext)
1397    }
1398
1399    /// Encrypts `plaintext` into a caller-provided buffer as
1400    /// `ciphertext || tag || nonce` and returns the written length.
1401    ///
1402    /// No heap allocation is performed.
1403    ///
1404    /// # Errors
1405    ///
1406    /// Returns [`Error::OsEntropy`] if nonce generation fails,
1407    /// [`Error::OutputTooSmall`] if `out` is shorter than
1408    /// `plaintext.len() + TAG_SIZE + NONCE_SIZE`, [`Error::InputTooLarge`] if
1409    /// `plaintext` or `aad` exceed the AES-GCM limits, or [`Error::Encrypt`]
1410    /// if the backend rejects encryption.
1411    pub fn encrypt_to(
1412        &mut self,
1413        aad: &[u8],
1414        plaintext: &[u8],
1415        out: &mut [u8],
1416    ) -> Result<usize, Error> {
1417        let nonce = self.next_nonce()?;
1418        self.state.encrypt_envelope_to(&nonce, aad, plaintext, out)
1419    }
1420
1421    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1422    #[doc(hidden)]
1423    pub fn encrypt_with_nonce(
1424        &self,
1425        nonce: &[u8],
1426        aad: &[u8],
1427        plaintext: &[u8],
1428    ) -> Result<Vec<u8>, Error> {
1429        self.state.encrypt(nonce, aad, plaintext)
1430    }
1431
1432    /// Encrypts `plaintext` into a caller-provided buffer as
1433    /// `ciphertext || tag` and returns the written length.
1434    ///
1435    /// No heap allocation is performed.
1436    ///
1437    /// # Errors
1438    ///
1439    /// Returns [`Error::OutputTooSmall`] if `out` is shorter than
1440    /// `plaintext.len() + TAG_SIZE`, plus the same errors as
1441    /// [`Self::encrypt_with_nonce`].
1442    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1443    #[doc(hidden)]
1444    pub fn encrypt_with_nonce_to(
1445        &self,
1446        nonce: &[u8],
1447        aad: &[u8],
1448        plaintext: &[u8],
1449        out: &mut [u8],
1450    ) -> Result<usize, Error> {
1451        self.state.encrypt_to(nonce, aad, plaintext, out)
1452    }
1453
1454    /// Decrypts `ciphertext || tag || nonce` and returns plaintext.
1455    ///
1456    /// # Errors
1457    ///
1458    /// Returns [`Error::CiphertextTooShort`], [`Error::InputTooLarge`], or
1459    /// [`Error::Decrypt`] if authentication fails.
1460    pub fn decrypt(&self, aad: &[u8], ciphertext_tag_nonce: &[u8]) -> Result<Vec<u8>, Error> {
1461        self.state.decrypt_envelope(aad, ciphertext_tag_nonce)
1462    }
1463
1464    /// Decrypts `ciphertext || tag || nonce` into a caller-provided buffer and
1465    /// returns the plaintext length.
1466    ///
1467    /// Decrypts into `out` before the final tag comparison so CTR and GHASH can
1468    /// run in one fused pass. If authentication fails, the plaintext-length
1469    /// prefix of `out` is zeroized before returning [`Error::Decrypt`].
1470    ///
1471    /// # Errors
1472    ///
1473    /// Returns [`Error::CiphertextTooShort`], [`Error::OutputTooSmall`],
1474    /// [`Error::InputTooLarge`], or [`Error::Decrypt`] if authentication fails.
1475    pub fn decrypt_to(
1476        &self,
1477        aad: &[u8],
1478        ciphertext_tag_nonce: &[u8],
1479        out: &mut [u8],
1480    ) -> Result<usize, Error> {
1481        self.state
1482            .decrypt_envelope_to(aad, ciphertext_tag_nonce, out)
1483    }
1484
1485    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1486    #[doc(hidden)]
1487    pub fn decrypt_with_nonce(
1488        &self,
1489        nonce: &[u8],
1490        aad: &[u8],
1491        ciphertext_and_tag: &[u8],
1492    ) -> Result<Vec<u8>, Error> {
1493        self.state.decrypt(nonce, aad, ciphertext_and_tag)
1494    }
1495
1496    /// Decrypts `ciphertext || tag` into a caller-provided buffer and returns
1497    /// the plaintext length.
1498    ///
1499    /// Decrypts into `out` before the final tag comparison so CTR and GHASH can
1500    /// run in one fused pass. If authentication fails, the plaintext-length
1501    /// prefix of `out` is zeroized before returning [`Error::Decrypt`].
1502    ///
1503    /// # Errors
1504    ///
1505    /// Returns [`Error::OutputTooSmall`] if `out` is shorter than the
1506    /// plaintext, plus the same errors as [`Self::decrypt_with_nonce`].
1507    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1508    #[doc(hidden)]
1509    pub fn decrypt_with_nonce_to(
1510        &self,
1511        nonce: &[u8],
1512        aad: &[u8],
1513        ciphertext_and_tag: &[u8],
1514        out: &mut [u8],
1515    ) -> Result<usize, Error> {
1516        self.state.decrypt_to(nonce, aad, ciphertext_and_tag, out)
1517    }
1518
1519    /// Encrypts and appends the nonce: `ciphertext || tag || nonce`.
1520    ///
1521    /// # Errors
1522    ///
1523    /// Returns the same errors as [`Self::encrypt_with_nonce`].
1524    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1525    #[doc(hidden)]
1526    pub fn encrypt_nonce_appended(&self, nonce: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
1527        self.state.encrypt_nonce_appended(nonce, plaintext)
1528    }
1529
1530    /// Encrypts the plaintext already in `in_out` in place, then appends the
1531    /// tag and nonce so the final layout is `ciphertext || tag || nonce`.
1532    ///
1533    /// If `in_out` has capacity for `plaintext.len() + TAG_SIZE + NONCE_SIZE`,
1534    /// this performs no heap allocation. The buffer is zeroized before
1535    /// returning [`Error::Encrypt`] if encryption fails after mutating it.
1536    ///
1537    /// # Errors
1538    ///
1539    /// Returns the same errors as [`Self::encrypt_with_nonce`].
1540    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1541    #[doc(hidden)]
1542    pub fn encrypt_nonce_appended_in_place(
1543        &self,
1544        nonce: &[u8],
1545        in_out: &mut Vec<u8>,
1546    ) -> Result<(), Error> {
1547        self.state.encrypt_nonce_appended_in_place(nonce, in_out)
1548    }
1549
1550    /// Encrypts into a caller-provided buffer with the nonce appended and
1551    /// returns the written length. No heap allocation is performed.
1552    ///
1553    /// # Errors
1554    ///
1555    /// Returns [`Error::OutputTooSmall`] if `out` is shorter than
1556    /// `plaintext.len() + TAG_SIZE + NONCE_SIZE`, plus the same errors as
1557    /// [`Self::encrypt_with_nonce`].
1558    #[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1559    #[doc(hidden)]
1560    pub fn encrypt_nonce_appended_to(
1561        &self,
1562        nonce: &[u8],
1563        plaintext: &[u8],
1564        out: &mut [u8],
1565    ) -> Result<usize, Error> {
1566        encrypt_nonce_appended_to(&self.state, nonce, plaintext, out)
1567    }
1568
1569    /// Decrypts the nonce-appended layout: `ciphertext || tag || nonce`.
1570    ///
1571    /// # Errors
1572    ///
1573    /// Returns [`Error::CiphertextTooShort`] if the input cannot contain a tag
1574    /// and nonce. Returns [`Error::InvalidNonceLength`],
1575    /// [`Error::InputTooLarge`], or [`Error::Decrypt`] for malformed
1576    /// nonce/authentication failures.
1577    #[doc(hidden)]
1578    pub fn decrypt_nonce_appended(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
1579        self.state.decrypt_envelope(&[], data)
1580    }
1581
1582    /// Decrypts the nonce-appended layout into a caller-provided buffer and
1583    /// returns the plaintext length.
1584    ///
1585    /// # Errors
1586    ///
1587    /// Returns the same errors as [`Self::decrypt_nonce_appended`] plus
1588    /// [`Error::OutputTooSmall`].
1589    #[doc(hidden)]
1590    pub fn decrypt_nonce_appended_to(&self, data: &[u8], out: &mut [u8]) -> Result<usize, Error> {
1591        self.state.decrypt_envelope_to(&[], data, out)
1592    }
1593
1594    /// Encrypts `plaintext` under a library-generated unique nonce, returning
1595    /// the nonce alongside `ciphertext || tag`.
1596    ///
1597    /// The nonce is drawn from a per-instance sequence (96-bit OS-seeded salt
1598    /// plus a 64-bit counter, re-salted on fork; see [`crate`]); callers do
1599    /// not manage nonce uniqueness. The returned nonce must be retained to
1600    /// decrypt.
1601    ///
1602    /// # Errors
1603    ///
1604    /// Returns [`Error::OsEntropy`] if seeding the nonce sequence fails, plus
1605    /// the same errors as [`Self::encrypt`].
1606    #[doc(hidden)]
1607    pub fn encrypt_with_generated_nonce(
1608        &mut self,
1609        aad: &[u8],
1610        plaintext: &[u8],
1611    ) -> Result<([u8; NONCE_SIZE], Vec<u8>), Error> {
1612        let nonce = self.next_nonce()?;
1613        let ciphertext = self.state.encrypt(&nonce, aad, plaintext)?;
1614        Ok((nonce, ciphertext))
1615    }
1616
1617    /// Encrypts `plaintext` under a library-generated unique nonce and returns
1618    /// the self-framed `ciphertext || tag || nonce` layout (empty AAD).
1619    ///
1620    /// # Errors
1621    ///
1622    /// Returns the same errors as [`Self::encrypt_with_generated_nonce`].
1623    #[doc(hidden)]
1624    pub fn encrypt_nonce_appended_generated(&mut self, plaintext: &[u8]) -> Result<Vec<u8>, Error> {
1625        let nonce = self.next_nonce()?;
1626        self.state.encrypt_nonce_appended(&nonce, plaintext)
1627    }
1628
1629    fn next_nonce(&mut self) -> Result<[u8; NONCE_SIZE], Error> {
1630        match self.nonce_gen {
1631            Some(ref mut g) => g.next(),
1632            None => self.nonce_gen.insert(nonce::NonceGen::new()?).next(),
1633        }
1634    }
1635}
1636
1637impl std::fmt::Debug for HardwareAes256Gcm {
1638    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1639        f.debug_struct("HardwareAes256Gcm").finish_non_exhaustive()
1640    }
1641}
1642
1643fn nonce_from_slice(nonce: &[u8]) -> Result<[u8; NONCE_SIZE], Error> {
1644    nonce.try_into().map_err(|_| Error::InvalidNonceLength)
1645}
1646
1647fn append_tag_nonce(out: &mut Vec<u8>, tag: &[u8; TAG_SIZE], nonce: &[u8; NONCE_SIZE]) {
1648    debug_assert!(out.capacity() >= out.len() + TAG_SIZE + NONCE_SIZE);
1649
1650    let start = out.len();
1651    let trailer = &mut out.spare_capacity_mut()[..TAG_SIZE + NONCE_SIZE];
1652    for (slot, byte) in trailer[..TAG_SIZE].iter_mut().zip(tag) {
1653        slot.write(*byte);
1654    }
1655    for (slot, byte) in trailer[TAG_SIZE..].iter_mut().zip(nonce) {
1656        slot.write(*byte);
1657    }
1658
1659    // SAFETY: the TAG_SIZE + NONCE_SIZE spare bytes immediately after the
1660    // original length were initialized above, and capacity was ensured by the
1661    // caller before encryption mutated the buffer.
1662    unsafe { out.set_len(start + TAG_SIZE + NONCE_SIZE) };
1663}
1664
1665/// Volatile wipe of caller-placed key-state storage using the widest stores
1666/// the pointer alignment allows.
1667///
1668/// # Safety
1669///
1670/// `bytes..bytes + len` must be writable.
1671#[allow(clippy::cast_ptr_alignment)] // Wide stores are alignment-checked at runtime.
1672unsafe fn volatile_wipe(bytes: *mut u8, len: usize) {
1673    let mut offset = 0_usize;
1674    if bytes.addr().is_multiple_of(core::mem::align_of::<u128>()) {
1675        while offset + core::mem::size_of::<u128>() <= len {
1676            // SAFETY: in bounds per the caller contract and aligned per the
1677            // check above.
1678            unsafe { ptr::write_volatile(bytes.add(offset).cast::<u128>(), 0) };
1679            offset += core::mem::size_of::<u128>();
1680        }
1681    }
1682    for offset in offset..len {
1683        // SAFETY: in bounds per the caller contract.
1684        unsafe { ptr::write_volatile(bytes.add(offset), 0) };
1685    }
1686    core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
1687}
1688
1689/// Shared no-alloc nonce-appended encryption: `ciphertext || tag || nonce`.
1690#[cfg(any(test, feature = "hazmat-explicit-nonce"))]
1691fn encrypt_nonce_appended_to(
1692    state: &KeyState,
1693    nonce: &[u8],
1694    plaintext: &[u8],
1695    out: &mut [u8],
1696) -> Result<usize, Error> {
1697    let total = plaintext
1698        .len()
1699        .checked_add(TAG_SIZE + NONCE_SIZE)
1700        .ok_or(Error::InputTooLarge)?;
1701    if out.len() < total {
1702        return Err(Error::OutputTooSmall);
1703    }
1704    let written = state.encrypt_to(nonce, &[], plaintext, out)?;
1705    // encrypt_to validated the nonce length, so this copy is exactly
1706    // NONCE_SIZE bytes.
1707    out[written..total].copy_from_slice(nonce);
1708    Ok(total)
1709}
1710
1711fn validate_gcm_lengths(aad_len: usize, data_len: usize) -> Result<(), Error> {
1712    if len_exceeds_u64_limit(data_len, MAX_GCM_DATA_LEN)
1713        || len_exceeds_u64_limit(aad_len, MAX_GHASH_INPUT_LEN)
1714        || len_exceeds_u64_limit(data_len, MAX_GHASH_INPUT_LEN)
1715    {
1716        return Err(Error::InputTooLarge);
1717    }
1718    Ok(())
1719}
1720
1721fn len_exceeds_u64_limit(len: usize, limit: u64) -> bool {
1722    u64::try_from(len).map_or(true, |len| len > limit)
1723}
1724
1725fn j0(nonce: &[u8; NONCE_SIZE]) -> [u8; 16] {
1726    let mut out = [0_u8; 16];
1727    out[..NONCE_SIZE].copy_from_slice(nonce);
1728    out[15] = 1;
1729    out
1730}
1731
1732/// Operates on the public nonce-derived counter block; constant time anyway
1733/// (`wrapping_add` carries without branching).
1734pub(crate) fn increment_counter(counter: &mut [u8; 16]) {
1735    let mut low_bytes = [0_u8; 4];
1736    low_bytes.copy_from_slice(&counter[12..]);
1737    let low = u32::from_be_bytes(low_bytes).wrapping_add(1);
1738    counter[12..].copy_from_slice(&low.to_be_bytes());
1739}
1740
1741/// Stable-symbol `extern "C"` wrapper so the SAW LLVM-bitcode verifier can prove
1742/// the *compiled* `increment_counter` matches its Cryptol spec (`proofs/saw/`).
1743/// Build-time only; `saw-verify` is never a shipped feature.
1744#[cfg(feature = "saw-verify")]
1745#[no_mangle]
1746#[allow(clippy::not_unsafe_ptr_arg_deref)]
1747pub extern "C" fn saw_increment_counter(counter: *mut u8) {
1748    // SAFETY: SAW calls this with a valid, writable 16-byte buffer.
1749    let c = unsafe { &mut *(counter.cast::<[u8; 16]>()) };
1750    increment_counter(c);
1751}
1752
1753/// Stable-symbol `extern "C"` wrapper around `j0` for SAW (`proofs/saw/`).
1754/// Build-time only; never shipped.
1755#[cfg(feature = "saw-verify")]
1756#[no_mangle]
1757#[allow(clippy::not_unsafe_ptr_arg_deref)]
1758pub extern "C" fn saw_j0(nonce: *const u8, out: *mut u8) {
1759    // SAFETY: SAW calls this with a readable 12-byte `nonce` and writable 16-byte `out`.
1760    let n = unsafe { &*(nonce.cast::<[u8; NONCE_SIZE]>()) };
1761    let block = j0(n);
1762    // SAFETY: `out` is a writable 16-byte buffer.
1763    unsafe { core::ptr::copy_nonoverlapping(block.as_ptr(), out, 16) };
1764}
1765
1766fn constant_time_eq(expected: &[u8; TAG_SIZE], actual: &[u8]) -> bool {
1767    // subtle's slice impl compares lengths first (length is public) and then
1768    // the contents in constant time behind optimization barriers, so the
1769    // compiler cannot reintroduce an early-exit byte comparison. This is the
1770    // one place in the crate where secret-derived values are compared.
1771    expected.as_slice().ct_eq(actual).into()
1772}
1773
1774/// Non-inlined wrapper so the constant-time verifier can disassemble the tag
1775/// comparison as a named symbol and confirm it compiles branch-free, with the
1776/// loop length fixed to `TAG_SIZE` (see `proofs/constant-time/verify.sh`).
1777/// Build-time only; `ct-verify` is never a shipped feature.
1778#[cfg(feature = "ct-verify")]
1779#[inline(never)]
1780#[must_use]
1781pub fn ct_verify_constant_time_eq(expected: &[u8; TAG_SIZE], actual: &[u8; TAG_SIZE]) -> bool {
1782    constant_time_eq(
1783        core::hint::black_box(expected),
1784        core::hint::black_box(actual.as_slice()),
1785    )
1786}
1787
1788#[cfg(test)]
1789mod tests {
1790    #![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
1791
1792    use super::{
1793        increment_counter, j0, Error, HardwareAes256Gcm, HardwareAes256GcmIn,
1794        HardwareAes256GcmKeyState, UninitKeyStateSlot, NONCE_SIZE, TAG_SIZE,
1795    };
1796    use core::mem::ManuallyDrop;
1797
1798    #[repr(align(64))]
1799    struct AlignedStorage<const N: usize>([u8; N]);
1800
1801    /// Anchors the Z3 model in `proofs/prove_composition.py` to the real compiled
1802    /// code: `increment_counter` is the SP 800-38D `inc_32` (big-endian counter in
1803    /// the trailing four bytes, wrapping mod 2^32, leading 96 bits preserved).
1804    #[test]
1805    fn increment_counter_is_be32_inc_leaving_high_96_bits() {
1806        // Wrap across the 32-bit boundary; the leading 12 bytes must not change.
1807        let mut counter = [0_u8; 16];
1808        counter[..12].copy_from_slice(&[0xAA; 12]);
1809        counter[12..].copy_from_slice(&u32::MAX.to_be_bytes());
1810        increment_counter(&mut counter);
1811        assert_eq!(
1812            &counter[..12],
1813            &[0xAA; 12],
1814            "leading 96 bits must be preserved"
1815        );
1816        assert_eq!(
1817            &counter[12..],
1818            &0_u32.to_be_bytes(),
1819            "low 32 bits must wrap to 0"
1820        );
1821
1822        // Plain carry into the next byte, big-endian.
1823        let mut counter = [0_u8; 16];
1824        counter[12..].copy_from_slice(&0x0000_00FF_u32.to_be_bytes());
1825        increment_counter(&mut counter);
1826        assert_eq!(&counter[12..], &0x0000_0100_u32.to_be_bytes());
1827
1828        // J0 for a 96-bit nonce is IV || 0^31 || 1 (SP 800-38D).
1829        let nonce = [0x11_u8; NONCE_SIZE];
1830        let block = j0(&nonce);
1831        assert_eq!(&block[..NONCE_SIZE], &nonce);
1832        assert_eq!(&block[NONCE_SIZE..], &[0, 0, 0, 1]);
1833    }
1834
1835    #[test]
1836    fn rejects_wrong_key_length() {
1837        let Err(err) = HardwareAes256Gcm::new(&[0_u8; 31]) else {
1838            panic!("31-byte key should be rejected");
1839        };
1840        assert_eq!(err, Error::InvalidKeyLength);
1841    }
1842
1843    #[test]
1844    fn caller_placed_key_state_rejects_wrong_key_length() {
1845        let layout = HardwareAes256Gcm::key_state_layout();
1846        let mut storage = AlignedStorage::<512>([0_u8; 512]);
1847        let slot = UninitKeyStateSlot::new(&mut storage.0[..layout.size]).expect("valid slot");
1848        let Err(err) = HardwareAes256GcmIn::new_in(&[0_u8; 31], slot) else {
1849            panic!("31-byte key should be rejected");
1850        };
1851        assert_eq!(err, Error::InvalidKeyLength);
1852    }
1853
1854    #[test]
1855    fn inline_owned_key_state_rejects_wrong_key_length() {
1856        let Err(err) = HardwareAes256GcmKeyState::new(&[0_u8; 31]) else {
1857            panic!("31-byte key should be rejected");
1858        };
1859        assert_eq!(err, Error::InvalidKeyLength);
1860    }
1861
1862    #[test]
1863    fn rejects_ciphertext_shorter_than_tag() {
1864        let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1865        let Err(err) =
1866            key.decrypt_with_nonce(&[0_u8; NONCE_SIZE], &[], &[0_u8; super::TAG_SIZE - 1])
1867        else {
1868            panic!("input shorter than the tag should be rejected");
1869        };
1870        assert_eq!(err, Error::Decrypt);
1871    }
1872
1873    #[test]
1874    fn rejects_nonce_appended_shorter_than_tag_and_nonce() {
1875        let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1876        let short = [0_u8; super::TAG_SIZE + NONCE_SIZE - 1];
1877        let Err(err) = key.decrypt_nonce_appended(&short) else {
1878            panic!("input shorter than tag plus nonce should be rejected");
1879        };
1880        assert_eq!(err, Error::CiphertextTooShort);
1881
1882        let mut out = [0_u8; 64];
1883        let Err(err) = key.decrypt_nonce_appended_to(&short, &mut out) else {
1884            panic!("input shorter than tag plus nonce should be rejected");
1885        };
1886        assert_eq!(err, Error::CiphertextTooShort);
1887    }
1888
1889    #[test]
1890    fn decrypt_to_writes_plaintext_into_caller_buffer() {
1891        let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1892        let nonce = [9_u8; NONCE_SIZE];
1893        let ciphertext = key
1894            .encrypt_with_nonce(&nonce, b"aad", b"plaintext")
1895            .expect("encryption should succeed");
1896
1897        let mut exact = [0_u8; 9];
1898        let written = key
1899            .decrypt_with_nonce_to(&nonce, b"aad", &ciphertext, &mut exact)
1900            .expect("decryption into an exact-size buffer should succeed");
1901        assert_eq!(written, 9);
1902        assert_eq!(&exact, b"plaintext");
1903
1904        let mut oversized = [0xff_u8; 32];
1905        let written = key
1906            .decrypt_with_nonce_to(&nonce, b"aad", &ciphertext, &mut oversized)
1907            .expect("decryption into a larger buffer should succeed");
1908        assert_eq!(&oversized[..written], b"plaintext");
1909
1910        let mut short = [0_u8; 8];
1911        let Err(err) = key.decrypt_with_nonce_to(&nonce, b"aad", &ciphertext, &mut short) else {
1912            panic!("short output buffer should be rejected");
1913        };
1914        assert_eq!(err, Error::OutputTooSmall);
1915        assert_eq!(short, [0_u8; 8], "no plaintext may leak into short buffers");
1916
1917        let mut tampered = ciphertext;
1918        *tampered.last_mut().expect("tag byte") ^= 1;
1919        let mut invalid_out = [0xff_u8; 9];
1920        let Err(err) = key.decrypt_with_nonce_to(&nonce, b"aad", &tampered, &mut invalid_out)
1921        else {
1922            panic!("invalid authentication tag should be rejected");
1923        };
1924        assert_eq!(err, Error::Decrypt);
1925        assert_eq!(
1926            invalid_out, [0_u8; 9],
1927            "transient plaintext must be wiped on authentication failure"
1928        );
1929    }
1930
1931    #[test]
1932    fn encrypt_to_matches_allocating_encrypt() {
1933        let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1934        let nonce = [9_u8; NONCE_SIZE];
1935        let plaintext = [0x5a_u8; 200];
1936        let expected = key
1937            .encrypt_with_nonce(&nonce, b"aad", &plaintext)
1938            .expect("encryption should succeed");
1939
1940        let mut exact = [0_u8; 200 + super::TAG_SIZE];
1941        let written = key
1942            .encrypt_with_nonce_to(&nonce, b"aad", &plaintext, &mut exact)
1943            .expect("encryption into an exact-size buffer should succeed");
1944        assert_eq!(written, exact.len());
1945        assert_eq!(exact.as_slice(), expected.as_slice());
1946
1947        let mut oversized = [0_u8; 256];
1948        let written = key
1949            .encrypt_with_nonce_to(&nonce, b"aad", &plaintext, &mut oversized)
1950            .expect("encryption into a larger buffer should succeed");
1951        assert_eq!(&oversized[..written], expected.as_slice());
1952
1953        let mut short = [0_u8; 200 + super::TAG_SIZE - 1];
1954        let Err(err) = key.encrypt_with_nonce_to(&nonce, b"aad", &plaintext, &mut short) else {
1955            panic!("short output buffer should be rejected");
1956        };
1957        assert_eq!(err, Error::OutputTooSmall);
1958        assert!(
1959            short.iter().all(|byte| *byte == 0),
1960            "no ciphertext may be written into short buffers"
1961        );
1962    }
1963
1964    #[test]
1965    fn encrypt_nonce_appended_to_matches_allocating_variant() {
1966        let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1967        let nonce = [9_u8; NONCE_SIZE];
1968        let expected = key
1969            .encrypt_nonce_appended(&nonce, b"plaintext")
1970            .expect("encryption should succeed");
1971
1972        let mut out = [0_u8; 9 + super::TAG_SIZE + NONCE_SIZE];
1973        let written = key
1974            .encrypt_nonce_appended_to(&nonce, b"plaintext", &mut out)
1975            .expect("encryption should succeed");
1976        assert_eq!(written, out.len());
1977        assert_eq!(out.as_slice(), expected.as_slice());
1978
1979        let mut short = [0_u8; 9 + super::TAG_SIZE + NONCE_SIZE - 1];
1980        let Err(err) = key.encrypt_nonce_appended_to(&nonce, b"plaintext", &mut short) else {
1981            panic!("short output buffer should be rejected");
1982        };
1983        assert_eq!(err, Error::OutputTooSmall);
1984    }
1985
1986    #[test]
1987    fn encrypt_nonce_appended_in_place_uses_existing_vec_capacity() {
1988        let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
1989        let nonce = [9_u8; NONCE_SIZE];
1990        let expected = key
1991            .encrypt_nonce_appended(&nonce, b"plaintext")
1992            .expect("encryption should succeed");
1993
1994        let mut in_out = Vec::with_capacity(b"plaintext".len() + super::TAG_SIZE + NONCE_SIZE);
1995        in_out.extend_from_slice(b"plaintext");
1996        let ptr_before = in_out.as_ptr();
1997        key.encrypt_nonce_appended_in_place(&nonce, &mut in_out)
1998            .expect("in-place encryption should succeed");
1999
2000        assert_eq!(in_out, expected);
2001        assert_eq!(
2002            in_out.as_ptr(),
2003            ptr_before,
2004            "pre-reserved in-place encryption should not reallocate"
2005        );
2006        assert_eq!(
2007            key.decrypt_nonce_appended(&in_out)
2008                .expect("decryption should succeed"),
2009            b"plaintext"
2010        );
2011    }
2012
2013    #[test]
2014    fn decrypt_nonce_appended_to_round_trips() {
2015        let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
2016        let nonce = [9_u8; NONCE_SIZE];
2017        let data = key
2018            .encrypt_nonce_appended(&nonce, b"plaintext")
2019            .expect("encryption should succeed");
2020
2021        let mut out = [0_u8; 32];
2022        let written = key
2023            .decrypt_nonce_appended_to(&data, &mut out)
2024            .expect("decryption should succeed");
2025        assert_eq!(&out[..written], b"plaintext");
2026    }
2027
2028    #[test]
2029    fn generated_nonce_round_trips_and_is_unique() {
2030        let mut key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
2031
2032        let (nonce_a, ct_a) = key
2033            .encrypt_with_generated_nonce(b"aad", b"plaintext")
2034            .expect("encryption should succeed");
2035        let (nonce_b, _ct_b) = key
2036            .encrypt_with_generated_nonce(b"aad", b"plaintext")
2037            .expect("encryption should succeed");
2038
2039        // Same key + same plaintext must not reuse a nonce.
2040        assert_ne!(nonce_a, nonce_b, "generated nonces must be unique");
2041        // The returned nonce decrypts its ciphertext.
2042        assert_eq!(
2043            key.decrypt_with_nonce(&nonce_a, b"aad", &ct_a)
2044                .expect("decryption should succeed"),
2045            b"plaintext"
2046        );
2047    }
2048
2049    #[test]
2050    fn generated_nonce_appended_layout_round_trips() {
2051        let mut key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
2052        let blob = key
2053            .encrypt_nonce_appended_generated(b"plaintext")
2054            .expect("encryption should succeed");
2055        assert_eq!(
2056            blob.len(),
2057            b"plaintext".len() + super::TAG_SIZE + NONCE_SIZE
2058        );
2059        assert_eq!(
2060            key.decrypt_nonce_appended(&blob)
2061                .expect("decryption should succeed"),
2062            b"plaintext"
2063        );
2064    }
2065
2066    #[test]
2067    fn caller_placed_generated_nonce_round_trips() {
2068        let layout = HardwareAes256Gcm::key_state_layout();
2069        let mut storage = AlignedStorage::<512>([0_u8; 512]);
2070        let slot = UninitKeyStateSlot::new(&mut storage.0[..layout.size]).expect("valid slot");
2071        let mut key = HardwareAes256GcmIn::new_in(&[7_u8; 32], slot).expect("valid test key");
2072        let (nonce, ct) = key
2073            .encrypt_with_generated_nonce(&[], b"plaintext")
2074            .expect("encryption should succeed");
2075        assert_eq!(
2076            key.decrypt_with_nonce(&nonce, &[], &ct)
2077                .expect("decryption should succeed"),
2078            b"plaintext"
2079        );
2080    }
2081
2082    /// Exercises *every* `HardwareAes256GcmIn` explicit-buffer / nonce-appended
2083    /// method with a verified round trip (and byte-exact cross-validation against
2084    /// `HardwareAes256Gcm` for the explicit-nonce paths). Without this, the thin
2085    /// delegations to the underlying key state are called but their output is
2086    /// never checked, so a wrong (or stubbed) wrapper survives - a gap surfaced
2087    /// by mutation testing (see docs/mutation-testing.md).
2088    #[test]
2089    fn caller_placed_in_buffer_methods_round_trip() {
2090        const KEY: [u8; 32] = [7_u8; 32];
2091        const NONCE: [u8; NONCE_SIZE] = [0x24; NONCE_SIZE];
2092        let pt = b"caller-placed in-buffer methods".to_vec();
2093        let aad = b"header".as_slice();
2094
2095        let layout = HardwareAes256Gcm::key_state_layout();
2096        let mut storage = AlignedStorage::<512>([0_u8; 512]);
2097        let slot = UninitKeyStateSlot::new(&mut storage.0[..layout.size]).expect("valid slot");
2098        let mut key = HardwareAes256GcmIn::new_in(&KEY, slot).expect("valid test key");
2099        // Independent reference for byte-exact cross-validation.
2100        let reference = HardwareAes256Gcm::new(&KEY).expect("reference key");
2101
2102        // --- explicit nonce: byte-identical to the reference, and round-trips ---
2103        let ct = key
2104            .encrypt_with_nonce(&NONCE, aad, &pt)
2105            .expect("encrypt_with_nonce");
2106        assert_eq!(
2107            ct,
2108            reference.encrypt_with_nonce(&NONCE, aad, &pt).unwrap(),
2109            "encrypt_with_nonce diverged from the reference cipher"
2110        );
2111        assert_eq!(key.decrypt_with_nonce(&NONCE, aad, &ct).unwrap(), pt);
2112
2113        let mut buf = vec![0_u8; pt.len() + TAG_SIZE];
2114        let n = key
2115            .encrypt_with_nonce_to(&NONCE, aad, &pt, &mut buf)
2116            .expect("encrypt_with_nonce_to");
2117        assert_eq!(n, pt.len() + TAG_SIZE);
2118        assert_eq!(&buf[..n], &ct[..]);
2119        let mut out = vec![0_u8; pt.len()];
2120        let m = key
2121            .decrypt_with_nonce_to(&NONCE, aad, &buf[..n], &mut out)
2122            .expect("decrypt_with_nonce_to");
2123        assert_eq!(&out[..m], &pt[..]);
2124
2125        // --- nonce-appended (empty AAD), explicit nonce ---
2126        let env = key
2127            .encrypt_nonce_appended(&NONCE, &pt)
2128            .expect("encrypt_nonce_appended");
2129        assert_eq!(key.decrypt_nonce_appended(&env).unwrap(), pt);
2130
2131        let mut env_buf = vec![0_u8; pt.len() + TAG_SIZE + NONCE_SIZE];
2132        let n = key
2133            .encrypt_nonce_appended_to(&NONCE, &pt, &mut env_buf)
2134            .expect("encrypt_nonce_appended_to");
2135        assert_eq!(n, env_buf.len());
2136        assert_eq!(env_buf, env);
2137        let mut out = vec![0_u8; pt.len()];
2138        let m = key
2139            .decrypt_nonce_appended_to(&env_buf, &mut out)
2140            .expect("decrypt_nonce_appended_to");
2141        assert_eq!(&out[..m], &pt[..]);
2142
2143        // In-place encrypt: from an under-capacity Vec (forces the reserve path)
2144        // and from an exact-capacity Vec; both must round-trip.
2145        for prealloc in [0_usize, pt.len() + TAG_SIZE + NONCE_SIZE] {
2146            let mut in_out = Vec::with_capacity(prealloc);
2147            in_out.extend_from_slice(&pt);
2148            key.encrypt_nonce_appended_in_place(&NONCE, &mut in_out)
2149                .expect("encrypt_nonce_appended_in_place");
2150            assert_eq!(in_out, env, "in-place layout must match");
2151            assert_eq!(key.decrypt_nonce_appended(&in_out).unwrap(), pt);
2152        }
2153
2154        // --- generated nonce: round-trips, and successive outputs differ ----
2155        let mut out = vec![0_u8; pt.len() + TAG_SIZE + NONCE_SIZE];
2156        let n = key.encrypt_to(aad, &pt, &mut out).expect("encrypt_to");
2157        assert_eq!(n, out.len());
2158        let mut dec = vec![0_u8; pt.len()];
2159        let m = key.decrypt_to(aad, &out, &mut dec).expect("decrypt_to");
2160        assert_eq!(&dec[..m], &pt[..]);
2161
2162        let g1 = key.encrypt_nonce_appended_generated(&pt).unwrap();
2163        let g2 = key.encrypt_nonce_appended_generated(&pt).unwrap();
2164        assert_ne!(g1, g2, "generated nonces must differ between calls");
2165        assert_eq!(key.decrypt_nonce_appended(&g1).unwrap(), pt);
2166        assert_eq!(key.decrypt_nonce_appended(&g2).unwrap(), pt);
2167
2168        drop(reference);
2169    }
2170
2171    /// The owned-key-state `encrypt_to` must write a real envelope that decrypts
2172    /// back (a delegation previously not output-verified; mutation testing).
2173    #[test]
2174    fn owned_key_state_encrypt_to_round_trips() {
2175        let mut key = HardwareAes256GcmKeyState::new(&[9_u8; 32]).expect("valid key");
2176        let pt = b"owned key state encrypt_to path";
2177        let mut out = vec![0_u8; pt.len() + TAG_SIZE + NONCE_SIZE];
2178        let n = key.encrypt_to(b"aad", pt, &mut out).expect("encrypt_to");
2179        assert_eq!(n, out.len());
2180        assert_eq!(key.decrypt(b"aad", &out).expect("decrypt"), pt);
2181    }
2182
2183    /// `validate_gcm_lengths` must reject an input that exceeds *any one* of the
2184    /// length limits (not only all of them) - exercised with length *values*, so
2185    /// no allocation is needed. Pins the `||` chain against a `&&` regression
2186    /// (mutation testing) and complements the Kani harness, which `cargo test`
2187    /// does not run.
2188    #[test]
2189    fn gcm_length_validation_rejects_each_over_limit() {
2190        use super::{validate_gcm_lengths, MAX_GCM_DATA_LEN, MAX_GHASH_INPUT_LEN};
2191        assert!(validate_gcm_lengths(16, 16).is_ok());
2192        // Over the GCM counter limit on the data (AAD in range).
2193        if let Ok(over) = usize::try_from(MAX_GCM_DATA_LEN + 1) {
2194            assert_eq!(validate_gcm_lengths(16, over), Err(Error::InputTooLarge));
2195        }
2196        // Over the GHASH 64-bit length-field limit on the AAD (data in range).
2197        if let Ok(over) = usize::try_from(MAX_GHASH_INPUT_LEN + 1) {
2198            assert_eq!(validate_gcm_lengths(over, 16), Err(Error::InputTooLarge));
2199        }
2200        // Exactly at the GCM data limit is accepted.
2201        if let Ok(lim) = usize::try_from(MAX_GCM_DATA_LEN) {
2202            assert!(validate_gcm_lengths(0, lim).is_ok());
2203        }
2204    }
2205
2206    #[test]
2207    fn generated_nonce_does_not_change_key_state_size() {
2208        // The nonce generator lives on the handle, not the placed key state.
2209        assert_eq!(HardwareAes256Gcm::key_state_layout().size, 368);
2210        assert_eq!(HardwareAes256Gcm::state_size(), 368);
2211    }
2212
2213    #[test]
2214    fn inline_owned_key_state_layout_matches_reported_layout() {
2215        let layout = HardwareAes256Gcm::key_state_layout();
2216        assert!(std::mem::size_of::<HardwareAes256GcmKeyState>() >= layout.size);
2217        assert!(std::mem::align_of::<HardwareAes256GcmKeyState>() >= layout.align);
2218        assert_eq!(HardwareAes256GcmKeyState::state_size(), layout.size);
2219    }
2220
2221    #[test]
2222    fn inline_owned_key_state_default_envelope_round_trips() {
2223        let mut key = HardwareAes256GcmKeyState::new(&[7_u8; 32]).expect("valid test key");
2224        let first = key
2225            .encrypt(b"aad", b"plaintext")
2226            .expect("encryption should succeed");
2227        let second = key
2228            .encrypt(b"aad", b"plaintext")
2229            .expect("encryption should succeed");
2230
2231        assert_eq!(
2232            key.decrypt(b"aad", &first)
2233                .expect("decryption should succeed"),
2234            b"plaintext"
2235        );
2236        assert_eq!(
2237            key.decrypt(b"aad", &second)
2238                .expect("decryption should succeed"),
2239            b"plaintext"
2240        );
2241        assert_ne!(
2242            &first[first.len() - NONCE_SIZE..],
2243            &second[second.len() - NONCE_SIZE..],
2244            "generated nonces must be unique"
2245        );
2246
2247        let mut out = [0_u8; 9];
2248        let written = key
2249            .decrypt_to(b"aad", &first, &mut out)
2250            .expect("decryption into caller buffer should succeed");
2251        assert_eq!(written, out.len());
2252        assert_eq!(&out, b"plaintext");
2253    }
2254
2255    #[test]
2256    fn inline_owned_key_state_round_trips_and_uses_existing_vec_capacity() {
2257        let key = HardwareAes256GcmKeyState::new(&[7_u8; 32]).expect("valid test key");
2258        let nonce = [9_u8; NONCE_SIZE];
2259        let expected = key
2260            .encrypt_nonce_appended(&nonce, b"plaintext")
2261            .expect("encryption should succeed");
2262        assert_eq!(
2263            key.decrypt_nonce_appended(&expected)
2264                .expect("decryption should succeed"),
2265            b"plaintext"
2266        );
2267
2268        let mut in_out = Vec::with_capacity(b"plaintext".len() + super::TAG_SIZE + NONCE_SIZE);
2269        in_out.extend_from_slice(b"plaintext");
2270        let ptr_before = in_out.as_ptr();
2271        key.encrypt_nonce_appended_in_place(&nonce, &mut in_out)
2272            .expect("in-place encryption should succeed");
2273        assert_eq!(in_out, expected);
2274        assert_eq!(
2275            in_out.as_ptr(),
2276            ptr_before,
2277            "pre-reserved in-place encryption should not reallocate"
2278        );
2279    }
2280
2281    #[test]
2282    fn inline_owned_key_state_wipes_storage_on_drop() {
2283        let layout = HardwareAes256Gcm::key_state_layout();
2284        let mut key =
2285            ManuallyDrop::new(HardwareAes256GcmKeyState::new(&[7_u8; 32]).expect("valid test key"));
2286
2287        // SAFETY: key lives in this stack frame and ManuallyDrop keeps the
2288        // backing bytes valid after its Drop impl runs (the value is logically
2289        // dropped but not freed). The storage pointer is taken *after* the drop
2290        // so its provenance is not invalidated by the `&mut key` the drop uses -
2291        // reading the just-wiped bytes through a freshly-derived pointer.
2292        unsafe {
2293            ManuallyDrop::drop(&mut key);
2294            let storage = key.storage.bytes_ptr_mut();
2295            let bytes = std::slice::from_raw_parts(storage.cast_const(), layout.size);
2296            assert!(bytes.iter().all(|byte| *byte == 0));
2297        }
2298    }
2299
2300    #[test]
2301    fn caller_placed_key_state_is_usable_across_threads() {
2302        let layout = HardwareAes256Gcm::key_state_layout();
2303        let mut storage = AlignedStorage::<512>([0_u8; 512]);
2304        let slot = UninitKeyStateSlot::new(&mut storage.0[..layout.size]).expect("valid slot");
2305        let mut key = HardwareAes256GcmIn::new_in(&[7_u8; 32], slot).expect("valid test key");
2306        let ciphertext = key
2307            .encrypt(&[], b"plaintext")
2308            .expect("encryption should succeed");
2309
2310        std::thread::scope(|scope| {
2311            for _ in 0..4 {
2312                scope.spawn(|| {
2313                    assert_eq!(
2314                        key.decrypt(&[], &ciphertext)
2315                            .expect("shared decryption should succeed"),
2316                        b"plaintext"
2317                    );
2318                });
2319            }
2320        });
2321    }
2322
2323    #[test]
2324    fn rejects_wrong_nonce_length() {
2325        let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
2326        let Err(err) = key.encrypt_with_nonce(&[0_u8; NONCE_SIZE - 1], &[], b"data") else {
2327            panic!("short nonce should be rejected");
2328        };
2329        assert_eq!(err, Error::InvalidNonceLength);
2330    }
2331
2332    #[test]
2333    #[cfg(target_pointer_width = "64")]
2334    fn rejects_inputs_that_exceed_gcm_counter_limit() {
2335        let max_data_len =
2336            usize::try_from(super::MAX_GCM_DATA_LEN).expect("64-bit usize holds GCM data limit");
2337        assert_eq!(
2338            super::validate_gcm_lengths(0, max_data_len + 1),
2339            Err(Error::InputTooLarge)
2340        );
2341    }
2342
2343    #[test]
2344    #[cfg(target_pointer_width = "64")]
2345    fn accepts_inputs_at_gcm_counter_limit() {
2346        let max_data_len =
2347            usize::try_from(super::MAX_GCM_DATA_LEN).expect("64-bit usize holds GCM data limit");
2348        assert_eq!(super::validate_gcm_lengths(0, max_data_len), Ok(()));
2349    }
2350
2351    #[test]
2352    fn nonce_appended_round_trips() {
2353        let key = HardwareAes256Gcm::new(&[7_u8; 32]).expect("valid test key");
2354        let nonce = [9_u8; NONCE_SIZE];
2355        let ciphertext = key
2356            .encrypt_nonce_appended(&nonce, b"plaintext")
2357            .expect("encryption should succeed");
2358        assert_eq!(
2359            key.decrypt_nonce_appended(&ciphertext)
2360                .expect("decryption should succeed"),
2361            b"plaintext"
2362        );
2363    }
2364
2365    #[test]
2366    fn reports_nonzero_key_state_layout() {
2367        let layout = HardwareAes256Gcm::key_state_layout();
2368        assert!(layout.size >= super::KEY_SIZE);
2369        assert!(layout.align.is_power_of_two());
2370        assert!(layout.size <= 384);
2371        assert_eq!(layout.size, HardwareAes256Gcm::state_size());
2372    }
2373
2374    #[test]
2375    fn caller_placed_key_state_round_trips_and_wipes_storage() {
2376        let layout = HardwareAes256Gcm::key_state_layout();
2377        let mut storage = AlignedStorage::<512>([0xa5; 512]);
2378
2379        {
2380            let slot = UninitKeyStateSlot::new(&mut storage.0[..layout.size]).expect("valid slot");
2381            let mut key = HardwareAes256GcmIn::new_in(&[7_u8; 32], slot).expect("valid test key");
2382            let ciphertext = key
2383                .encrypt(&[], b"plaintext")
2384                .expect("encryption should succeed");
2385            assert_eq!(
2386                key.decrypt(&[], &ciphertext)
2387                    .expect("decryption should succeed"),
2388                b"plaintext"
2389            );
2390        }
2391
2392        assert!(storage.0[..layout.size].iter().all(|byte| *byte == 0));
2393    }
2394
2395    #[test]
2396    fn caller_placed_key_state_validates_storage_before_key() {
2397        let layout = HardwareAes256Gcm::key_state_layout();
2398        let mut short = AlignedStorage::<512>([0_u8; 512]);
2399        let err = UninitKeyStateSlot::new(&mut short.0[..layout.size - 1])
2400            .expect_err("short storage should fail");
2401        assert_eq!(err, Error::KeyStateStorageTooSmall);
2402
2403        let mut misaligned = AlignedStorage::<512>([0_u8; 512]);
2404        let start = 1;
2405        let end = start + layout.size;
2406        let err = UninitKeyStateSlot::new(&mut misaligned.0[start..end])
2407            .expect_err("misaligned storage should fail");
2408        assert_eq!(err, Error::KeyStateStorageMisaligned);
2409    }
2410
2411    fn hx(s: &str) -> Vec<u8> {
2412        (0..s.len())
2413            .step_by(2)
2414            .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
2415            .collect()
2416    }
2417
2418    #[test]
2419    fn detached_round_trips_and_matches_nonce_appended_path() {
2420        const KEY: [u8; 32] = [0x5b; 32];
2421        const NONCE: [u8; NONCE_SIZE] = [0x42; NONCE_SIZE];
2422        let key = HardwareAes256GcmKeyState::new(&KEY).expect("valid key");
2423        let pt = b"detached seal/open round-trip payload".to_vec();
2424        let aad = b"associated-data".as_slice();
2425
2426        // Allocating detached seal/open with AAD.
2427        let (ct, tag) = key.seal_detached(&NONCE, aad, &pt).expect("seal_detached");
2428        assert_eq!(ct.len(), pt.len(), "ciphertext is plaintext-length");
2429        assert_eq!(key.open_detached(&NONCE, aad, &ct, &tag).unwrap(), pt);
2430
2431        // In-place detached seal/open must agree with the allocating variant.
2432        let mut buf = pt.clone();
2433        let tag2 = key
2434            .seal_in_place_detached(&NONCE, aad, &mut buf)
2435            .expect("seal_in_place_detached");
2436        assert_eq!(buf, ct, "in-place ciphertext matches allocating");
2437        assert_eq!(tag2, tag, "in-place tag matches allocating");
2438        key.open_in_place_detached(&NONCE, aad, &mut buf, &tag2)
2439            .expect("open_in_place_detached");
2440        assert_eq!(buf, pt, "in-place open recovers plaintext");
2441
2442        // Detached output (empty AAD) must equal the validated nonce-appended
2443        // envelope minus its trailing nonce — ties the new primitive to the
2444        // existing explicit-nonce path byte-for-byte.
2445        let env = key.encrypt_nonce_appended(&NONCE, &pt).expect("appended");
2446        let (ct0, tag0) = key.seal_detached(&NONCE, &[], &pt).expect("seal_detached");
2447        let mut joined = ct0;
2448        joined.extend_from_slice(&tag0);
2449        assert_eq!(joined, env[..env.len() - NONCE_SIZE]);
2450    }
2451
2452    #[test]
2453    fn detached_seal_reproduces_nist_vector() {
2454        // NIST CAVP AES-256-GCM, AADlen=0, Taglen=128. The detached, explicit-
2455        // nonce path must reproduce a published (key, nonce, pt) -> (ct, tag)
2456        // vector exactly — the core reason this primitive exists.
2457        let key = HardwareAes256GcmKeyState::new(&hx(
2458            "31bdadd96698c204aa9ce1448ea94ae1fb4a9a0b3c9d773b51bb1822666b8f22",
2459        ))
2460        .expect("valid key");
2461        let nonce = hx("0d18e06c7c725ac9e362e1ce");
2462        let pt = hx("2db5168e932556f8089a0622981d017d");
2463        let (ct, tag) = key.seal_detached(&nonce, &[], &pt).expect("seal_detached");
2464        assert_eq!(ct, hx("fa4362189661d163fcd6a56d8bf0405a"));
2465        assert_eq!(tag.as_slice(), hx("d636ac1bbedd5cc3ee727dc2ab4a9489"));
2466        // And the open side authenticates the same vector.
2467        assert_eq!(key.open_detached(&nonce, &[], &ct, &tag).unwrap(), pt);
2468    }
2469
2470    #[test]
2471    fn detached_open_rejects_wrong_tag_and_aad() {
2472        const KEY: [u8; 32] = [0x11; 32];
2473        const NONCE: [u8; NONCE_SIZE] = [0x22; NONCE_SIZE];
2474        let key = HardwareAes256GcmKeyState::new(&KEY).expect("valid key");
2475        let pt = b"authenticate me".to_vec();
2476        let (ct, mut tag) = key.seal_detached(&NONCE, b"aad", &pt).expect("seal");
2477
2478        // Wrong AAD fails.
2479        assert_eq!(
2480            key.open_detached(&NONCE, b"other", &ct, &tag),
2481            Err(Error::Decrypt)
2482        );
2483        // Tampered tag fails.
2484        tag[0] ^= 0xff;
2485        assert_eq!(
2486            key.open_detached(&NONCE, b"aad", &ct, &tag),
2487            Err(Error::Decrypt)
2488        );
2489        // Wrong-length tag fails.
2490        assert_eq!(
2491            key.open_detached(&NONCE, b"aad", &ct, &tag[..15]),
2492            Err(Error::Decrypt)
2493        );
2494    }
2495
2496    #[test]
2497    fn detached_rejects_bad_nonce_length() {
2498        let key = HardwareAes256GcmKeyState::new(&[3_u8; 32]).expect("valid key");
2499        assert_eq!(
2500            key.seal_detached(&[0_u8; 11], &[], b"x"),
2501            Err(Error::InvalidNonceLength)
2502        );
2503    }
2504}
2505
2506/// Kani bounded-model-checking harnesses for the intrinsic-free GCM logic.
2507///
2508/// Unlike the Z3 proofs in `proofs/`, which reason about a faithful *model* of
2509/// the code, Kani (CBMC) verifies the **actual compiled Rust** symbolically over
2510/// all inputs (bounded where noted) - so these are extraction-grade proofs of
2511/// the counter increment, the length validation, and the nonce parser. Run with
2512/// `cargo kani` (see docs/assurance.md). Compiled only under `cfg(kani)`.
2513#[cfg(kani)]
2514mod kani_proofs {
2515    use super::{
2516        constant_time_eq, increment_counter, j0, nonce_from_slice, validate_gcm_lengths, Error,
2517        MAX_GCM_DATA_LEN, MAX_GHASH_INPUT_LEN, NONCE_SIZE, TAG_SIZE,
2518    };
2519
2520    /// The compiled `increment_counter` is exactly SP 800-38D `inc_32`: the
2521    /// trailing 32 bits increment big-endian (wrapping), the leading 96 bits are
2522    /// untouched. Verified over all 2^128 counter blocks.
2523    #[kani::proof]
2524    fn increment_counter_is_be32_inc() {
2525        let mut counter: [u8; 16] = kani::any();
2526        let original = counter;
2527        increment_counter(&mut counter);
2528        assert!(counter[..12] == original[..12]);
2529        let low = u32::from_be_bytes([original[12], original[13], original[14], original[15]]);
2530        assert!(counter[12..] == low.wrapping_add(1).to_be_bytes());
2531    }
2532
2533    /// `j0` builds `IV || 0^31 || 1` for any 96-bit nonce.
2534    #[kani::proof]
2535    fn j0_layout() {
2536        let nonce: [u8; NONCE_SIZE] = kani::any();
2537        let block = j0(&nonce);
2538        assert!(block[..NONCE_SIZE] == nonce);
2539        assert!(block[NONCE_SIZE..] == [0, 0, 0, 1]);
2540    }
2541
2542    /// `validate_gcm_lengths` never panics and accepts exactly the lengths within
2543    /// both the GCM counter limit and the GHASH 64-bit length field.
2544    #[kani::proof]
2545    fn validate_gcm_lengths_matches_limits() {
2546        let aad_len: usize = kani::any();
2547        let data_len: usize = kani::any();
2548        let result = validate_gcm_lengths(aad_len, data_len);
2549        let aad = aad_len as u64;
2550        let data = data_len as u64;
2551        let in_range =
2552            data <= MAX_GCM_DATA_LEN && aad <= MAX_GHASH_INPUT_LEN && data <= MAX_GHASH_INPUT_LEN;
2553        assert!(result.is_ok() == in_range);
2554    }
2555
2556    /// `nonce_from_slice` never panics and returns Ok exactly when the slice is
2557    /// the 12-byte nonce length. Bounded to lengths 0..=24.
2558    #[kani::proof]
2559    fn nonce_from_slice_accepts_only_correct_length() {
2560        let len: usize = kani::any();
2561        kani::assume(len <= 24);
2562        let buf = [0_u8; 24];
2563        let result = nonce_from_slice(&buf[..len]);
2564        match result {
2565            Ok(n) => assert!(len == NONCE_SIZE && n.len() == NONCE_SIZE),
2566            Err(e) => assert!(len != NONCE_SIZE && matches!(e, Error::InvalidNonceLength)),
2567        }
2568    }
2569
2570    /// The authentication decision is functionally exact: for equal-length
2571    /// inputs `constant_time_eq` is bytewise equality. (Constant-*time*-ness is
2572    /// covered separately by the dudect harnesses; this proves *correctness* -
2573    /// that the masking/folding never accepts a wrong tag or rejects a right one.)
2574    /// Verified over all tag-pairs.
2575    ///
2576    /// Only the equal-length case is model-checked: callers always pass a
2577    /// `TAG_SIZE` slice (`split_ciphertext_tag`/the decrypt split are proven to
2578    /// hand over exactly `TAG_SIZE` bytes), and `subtle`'s variable-length slice
2579    /// loop does not bound under CBMC.
2580    #[kani::proof]
2581    fn constant_time_eq_equals_bytewise_equality() {
2582        let expected: [u8; TAG_SIZE] = kani::any();
2583        let actual: [u8; TAG_SIZE] = kani::any();
2584        assert!(constant_time_eq(&expected, &actual) == (expected == actual));
2585    }
2586}