1use alloc::sync::Arc;
2
3use super::prekey::{Prekey, SALT_LEN, TAG_LEN, scrubbed};
4use crate::{Fixed, RevealSecret, RevealSecretMut};
5
6#[must_use = "holds secret material; bind it or chain a method call"]
36pub struct SealedFixed<const N: usize> {
37 ct: [u8; N],
38 tag: [u8; TAG_LEN],
39 salt: [u8; SALT_LEN],
40 prekey: Arc<Prekey>,
41}
42
43impl<const N: usize> SealedFixed<N> {
44 pub fn seal(secret: Fixed<[u8; N]>, prekey: &Arc<Prekey>) -> Self {
46 let salt = Prekey::fresh_salt();
47 let mut ct = [0u8; N];
48 let tag = scrubbed(|| secret.with_secret(|plain| prekey.seal_into(&salt, plain, &mut ct)));
49 Self {
50 ct,
51 tag,
52 salt,
53 prekey: Arc::clone(prekey),
54 }
55 }
56
57 pub fn from_random(prekey: &Arc<Prekey>) -> Self {
63 Self::seal(Fixed::from_random(), prekey)
64 }
65
66 #[inline]
68 pub fn with_secret<F, R>(&self, f: F) -> R
69 where
70 F: FnOnce(&[u8; N]) -> R,
71 {
72 scrubbed(|| self.open_and_call(f))
73 }
74
75 #[inline(never)]
80 fn open_and_call<F, R>(&self, f: F) -> R
81 where
82 F: FnOnce(&[u8; N]) -> R,
83 {
84 let mut plain = Fixed::new([0u8; N]);
85 plain.with_secret_mut(|buf| self.open_into(buf));
86 plain.with_secret(f)
87 }
88
89 pub fn with_secret_mut<F, R>(&mut self, f: F) -> R
95 where
96 F: FnOnce(&mut [u8; N]) -> R,
97 {
98 scrubbed(|| self.open_mutate_reseal(f))
99 }
100
101 #[inline(never)]
103 fn open_mutate_reseal<F, R>(&mut self, f: F) -> R
104 where
105 F: FnOnce(&mut [u8; N]) -> R,
106 {
107 let mut plain = Fixed::new([0u8; N]);
108 plain.with_secret_mut(|buf| self.open_into(buf));
109 let result = plain.with_secret_mut(f);
110 let salt = Prekey::fresh_salt();
111 self.tag = plain.with_secret(|p| self.prekey.seal_into(&salt, p, &mut self.ct));
112 self.salt = salt;
113 result
114 }
115
116 #[doc(hidden)]
120 #[inline]
121 pub fn __with_wrapped<T, W, F, R>(&self, wrap: W, f: F) -> R
122 where
123 W: FnOnce(Fixed<[u8; N]>) -> T,
124 F: FnOnce(&T) -> R,
125 {
126 scrubbed(|| self.open_wrap_call(wrap, f))
127 }
128
129 #[inline(never)]
131 fn open_wrap_call<T, W, F, R>(&self, wrap: W, f: F) -> R
132 where
133 W: FnOnce(Fixed<[u8; N]>) -> T,
134 F: FnOnce(&T) -> R,
135 {
136 let mut plain = Fixed::new([0u8; N]);
137 plain.with_secret_mut(|buf| self.open_into(buf));
138 let wrapped = wrap(plain);
139 f(&wrapped)
140 }
141
142 #[doc(hidden)]
145 #[inline]
146 pub fn __with_wrapped_mut<T, W, U, F, R>(&mut self, wrap: W, unwrap: U, f: F) -> R
147 where
148 W: FnOnce(Fixed<[u8; N]>) -> T,
149 U: FnOnce(T) -> Fixed<[u8; N]>,
150 F: FnOnce(&mut T) -> R,
151 {
152 scrubbed(|| self.open_wrap_mutate_reseal(wrap, unwrap, f))
153 }
154
155 #[inline(never)]
156 fn open_wrap_mutate_reseal<T, W, U, F, R>(&mut self, wrap: W, unwrap: U, f: F) -> R
157 where
158 W: FnOnce(Fixed<[u8; N]>) -> T,
159 U: FnOnce(T) -> Fixed<[u8; N]>,
160 F: FnOnce(&mut T) -> R,
161 {
162 let mut plain = Fixed::new([0u8; N]);
163 plain.with_secret_mut(|buf| self.open_into(buf));
164 let mut wrapped = wrap(plain);
165 let result = f(&mut wrapped);
166 let plain = unwrap(wrapped);
167 let salt = Prekey::fresh_salt();
168 self.tag = plain.with_secret(|p| self.prekey.seal_into(&salt, p, &mut self.ct));
169 self.salt = salt;
170 result
171 }
172
173 #[inline(always)]
183 pub fn unseal(self) -> Fixed<[u8; N]> {
184 let mut plain = Fixed::new([0u8; N]);
185 scrubbed(|| plain.with_secret_mut(|buf| self.open_into_out_of_line(buf)));
186 plain
187 }
188
189 #[inline(never)]
190 fn open_into_out_of_line(&self, buf: &mut [u8; N]) {
191 self.open_into(buf);
192 }
193
194 #[inline(always)]
196 pub const fn len(&self) -> usize {
197 N
198 }
199
200 #[inline(always)]
203 pub const fn is_empty(&self) -> bool {
204 N == 0
205 }
206
207 #[inline(always)]
208 fn open_into(&self, buf: &mut [u8; N]) {
209 buf.copy_from_slice(&self.ct);
210 self.prekey.open_in_place(&self.salt, &self.tag, buf);
211 }
212}
213
214impl<const N: usize> crate::scoped::RevealScoped for SealedFixed<N> {
215 type Inner = [u8; N];
216
217 #[inline]
218 fn with_secret<F, R>(&self, f: F) -> R
219 where
220 F: FnOnce(&[u8; N]) -> R,
221 {
222 SealedFixed::with_secret(self, f)
223 }
224}
225
226#[cfg(feature = "ct-eq")]
229impl<const N: usize> crate::ConstantTimeEq for SealedFixed<N> {
230 fn ct_eq(&self, other: &Self) -> bool {
231 self.with_secret(|a| other.with_secret(|b| crate::ConstantTimeEq::ct_eq(a, b)))
232 }
233}
234
235impl<const N: usize> core::fmt::Debug for SealedFixed<N> {
237 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
238 f.write_str("[REDACTED]")
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 use crate::sealed::prekey::tests::quota;
246
247 #[test]
250 fn a_failed_reseal_leaves_ciphertext_not_plaintext() {
251 let _q = quota();
252 let prekey = Prekey::new();
253 let mut s = SealedFixed::seal(Fixed::new([0x42u8; 32]), &prekey);
254 let before = s.ct;
255 let r = std::panic::catch_unwind(core::panic::AssertUnwindSafe(|| {
256 s.with_secret_mut(|b| {
257 *b = [0x99u8; 32];
258 crate::sealed::prekey::tests::fail_next_derivation();
259 });
260 }));
261 assert!(r.is_err());
262 assert_ne!(
263 s.ct, [0x99u8; 32],
264 "the new plaintext was left in the ciphertext"
265 );
266 assert_eq!(s.ct, before);
267 s.with_secret(|b| assert_eq!(b, &[0x42u8; 32]));
268 }
269
270 #[test]
271 fn the_ciphertext_is_not_the_plaintext_and_differs_per_seal() {
272 let _q = quota();
273 let prekey = Prekey::new();
274 let a = SealedFixed::seal(Fixed::new([0x42u8; 32]), &prekey);
275 let b = SealedFixed::seal(Fixed::new([0x42u8; 32]), &prekey);
276 assert_ne!(a.ct, [0x42u8; 32]);
277 assert_ne!(a.salt, b.salt);
278 assert_ne!(a.ct, b.ct);
279 }
280
281 #[test]
282 fn with_secret_mut_reseals_under_a_fresh_salt() {
283 let _q = quota();
284 let prekey = Prekey::new();
285 let mut s = SealedFixed::seal(Fixed::new([1u8; 16]), &prekey);
286 let (salt, ct) = (s.salt, s.ct);
287 s.with_secret_mut(|_| {});
288 assert_ne!(s.salt, salt);
289 assert_ne!(s.ct, ct);
290 s.with_secret(|p| assert_eq!(p, &[1u8; 16]));
291 }
292
293 #[test]
294 #[should_panic(expected = "sealed memory was modified or corrupted")]
295 fn a_flipped_ciphertext_bit_panics() {
296 let _q = quota();
297 let prekey = Prekey::new();
298 let mut s = SealedFixed::seal(Fixed::new([1u8; 32]), &prekey);
299 s.ct[3] ^= 0x10;
300 s.with_secret(|_| ());
301 }
302
303 #[test]
304 #[should_panic(expected = "sealed memory was modified or corrupted")]
305 fn a_flipped_salt_bit_panics() {
306 let _q = quota();
307 let prekey = Prekey::new();
308 let mut s = SealedFixed::seal(Fixed::new([1u8; 32]), &prekey);
309 s.salt[31] ^= 1;
310 let _ = s.unseal();
311 }
312}