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, Aes256GcmSiv, ChaCha20Poly1305};
220 use ic_core::ErrorKind;
221
222 const KEY: [u8; 32] = [7; 32];
223
224 fn seal_one<A: Aead>(tx: &mut Sealer<A>, msg: &[u8]) -> ([u8; NONCE_LEN], [u8; 64], [u8; 16]) {
225 let mut buf = [0u8; 64];
226 buf[..msg.len()].copy_from_slice(msg);
227 let mut tag = [0u8; 16];
228 let n = tx.seal(b"aad", &mut buf[..msg.len()], &mut tag).unwrap();
229 (n, buf, tag)
230 }
231
232 #[test]
235 fn nonces_are_the_fixed_field_then_a_counter() {
236 let mut tx = Sealer::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
237 for i in 0..3u64 {
238 let (n, _, _) = seal_one(&mut tx, b"m");
239 assert_eq!(&n[..4], b"abcd");
240 assert_eq!(n[4..], i.to_be_bytes());
241 }
242 assert_eq!(tx.counter(), 3);
243 }
244
245 #[test]
248 fn a_seal_is_the_bare_aead_under_the_reported_nonce() {
249 let mut tx = Sealer::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
250 seal_one(&mut tx, b"first");
251 let (n, ct, tag) = seal_one(&mut tx, b"second");
252 let bare = Aes256Gcm::new(&KEY).unwrap();
253 let mut buf = *b"second";
254 let mut t = [0u8; 16];
255 bare.seal_detached(&n, b"aad", &mut buf, &mut t).unwrap();
256 assert_eq!(&ct[..6], &buf);
257 assert_eq!(tag, t);
258 }
259
260 #[test]
261 fn every_twelve_byte_aead_round_trips() {
262 fn round<A: Aead>() {
263 let key = [9u8; 32];
264 let key = &key[..A::KEY_LEN];
265 let mut tx = Sealer::<A>::new(key, *b"wxyz").unwrap();
266 let mut rx = Opener::<A>::new(key, *b"wxyz").unwrap();
267 for _ in 0..3 {
268 let (n, mut ct, tag) = seal_one(&mut tx, b"hello");
269 rx.open(&n, b"aad", &mut ct[..5], &tag).unwrap();
270 assert_eq!(&ct[..5], b"hello");
271 }
272 }
273 round::<Aes128Gcm>();
274 round::<Aes256Gcm>();
275 round::<ChaCha20Poly1305>();
276 round::<Aes256GcmSiv>();
277 }
278
279 #[test]
280 fn a_replay_or_an_earlier_counter_is_refused() {
281 let mut tx = Sealer::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
282 let mut rx = Opener::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
283 let (n0, ct0, t0) = seal_one(&mut tx, b"zero");
284 let (n1, ct1, t1) = seal_one(&mut tx, b"one!");
285 let mut buf = ct1;
286 rx.open(&n1, b"aad", &mut buf[..4], &t1).unwrap();
287 assert_eq!(rx.next_counter(), 2);
288 let mut buf = ct1;
290 let e = rx.open(&n1, b"aad", &mut buf[..4], &t1).unwrap_err();
291 assert_eq!(e.kind(), ErrorKind::AuthenticationFailed);
292 let mut buf = ct0;
294 let e = rx.open(&n0, b"aad", &mut buf[..4], &t0).unwrap_err();
295 assert_eq!(e.kind(), ErrorKind::AuthenticationFailed);
296 assert_eq!(rx.next_counter(), 2, "a refusal does not move the window");
297 }
298
299 #[test]
300 fn a_skipped_counter_is_accepted_so_a_lost_message_does_not_stall() {
301 let mut tx = Sealer::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
302 let mut rx = Opener::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
303 seal_one(&mut tx, b"lost");
304 let (n, mut ct, t) = seal_one(&mut tx, b"kept");
305 rx.open(&n, b"aad", &mut ct[..4], &t).unwrap();
306 assert_eq!(&ct[..4], b"kept");
307 }
308
309 #[test]
310 fn another_senders_fixed_field_is_refused() {
311 let mut tx = Sealer::<Aes256Gcm>::new(&KEY, *b"s->c").unwrap();
312 let mut rx = Opener::<Aes256Gcm>::new(&KEY, *b"c->s").unwrap();
313 let (n, mut ct, t) = seal_one(&mut tx, b"hi");
314 assert!(rx.open(&n, b"aad", &mut ct[..2], &t).is_err());
315 assert_eq!(rx.next_counter(), 0);
316 }
317
318 #[test]
319 fn a_forgery_does_not_advance_the_opener() {
320 let mut tx = Sealer::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
321 let mut rx = Opener::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
322 let (n, mut ct, mut t) = seal_one(&mut tx, b"hi");
323 t[0] ^= 1;
324 assert!(rx.open(&n, b"aad", &mut ct[..2], &t).is_err());
325 assert_eq!(rx.next_counter(), 0);
326 }
327
328 #[test]
330 fn an_exhausted_sealer_refuses_rather_than_wrapping() {
331 let mut tx = Sealer::<Aes256Gcm>::resume(&KEY, *b"abcd", u64::MAX - 1).unwrap();
332 seal_one(&mut tx, b"last");
333 let mut buf = *b"over";
334 let mut tag = [0u8; 16];
335 let e = tx.seal(b"", &mut buf, &mut tag).unwrap_err();
336 assert_eq!(e.kind(), ErrorKind::CounterExhausted);
337 assert_eq!(tx.counter(), u64::MAX);
338 }
339
340 #[test]
341 fn resume_continues_where_the_last_sealer_stopped() {
342 let mut a = Sealer::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
343 seal_one(&mut a, b"0");
344 seal_one(&mut a, b"1");
345 let mut b = Sealer::<Aes256Gcm>::resume(&KEY, *b"abcd", a.counter()).unwrap();
346 let (n, _, _) = seal_one(&mut b, b"2");
347 assert_eq!(n[4..], 2u64.to_be_bytes());
348 }
349
350 #[test]
351 fn debug_shows_no_key() {
352 let tx = Sealer::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
353 let s = format!("{tx:?}");
354 assert!(s.contains("counter") && !s.contains("aead"), "{s}");
355 }
356}