use crate::polyval::Polyval;
use ic_core::traits::{Aead, Algorithm, BlockCipher, SelfTest};
use ic_core::{ensure, Result, Zeroize};
pub const NONCE_LEN: usize = 12;
pub const TAG_LEN: usize = 16;
const BLOCK_LEN: usize = 16;
fn derive_keys<C: BlockCipher>(
key_cipher: &C,
nonce: &[u8],
auth_key: &mut [u8; 16],
enc_key: &mut [u8],
) -> Result<()> {
let mut block = [0u8; BLOCK_LEN];
block[4..].copy_from_slice(nonce);
let mut take = |counter: u32, out: &mut [u8]| -> Result<()> {
block[0..4].copy_from_slice(&counter.to_le_bytes());
let mut b = block;
key_cipher.encrypt_block(&mut b)?;
out.copy_from_slice(&b[..8]);
b.zeroize();
Ok(())
};
take(0, &mut auth_key[0..8])?;
take(1, &mut auth_key[8..16])?;
for (i, chunk) in enc_key.chunks_mut(8).enumerate() {
take(2 + i as u32, chunk)?;
}
block.zeroize();
Ok(())
}
fn authenticate(auth_key: [u8; 16], aad: &[u8], message: &[u8]) -> [u8; 16] {
let mut p = Polyval::new(auth_key);
p.update_padded(aad);
p.update_padded(message);
let mut lengths = [0u8; BLOCK_LEN];
lengths[0..8].copy_from_slice(&((aad.len() as u64) * 8).to_le_bytes());
lengths[8..16].copy_from_slice(&((message.len() as u64) * 8).to_le_bytes());
p.update_block(&lengths);
p.finish()
}
fn ctr_xor<C: BlockCipher>(cipher: &C, counter_block: &[u8; 16], in_out: &mut [u8]) -> Result<()> {
let mut block = *counter_block;
for chunk in in_out.chunks_mut(BLOCK_LEN) {
let mut keystream = block;
cipher.encrypt_block(&mut keystream)?;
for (b, k) in chunk.iter_mut().zip(keystream.iter()) {
*b ^= *k;
}
keystream.zeroize();
let counter = u32::from_le_bytes(block[0..4].try_into().unwrap());
block[0..4].copy_from_slice(&counter.wrapping_add(1).to_le_bytes());
}
block.zeroize();
Ok(())
}
macro_rules! gcm_siv {
($name:ident, $cipher:ty, $key_len:literal, $id:literal, $disp:literal,
$kat_ct:literal, $kat_tag:literal) => {
#[doc = concat!("RFC 8452 ", $disp, ". See the module docs on verification status.")]
pub struct $name {
key: [u8; $key_len],
}
impl Algorithm for $name {
const ID: &'static str = $id;
const NAME: &'static str = $disp;
}
impl Drop for $name {
fn drop(&mut self) {
self.key.zeroize();
}
}
impl Aead for $name {
const KEY_LEN: usize = $key_len;
const NONCE_LEN: usize = NONCE_LEN;
const TAG_LEN: usize = TAG_LEN;
fn new(key: &[u8]) -> Result<Self> {
ensure!(key.len() == $key_len, InvalidLength, "aes-gcm-siv key");
let mut k = [0u8; $key_len];
k.copy_from_slice(key);
Ok(Self { key: k })
}
fn seal_detached(
&self,
nonce: &[u8],
aad: &[u8],
in_out: &mut [u8],
tag: &mut [u8],
) -> Result<()> {
ensure!(nonce.len() == NONCE_LEN, InvalidLength, "aes-gcm-siv nonce");
ensure!(tag.len() == TAG_LEN, InvalidLength, "aes-gcm-siv tag");
let key_cipher = <$cipher>::new(&self.key)?;
let mut auth_key = [0u8; 16];
let mut enc_key = [0u8; $key_len];
derive_keys(&key_cipher, nonce, &mut auth_key, &mut enc_key)?;
let message_cipher = <$cipher>::new(&enc_key)?;
enc_key.zeroize();
let mut s = authenticate(auth_key, aad, in_out);
auth_key.zeroize();
for (b, n) in s.iter_mut().zip(nonce.iter()) {
*b ^= *n;
}
s[15] &= 0x7f;
message_cipher.encrypt_block(&mut s)?;
tag.copy_from_slice(&s);
let mut counter_block = s;
counter_block[15] |= 0x80;
ctr_xor(&message_cipher, &counter_block, in_out)?;
counter_block.zeroize();
s.zeroize();
Ok(())
}
fn open_detached(
&self,
nonce: &[u8],
aad: &[u8],
in_out: &mut [u8],
tag: &[u8],
) -> Result<()> {
ensure!(nonce.len() == NONCE_LEN, InvalidLength, "aes-gcm-siv nonce");
ensure!(tag.len() == TAG_LEN, InvalidLength, "aes-gcm-siv tag");
let key_cipher = <$cipher>::new(&self.key)?;
let mut auth_key = [0u8; 16];
let mut enc_key = [0u8; $key_len];
derive_keys(&key_cipher, nonce, &mut auth_key, &mut enc_key)?;
let message_cipher = <$cipher>::new(&enc_key)?;
enc_key.zeroize();
let mut counter_block = [0u8; BLOCK_LEN];
counter_block.copy_from_slice(tag);
counter_block[15] |= 0x80;
ctr_xor(&message_cipher, &counter_block, in_out)?;
counter_block.zeroize();
let mut s = authenticate(auth_key, aad, in_out);
auth_key.zeroize();
for (b, n) in s.iter_mut().zip(nonce.iter()) {
*b ^= *n;
}
s[15] &= 0x7f;
message_cipher.encrypt_block(&mut s)?;
let ok = ic_core::ct::verify(&s, tag);
s.zeroize();
if !ok {
in_out.zeroize();
return Err(ic_core::err!(AuthenticationFailed, $id));
}
Ok(())
}
}
impl SelfTest for $name {
fn self_test() -> Result<()> {
let mut key = [0u8; $key_len];
key[0] = 0x01;
let kat = <Self as Aead>::new(&key)?;
let mut nonce_kat = [0u8; NONCE_LEN];
nonce_kat[0] = 0x03;
let mut buf = [0x02u8, 0, 0, 0, 0, 0, 0, 0];
let mut tag = [0u8; TAG_LEN];
kat.seal_detached(&nonce_kat, &[0x01], &mut buf, &mut tag)?;
let mut want_ct = [0u8; 8];
let mut want_tag = [0u8; TAG_LEN];
ic_core::codec::hex_decode($kat_ct, &mut want_ct)?;
ic_core::codec::hex_decode($kat_tag, &mut want_tag)?;
ensure!(
ic_core::ct::verify(&want_ct, &buf) && ic_core::ct::verify(&want_tag, &tag),
SelfTestFailed,
$id
);
let cipher = <Self as Aead>::new(&[0x42u8; $key_len])?;
let nonce = [0x24u8; NONCE_LEN];
let mut a = *b"self-test message";
let mut tag_a = [0u8; TAG_LEN];
cipher.seal_detached(&nonce, b"aad", &mut a, &mut tag_a)?;
let mut b = *b"self-test message";
let mut tag_b = [0u8; TAG_LEN];
cipher.seal_detached(&nonce, b"aad", &mut b, &mut tag_b)?;
ensure!(a == b && tag_a == tag_b, SelfTestFailed, $id);
cipher.open_detached(&nonce, b"aad", &mut a, &tag_a)?;
ensure!(&a == b"self-test message", SelfTestFailed, $id);
let mut bad = tag_a;
bad[0] ^= 1;
ensure!(
cipher.open_detached(&nonce, b"aad", &mut b, &bad).is_err(),
SelfTestFailed,
$id
);
let mut c = *b"self-test message";
let mut tag_c = [0u8; TAG_LEN];
cipher.seal_detached(&nonce, b"aad", &mut c, &mut tag_c)?;
ensure!(
cipher
.open_detached(&nonce, b"other", &mut c, &tag_c)
.is_err(),
SelfTestFailed,
$id
);
Ok(())
}
}
};
}
gcm_siv!(
Aes128GcmSiv,
crate::Aes128,
16,
"aes-128-gcm-siv",
"AES-128-GCM-SIV",
b"1e6daba35669f427",
b"3b0a1a2560969cdf790d99759abd1508"
);
gcm_siv!(
Aes256GcmSiv,
crate::Aes256,
32,
"aes-256-gcm-siv",
"AES-256-GCM-SIV",
b"1de22967237a8132",
b"91213f267e3b452f02d01ae33e4ec854"
);
#[cfg(test)]
mod tests {
use super::*;
fn seal(cipher: &Aes256GcmSiv, nonce: &[u8], aad: &[u8], pt: &[u8]) -> (Vec<u8>, [u8; 16]) {
let mut buf = pt.to_vec();
let mut tag = [0u8; TAG_LEN];
cipher
.seal_detached(nonce, aad, &mut buf, &mut tag)
.unwrap();
(buf, tag)
}
#[test]
fn round_trips_at_every_length_boundary() {
let cipher = Aes256GcmSiv::new(&[0x11u8; 32]).unwrap();
let nonce = [0x22u8; NONCE_LEN];
for len in [0usize, 1, 15, 16, 17, 31, 32, 33, 255, 256] {
let pt = vec![0x5au8; len];
let (mut ct, tag) = seal(&cipher, &nonce, b"aad", &pt);
assert_eq!(ct.len(), len, "length is preserved");
if len > 0 {
assert_ne!(ct, pt, "and the plaintext is not passed through");
}
cipher.open_detached(&nonce, b"aad", &mut ct, &tag).unwrap();
assert_eq!(ct, pt, "round trip at {len} bytes");
}
}
#[test]
fn nonce_reuse_does_not_leak_the_plaintext_xor() {
let cipher = Aes256GcmSiv::new(&[0x33u8; 32]).unwrap();
let nonce = [0x44u8; NONCE_LEN];
let p1 = vec![0xaau8; 64];
let mut p2 = vec![0xaau8; 64];
p2[0] ^= 0x01;
let (c1, _) = seal(&cipher, &nonce, b"", &p1);
let (c2, _) = seal(&cipher, &nonce, b"", &p2);
let cipher_xor: Vec<u8> = c1.iter().zip(c2.iter()).map(|(a, b)| a ^ b).collect();
let plain_xor: Vec<u8> = p1.iter().zip(p2.iter()).map(|(a, b)| a ^ b).collect();
assert_ne!(
cipher_xor, plain_xor,
"a one-bit plaintext change must change the whole keystream"
);
let differing = cipher_xor.iter().filter(|b| **b != 0).count();
assert!(
differing > 48,
"only {differing} of 64 ciphertext bytes changed; the keystream is \
not depending on the plaintext"
);
}
#[test]
fn encryption_is_deterministic() {
let cipher = Aes256GcmSiv::new(&[0x55u8; 32]).unwrap();
let nonce = [0x66u8; NONCE_LEN];
let (a, ta) = seal(&cipher, &nonce, b"aad", b"same message");
let (b, tb) = seal(&cipher, &nonce, b"aad", b"same message");
assert_eq!((a, ta), (b, tb));
}
#[test]
fn every_input_is_authenticated() {
let cipher = Aes256GcmSiv::new(&[0x77u8; 32]).unwrap();
let nonce = [0x88u8; NONCE_LEN];
let (ct, tag) = seal(&cipher, &nonce, b"aad", b"message");
let mut buf = ct.clone();
let mut other_nonce = nonce;
other_nonce[0] ^= 1;
assert!(cipher
.open_detached(&other_nonce, b"aad", &mut buf, &tag)
.is_err());
let mut buf = ct.clone();
assert!(cipher
.open_detached(&nonce, b"aae", &mut buf, &tag)
.is_err());
let mut buf = ct.clone();
buf[0] ^= 1;
assert!(cipher
.open_detached(&nonce, b"aad", &mut buf, &tag)
.is_err());
let mut buf = ct.clone();
let mut bad = tag;
bad[15] ^= 1;
assert!(cipher
.open_detached(&nonce, b"aad", &mut buf, &bad)
.is_err());
}
#[test]
fn a_failed_open_clears_the_buffer() {
let cipher = Aes256GcmSiv::new(&[0x99u8; 32]).unwrap();
let nonce = [0xaau8; NONCE_LEN];
let (ct, tag) = seal(&cipher, &nonce, b"", b"secret plaintext");
let mut buf = ct;
let mut bad = tag;
bad[0] ^= 1;
assert!(cipher.open_detached(&nonce, b"", &mut buf, &bad).is_err());
assert!(
buf.iter().all(|b| *b == 0),
"the buffer still held data after a failed open"
);
}
#[test]
fn the_aad_boundary_is_authenticated() {
let cipher = Aes256GcmSiv::new(&[0xbbu8; 32]).unwrap();
let nonce = [0xccu8; NONCE_LEN];
let (_, tag_a) = seal(&cipher, &nonce, b"abc", b"def");
let (_, tag_b) = seal(&cipher, &nonce, b"ab", b"cdef");
assert_ne!(tag_a, tag_b, "the aad/message split must be bound in");
}
#[test]
fn key_sizes_and_lengths_are_checked() {
assert!(Aes128GcmSiv::new(&[0u8; 16]).is_ok());
assert!(Aes128GcmSiv::new(&[0u8; 32]).is_err());
assert!(Aes256GcmSiv::new(&[0u8; 16]).is_err());
let cipher = Aes128GcmSiv::new(&[0u8; 16]).unwrap();
let mut buf = [0u8; 8];
let mut tag = [0u8; TAG_LEN];
assert!(cipher
.seal_detached(&[0u8; 11], b"", &mut buf, &mut tag)
.is_err());
assert!(cipher
.seal_detached(&[0u8; 12], b"", &mut buf, &mut [0u8; 15])
.is_err());
}
#[test]
fn both_self_tests_pass() {
Aes128GcmSiv::self_test().unwrap();
Aes256GcmSiv::self_test().unwrap();
}
#[test]
fn the_two_key_sizes_are_distinct() {
let mut key = [0u8; 32];
key[..16].copy_from_slice(&[0x5au8; 16]);
let small = Aes128GcmSiv::new(&key[..16]).unwrap();
let large = Aes256GcmSiv::new(&key).unwrap();
let nonce = [0u8; NONCE_LEN];
let mut a = *b"message";
let mut b = *b"message";
let mut ta = [0u8; TAG_LEN];
let mut tb = [0u8; TAG_LEN];
small.seal_detached(&nonce, b"", &mut a, &mut ta).unwrap();
large.seal_detached(&nonce, b"", &mut b, &mut tb).unwrap();
assert_ne!((a, ta), (b, tb));
}
}