#![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg"
)]
#![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms)]
#![cfg_attr(all(feature = "getrandom", feature = "std"), doc = "```")]
#![cfg_attr(not(all(feature = "getrandom", feature = "std")), doc = "```ignore")]
#![cfg_attr(
all(feature = "getrandom", feature = "heapless", feature = "std"),
doc = "```"
)]
#![cfg_attr(
not(all(feature = "getrandom", feature = "heapless", feature = "std")),
doc = "```ignore"
)]
pub use aead::{self, consts, AeadCore, AeadInPlace, Error, KeyInit, KeySizeUser};
pub use cipher;
use aead::{
consts::{U0, U16, U24, U32, U8},
generic_array::GenericArray,
Buffer,
};
use cipher::{IvSizeUser, KeyIvInit, StreamCipher};
use core::marker::PhantomData;
use poly1305::Poly1305;
use zeroize::{Zeroize, Zeroizing};
#[cfg(feature = "chacha20")]
use chacha20::{hchacha, ChaCha20Legacy as ChaCha20};
#[cfg(feature = "salsa20")]
use salsa20::{hsalsa, Salsa20};
#[cfg(any(feature = "chacha20", feature = "salsa20"))]
use cipher::consts::U10;
pub type Key = GenericArray<u8, U32>;
pub type Nonce = GenericArray<u8, U24>;
pub type Tag = GenericArray<u8, U16>;
#[cfg(feature = "chacha20")]
pub type XChaCha20Poly1305 = SecretBox<ChaCha20>;
#[cfg(feature = "salsa20")]
pub type XSalsa20Poly1305 = SecretBox<Salsa20>;
pub struct SecretBox<C> {
key: Key,
cipher: PhantomData<C>,
}
impl<C> SecretBox<C> {
pub const KEY_SIZE: usize = 32;
pub const NONCE_SIZE: usize = 24;
pub const TAG_SIZE: usize = 16;
}
impl<C> SecretBox<C>
where
C: Kdf + KeyIvInit + KeySizeUser<KeySize = U32> + IvSizeUser<IvSize = U8> + StreamCipher,
{
fn init_cipher_and_mac(&self, nonce: &Nonce) -> (C, Poly1305) {
let (nonce_prefix, nonce_suffix) = nonce.split_at(16);
let subkey = Zeroizing::new(C::kdf(&self.key, nonce_prefix.as_ref().into()));
let mut cipher = C::new(&subkey, nonce_suffix.as_ref().into());
let mut mac_key = Zeroizing::new(poly1305::Key::default());
cipher.apply_keystream(&mut mac_key);
let mac = Poly1305::new(&mac_key);
(cipher, mac)
}
}
impl<C> Clone for SecretBox<C> {
fn clone(&self) -> Self {
Self {
key: self.key,
cipher: PhantomData,
}
}
}
impl<C> KeySizeUser for SecretBox<C> {
type KeySize = U32;
}
impl<C> KeyInit for SecretBox<C> {
fn new(key: &Key) -> Self {
SecretBox {
key: *key,
cipher: PhantomData,
}
}
}
impl<C> AeadCore for SecretBox<C> {
type NonceSize = U24;
type TagSize = U16;
type CiphertextOverhead = U0;
}
impl<C> AeadInPlace for SecretBox<C>
where
C: Kdf + KeyIvInit + KeySizeUser<KeySize = U32> + IvSizeUser<IvSize = U8> + StreamCipher,
{
fn encrypt_in_place(
&self,
nonce: &Nonce,
associated_data: &[u8],
buffer: &mut dyn Buffer,
) -> Result<(), Error> {
let pt_len = buffer.len();
buffer.extend_from_slice(Tag::default().as_slice())?;
buffer.as_mut().copy_within(..pt_len, Self::TAG_SIZE);
let tag = self.encrypt_in_place_detached(
nonce,
associated_data,
&mut buffer.as_mut()[Self::TAG_SIZE..],
)?;
buffer.as_mut()[..Self::TAG_SIZE].copy_from_slice(tag.as_slice());
Ok(())
}
fn encrypt_in_place_detached(
&self,
nonce: &Nonce,
associated_data: &[u8],
buffer: &mut [u8],
) -> Result<Tag, Error> {
if !associated_data.is_empty() {
return Err(Error);
}
let (mut cipher, mac) = self.init_cipher_and_mac(nonce);
cipher.apply_keystream(buffer);
Ok(mac.compute_unpadded(buffer))
}
fn decrypt_in_place(
&self,
nonce: &Nonce,
associated_data: &[u8],
buffer: &mut dyn Buffer,
) -> Result<(), Error> {
if buffer.len() < Self::TAG_SIZE {
return Err(Error);
}
let tag = Tag::clone_from_slice(&buffer.as_ref()[..Self::TAG_SIZE]);
self.decrypt_in_place_detached(
nonce,
associated_data,
&mut buffer.as_mut()[Self::TAG_SIZE..],
&tag,
)?;
let pt_len = buffer.len() - Self::TAG_SIZE;
buffer.as_mut().copy_within(Self::TAG_SIZE.., 0);
buffer.truncate(pt_len);
Ok(())
}
fn decrypt_in_place_detached(
&self,
nonce: &Nonce,
associated_data: &[u8],
buffer: &mut [u8],
tag: &Tag,
) -> Result<(), Error> {
if !associated_data.is_empty() {
return Err(Error);
}
let (mut cipher, mac) = self.init_cipher_and_mac(nonce);
let expected_tag = mac.compute_unpadded(buffer);
use subtle::ConstantTimeEq;
if expected_tag.ct_eq(tag).into() {
cipher.apply_keystream(buffer);
Ok(())
} else {
Err(Error)
}
}
}
impl<C> Drop for SecretBox<C> {
fn drop(&mut self) {
self.key.as_mut_slice().zeroize();
}
}
pub trait Kdf {
fn kdf(key: &Key, nonce: &GenericArray<u8, U16>) -> Key;
}
impl<C> Kdf for SecretBox<C>
where
C: Kdf,
{
fn kdf(key: &Key, nonce: &GenericArray<u8, U16>) -> Key {
C::kdf(key, nonce)
}
}
#[cfg(feature = "chacha20")]
impl Kdf for ChaCha20 {
fn kdf(key: &Key, nonce: &GenericArray<u8, U16>) -> Key {
hchacha::<U10>(key, nonce)
}
}
#[cfg(feature = "salsa20")]
impl Kdf for Salsa20 {
fn kdf(key: &Key, nonce: &GenericArray<u8, U16>) -> Key {
hsalsa::<U10>(key, nonce)
}
}