use super::TagMismatch;
use super::chacha20::hchacha20;
use super::chacha20poly1305::ChaCha20Poly1305;
#[derive(Clone)]
pub struct XChaCha20Poly1305 {
key: [u8; 32],
}
impl XChaCha20Poly1305 {
pub fn new(key: &[u8; 32]) -> Self {
XChaCha20Poly1305 { key: *key }
}
fn inner(&self, nonce: &[u8; 24]) -> (ChaCha20Poly1305, [u8; 12]) {
let mut n16 = [0u8; 16];
n16.copy_from_slice(&nonce[..16]);
let subkey = hchacha20(&self.key, &n16);
let aead = ChaCha20Poly1305::new(&subkey);
let mut inner_nonce = [0u8; 12];
inner_nonce[4..].copy_from_slice(&nonce[16..]);
(aead, inner_nonce)
}
pub fn encrypt(&self, nonce: &[u8; 24], aad: &[u8], buffer: &mut [u8]) -> [u8; 16] {
let (aead, inner_nonce) = self.inner(nonce);
aead.encrypt(&inner_nonce, aad, buffer)
}
pub fn decrypt(
&self,
nonce: &[u8; 24],
aad: &[u8],
buffer: &mut [u8],
tag: &[u8; 16],
) -> Result<(), TagMismatch> {
let (aead, inner_nonce) = self.inner(nonce);
aead.decrypt(&inner_nonce, aad, buffer, tag)
}
}
impl Drop for XChaCha20Poly1305 {
fn drop(&mut self) {
self.key = [0u8; 32];
let _ = core::hint::black_box(&self.key);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_util::{from_hex, from_hex_vec};
#[test]
fn draft_aead_vector() {
let key =
from_hex::<32>("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f");
let nonce = from_hex::<24>("404142434445464748494a4b4c4d4e4f5051525354555657");
let aad = from_hex::<12>("50515253c0c1c2c3c4c5c6c7");
let plaintext = from_hex_vec(
"4c616469657320616e642047656e746c\
656d656e206f662074686520636c6173\
73206f66202739393a20496620492063\
6f756c64206f6666657220796f75206f\
6e6c79206f6e652074697020666f7220\
746865206675747572652c2073756e73\
637265656e20776f756c642062652069\
742e",
);
let aead = XChaCha20Poly1305::new(&key);
let mut buf = plaintext.clone();
let tag = aead.encrypt(&nonce, &aad, &mut buf);
let expected_ct = from_hex_vec(
"bd6d179d3e83d43b9576579493c0e939\
572a1700252bfaccbed2902c21396cbb\
731c7f1b0b4aa6440bf3a82f4eda7e39\
ae64c6708c54c216cb96b72e1213b452\
2f8c9ba40db5d945b11b69b982c1bb9e\
3f3fac2bc369488f76b2383565d3fff9\
21f9664c97637da9768812f615c68b13\
b52e",
);
assert_eq!(buf, expected_ct);
assert_eq!(tag, from_hex::<16>("c0875924c1c7987947deafd8780acf49"));
aead.decrypt(&nonce, &aad, &mut buf, &tag).unwrap();
assert_eq!(buf, plaintext);
}
#[test]
fn reject_tamper() {
let key = [7u8; 32];
let nonce = [9u8; 24];
let aead = XChaCha20Poly1305::new(&key);
let pt = *b"XChaCha20-Poly1305 tamper test..";
let mut buf = pt;
let tag = aead.encrypt(&nonce, b"hdr", &mut buf);
let ciphertext = buf;
let mut bad = tag;
bad[0] ^= 1;
assert_eq!(
aead.decrypt(&nonce, b"hdr", &mut buf, &bad),
Err(TagMismatch)
);
assert_eq!(buf, ciphertext, "buffer untouched on auth failure");
}
}