secure_gate/sealed/
dynamic.rs1use alloc::boxed::Box;
2use alloc::string::String;
3use alloc::sync::Arc;
4use alloc::vec::Vec;
5
6use super::prekey::{Prekey, SALT_LEN, TAG_LEN, scrubbed};
7use crate::{Dynamic, RevealSecret};
8
9mod private {
10 pub trait Sealed {}
11 impl Sealed for alloc::vec::Vec<u8> {}
12 impl Sealed for alloc::string::String {}
13}
14
15pub trait SealedPayload: zeroize::Zeroize + private::Sealed + Sized {
18 type Mut: ?Sized;
22
23 #[doc(hidden)]
24 fn bytes(&self) -> &[u8];
25
26 #[doc(hidden)]
27 fn from_plain(plain: Dynamic<Vec<u8>>) -> Dynamic<Self>;
28
29 #[doc(hidden)]
30 fn view_mut(buf: &mut [u8]) -> &mut Self::Mut;
31}
32
33impl SealedPayload for Vec<u8> {
34 type Mut = [u8];
35
36 fn bytes(&self) -> &[u8] {
37 self
38 }
39
40 fn from_plain(plain: Dynamic<Vec<u8>>) -> Dynamic<Self> {
41 plain
42 }
43
44 fn view_mut(buf: &mut [u8]) -> &mut [u8] {
45 buf
46 }
47}
48
49impl SealedPayload for String {
50 type Mut = str;
51
52 fn bytes(&self) -> &[u8] {
53 self.as_bytes()
54 }
55
56 fn from_plain(plain: Dynamic<Vec<u8>>) -> Dynamic<Self> {
58 match String::from_utf8(plain.into_inner()) {
59 Ok(s) => Dynamic::new(s),
60 Err(not_utf8) => {
61 let mut bytes = not_utf8.into_bytes();
63 zeroize::Zeroize::zeroize(&mut bytes);
64 panic!("authenticated plaintext of a sealed String is UTF-8");
65 }
66 }
67 }
68
69 fn view_mut(buf: &mut [u8]) -> &mut str {
70 core::str::from_utf8_mut(buf).expect("authenticated plaintext of a sealed String is UTF-8")
71 }
72}
73
74#[must_use = "holds secret material; bind it or chain a method call"]
97pub struct SealedDynamic<T: SealedPayload> {
98 ct: Box<[u8]>,
99 tag: [u8; TAG_LEN],
100 salt: [u8; SALT_LEN],
101 prekey: Arc<Prekey>,
102 _payload: core::marker::PhantomData<fn() -> T>,
103}
104
105impl<T: SealedPayload> SealedDynamic<T> {
106 pub fn seal(secret: Dynamic<T>, prekey: &Arc<Prekey>) -> Self {
108 let salt = Prekey::fresh_salt();
109 let mut buf =
112 zeroize::Zeroizing::new(secret.with_secret(|plain| Box::<[u8]>::from(plain.bytes())));
113 let tag = scrubbed(|| prekey.seal_in_place(&salt, &mut buf));
114 let ct = core::mem::take(&mut *buf);
115 Self {
116 ct,
117 tag,
118 salt,
119 prekey: Arc::clone(prekey),
120 _payload: core::marker::PhantomData,
121 }
122 }
123
124 pub fn with_secret<F, R>(&self, f: F) -> R
126 where
127 F: FnOnce(&T) -> R,
128 {
129 scrubbed(|| {
130 let plain = T::from_plain(self.open());
131 plain.with_secret(f)
132 })
133 }
134
135 pub fn with_secret_mut<F, R>(&mut self, f: F) -> R
145 where
146 F: FnOnce(&mut T::Mut) -> R,
147 {
148 scrubbed(|| {
149 let mut plain = self.open();
150 let result =
151 crate::RevealSecretMut::with_secret_mut(&mut plain, |buf| f(T::view_mut(buf)));
152 let salt = Prekey::fresh_salt();
153 plain.with_secret(|p| self.ct.copy_from_slice(p));
154 self.tag = self.prekey.seal_in_place(&salt, &mut self.ct);
155 self.salt = salt;
156 result
157 })
158 }
159
160 pub fn unseal(self) -> Dynamic<T> {
163 scrubbed(|| T::from_plain(self.open()))
164 }
165
166 #[inline]
169 pub fn len(&self) -> usize {
170 self.ct.len()
171 }
172
173 #[inline]
175 pub fn is_empty(&self) -> bool {
176 self.ct.is_empty()
177 }
178
179 fn open(&self) -> Dynamic<Vec<u8>> {
180 Dynamic::<Vec<u8>>::new_with(self.ct.len(), |buf| self.open_into(buf))
181 }
182
183 fn open_into(&self, buf: &mut [u8]) {
184 buf.copy_from_slice(&self.ct);
185 self.prekey.open_in_place(&self.salt, &self.tag, buf);
186 }
187}
188
189impl<T: SealedPayload> crate::scoped::RevealScoped for SealedDynamic<T> {
190 type Inner = T;
191
192 #[inline]
193 fn with_secret<F, R>(&self, f: F) -> R
194 where
195 F: FnOnce(&T) -> R,
196 {
197 SealedDynamic::with_secret(self, f)
198 }
199}
200
201#[cfg(feature = "ct-eq")]
204impl<T: SealedPayload> crate::ConstantTimeEq for SealedDynamic<T> {
205 fn ct_eq(&self, other: &Self) -> bool {
206 if self.len() != other.len() {
207 return false;
208 }
209 self.with_secret(|a| {
210 other.with_secret(|b| crate::ConstantTimeEq::ct_eq(a.bytes(), b.bytes()))
211 })
212 }
213}
214
215impl<T: SealedPayload> core::fmt::Debug for SealedDynamic<T> {
217 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
218 f.write_str("[REDACTED]")
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use crate::sealed::prekey::tests::quota;
226
227 #[test]
228 fn the_ciphertext_is_not_the_plaintext_and_differs_per_seal() {
229 let _q = quota();
230 let prekey = Prekey::new();
231 let a = SealedDynamic::seal(Dynamic::<Vec<u8>>::new(alloc::vec![7u8; 40]), &prekey);
232 let b = SealedDynamic::seal(Dynamic::<Vec<u8>>::new(alloc::vec![7u8; 40]), &prekey);
233 assert_ne!(&*a.ct, &[7u8; 40][..]);
234 assert_ne!(a.salt, b.salt);
235 assert_ne!(a.ct, b.ct);
236 }
237
238 #[test]
239 fn with_secret_mut_reseals_under_a_fresh_salt() {
240 let _q = quota();
241 let prekey = Prekey::new();
242 let mut s = SealedDynamic::seal(Dynamic::<String>::new(String::from("abc")), &prekey);
243 let (salt, ct) = (s.salt, s.ct.clone());
244 s.with_secret_mut(|p: &mut str| p.make_ascii_uppercase());
245 assert_ne!(s.salt, salt);
246 assert_ne!(s.ct, ct);
247 s.with_secret(|p| assert_eq!(p, "ABC"));
248 }
249
250 #[test]
251 #[should_panic(expected = "sealed memory was modified or corrupted")]
252 fn a_flipped_tag_bit_panics() {
253 let _q = quota();
254 let prekey = Prekey::new();
255 let mut s = SealedDynamic::seal(Dynamic::<Vec<u8>>::new(alloc::vec![1u8; 5]), &prekey);
256 s.tag[15] ^= 0x80;
257 s.with_secret(|_| ());
258 }
259}