use alloc::sync::Arc;
use super::prekey::{Prekey, SALT_LEN, TAG_LEN, scrubbed};
use crate::{Fixed, RevealSecret, RevealSecretMut};
#[must_use = "holds secret material; bind it or chain a method call"]
pub struct SealedFixed<const N: usize> {
ct: [u8; N],
tag: [u8; TAG_LEN],
salt: [u8; SALT_LEN],
prekey: Arc<Prekey>,
}
impl<const N: usize> SealedFixed<N> {
pub fn seal(secret: Fixed<[u8; N]>, prekey: &Arc<Prekey>) -> Self {
let salt = Prekey::fresh_salt();
let mut ct = [0u8; N];
secret.with_secret(|plain| ct.copy_from_slice(plain));
let tag = scrubbed(|| prekey.seal_in_place(&salt, &mut ct));
Self {
ct,
tag,
salt,
prekey: Arc::clone(prekey),
}
}
pub fn from_random(prekey: &Arc<Prekey>) -> Self {
Self::seal(Fixed::from_random(), prekey)
}
#[inline]
pub fn with_secret<F, R>(&self, f: F) -> R
where
F: FnOnce(&[u8; N]) -> R,
{
scrubbed(|| self.open_and_call(f))
}
#[inline(never)]
fn open_and_call<F, R>(&self, f: F) -> R
where
F: FnOnce(&[u8; N]) -> R,
{
let mut plain = Fixed::new([0u8; N]);
plain.with_secret_mut(|buf| self.open_into(buf));
plain.with_secret(f)
}
pub fn with_secret_mut<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&mut [u8; N]) -> R,
{
scrubbed(|| self.open_mutate_reseal(f))
}
#[inline(never)]
fn open_mutate_reseal<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&mut [u8; N]) -> R,
{
let mut plain = Fixed::new([0u8; N]);
plain.with_secret_mut(|buf| self.open_into(buf));
let result = plain.with_secret_mut(f);
let salt = Prekey::fresh_salt();
plain.with_secret(|p| self.ct.copy_from_slice(p));
self.tag = self.prekey.seal_in_place(&salt, &mut self.ct);
self.salt = salt;
result
}
#[doc(hidden)]
#[inline]
pub fn __with_wrapped<T, W, F, R>(&self, wrap: W, f: F) -> R
where
W: FnOnce(Fixed<[u8; N]>) -> T,
F: FnOnce(&T) -> R,
{
scrubbed(|| self.open_wrap_call(wrap, f))
}
#[inline(never)]
fn open_wrap_call<T, W, F, R>(&self, wrap: W, f: F) -> R
where
W: FnOnce(Fixed<[u8; N]>) -> T,
F: FnOnce(&T) -> R,
{
let mut plain = Fixed::new([0u8; N]);
plain.with_secret_mut(|buf| self.open_into(buf));
let wrapped = wrap(plain);
f(&wrapped)
}
#[doc(hidden)]
#[inline]
pub fn __with_wrapped_mut<T, W, U, F, R>(&mut self, wrap: W, unwrap: U, f: F) -> R
where
W: FnOnce(Fixed<[u8; N]>) -> T,
U: FnOnce(T) -> Fixed<[u8; N]>,
F: FnOnce(&mut T) -> R,
{
scrubbed(|| self.open_wrap_mutate_reseal(wrap, unwrap, f))
}
#[inline(never)]
fn open_wrap_mutate_reseal<T, W, U, F, R>(&mut self, wrap: W, unwrap: U, f: F) -> R
where
W: FnOnce(Fixed<[u8; N]>) -> T,
U: FnOnce(T) -> Fixed<[u8; N]>,
F: FnOnce(&mut T) -> R,
{
let mut plain = Fixed::new([0u8; N]);
plain.with_secret_mut(|buf| self.open_into(buf));
let mut wrapped = wrap(plain);
let result = f(&mut wrapped);
let plain = unwrap(wrapped);
let salt = Prekey::fresh_salt();
plain.with_secret(|p| self.ct.copy_from_slice(p));
self.tag = self.prekey.seal_in_place(&salt, &mut self.ct);
self.salt = salt;
result
}
#[inline(always)]
pub fn unseal(self) -> Fixed<[u8; N]> {
let mut plain = Fixed::new([0u8; N]);
scrubbed(|| plain.with_secret_mut(|buf| self.open_into_out_of_line(buf)));
plain
}
#[inline(never)]
fn open_into_out_of_line(&self, buf: &mut [u8; N]) {
self.open_into(buf);
}
#[inline(always)]
pub const fn len(&self) -> usize {
N
}
#[inline(always)]
pub const fn is_empty(&self) -> bool {
N == 0
}
#[inline(always)]
fn open_into(&self, buf: &mut [u8; N]) {
buf.copy_from_slice(&self.ct);
self.prekey.open_in_place(&self.salt, &self.tag, buf);
}
}
impl<const N: usize> crate::scoped::RevealScoped for SealedFixed<N> {
type Inner = [u8; N];
#[inline]
fn with_secret<F, R>(&self, f: F) -> R
where
F: FnOnce(&[u8; N]) -> R,
{
SealedFixed::with_secret(self, f)
}
}
#[cfg(feature = "ct-eq")]
impl<const N: usize> crate::ConstantTimeEq for SealedFixed<N> {
fn ct_eq(&self, other: &Self) -> bool {
self.with_secret(|a| other.with_secret(|b| crate::ConstantTimeEq::ct_eq(a, b)))
}
}
impl<const N: usize> core::fmt::Debug for SealedFixed<N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("[REDACTED]")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sealed::prekey::tests::quota;
#[test]
fn the_ciphertext_is_not_the_plaintext_and_differs_per_seal() {
let _q = quota();
let prekey = Prekey::new();
let a = SealedFixed::seal(Fixed::new([0x42u8; 32]), &prekey);
let b = SealedFixed::seal(Fixed::new([0x42u8; 32]), &prekey);
assert_ne!(a.ct, [0x42u8; 32]);
assert_ne!(a.salt, b.salt);
assert_ne!(a.ct, b.ct);
}
#[test]
fn with_secret_mut_reseals_under_a_fresh_salt() {
let _q = quota();
let prekey = Prekey::new();
let mut s = SealedFixed::seal(Fixed::new([1u8; 16]), &prekey);
let (salt, ct) = (s.salt, s.ct);
s.with_secret_mut(|_| {});
assert_ne!(s.salt, salt);
assert_ne!(s.ct, ct);
s.with_secret(|p| assert_eq!(p, &[1u8; 16]));
}
#[test]
#[should_panic(expected = "sealed memory was modified or corrupted")]
fn a_flipped_ciphertext_bit_panics() {
let _q = quota();
let prekey = Prekey::new();
let mut s = SealedFixed::seal(Fixed::new([1u8; 32]), &prekey);
s.ct[3] ^= 0x10;
s.with_secret(|_| ());
}
#[test]
#[should_panic(expected = "sealed memory was modified or corrupted")]
fn a_flipped_salt_bit_panics() {
let _q = quota();
let prekey = Prekey::new();
let mut s = SealedFixed::seal(Fixed::new([1u8; 32]), &prekey);
s.salt[31] ^= 1;
let _ = s.unseal();
}
}