1use core::fmt;
55use ic_core::traits::Aead;
56use ic_core::{ensure, Result};
57
58pub const NONCE_LEN: usize = 12;
60
61pub const FIXED_LEN: usize = 4;
63
64fn nonce(fixed: &[u8; FIXED_LEN], counter: u64) -> Result<[u8; NONCE_LEN]> {
67 ensure!(
68 counter != u64::MAX,
69 CounterExhausted,
70 "sealer counter exhausted; it never reuses a nonce"
71 );
72 let mut n = [0u8; NONCE_LEN];
73 n[..FIXED_LEN].copy_from_slice(fixed);
74 n[FIXED_LEN..].copy_from_slice(&counter.to_be_bytes());
75 Ok(n)
76}
77
78fn check_nonce_len<A: Aead>() -> Result<()> {
80 ensure!(
81 A::NONCE_LEN == NONCE_LEN,
82 Unsupported,
83 "sealer needs an AEAD with a 12-byte nonce"
84 );
85 Ok(())
86}
87
88pub struct Sealer<A: Aead> {
90 aead: A,
91 fixed: [u8; FIXED_LEN],
92 counter: u64,
93}
94
95impl<A: Aead> Sealer<A> {
96 pub fn new(key: &[u8], fixed: [u8; FIXED_LEN]) -> Result<Self> {
101 Self::resume(key, fixed, 0)
102 }
103
104 pub fn resume(key: &[u8], fixed: [u8; FIXED_LEN], counter: u64) -> Result<Self> {
111 check_nonce_len::<A>()?;
112 Ok(Self {
113 aead: A::new(key)?,
114 fixed,
115 counter,
116 })
117 }
118
119 pub fn counter(&self) -> u64 {
121 self.counter
122 }
123
124 pub fn seal(
129 &mut self,
130 aad: &[u8],
131 in_out: &mut [u8],
132 tag: &mut [u8],
133 ) -> Result<[u8; NONCE_LEN]> {
134 let n = nonce(&self.fixed, self.counter)?;
135 self.aead.seal_detached(&n, aad, in_out, tag)?;
136 self.counter += 1;
137 Ok(n)
138 }
139}
140
141impl<A: Aead> fmt::Debug for Sealer<A> {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 f.debug_struct("Sealer")
144 .field("fixed", &self.fixed)
145 .field("counter", &self.counter)
146 .finish_non_exhaustive()
147 }
148}
149
150pub struct Opener<A: Aead> {
152 aead: A,
153 fixed: [u8; FIXED_LEN],
154 next: u64,
155}
156
157impl<A: Aead> Opener<A> {
158 pub fn new(key: &[u8], fixed: [u8; FIXED_LEN]) -> Result<Self> {
160 check_nonce_len::<A>()?;
161 Ok(Self {
162 aead: A::new(key)?,
163 fixed,
164 next: 0,
165 })
166 }
167
168 pub fn next_counter(&self) -> u64 {
170 self.next
171 }
172
173 pub fn open(&mut self, nonce: &[u8], aad: &[u8], in_out: &mut [u8], tag: &[u8]) -> Result<()> {
181 ensure!(
182 nonce.len() == NONCE_LEN,
183 InvalidLength,
184 "sealer nonce must be 12 bytes"
185 );
186 ensure!(
189 nonce[..FIXED_LEN] == self.fixed,
190 AuthenticationFailed,
191 "sealer nonce is from another sender"
192 );
193 let mut c = [0u8; 8];
194 c.copy_from_slice(&nonce[FIXED_LEN..]);
195 let counter = u64::from_be_bytes(c);
196 ensure!(
197 counter >= self.next && counter != u64::MAX,
198 AuthenticationFailed,
199 "sealer nonce replayed or out of order"
200 );
201 self.aead.open_detached(nonce, aad, in_out, tag)?;
202 self.next = counter + 1;
203 Ok(())
204 }
205}
206
207impl<A: Aead> fmt::Debug for Opener<A> {
208 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209 f.debug_struct("Opener")
210 .field("fixed", &self.fixed)
211 .field("next", &self.next)
212 .finish_non_exhaustive()
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219 use crate::{Aes128Gcm, Aes256Gcm};
220 #[cfg(not(ic_approved_only))]
221 use crate::{Aes256GcmSiv, ChaCha20Poly1305};
222 use ic_core::ErrorKind;
223
224 const KEY: [u8; 32] = [7; 32];
225
226 fn seal_one<A: Aead>(tx: &mut Sealer<A>, msg: &[u8]) -> ([u8; NONCE_LEN], [u8; 64], [u8; 16]) {
227 let mut buf = [0u8; 64];
228 buf[..msg.len()].copy_from_slice(msg);
229 let mut tag = [0u8; 16];
230 let n = tx.seal(b"aad", &mut buf[..msg.len()], &mut tag).unwrap();
231 (n, buf, tag)
232 }
233
234 #[test]
237 fn nonces_are_the_fixed_field_then_a_counter() {
238 let mut tx = Sealer::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
239 for i in 0..3u64 {
240 let (n, _, _) = seal_one(&mut tx, b"m");
241 assert_eq!(&n[..4], b"abcd");
242 assert_eq!(n[4..], i.to_be_bytes());
243 }
244 assert_eq!(tx.counter(), 3);
245 }
246
247 #[test]
250 fn a_seal_is_the_bare_aead_under_the_reported_nonce() {
251 let mut tx = Sealer::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
252 seal_one(&mut tx, b"first");
253 let (n, ct, tag) = seal_one(&mut tx, b"second");
254 let bare = Aes256Gcm::new(&KEY).unwrap();
255 let mut buf = *b"second";
256 let mut t = [0u8; 16];
257 bare.seal_detached(&n, b"aad", &mut buf, &mut t).unwrap();
258 assert_eq!(&ct[..6], &buf);
259 assert_eq!(tag, t);
260 }
261
262 #[test]
263 fn every_twelve_byte_aead_round_trips() {
264 fn round<A: Aead>() {
265 let key = [9u8; 32];
266 let key = &key[..A::KEY_LEN];
267 let mut tx = Sealer::<A>::new(key, *b"wxyz").unwrap();
268 let mut rx = Opener::<A>::new(key, *b"wxyz").unwrap();
269 for _ in 0..3 {
270 let (n, mut ct, tag) = seal_one(&mut tx, b"hello");
271 rx.open(&n, b"aad", &mut ct[..5], &tag).unwrap();
272 assert_eq!(&ct[..5], b"hello");
273 }
274 }
275 round::<Aes128Gcm>();
276 round::<Aes256Gcm>();
277 #[cfg(not(ic_approved_only))]
278 round::<ChaCha20Poly1305>();
279 #[cfg(not(ic_approved_only))]
280 round::<Aes256GcmSiv>();
281 }
282
283 #[test]
284 fn a_replay_or_an_earlier_counter_is_refused() {
285 let mut tx = Sealer::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
286 let mut rx = Opener::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
287 let (n0, ct0, t0) = seal_one(&mut tx, b"zero");
288 let (n1, ct1, t1) = seal_one(&mut tx, b"one!");
289 let mut buf = ct1;
290 rx.open(&n1, b"aad", &mut buf[..4], &t1).unwrap();
291 assert_eq!(rx.next_counter(), 2);
292 let mut buf = ct1;
294 let e = rx.open(&n1, b"aad", &mut buf[..4], &t1).unwrap_err();
295 assert_eq!(e.kind(), ErrorKind::AuthenticationFailed);
296 let mut buf = ct0;
298 let e = rx.open(&n0, b"aad", &mut buf[..4], &t0).unwrap_err();
299 assert_eq!(e.kind(), ErrorKind::AuthenticationFailed);
300 assert_eq!(rx.next_counter(), 2, "a refusal does not move the window");
301 }
302
303 #[test]
304 fn a_skipped_counter_is_accepted_so_a_lost_message_does_not_stall() {
305 let mut tx = Sealer::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
306 let mut rx = Opener::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
307 seal_one(&mut tx, b"lost");
308 let (n, mut ct, t) = seal_one(&mut tx, b"kept");
309 rx.open(&n, b"aad", &mut ct[..4], &t).unwrap();
310 assert_eq!(&ct[..4], b"kept");
311 }
312
313 #[test]
314 fn another_senders_fixed_field_is_refused() {
315 let mut tx = Sealer::<Aes256Gcm>::new(&KEY, *b"s->c").unwrap();
316 let mut rx = Opener::<Aes256Gcm>::new(&KEY, *b"c->s").unwrap();
317 let (n, mut ct, t) = seal_one(&mut tx, b"hi");
318 assert!(rx.open(&n, b"aad", &mut ct[..2], &t).is_err());
319 assert_eq!(rx.next_counter(), 0);
320 }
321
322 #[test]
323 fn a_forgery_does_not_advance_the_opener() {
324 let mut tx = Sealer::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
325 let mut rx = Opener::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
326 let (n, mut ct, mut t) = seal_one(&mut tx, b"hi");
327 t[0] ^= 1;
328 assert!(rx.open(&n, b"aad", &mut ct[..2], &t).is_err());
329 assert_eq!(rx.next_counter(), 0);
330 }
331
332 #[test]
334 fn an_exhausted_sealer_refuses_rather_than_wrapping() {
335 let mut tx = Sealer::<Aes256Gcm>::resume(&KEY, *b"abcd", u64::MAX - 1).unwrap();
336 seal_one(&mut tx, b"last");
337 let mut buf = *b"over";
338 let mut tag = [0u8; 16];
339 let e = tx.seal(b"", &mut buf, &mut tag).unwrap_err();
340 assert_eq!(e.kind(), ErrorKind::CounterExhausted);
341 assert_eq!(tx.counter(), u64::MAX);
342 }
343
344 #[test]
345 fn resume_continues_where_the_last_sealer_stopped() {
346 let mut a = Sealer::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
347 seal_one(&mut a, b"0");
348 seal_one(&mut a, b"1");
349 let mut b = Sealer::<Aes256Gcm>::resume(&KEY, *b"abcd", a.counter()).unwrap();
350 let (n, _, _) = seal_one(&mut b, b"2");
351 assert_eq!(n[4..], 2u64.to_be_bytes());
352 }
353
354 #[test]
355 fn debug_shows_no_key() {
356 let tx = Sealer::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
357 let s = format!("{tx:?}");
358 assert!(s.contains("counter") && !s.contains("aead"), "{s}");
359 }
360}