#![allow(unsafe_code)]
use core::{
marker::PhantomData,
mem::MaybeUninit,
ptr::{self, NonNull},
};
use zeroize::Zeroize as _;
use super::{
aes, append_tag_nonce, constant_time_eq, ghash, nonce, nonce_from_slice, volatile_wipe, Error,
KeyStateLayout, KEY_SIZE, NONCE_SIZE, TAG_SIZE,
};
const AES_BLOCK_SIZE: usize = 16;
const PAR_BLOCKS: usize = aes::PAR_BLOCKS;
const PAR_BYTES: usize = PAR_BLOCKS * AES_BLOCK_SIZE;
const AUTH_KEY_SIZE: usize = 16;
const ENC_KEY_SIZE: usize = 32;
const MAX_SIV_LEN: u64 = 1 << 36;
fn hardware_available() -> bool {
aes::hardware_available() && ghash::hardware_available()
}
fn len_exceeds_siv_limit(len: usize) -> bool {
u64::try_from(len).map_or(true, |len| len > MAX_SIV_LEN)
}
fn validate_siv_lengths(aad_len: usize, data_len: usize) -> Result<(), Error> {
if len_exceeds_siv_limit(aad_len) || len_exceeds_siv_limit(data_len) {
return Err(Error::InputTooLarge);
}
Ok(())
}
fn derive_keys(
master: &aes::Aes256,
nonce: &[u8; NONCE_SIZE],
) -> ([u8; AUTH_KEY_SIZE], [u8; ENC_KEY_SIZE]) {
let mut input = [0_u8; AES_BLOCK_SIZE];
input[4..].copy_from_slice(nonce);
let mut counter: u32 = 0;
let mut derive_into = |slot: &mut [u8]| {
input[..4].copy_from_slice(&counter.to_le_bytes());
let mut block = input;
master.encrypt_block(&mut block);
slot.copy_from_slice(&block[..8]);
block.zeroize();
counter += 1;
};
let mut auth_key = [0_u8; AUTH_KEY_SIZE];
for slot in auth_key.chunks_mut(8) {
derive_into(slot);
}
let mut enc_key = [0_u8; ENC_KEY_SIZE];
for slot in enc_key.chunks_mut(8) {
derive_into(slot);
}
(auth_key, enc_key)
}
fn build_message_cipher(enc_key: &[u8; ENC_KEY_SIZE]) -> Option<aes::Aes256> {
let mut slot = MaybeUninit::<aes::Aes256>::uninit();
aes::Aes256::init_in_place(slot.as_mut_ptr(), enc_key)?;
Some(unsafe { slot.assume_init() })
}
fn polyval_digest(
auth_key: &[u8; AUTH_KEY_SIZE],
aad: &[u8],
message: &[u8],
) -> Option<[u8; AES_BLOCK_SIZE]> {
let mut powers = ghash::Polyval::key_powers(auth_key)?;
let polyval = ghash::Polyval::new(&powers);
powers.zeroize();
let mut polyval = polyval?;
polyval.absorb_padded(aad);
polyval.absorb_padded(message);
polyval.finalize_with_lengths(aad.len(), message.len())
}
fn increment_siv_counter(counter: &mut [u8; AES_BLOCK_SIZE]) {
let mut low = [0_u8; 4];
low.copy_from_slice(&counter[..4]);
let next = u32::from_le_bytes(low).wrapping_add(1);
counter[..4].copy_from_slice(&next.to_le_bytes());
}
fn ctr_apply(cipher: &aes::Aes256, counter: &mut [u8; AES_BLOCK_SIZE], data: &mut [u8]) {
let mut batches = data.chunks_exact_mut(PAR_BYTES);
for batch in &mut batches {
let mut keystream = [[0_u8; AES_BLOCK_SIZE]; PAR_BLOCKS];
for block in &mut keystream {
*block = *counter;
increment_siv_counter(counter);
}
cipher.encrypt8(&mut keystream);
for (key_block, data_block) in keystream.iter().zip(batch.chunks_exact_mut(AES_BLOCK_SIZE))
{
for (byte, key_byte) in data_block.iter_mut().zip(key_block.iter()) {
*byte ^= key_byte;
}
}
}
for data_block in batches.into_remainder().chunks_mut(AES_BLOCK_SIZE) {
let mut keystream = *counter;
cipher.encrypt_block(&mut keystream);
increment_siv_counter(counter);
for (byte, key_byte) in data_block.iter_mut().zip(keystream.iter()) {
*byte ^= key_byte;
}
}
}
fn siv_tag(
cipher: &aes::Aes256,
nonce: &[u8; NONCE_SIZE],
mut digest: [u8; AES_BLOCK_SIZE],
) -> [u8; TAG_SIZE] {
for (byte, nonce_byte) in digest[..NONCE_SIZE].iter_mut().zip(nonce.iter()) {
*byte ^= nonce_byte;
}
digest[AES_BLOCK_SIZE - 1] &= 0x7f;
cipher.encrypt_block(&mut digest);
digest
}
fn siv_seal(
master: &aes::Aes256,
nonce: &[u8; NONCE_SIZE],
aad: &[u8],
data: &mut [u8],
) -> Option<[u8; TAG_SIZE]> {
let (mut auth_key, mut enc_key) = derive_keys(master, nonce);
let cipher = build_message_cipher(&enc_key);
enc_key.zeroize();
let Some(cipher) = cipher else {
auth_key.zeroize();
return None;
};
let digest = polyval_digest(&auth_key, aad, data);
auth_key.zeroize();
let digest = digest?;
let tag = siv_tag(&cipher, nonce, digest);
let mut counter = tag;
counter[AES_BLOCK_SIZE - 1] |= 0x80;
ctr_apply(&cipher, &mut counter, data);
Some(tag)
}
fn siv_open(
master: &aes::Aes256,
nonce: &[u8; NONCE_SIZE],
aad: &[u8],
data: &mut [u8],
tag: &[u8; TAG_SIZE],
) -> bool {
let (mut auth_key, mut enc_key) = derive_keys(master, nonce);
let cipher = build_message_cipher(&enc_key);
enc_key.zeroize();
let Some(cipher) = cipher else {
auth_key.zeroize();
return false;
};
let mut counter = *tag;
counter[AES_BLOCK_SIZE - 1] |= 0x80;
ctr_apply(&cipher, &mut counter, data);
let digest = polyval_digest(&auth_key, aad, data);
auth_key.zeroize();
let Some(digest) = digest else {
return false;
};
let expected = siv_tag(&cipher, nonce, digest);
constant_time_eq(&expected, tag)
}
struct SivKeyState {
master: aes::Aes256,
}
impl SivKeyState {
fn init_in_place(dst: NonNull<Self>, key: &[u8; KEY_SIZE]) -> Result<(), Error> {
if !hardware_available() {
return Err(Error::UnsupportedCpu);
}
let dst = dst.as_ptr();
let master_ptr = unsafe { ptr::addr_of_mut!((*dst).master) };
aes::Aes256::init_in_place(master_ptr, key).ok_or(Error::UnsupportedCpu)?;
Ok(())
}
fn encrypt(&self, nonce: &[u8], aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
let total = plaintext
.len()
.checked_add(TAG_SIZE)
.ok_or(Error::InputTooLarge)?;
let mut out = vec![0_u8; total];
self.encrypt_to(nonce, aad, plaintext, &mut out)?;
Ok(out)
}
fn encrypt_to(
&self,
nonce: &[u8],
aad: &[u8],
plaintext: &[u8],
out: &mut [u8],
) -> Result<usize, Error> {
let nonce = nonce_from_slice(nonce)?;
validate_siv_lengths(aad.len(), plaintext.len())?;
let total = plaintext
.len()
.checked_add(TAG_SIZE)
.ok_or(Error::InputTooLarge)?;
if out.len() < total {
return Err(Error::OutputTooSmall);
}
let (ciphertext, rest) = out.split_at_mut(plaintext.len());
ciphertext.copy_from_slice(plaintext);
if let Some(tag) = siv_seal(&self.master, &nonce, aad, ciphertext) {
rest[..TAG_SIZE].copy_from_slice(&tag);
Ok(total)
} else {
ciphertext.zeroize();
Err(Error::Encrypt)
}
}
fn decrypt(
&self,
nonce: &[u8],
aad: &[u8],
ciphertext_and_tag: &[u8],
) -> Result<Vec<u8>, Error> {
let nonce = nonce_from_slice(nonce)?;
let (ciphertext, tag) = split_ciphertext_tag(ciphertext_and_tag)?;
validate_siv_lengths(aad.len(), ciphertext.len())?;
let mut out = vec![0_u8; ciphertext.len()];
out.copy_from_slice(ciphertext);
if siv_open(&self.master, &nonce, aad, &mut out, tag) {
Ok(out)
} else {
out.zeroize();
Err(Error::Decrypt)
}
}
fn decrypt_to(
&self,
nonce: &[u8],
aad: &[u8],
ciphertext_and_tag: &[u8],
out: &mut [u8],
) -> Result<usize, Error> {
let nonce = nonce_from_slice(nonce)?;
let (ciphertext, tag) = split_ciphertext_tag(ciphertext_and_tag)?;
validate_siv_lengths(aad.len(), ciphertext.len())?;
if out.len() < ciphertext.len() {
return Err(Error::OutputTooSmall);
}
let out = &mut out[..ciphertext.len()];
out.copy_from_slice(ciphertext);
if siv_open(&self.master, &nonce, aad, out, tag) {
Ok(ciphertext.len())
} else {
out.zeroize();
Err(Error::Decrypt)
}
}
fn encrypt_nonce_appended(&self, nonce: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
self.encrypt_envelope(nonce, &[], plaintext)
}
fn encrypt_envelope(
&self,
nonce: &[u8],
aad: &[u8],
plaintext: &[u8],
) -> Result<Vec<u8>, Error> {
let nonce = nonce_from_slice(nonce)?;
validate_siv_lengths(aad.len(), plaintext.len())?;
let total = plaintext
.len()
.checked_add(TAG_SIZE + NONCE_SIZE)
.ok_or(Error::InputTooLarge)?;
let mut out = Vec::with_capacity(total);
out.extend_from_slice(plaintext);
let Some(tag) = siv_seal(&self.master, &nonce, aad, out.as_mut_slice()) else {
out.zeroize();
return Err(Error::Encrypt);
};
append_tag_nonce(&mut out, &tag, &nonce);
Ok(out)
}
fn encrypt_envelope_to(
&self,
nonce: &[u8],
aad: &[u8],
plaintext: &[u8],
out: &mut [u8],
) -> Result<usize, Error> {
let nonce = nonce_from_slice(nonce)?;
validate_siv_lengths(aad.len(), plaintext.len())?;
let total = plaintext
.len()
.checked_add(TAG_SIZE + NONCE_SIZE)
.ok_or(Error::InputTooLarge)?;
if out.len() < total {
return Err(Error::OutputTooSmall);
}
let ciphertext_tag_len = plaintext.len() + TAG_SIZE;
self.encrypt_to(&nonce, aad, plaintext, &mut out[..ciphertext_tag_len])?;
out[ciphertext_tag_len..total].copy_from_slice(&nonce);
Ok(total)
}
#[cfg(any(test, feature = "hazmat-explicit-nonce"))]
fn encrypt_nonce_appended_in_place(
&self,
nonce: &[u8],
in_out: &mut Vec<u8>,
) -> Result<(), Error> {
let nonce = nonce_from_slice(nonce)?;
validate_siv_lengths(0, in_out.len())?;
let total = in_out
.len()
.checked_add(TAG_SIZE + NONCE_SIZE)
.ok_or(Error::InputTooLarge)?;
if in_out.capacity() < total {
in_out.reserve_exact(total - in_out.len());
}
let Some(tag) = siv_seal(&self.master, &nonce, &[], in_out.as_mut_slice()) else {
in_out.zeroize();
return Err(Error::Encrypt);
};
append_tag_nonce(in_out, &tag, &nonce);
Ok(())
}
fn decrypt_envelope(&self, aad: &[u8], data: &[u8]) -> Result<Vec<u8>, Error> {
let (ciphertext_and_tag, nonce) = split_trailing_nonce(data)?;
self.decrypt(nonce, aad, ciphertext_and_tag)
}
fn decrypt_envelope_to(&self, aad: &[u8], data: &[u8], out: &mut [u8]) -> Result<usize, Error> {
let (ciphertext_and_tag, nonce) = split_trailing_nonce(data)?;
self.decrypt_to(nonce, aad, ciphertext_and_tag, out)
}
}
fn split_ciphertext_tag(input: &[u8]) -> Result<(&[u8], &[u8; TAG_SIZE]), Error> {
if input.len() < TAG_SIZE {
return Err(Error::Decrypt);
}
let tag_pos = input.len() - TAG_SIZE;
let (ciphertext, tag) = input.split_at(tag_pos);
let tag: &[u8; TAG_SIZE] = tag.try_into().map_err(|_| Error::Decrypt)?;
Ok((ciphertext, tag))
}
fn init_siv_key_state_at(key: &[u8], state: NonNull<SivKeyState>) -> Result<(), Error> {
if key.len() != KEY_SIZE {
return Err(Error::InvalidKeyLength);
}
let key: &[u8; KEY_SIZE] = key.try_into().map_err(|_| Error::InvalidKeyLength)?;
SivKeyState::init_in_place(state, key)
}
#[cfg(any(test, feature = "hazmat-explicit-nonce"))]
fn encrypt_nonce_appended_to(
state: &SivKeyState,
nonce: &[u8],
plaintext: &[u8],
out: &mut [u8],
) -> Result<usize, Error> {
let total = plaintext
.len()
.checked_add(TAG_SIZE + NONCE_SIZE)
.ok_or(Error::InputTooLarge)?;
if out.len() < total {
return Err(Error::OutputTooSmall);
}
let written = state.encrypt_to(nonce, &[], plaintext, out)?;
out[written..total].copy_from_slice(nonce);
Ok(total)
}
fn split_trailing_nonce(data: &[u8]) -> Result<(&[u8], &[u8]), Error> {
if data.len() < TAG_SIZE + NONCE_SIZE {
return Err(Error::CiphertextTooShort);
}
let nonce_pos = data.len() - NONCE_SIZE;
Ok(data.split_at(nonce_pos))
}
const _: () = {
const fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<SivKeyState>();
};
pub struct HardwareAes256GcmSiv {
state: Box<SivKeyState>,
nonce_gen: Option<nonce::NonceGen>,
}
impl HardwareAes256GcmSiv {
pub fn new(key: &[u8]) -> Result<Self, Error> {
if key.len() != KEY_SIZE {
return Err(Error::InvalidKeyLength);
}
let mut state = Box::<SivKeyState>::new_uninit();
let ptr = unsafe { NonNull::new_unchecked(state.as_mut_ptr()) };
init_siv_key_state_at(key, ptr)?;
let state = unsafe { state.assume_init() };
Ok(Self {
state,
nonce_gen: None,
})
}
#[must_use]
pub fn hardware_available() -> bool {
hardware_available()
}
#[must_use]
pub const fn state_size() -> usize {
std::mem::size_of::<SivKeyState>()
}
#[must_use]
pub const fn key_state_layout() -> KeyStateLayout {
KeyStateLayout {
size: std::mem::size_of::<SivKeyState>(),
align: std::mem::align_of::<SivKeyState>(),
}
}
pub fn encrypt(&mut self, aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
let nonce = self.next_nonce()?;
self.state.encrypt_envelope(&nonce, aad, plaintext)
}
pub fn encrypt_to(
&mut self,
aad: &[u8],
plaintext: &[u8],
out: &mut [u8],
) -> Result<usize, Error> {
let nonce = self.next_nonce()?;
self.state.encrypt_envelope_to(&nonce, aad, plaintext, out)
}
#[cfg(any(test, feature = "hazmat-explicit-nonce"))]
#[doc(hidden)]
pub fn encrypt_with_nonce(
&self,
nonce: &[u8],
aad: &[u8],
plaintext: &[u8],
) -> Result<Vec<u8>, Error> {
self.state.encrypt(nonce, aad, plaintext)
}
#[cfg(any(test, feature = "hazmat-explicit-nonce"))]
#[doc(hidden)]
pub fn encrypt_with_nonce_to(
&self,
nonce: &[u8],
aad: &[u8],
plaintext: &[u8],
out: &mut [u8],
) -> Result<usize, Error> {
self.state.encrypt_to(nonce, aad, plaintext, out)
}
pub fn decrypt(&self, aad: &[u8], ciphertext_tag_nonce: &[u8]) -> Result<Vec<u8>, Error> {
self.state.decrypt_envelope(aad, ciphertext_tag_nonce)
}
pub fn decrypt_to(
&self,
aad: &[u8],
ciphertext_tag_nonce: &[u8],
out: &mut [u8],
) -> Result<usize, Error> {
self.state
.decrypt_envelope_to(aad, ciphertext_tag_nonce, out)
}
#[cfg(any(test, feature = "hazmat-explicit-nonce"))]
#[doc(hidden)]
pub fn decrypt_with_nonce(
&self,
nonce: &[u8],
aad: &[u8],
ciphertext_and_tag: &[u8],
) -> Result<Vec<u8>, Error> {
self.state.decrypt(nonce, aad, ciphertext_and_tag)
}
#[cfg(any(test, feature = "hazmat-explicit-nonce"))]
#[doc(hidden)]
pub fn decrypt_with_nonce_to(
&self,
nonce: &[u8],
aad: &[u8],
ciphertext_and_tag: &[u8],
out: &mut [u8],
) -> Result<usize, Error> {
self.state.decrypt_to(nonce, aad, ciphertext_and_tag, out)
}
#[cfg(any(test, feature = "hazmat-explicit-nonce"))]
#[doc(hidden)]
pub fn encrypt_nonce_appended(&self, nonce: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
self.state.encrypt_nonce_appended(nonce, plaintext)
}
#[cfg(any(test, feature = "hazmat-explicit-nonce"))]
#[doc(hidden)]
pub fn encrypt_nonce_appended_in_place(
&self,
nonce: &[u8],
in_out: &mut Vec<u8>,
) -> Result<(), Error> {
self.state.encrypt_nonce_appended_in_place(nonce, in_out)
}
#[cfg(any(test, feature = "hazmat-explicit-nonce"))]
#[doc(hidden)]
pub fn encrypt_nonce_appended_to(
&self,
nonce: &[u8],
plaintext: &[u8],
out: &mut [u8],
) -> Result<usize, Error> {
encrypt_nonce_appended_to(&self.state, nonce, plaintext, out)
}
#[doc(hidden)]
pub fn decrypt_nonce_appended(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
self.state.decrypt_envelope(&[], data)
}
#[doc(hidden)]
pub fn decrypt_nonce_appended_to(&self, data: &[u8], out: &mut [u8]) -> Result<usize, Error> {
self.state.decrypt_envelope_to(&[], data, out)
}
#[doc(hidden)]
pub fn encrypt_with_generated_nonce(
&mut self,
aad: &[u8],
plaintext: &[u8],
) -> Result<([u8; NONCE_SIZE], Vec<u8>), Error> {
let nonce = self.next_nonce()?;
let ciphertext = self.state.encrypt(&nonce, aad, plaintext)?;
Ok((nonce, ciphertext))
}
#[doc(hidden)]
pub fn encrypt_nonce_appended_generated(&mut self, plaintext: &[u8]) -> Result<Vec<u8>, Error> {
let nonce = self.next_nonce()?;
self.state.encrypt_nonce_appended(&nonce, plaintext)
}
fn next_nonce(&mut self) -> Result<[u8; NONCE_SIZE], Error> {
match self.nonce_gen {
Some(ref mut g) => g.next(),
None => self.nonce_gen.insert(nonce::NonceGen::new()?).next(),
}
}
}
impl std::fmt::Debug for HardwareAes256GcmSiv {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HardwareAes256GcmSiv")
.finish_non_exhaustive()
}
}
#[repr(transparent)]
struct AlignedSivStorage(MaybeUninit<SivKeyState>);
impl AlignedSivStorage {
#[inline]
const fn uninit() -> Self {
Self(MaybeUninit::uninit())
}
#[inline]
fn state_ptr(&self) -> *const SivKeyState {
self.0.as_ptr()
}
#[inline]
fn state_ptr_mut(&mut self) -> NonNull<SivKeyState> {
unsafe { NonNull::new_unchecked(self.0.as_mut_ptr()) }
}
#[inline]
fn bytes_ptr_mut(&mut self) -> *mut u8 {
self.0.as_mut_ptr().cast::<u8>()
}
}
pub struct HardwareAes256GcmSivKeyState {
storage: AlignedSivStorage,
nonce_gen: Option<nonce::NonceGen>,
}
impl HardwareAes256GcmSivKeyState {
#[inline]
pub fn new(key: &[u8]) -> Result<Self, Error> {
let mut storage = AlignedSivStorage::uninit();
init_siv_key_state_at(key, storage.state_ptr_mut())?;
Ok(Self {
storage,
nonce_gen: None,
})
}
#[must_use]
pub const fn state_size() -> usize {
std::mem::size_of::<SivKeyState>()
}
pub fn encrypt(&mut self, aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
let nonce = self.next_nonce()?;
self.state_ref().encrypt_envelope(&nonce, aad, plaintext)
}
pub fn encrypt_to(
&mut self,
aad: &[u8],
plaintext: &[u8],
out: &mut [u8],
) -> Result<usize, Error> {
let nonce = self.next_nonce()?;
self.state_ref()
.encrypt_envelope_to(&nonce, aad, plaintext, out)
}
pub fn decrypt(&self, aad: &[u8], ciphertext_tag_nonce: &[u8]) -> Result<Vec<u8>, Error> {
self.state_ref().decrypt_envelope(aad, ciphertext_tag_nonce)
}
pub fn decrypt_to(
&self,
aad: &[u8],
ciphertext_tag_nonce: &[u8],
out: &mut [u8],
) -> Result<usize, Error> {
self.state_ref()
.decrypt_envelope_to(aad, ciphertext_tag_nonce, out)
}
#[inline]
#[cfg(any(test, feature = "hazmat-explicit-nonce"))]
#[doc(hidden)]
pub fn encrypt_nonce_appended(&self, nonce: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
self.state_ref().encrypt_nonce_appended(nonce, plaintext)
}
#[inline]
#[cfg(any(test, feature = "hazmat-explicit-nonce"))]
#[doc(hidden)]
pub fn encrypt_nonce_appended_in_place(
&self,
nonce: &[u8],
in_out: &mut Vec<u8>,
) -> Result<(), Error> {
self.state_ref()
.encrypt_nonce_appended_in_place(nonce, in_out)
}
#[inline]
#[cfg(any(test, feature = "hazmat-explicit-nonce"))]
#[doc(hidden)]
pub fn decrypt_nonce_appended(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
self.state_ref().decrypt_envelope(&[], data)
}
fn next_nonce(&mut self) -> Result<[u8; NONCE_SIZE], Error> {
match self.nonce_gen {
Some(ref mut g) => g.next(),
None => self.nonce_gen.insert(nonce::NonceGen::new()?).next(),
}
}
fn state_ref(&self) -> &SivKeyState {
unsafe { &*self.storage.state_ptr() }
}
}
impl Drop for HardwareAes256GcmSivKeyState {
fn drop(&mut self) {
let size = Self::state_size();
unsafe { volatile_wipe(self.storage.bytes_ptr_mut(), size) };
}
}
impl std::fmt::Debug for HardwareAes256GcmSivKeyState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HardwareAes256GcmSivKeyState")
.finish_non_exhaustive()
}
}
pub struct SivUninitKeyStateSlot<'a> {
storage: &'a mut [u8],
}
impl<'a> SivUninitKeyStateSlot<'a> {
pub fn new(storage: &'a mut [u8]) -> Result<Self, Error> {
let layout = HardwareAes256GcmSiv::key_state_layout();
if storage.len() < layout.size {
return Err(Error::KeyStateStorageTooSmall);
}
if !storage.as_ptr().addr().is_multiple_of(layout.align) {
return Err(Error::KeyStateStorageMisaligned);
}
Ok(Self { storage })
}
}
impl std::fmt::Debug for SivUninitKeyStateSlot<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SivUninitKeyStateSlot")
.field("len", &self.storage.len())
.finish_non_exhaustive()
}
}
fn init_siv_key_state_in_slot(
key: &[u8],
slot: SivUninitKeyStateSlot<'_>,
) -> Result<NonNull<u8>, Error> {
let SivUninitKeyStateSlot { storage } = slot;
let storage = unsafe { NonNull::new_unchecked(storage.as_mut_ptr()) };
#[allow(clippy::cast_ptr_alignment)]
let state = storage.cast::<SivKeyState>();
init_siv_key_state_at(key, state)?;
Ok(storage)
}
struct OpaqueSivState<'a> {
storage: NonNull<u8>,
_marker: PhantomData<&'a mut [u8]>,
}
unsafe impl Send for OpaqueSivState<'_> {}
unsafe impl Sync for OpaqueSivState<'_> {}
impl OpaqueSivState<'_> {
fn state_ptr(&self) -> *mut SivKeyState {
#[allow(clippy::cast_ptr_alignment)]
self.storage.as_ptr().cast::<SivKeyState>()
}
}
impl Drop for OpaqueSivState<'_> {
fn drop(&mut self) {
let size = HardwareAes256GcmSiv::key_state_layout().size;
unsafe { volatile_wipe(self.storage.as_ptr(), size) };
}
}
pub struct HardwareAes256GcmSivIn<'a> {
state: OpaqueSivState<'a>,
nonce_gen: Option<nonce::NonceGen>,
}
impl<'a> HardwareAes256GcmSivIn<'a> {
pub fn new_in(key: &[u8], slot: SivUninitKeyStateSlot<'a>) -> Result<Self, Error> {
let storage = init_siv_key_state_in_slot(key, slot)?;
Ok(Self {
state: OpaqueSivState {
storage,
_marker: PhantomData,
},
nonce_gen: None,
})
}
pub fn encrypt(&mut self, aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
let nonce = self.next_nonce()?;
self.state_ref().encrypt_envelope(&nonce, aad, plaintext)
}
pub fn encrypt_to(
&mut self,
aad: &[u8],
plaintext: &[u8],
out: &mut [u8],
) -> Result<usize, Error> {
let nonce = self.next_nonce()?;
self.state_ref()
.encrypt_envelope_to(&nonce, aad, plaintext, out)
}
#[cfg(any(test, feature = "hazmat-explicit-nonce"))]
#[doc(hidden)]
pub fn encrypt_with_nonce(
&self,
nonce: &[u8],
aad: &[u8],
plaintext: &[u8],
) -> Result<Vec<u8>, Error> {
self.state_ref().encrypt(nonce, aad, plaintext)
}
#[cfg(any(test, feature = "hazmat-explicit-nonce"))]
#[doc(hidden)]
pub fn encrypt_with_nonce_to(
&self,
nonce: &[u8],
aad: &[u8],
plaintext: &[u8],
out: &mut [u8],
) -> Result<usize, Error> {
self.state_ref().encrypt_to(nonce, aad, plaintext, out)
}
pub fn decrypt(&self, aad: &[u8], ciphertext_tag_nonce: &[u8]) -> Result<Vec<u8>, Error> {
self.state_ref().decrypt_envelope(aad, ciphertext_tag_nonce)
}
pub fn decrypt_to(
&self,
aad: &[u8],
ciphertext_tag_nonce: &[u8],
out: &mut [u8],
) -> Result<usize, Error> {
self.state_ref()
.decrypt_envelope_to(aad, ciphertext_tag_nonce, out)
}
#[cfg(any(test, feature = "hazmat-explicit-nonce"))]
#[doc(hidden)]
pub fn decrypt_with_nonce(
&self,
nonce: &[u8],
aad: &[u8],
ciphertext_and_tag: &[u8],
) -> Result<Vec<u8>, Error> {
self.state_ref().decrypt(nonce, aad, ciphertext_and_tag)
}
#[cfg(any(test, feature = "hazmat-explicit-nonce"))]
#[doc(hidden)]
pub fn decrypt_with_nonce_to(
&self,
nonce: &[u8],
aad: &[u8],
ciphertext_and_tag: &[u8],
out: &mut [u8],
) -> Result<usize, Error> {
self.state_ref()
.decrypt_to(nonce, aad, ciphertext_and_tag, out)
}
#[cfg(any(test, feature = "hazmat-explicit-nonce"))]
#[doc(hidden)]
pub fn encrypt_nonce_appended(&self, nonce: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
self.state_ref().encrypt_nonce_appended(nonce, plaintext)
}
#[cfg(any(test, feature = "hazmat-explicit-nonce"))]
#[doc(hidden)]
pub fn encrypt_nonce_appended_in_place(
&self,
nonce: &[u8],
in_out: &mut Vec<u8>,
) -> Result<(), Error> {
self.state_ref()
.encrypt_nonce_appended_in_place(nonce, in_out)
}
#[cfg(any(test, feature = "hazmat-explicit-nonce"))]
#[doc(hidden)]
pub fn encrypt_nonce_appended_to(
&self,
nonce: &[u8],
plaintext: &[u8],
out: &mut [u8],
) -> Result<usize, Error> {
encrypt_nonce_appended_to(self.state_ref(), nonce, plaintext, out)
}
#[doc(hidden)]
pub fn decrypt_nonce_appended(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
self.state_ref().decrypt_envelope(&[], data)
}
#[doc(hidden)]
pub fn decrypt_nonce_appended_to(&self, data: &[u8], out: &mut [u8]) -> Result<usize, Error> {
self.state_ref().decrypt_envelope_to(&[], data, out)
}
#[doc(hidden)]
pub fn encrypt_with_generated_nonce(
&mut self,
aad: &[u8],
plaintext: &[u8],
) -> Result<([u8; NONCE_SIZE], Vec<u8>), Error> {
let nonce = self.next_nonce()?;
let ciphertext = self.state_ref().encrypt(&nonce, aad, plaintext)?;
Ok((nonce, ciphertext))
}
#[doc(hidden)]
pub fn encrypt_nonce_appended_generated(&mut self, plaintext: &[u8]) -> Result<Vec<u8>, Error> {
let nonce = self.next_nonce()?;
self.state_ref().encrypt_nonce_appended(&nonce, plaintext)
}
fn next_nonce(&mut self) -> Result<[u8; NONCE_SIZE], Error> {
match self.nonce_gen {
Some(ref mut g) => g.next(),
None => self.nonce_gen.insert(nonce::NonceGen::new()?).next(),
}
}
fn state_ref(&self) -> &SivKeyState {
unsafe { &*self.state.state_ptr() }
}
}
impl std::fmt::Debug for HardwareAes256GcmSivIn<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HardwareAes256GcmSivIn")
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use super::{
aes, build_message_cipher, ctr_apply, derive_keys, increment_siv_counter, polyval_digest,
validate_siv_lengths, HardwareAes256GcmSiv, HardwareAes256GcmSivKeyState, AES_BLOCK_SIZE,
MAX_SIV_LEN, NONCE_SIZE, TAG_SIZE,
};
fn cipher() -> aes::Aes256 {
build_message_cipher(&[0x42_u8; 32]).expect("hardware AES available in tests")
}
#[test]
fn siv_public_methods_round_trip_and_agree() {
const KEY: [u8; 32] = [0x5a; 32];
const NONCE: [u8; NONCE_SIZE] = [0x33; NONCE_SIZE];
let aad = b"siv header".as_slice();
let pt = b"AES-256-GCM-SIV explicit method coverage".to_vec();
let mut siv = HardwareAes256GcmSiv::new(&KEY).expect("valid key");
let ct_tag = siv.encrypt_with_nonce(&NONCE, aad, &pt).unwrap();
assert_eq!(ct_tag.len(), pt.len() + TAG_SIZE);
assert_eq!(siv.decrypt_with_nonce(&NONCE, aad, &ct_tag).unwrap(), pt);
let mut buf = vec![0_u8; pt.len() + TAG_SIZE];
let n = siv
.encrypt_with_nonce_to(&NONCE, aad, &pt, &mut buf)
.unwrap();
assert_eq!(&buf[..n], &ct_tag[..]);
let mut out = vec![0_u8; pt.len()];
let m = siv
.decrypt_with_nonce_to(&NONCE, aad, &ct_tag, &mut out)
.unwrap();
assert_eq!(&out[..m], &pt[..]);
let env = siv.encrypt_nonce_appended(&NONCE, &pt).unwrap();
assert_eq!(siv.decrypt_nonce_appended(&env).unwrap(), pt);
let mut env_buf = vec![0_u8; pt.len() + TAG_SIZE + NONCE_SIZE];
let n = siv
.encrypt_nonce_appended_to(&NONCE, &pt, &mut env_buf)
.unwrap();
assert_eq!(n, env_buf.len());
assert_eq!(env_buf, env);
let mut out = vec![0_u8; pt.len()];
let m = siv.decrypt_nonce_appended_to(&env_buf, &mut out).unwrap();
assert_eq!(&out[..m], &pt[..]);
for prealloc in [0_usize, pt.len() + TAG_SIZE + NONCE_SIZE] {
let mut in_out = Vec::with_capacity(prealloc);
in_out.extend_from_slice(&pt);
siv.encrypt_nonce_appended_in_place(&NONCE, &mut in_out)
.unwrap();
assert_eq!(in_out, env);
assert_eq!(siv.decrypt_nonce_appended(&in_out).unwrap(), pt);
}
let mut out = vec![0_u8; pt.len() + TAG_SIZE + NONCE_SIZE];
let n = siv.encrypt_to(aad, &pt, &mut out).unwrap();
assert_eq!(n, out.len());
let mut dec = vec![0_u8; pt.len()];
let m = siv.decrypt_to(aad, &out, &mut dec).unwrap();
assert_eq!(&dec[..m], &pt[..]);
let g = siv.encrypt_nonce_appended_generated(&pt).unwrap();
assert_eq!(siv.decrypt_nonce_appended(&g).unwrap(), pt);
let (gn, gct) = siv.encrypt_with_generated_nonce(aad, &pt).unwrap();
assert_eq!(siv.decrypt_with_nonce(&gn, aad, &gct).unwrap(), pt);
let mut owned2 = HardwareAes256GcmSivKeyState::new(&KEY).expect("valid key");
let e = owned2.encrypt(aad, &pt).unwrap();
assert_eq!(owned2.decrypt(aad, &e).unwrap(), pt);
}
#[test]
fn siv_length_validation_rejects_each_over_limit() {
assert!(validate_siv_lengths(16, 16).is_ok());
if let Ok(over) = usize::try_from(MAX_SIV_LEN + 1) {
assert!(validate_siv_lengths(over, 16).is_err());
assert!(validate_siv_lengths(16, over).is_err());
}
if let Ok(lim) = usize::try_from(MAX_SIV_LEN) {
assert!(validate_siv_lengths(lim, lim).is_ok());
}
}
#[test]
fn counter_wraps_low_32_bits_only() {
let mut counter = [0_u8; AES_BLOCK_SIZE];
counter[..4].copy_from_slice(&u32::MAX.to_le_bytes());
counter[8] = 0xAB;
counter[AES_BLOCK_SIZE - 1] = 0x80;
increment_siv_counter(&mut counter);
assert_eq!(
&counter[..4],
&0_u32.to_le_bytes(),
"low 32 bits must wrap to 0"
);
assert_eq!(counter[8], 0xAB, "high bytes must be preserved");
assert_eq!(
counter[AES_BLOCK_SIZE - 1],
0x80,
"0x80 marker must be preserved"
);
}
#[test]
fn counter_increments_without_carry_into_high_bytes() {
let mut counter = [0_u8; AES_BLOCK_SIZE];
counter[..4].copy_from_slice(&0x00FF_FFFF_u32.to_le_bytes());
counter[4] = 0x99; increment_siv_counter(&mut counter);
assert_eq!(&counter[..4], &0x0100_0000_u32.to_le_bytes());
assert_eq!(counter[4], 0x99);
}
#[test]
fn ctr_apply_matches_blockwise_across_32bit_wrap() {
let cipher = cipher();
let mut start = [0_u8; AES_BLOCK_SIZE];
start[..4].copy_from_slice(&0xFFFF_FFFE_u32.to_le_bytes());
start[4..].copy_from_slice(&[0x11_u8; 12]);
start[AES_BLOCK_SIZE - 1] = 0x80;
let plaintext = [0xA5_u8; AES_BLOCK_SIZE * 5];
let mut expected = plaintext;
let mut ctr = 0xFFFF_FFFE_u32;
for block in expected.chunks_mut(AES_BLOCK_SIZE) {
let mut keystream = start;
keystream[..4].copy_from_slice(&ctr.to_le_bytes());
cipher.encrypt_block(&mut keystream);
for (byte, key_byte) in block.iter_mut().zip(keystream.iter()) {
*byte ^= key_byte;
}
ctr = ctr.wrapping_add(1);
}
let mut got = plaintext;
let mut counter = start;
ctr_apply(&cipher, &mut counter, &mut got);
assert_eq!(got, expected, "CTR output must match across the wrap");
assert_eq!(&counter[..4], &3_u32.to_le_bytes());
assert_eq!(&counter[4..], &start[4..]);
}
#[test]
fn ctr_apply_batch_and_tail_round_trip() {
let cipher = cipher();
let plaintext = [0x5A_u8; AES_BLOCK_SIZE * 9 + 7];
let mut data = plaintext;
let mut counter = [0_u8; AES_BLOCK_SIZE];
counter[AES_BLOCK_SIZE - 1] = 0x80;
ctr_apply(&cipher, &mut counter, &mut data);
assert_ne!(data, plaintext, "CTR must transform the data");
let mut counter = [0_u8; AES_BLOCK_SIZE];
counter[AES_BLOCK_SIZE - 1] = 0x80;
ctr_apply(&cipher, &mut counter, &mut data);
assert_eq!(data, plaintext, "applying CTR twice must restore the input");
}
#[test]
fn derive_keys_is_deterministic_and_nonce_dependent() {
let master = cipher();
let nonce_a = [0x07_u8; NONCE_SIZE];
let mut nonce_b = nonce_a;
nonce_b[0] ^= 1;
let (auth_a, enc_a) = derive_keys(&master, &nonce_a);
let (auth_a2, enc_a2) = derive_keys(&master, &nonce_a);
assert_eq!(auth_a, auth_a2);
assert_eq!(enc_a, enc_a2);
let (auth_b, enc_b) = derive_keys(&master, &nonce_b);
assert_ne!(auth_a, auth_b, "auth key must depend on the nonce");
assert_ne!(enc_a, enc_b, "encryption key must depend on the nonce");
}
#[test]
fn polyval_digest_is_deterministic_and_input_sensitive() {
let auth_key = [0x33_u8; 16];
let aad = b"metadata";
let message = b"the quick brown fox";
let d1 = polyval_digest(&auth_key, aad, message).expect("hardware");
let d2 = polyval_digest(&auth_key, aad, message).expect("hardware");
assert_eq!(d1, d2);
let d_aad = polyval_digest(&auth_key, b"metadatb", message).expect("hardware");
assert_ne!(d1, d_aad, "digest must depend on AAD");
let d_msg = polyval_digest(&auth_key, aad, b"the quick brown FOX").expect("hardware");
assert_ne!(d1, d_msg, "digest must depend on the message");
}
}
#[cfg(kani)]
mod kani_proofs {
use super::{
increment_siv_counter, split_ciphertext_tag, split_trailing_nonce, validate_siv_lengths,
Error, MAX_SIV_LEN, NONCE_SIZE, TAG_SIZE,
};
#[kani::proof]
fn increment_siv_counter_is_le32_inc() {
let mut counter: [u8; 16] = kani::any();
let original = counter;
increment_siv_counter(&mut counter);
assert!(counter[4..] == original[4..]);
let low = u32::from_le_bytes([original[0], original[1], original[2], original[3]]);
assert!(counter[..4] == low.wrapping_add(1).to_le_bytes());
}
#[kani::proof]
fn validate_siv_lengths_matches_limits() {
let aad_len: usize = kani::any();
let data_len: usize = kani::any();
let result = validate_siv_lengths(aad_len, data_len);
let in_range = (aad_len as u64) <= MAX_SIV_LEN && (data_len as u64) <= MAX_SIV_LEN;
assert!(result.is_ok() == in_range);
}
#[kani::proof]
fn split_ciphertext_tag_boundary() {
let len: usize = kani::any();
kani::assume(len <= 48);
let buf = [0_u8; 48];
match split_ciphertext_tag(&buf[..len]) {
Ok((ct, tag)) => {
assert!(len >= TAG_SIZE);
assert!(ct.len() == len - TAG_SIZE);
assert!(tag.len() == TAG_SIZE);
}
Err(e) => {
assert!(len < TAG_SIZE);
assert!(matches!(e, Error::Decrypt));
}
}
}
#[kani::proof]
fn split_trailing_nonce_boundary() {
let len: usize = kani::any();
kani::assume(len <= 48);
let buf = [0_u8; 48];
match split_trailing_nonce(&buf[..len]) {
Ok((body, nonce)) => {
assert!(len >= TAG_SIZE + NONCE_SIZE);
assert!(nonce.len() == NONCE_SIZE);
assert!(body.len() == len - NONCE_SIZE);
}
Err(e) => {
assert!(len < TAG_SIZE + NONCE_SIZE);
assert!(matches!(e, Error::CiphertextTooShort));
}
}
}
}