use alloc::boxed::Box;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use super::prekey::{Prekey, SALT_LEN, TAG_LEN, scrubbed};
use crate::{Dynamic, RevealSecret};
mod private {
pub trait Sealed {}
impl Sealed for alloc::vec::Vec<u8> {}
impl Sealed for alloc::string::String {}
}
pub trait SealedPayload: zeroize::Zeroize + private::Sealed + Sized {
type Mut: ?Sized;
#[doc(hidden)]
fn bytes(&self) -> &[u8];
#[doc(hidden)]
fn from_plain(plain: Dynamic<Vec<u8>>) -> Dynamic<Self>;
#[doc(hidden)]
fn view_mut(buf: &mut [u8]) -> &mut Self::Mut;
}
impl SealedPayload for Vec<u8> {
type Mut = [u8];
fn bytes(&self) -> &[u8] {
self
}
fn from_plain(plain: Dynamic<Vec<u8>>) -> Dynamic<Self> {
plain
}
fn view_mut(buf: &mut [u8]) -> &mut [u8] {
buf
}
}
impl SealedPayload for String {
type Mut = str;
fn bytes(&self) -> &[u8] {
self.as_bytes()
}
fn from_plain(plain: Dynamic<Vec<u8>>) -> Dynamic<Self> {
match String::from_utf8(plain.into_inner()) {
Ok(s) => Dynamic::new(s),
Err(not_utf8) => {
let mut bytes = not_utf8.into_bytes();
zeroize::Zeroize::zeroize(&mut bytes);
panic!("authenticated plaintext of a sealed String is UTF-8");
}
}
}
fn view_mut(buf: &mut [u8]) -> &mut str {
core::str::from_utf8_mut(buf).expect("authenticated plaintext of a sealed String is UTF-8")
}
}
#[must_use = "holds secret material; bind it or chain a method call"]
pub struct SealedDynamic<T: SealedPayload> {
ct: Box<[u8]>,
tag: [u8; TAG_LEN],
salt: [u8; SALT_LEN],
prekey: Arc<Prekey>,
_payload: core::marker::PhantomData<fn() -> T>,
}
impl<T: SealedPayload> SealedDynamic<T> {
pub fn seal(secret: Dynamic<T>, prekey: &Arc<Prekey>) -> Self {
let salt = Prekey::fresh_salt();
let mut buf =
zeroize::Zeroizing::new(secret.with_secret(|plain| Box::<[u8]>::from(plain.bytes())));
let tag = scrubbed(|| prekey.seal_in_place(&salt, &mut buf));
let ct = core::mem::take(&mut *buf);
Self {
ct,
tag,
salt,
prekey: Arc::clone(prekey),
_payload: core::marker::PhantomData,
}
}
pub fn with_secret<F, R>(&self, f: F) -> R
where
F: FnOnce(&T) -> R,
{
scrubbed(|| {
let plain = T::from_plain(self.open());
plain.with_secret(f)
})
}
pub fn with_secret_mut<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&mut T::Mut) -> R,
{
scrubbed(|| {
let mut plain = self.open();
let result =
crate::RevealSecretMut::with_secret_mut(&mut plain, |buf| f(T::view_mut(buf)));
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
})
}
pub fn unseal(self) -> Dynamic<T> {
scrubbed(|| T::from_plain(self.open()))
}
#[inline]
pub fn len(&self) -> usize {
self.ct.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.ct.is_empty()
}
fn open(&self) -> Dynamic<Vec<u8>> {
Dynamic::<Vec<u8>>::new_with(self.ct.len(), |buf| self.open_into(buf))
}
fn open_into(&self, buf: &mut [u8]) {
buf.copy_from_slice(&self.ct);
self.prekey.open_in_place(&self.salt, &self.tag, buf);
}
}
impl<T: SealedPayload> crate::scoped::RevealScoped for SealedDynamic<T> {
type Inner = T;
#[inline]
fn with_secret<F, R>(&self, f: F) -> R
where
F: FnOnce(&T) -> R,
{
SealedDynamic::with_secret(self, f)
}
}
#[cfg(feature = "ct-eq")]
impl<T: SealedPayload> crate::ConstantTimeEq for SealedDynamic<T> {
fn ct_eq(&self, other: &Self) -> bool {
if self.len() != other.len() {
return false;
}
self.with_secret(|a| {
other.with_secret(|b| crate::ConstantTimeEq::ct_eq(a.bytes(), b.bytes()))
})
}
}
impl<T: SealedPayload> core::fmt::Debug for SealedDynamic<T> {
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 = SealedDynamic::seal(Dynamic::<Vec<u8>>::new(alloc::vec![7u8; 40]), &prekey);
let b = SealedDynamic::seal(Dynamic::<Vec<u8>>::new(alloc::vec![7u8; 40]), &prekey);
assert_ne!(&*a.ct, &[7u8; 40][..]);
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 = SealedDynamic::seal(Dynamic::<String>::new(String::from("abc")), &prekey);
let (salt, ct) = (s.salt, s.ct.clone());
s.with_secret_mut(|p: &mut str| p.make_ascii_uppercase());
assert_ne!(s.salt, salt);
assert_ne!(s.ct, ct);
s.with_secret(|p| assert_eq!(p, "ABC"));
}
#[test]
#[should_panic(expected = "sealed memory was modified or corrupted")]
fn a_flipped_tag_bit_panics() {
let _q = quota();
let prekey = Prekey::new();
let mut s = SealedDynamic::seal(Dynamic::<Vec<u8>>::new(alloc::vec![1u8; 5]), &prekey);
s.tag[15] ^= 0x80;
s.with_secret(|_| ());
}
}