mod chacha;
mod poly1305;
use chacha::{Backend, State, BUF};
use poly1305::Poly1305;
use zeroize::Zeroize;
pub const KEY_LEN: usize = 32;
pub const NONCE_LEN: usize = 12;
pub const TAG_LEN: usize = 16;
const MAX_LEN: u64 = (u32::MAX as u64 - 1) * 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Error;
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("aead error")
}
}
impl std::error::Error for Error {}
pub struct ChaCha20Poly1305 {
key: [u32; 8],
backend: Backend,
poly_min: usize,
}
impl Drop for ChaCha20Poly1305 {
fn drop(&mut self) {
self.key.zeroize();
}
}
impl ChaCha20Poly1305 {
pub fn new(key: &[u8; KEY_LEN]) -> Self {
let mut k = [0u32; 8];
for (w, c) in k.iter_mut().zip(key.chunks_exact(4)) {
*w = u32::from_le_bytes(c.try_into().unwrap());
}
Self { key: k, backend: Backend::detect(), poly_min: poly1305::simd_min() }
}
#[cfg(test)]
fn with_backend(key: &[u8; KEY_LEN], backend: Backend) -> Self {
let mut s = Self::new(key);
s.backend = backend;
s
}
fn state(&self, nonce: &[u8; NONCE_LEN]) -> State {
let mut n = [0u32; 3];
for (w, c) in n.iter_mut().zip(nonce.chunks_exact(4)) {
*w = u32::from_le_bytes(c.try_into().unwrap());
}
State { key: self.key, nonce: n }
}
pub fn seal_in_place(&self, nonce: &[u8; NONCE_LEN], aad: &[u8], buf: &mut [u8]) -> Result<[u8; TAG_LEN], Error> {
if buf.len() as u64 > MAX_LEN {
return Err(Error);
}
let st = self.state(nonce);
let mut ks = [0u8; BUF];
let (mut mac, first) = self.first_pass(&st, buf.len(), &mut ks);
mac.padded(aad, self.poly_min);
let used = self.xor_stream(&st, buf, &mut ks, first);
mac.padded(buf, self.poly_min);
let tag = finish(mac, aad.len(), buf.len());
wipe(&mut ks[..used]);
Ok(tag)
}
pub fn open_in_place(
&self,
nonce: &[u8; NONCE_LEN],
aad: &[u8],
buf: &mut [u8],
tag: &[u8; TAG_LEN],
) -> Result<(), Error> {
if buf.len() as u64 > MAX_LEN {
return Err(Error);
}
let st = self.state(nonce);
let mut ks = [0u8; BUF];
let (mut mac, first) = self.first_pass(&st, buf.len(), &mut ks);
mac.padded(aad, self.poly_min);
mac.padded(buf, self.poly_min);
let expected = finish(mac, aad.len(), buf.len());
if !ct_eq(&expected, tag) {
wipe(&mut ks[..64 * first]);
return Err(Error);
}
let used = self.xor_stream(&st, buf, &mut ks, first);
wipe(&mut ks[..used]);
Ok(())
}
pub fn open_framed_in_place<'a>(&self, data: &'a mut [u8]) -> Result<&'a mut [u8], Error> {
if data.len() < NONCE_LEN + TAG_LEN {
return Err(Error);
}
let (nonce, rest) = data.split_at_mut(NONCE_LEN);
let nonce: [u8; NONCE_LEN] = (&*nonce).try_into().unwrap();
let (body, tag) = rest.split_at_mut(rest.len() - TAG_LEN);
let tag: [u8; TAG_LEN] = (&*tag).try_into().unwrap();
self.open_in_place(&nonce, &[], body, &tag)?;
Ok(body)
}
#[inline]
fn first_pass(&self, st: &State, len: usize, ks: &mut [u8; BUF]) -> (Poly1305, usize) {
let want = 1 + len.div_ceil(64);
let n = self.backend.keystream(st, 0, want, ks);
let pk: &[u8; 32] = ks[..32].try_into().unwrap();
(Poly1305::new(pk), n)
}
#[inline]
fn xor_stream(&self, st: &State, buf: &mut [u8], ks: &mut [u8; BUF], first: usize) -> usize {
let mut used = 64 * first;
let avail = 64 * (first - 1);
let head = avail.min(buf.len());
xor(&mut buf[..head], &ks[64..64 + head]);
let mut counter = first as u32;
let mut rest = &mut buf[head..];
while !rest.is_empty() {
let want = rest.len().div_ceil(64);
let n = self.backend.keystream(st, counter, want, ks);
used = used.max(64 * n);
let take = (64 * n).min(rest.len());
let (now, later) = rest.split_at_mut(take);
xor(now, &ks[..take]);
rest = later;
counter = counter.wrapping_add(n as u32);
}
used
}
}
#[inline]
fn finish(mut mac: Poly1305, aad_len: usize, ct_len: usize) -> [u8; TAG_LEN] {
let mut lens = [0u8; 16];
lens[..8].copy_from_slice(&(aad_len as u64).to_le_bytes());
lens[8..].copy_from_slice(&(ct_len as u64).to_le_bytes());
mac.blocks(&lens);
mac.finish()
}
#[inline(always)]
fn xor(dst: &mut [u8], ks: &[u8]) {
for (d, k) in dst.iter_mut().zip(ks) {
*d ^= *k;
}
}
#[inline]
pub fn wipe(buf: &mut [u8]) {
unsafe { wipe_raw(buf.as_mut_ptr(), buf.len()) }
}
#[inline]
pub fn wipe_vec(v: &mut Vec<u8>) {
unsafe {
wipe_raw(v.as_mut_ptr(), v.capacity());
v.set_len(0);
}
}
#[inline]
unsafe fn wipe_raw(p: *mut u8, len: usize) {
use core::ptr::write_volatile;
let head = p.align_offset(8).min(len);
let words = (len - head) / 8;
for i in 0..head {
write_volatile(p.add(i), 0);
}
let w = p.add(head) as *mut u64;
for i in 0..words {
write_volatile(w.add(i), 0);
}
for i in head + 8 * words..len {
write_volatile(p.add(i), 0);
}
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
}
#[inline]
fn ct_eq(a: &[u8; 16], b: &[u8; 16]) -> bool {
let d = u128::from_ne_bytes(*a) ^ u128::from_ne_bytes(*b);
let folded = (d as u64) | ((d >> 64) as u64);
core::hint::black_box(folded) == 0
}
#[doc(hidden)]
#[cfg(target_pointer_width = "64")]
pub mod bench {
use super::chacha::Backend;
use super::poly1305::r64::{self, Poly1305};
use super::ChaCha20Poly1305;
pub fn chacha_backends() -> Vec<&'static str> {
Backend::available().into_iter().map(|(n, _)| n).collect()
}
pub fn default_chacha_backend() -> &'static str {
let chosen = Backend::detect();
Backend::available().into_iter().find(|(_, b)| *b == chosen).map_or("portable", |(n, _)| n)
}
pub fn poly_simd_available() -> bool {
r64::simd_available()
}
pub fn poly_simd_min() -> usize {
super::poly1305::simd_min()
}
pub fn cipher(key: &[u8; 32], chacha: &str, poly_simd_min: usize) -> Option<ChaCha20Poly1305> {
let (_, backend) = Backend::available().into_iter().find(|(n, _)| *n == chacha)?;
let mut c = ChaCha20Poly1305::new(key);
c.backend = backend;
c.poly_min = poly_simd_min;
Some(c)
}
pub fn poly1305_scalar(key: &[u8; 32], data: &[u8]) -> [u8; 16] {
let mut p = Poly1305::new(key);
p.blocks(data);
p.finish()
}
pub fn poly1305_simd(key: &[u8; 32], data: &[u8]) -> Option<[u8; 16]> {
#[cfg(target_arch = "x86_64")]
if r64::simd_available() && !data.is_empty() && data.len().is_multiple_of(64) {
let mut p = Poly1305::new(key);
unsafe { p.blocks_simd(data) };
return Some(p.finish());
}
let _ = (key, data);
None
}
}
#[cfg(test)]
mod tests;