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 secret.with_secret(|plain| ct.copy_from_slice(plain));
49 let tag = scrubbed(|| prekey.seal_in_place(&salt, &mut ct));
50 Self {
51 ct,
52 tag,
53 salt,
54 prekey: Arc::clone(prekey),
55 }
56 }
57
58 pub fn from_random(prekey: &Arc<Prekey>) -> Self {
64 Self::seal(Fixed::from_random(), prekey)
65 }
66
67 #[inline]
69 pub fn with_secret<F, R>(&self, f: F) -> R
70 where
71 F: FnOnce(&[u8; N]) -> R,
72 {
73 scrubbed(|| self.open_and_call(f))
74 }
75
76 #[inline(never)]
81 fn open_and_call<F, R>(&self, f: F) -> R
82 where
83 F: FnOnce(&[u8; N]) -> R,
84 {
85 let mut plain = Fixed::new([0u8; N]);
86 plain.with_secret_mut(|buf| self.open_into(buf));
87 plain.with_secret(f)
88 }
89
90 pub fn with_secret_mut<F, R>(&mut self, f: F) -> R
96 where
97 F: FnOnce(&mut [u8; N]) -> R,
98 {
99 scrubbed(|| self.open_mutate_reseal(f))
100 }
101
102 #[inline(never)]
104 fn open_mutate_reseal<F, R>(&mut self, f: F) -> R
105 where
106 F: FnOnce(&mut [u8; N]) -> R,
107 {
108 let mut plain = Fixed::new([0u8; N]);
109 plain.with_secret_mut(|buf| self.open_into(buf));
110 let result = plain.with_secret_mut(f);
111 let salt = Prekey::fresh_salt();
112 plain.with_secret(|p| self.ct.copy_from_slice(p));
113 self.tag = self.prekey.seal_in_place(&salt, &mut self.ct);
114 self.salt = salt;
115 result
116 }
117
118 #[doc(hidden)]
122 #[inline]
123 pub fn __with_wrapped<T, W, F, R>(&self, wrap: W, f: F) -> R
124 where
125 W: FnOnce(Fixed<[u8; N]>) -> T,
126 F: FnOnce(&T) -> R,
127 {
128 scrubbed(|| self.open_wrap_call(wrap, f))
129 }
130
131 #[inline(never)]
133 fn open_wrap_call<T, W, F, R>(&self, wrap: W, f: F) -> R
134 where
135 W: FnOnce(Fixed<[u8; N]>) -> T,
136 F: FnOnce(&T) -> R,
137 {
138 let mut plain = Fixed::new([0u8; N]);
139 plain.with_secret_mut(|buf| self.open_into(buf));
140 let wrapped = wrap(plain);
141 f(&wrapped)
142 }
143
144 #[doc(hidden)]
147 #[inline]
148 pub fn __with_wrapped_mut<T, W, U, F, R>(&mut self, wrap: W, unwrap: U, f: F) -> R
149 where
150 W: FnOnce(Fixed<[u8; N]>) -> T,
151 U: FnOnce(T) -> Fixed<[u8; N]>,
152 F: FnOnce(&mut T) -> R,
153 {
154 scrubbed(|| self.open_wrap_mutate_reseal(wrap, unwrap, f))
155 }
156
157 #[inline(never)]
158 fn open_wrap_mutate_reseal<T, W, U, F, R>(&mut self, wrap: W, unwrap: U, f: F) -> R
159 where
160 W: FnOnce(Fixed<[u8; N]>) -> T,
161 U: FnOnce(T) -> Fixed<[u8; N]>,
162 F: FnOnce(&mut T) -> R,
163 {
164 let mut plain = Fixed::new([0u8; N]);
165 plain.with_secret_mut(|buf| self.open_into(buf));
166 let mut wrapped = wrap(plain);
167 let result = f(&mut wrapped);
168 let plain = unwrap(wrapped);
169 let salt = Prekey::fresh_salt();
170 plain.with_secret(|p| self.ct.copy_from_slice(p));
171 self.tag = self.prekey.seal_in_place(&salt, &mut self.ct);
172 self.salt = salt;
173 result
174 }
175
176 #[inline(always)]
186 pub fn unseal(self) -> Fixed<[u8; N]> {
187 let mut plain = Fixed::new([0u8; N]);
188 scrubbed(|| plain.with_secret_mut(|buf| self.open_into_out_of_line(buf)));
189 plain
190 }
191
192 #[inline(never)]
193 fn open_into_out_of_line(&self, buf: &mut [u8; N]) {
194 self.open_into(buf);
195 }
196
197 #[inline(always)]
199 pub const fn len(&self) -> usize {
200 N
201 }
202
203 #[inline(always)]
206 pub const fn is_empty(&self) -> bool {
207 N == 0
208 }
209
210 #[inline(always)]
211 fn open_into(&self, buf: &mut [u8; N]) {
212 buf.copy_from_slice(&self.ct);
213 self.prekey.open_in_place(&self.salt, &self.tag, buf);
214 }
215}
216
217impl<const N: usize> crate::scoped::RevealScoped for SealedFixed<N> {
218 type Inner = [u8; N];
219
220 #[inline]
221 fn with_secret<F, R>(&self, f: F) -> R
222 where
223 F: FnOnce(&[u8; N]) -> R,
224 {
225 SealedFixed::with_secret(self, f)
226 }
227}
228
229#[cfg(feature = "ct-eq")]
232impl<const N: usize> crate::ConstantTimeEq for SealedFixed<N> {
233 fn ct_eq(&self, other: &Self) -> bool {
234 self.with_secret(|a| other.with_secret(|b| crate::ConstantTimeEq::ct_eq(a, b)))
235 }
236}
237
238impl<const N: usize> core::fmt::Debug for SealedFixed<N> {
240 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
241 f.write_str("[REDACTED]")
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use crate::sealed::prekey::tests::quota;
249
250 #[test]
251 fn the_ciphertext_is_not_the_plaintext_and_differs_per_seal() {
252 let _q = quota();
253 let prekey = Prekey::new();
254 let a = SealedFixed::seal(Fixed::new([0x42u8; 32]), &prekey);
255 let b = SealedFixed::seal(Fixed::new([0x42u8; 32]), &prekey);
256 assert_ne!(a.ct, [0x42u8; 32]);
257 assert_ne!(a.salt, b.salt);
258 assert_ne!(a.ct, b.ct);
259 }
260
261 #[test]
262 fn with_secret_mut_reseals_under_a_fresh_salt() {
263 let _q = quota();
264 let prekey = Prekey::new();
265 let mut s = SealedFixed::seal(Fixed::new([1u8; 16]), &prekey);
266 let (salt, ct) = (s.salt, s.ct);
267 s.with_secret_mut(|_| {});
268 assert_ne!(s.salt, salt);
269 assert_ne!(s.ct, ct);
270 s.with_secret(|p| assert_eq!(p, &[1u8; 16]));
271 }
272
273 #[test]
274 #[should_panic(expected = "sealed memory was modified or corrupted")]
275 fn a_flipped_ciphertext_bit_panics() {
276 let _q = quota();
277 let prekey = Prekey::new();
278 let mut s = SealedFixed::seal(Fixed::new([1u8; 32]), &prekey);
279 s.ct[3] ^= 0x10;
280 s.with_secret(|_| ());
281 }
282
283 #[test]
284 #[should_panic(expected = "sealed memory was modified or corrupted")]
285 fn a_flipped_salt_bit_panics() {
286 let _q = quota();
287 let prekey = Prekey::new();
288 let mut s = SealedFixed::seal(Fixed::new([1u8; 32]), &prekey);
289 s.salt[31] ^= 1;
290 let _ = s.unseal();
291 }
292}