use core::fmt;
use ic_core::traits::Aead;
use ic_core::{ensure, Result};
pub const NONCE_LEN: usize = 12;
pub const FIXED_LEN: usize = 4;
fn nonce(fixed: &[u8; FIXED_LEN], counter: u64) -> Result<[u8; NONCE_LEN]> {
ensure!(
counter != u64::MAX,
CounterExhausted,
"sealer counter exhausted; it never reuses a nonce"
);
let mut n = [0u8; NONCE_LEN];
n[..FIXED_LEN].copy_from_slice(fixed);
n[FIXED_LEN..].copy_from_slice(&counter.to_be_bytes());
Ok(n)
}
fn check_nonce_len<A: Aead>() -> Result<()> {
ensure!(
A::NONCE_LEN == NONCE_LEN,
Unsupported,
"sealer needs an AEAD with a 12-byte nonce"
);
Ok(())
}
pub struct Sealer<A: Aead> {
aead: A,
fixed: [u8; FIXED_LEN],
counter: u64,
}
impl<A: Aead> Sealer<A> {
pub fn new(key: &[u8], fixed: [u8; FIXED_LEN]) -> Result<Self> {
Self::resume(key, fixed, 0)
}
pub fn resume(key: &[u8], fixed: [u8; FIXED_LEN], counter: u64) -> Result<Self> {
check_nonce_len::<A>()?;
Ok(Self {
aead: A::new(key)?,
fixed,
counter,
})
}
pub fn counter(&self) -> u64 {
self.counter
}
pub fn seal(
&mut self,
aad: &[u8],
in_out: &mut [u8],
tag: &mut [u8],
) -> Result<[u8; NONCE_LEN]> {
let n = nonce(&self.fixed, self.counter)?;
self.aead.seal_detached(&n, aad, in_out, tag)?;
self.counter += 1;
Ok(n)
}
}
impl<A: Aead> fmt::Debug for Sealer<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Sealer")
.field("fixed", &self.fixed)
.field("counter", &self.counter)
.finish_non_exhaustive()
}
}
pub struct Opener<A: Aead> {
aead: A,
fixed: [u8; FIXED_LEN],
next: u64,
}
impl<A: Aead> Opener<A> {
pub fn new(key: &[u8], fixed: [u8; FIXED_LEN]) -> Result<Self> {
check_nonce_len::<A>()?;
Ok(Self {
aead: A::new(key)?,
fixed,
next: 0,
})
}
pub fn next_counter(&self) -> u64 {
self.next
}
pub fn open(&mut self, nonce: &[u8], aad: &[u8], in_out: &mut [u8], tag: &[u8]) -> Result<()> {
ensure!(
nonce.len() == NONCE_LEN,
InvalidLength,
"sealer nonce must be 12 bytes"
);
ensure!(
nonce[..FIXED_LEN] == self.fixed,
AuthenticationFailed,
"sealer nonce is from another sender"
);
let mut c = [0u8; 8];
c.copy_from_slice(&nonce[FIXED_LEN..]);
let counter = u64::from_be_bytes(c);
ensure!(
counter >= self.next && counter != u64::MAX,
AuthenticationFailed,
"sealer nonce replayed or out of order"
);
self.aead.open_detached(nonce, aad, in_out, tag)?;
self.next = counter + 1;
Ok(())
}
}
impl<A: Aead> fmt::Debug for Opener<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Opener")
.field("fixed", &self.fixed)
.field("next", &self.next)
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Aes128Gcm, Aes256Gcm, Aes256GcmSiv, ChaCha20Poly1305};
use ic_core::ErrorKind;
const KEY: [u8; 32] = [7; 32];
fn seal_one<A: Aead>(tx: &mut Sealer<A>, msg: &[u8]) -> ([u8; NONCE_LEN], [u8; 64], [u8; 16]) {
let mut buf = [0u8; 64];
buf[..msg.len()].copy_from_slice(msg);
let mut tag = [0u8; 16];
let n = tx.seal(b"aad", &mut buf[..msg.len()], &mut tag).unwrap();
(n, buf, tag)
}
#[test]
fn nonces_are_the_fixed_field_then_a_counter() {
let mut tx = Sealer::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
for i in 0..3u64 {
let (n, _, _) = seal_one(&mut tx, b"m");
assert_eq!(&n[..4], b"abcd");
assert_eq!(n[4..], i.to_be_bytes());
}
assert_eq!(tx.counter(), 3);
}
#[test]
fn a_seal_is_the_bare_aead_under_the_reported_nonce() {
let mut tx = Sealer::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
seal_one(&mut tx, b"first");
let (n, ct, tag) = seal_one(&mut tx, b"second");
let bare = Aes256Gcm::new(&KEY).unwrap();
let mut buf = *b"second";
let mut t = [0u8; 16];
bare.seal_detached(&n, b"aad", &mut buf, &mut t).unwrap();
assert_eq!(&ct[..6], &buf);
assert_eq!(tag, t);
}
#[test]
fn every_twelve_byte_aead_round_trips() {
fn round<A: Aead>() {
let key = [9u8; 32];
let key = &key[..A::KEY_LEN];
let mut tx = Sealer::<A>::new(key, *b"wxyz").unwrap();
let mut rx = Opener::<A>::new(key, *b"wxyz").unwrap();
for _ in 0..3 {
let (n, mut ct, tag) = seal_one(&mut tx, b"hello");
rx.open(&n, b"aad", &mut ct[..5], &tag).unwrap();
assert_eq!(&ct[..5], b"hello");
}
}
round::<Aes128Gcm>();
round::<Aes256Gcm>();
round::<ChaCha20Poly1305>();
round::<Aes256GcmSiv>();
}
#[test]
fn a_replay_or_an_earlier_counter_is_refused() {
let mut tx = Sealer::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
let mut rx = Opener::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
let (n0, ct0, t0) = seal_one(&mut tx, b"zero");
let (n1, ct1, t1) = seal_one(&mut tx, b"one!");
let mut buf = ct1;
rx.open(&n1, b"aad", &mut buf[..4], &t1).unwrap();
assert_eq!(rx.next_counter(), 2);
let mut buf = ct1;
let e = rx.open(&n1, b"aad", &mut buf[..4], &t1).unwrap_err();
assert_eq!(e.kind(), ErrorKind::AuthenticationFailed);
let mut buf = ct0;
let e = rx.open(&n0, b"aad", &mut buf[..4], &t0).unwrap_err();
assert_eq!(e.kind(), ErrorKind::AuthenticationFailed);
assert_eq!(rx.next_counter(), 2, "a refusal does not move the window");
}
#[test]
fn a_skipped_counter_is_accepted_so_a_lost_message_does_not_stall() {
let mut tx = Sealer::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
let mut rx = Opener::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
seal_one(&mut tx, b"lost");
let (n, mut ct, t) = seal_one(&mut tx, b"kept");
rx.open(&n, b"aad", &mut ct[..4], &t).unwrap();
assert_eq!(&ct[..4], b"kept");
}
#[test]
fn another_senders_fixed_field_is_refused() {
let mut tx = Sealer::<Aes256Gcm>::new(&KEY, *b"s->c").unwrap();
let mut rx = Opener::<Aes256Gcm>::new(&KEY, *b"c->s").unwrap();
let (n, mut ct, t) = seal_one(&mut tx, b"hi");
assert!(rx.open(&n, b"aad", &mut ct[..2], &t).is_err());
assert_eq!(rx.next_counter(), 0);
}
#[test]
fn a_forgery_does_not_advance_the_opener() {
let mut tx = Sealer::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
let mut rx = Opener::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
let (n, mut ct, mut t) = seal_one(&mut tx, b"hi");
t[0] ^= 1;
assert!(rx.open(&n, b"aad", &mut ct[..2], &t).is_err());
assert_eq!(rx.next_counter(), 0);
}
#[test]
fn an_exhausted_sealer_refuses_rather_than_wrapping() {
let mut tx = Sealer::<Aes256Gcm>::resume(&KEY, *b"abcd", u64::MAX - 1).unwrap();
seal_one(&mut tx, b"last");
let mut buf = *b"over";
let mut tag = [0u8; 16];
let e = tx.seal(b"", &mut buf, &mut tag).unwrap_err();
assert_eq!(e.kind(), ErrorKind::CounterExhausted);
assert_eq!(tx.counter(), u64::MAX);
}
#[test]
fn resume_continues_where_the_last_sealer_stopped() {
let mut a = Sealer::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
seal_one(&mut a, b"0");
seal_one(&mut a, b"1");
let mut b = Sealer::<Aes256Gcm>::resume(&KEY, *b"abcd", a.counter()).unwrap();
let (n, _, _) = seal_one(&mut b, b"2");
assert_eq!(n[4..], 2u64.to_be_bytes());
}
#[test]
fn debug_shows_no_key() {
let tx = Sealer::<Aes256Gcm>::new(&KEY, *b"abcd").unwrap();
let s = format!("{tx:?}");
assert!(s.contains("counter") && !s.contains("aead"), "{s}");
}
}