use self::block::{Block, BLOCK_LEN};
use crate::{constant_time, cpu, error, hkdf, polyfill};
use core::ops::RangeFrom;
pub use self::{
aes_gcm::{AES_128_GCM, AES_256_GCM},
chacha20_poly1305::CHACHA20_POLY1305,
nonce::{Nonce, NONCE_LEN},
};
pub trait NonceSequence {
fn advance(&mut self) -> Result<Nonce, error::Unspecified>;
}
pub trait BoundKey<N: NonceSequence>: core::fmt::Debug {
fn new(key: UnboundKey, nonce_sequence: N) -> Self;
fn algorithm(&self) -> &'static Algorithm;
}
pub struct OpeningKey<N: NonceSequence> {
key: UnboundKey,
nonce_sequence: N,
}
impl<N: NonceSequence> BoundKey<N> for OpeningKey<N> {
fn new(key: UnboundKey, nonce_sequence: N) -> Self {
Self {
key,
nonce_sequence,
}
}
#[inline]
fn algorithm(&self) -> &'static Algorithm {
self.key.algorithm
}
}
impl<N: NonceSequence> core::fmt::Debug for OpeningKey<N> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
f.debug_struct("OpeningKey")
.field("algorithm", &self.algorithm())
.finish()
}
}
impl<N: NonceSequence> OpeningKey<N> {
#[inline]
pub fn open_in_place<'in_out, A>(
&mut self,
aad: Aad<A>,
in_out: &'in_out mut [u8],
) -> Result<&'in_out mut [u8], error::Unspecified>
where
A: AsRef<[u8]>,
{
self.open_within(aad, in_out, 0..)
}
#[inline]
pub fn open_within<'in_out, A>(
&mut self,
aad: Aad<A>,
in_out: &'in_out mut [u8],
ciphertext_and_tag: RangeFrom<usize>,
) -> Result<&'in_out mut [u8], error::Unspecified>
where
A: AsRef<[u8]>,
{
open_within_(
&self.key,
self.nonce_sequence.advance()?,
aad,
in_out,
ciphertext_and_tag,
)
}
}
#[inline]
fn open_within_<'in_out, A: AsRef<[u8]>>(
key: &UnboundKey,
nonce: Nonce,
Aad(aad): Aad<A>,
in_out: &'in_out mut [u8],
ciphertext_and_tag: RangeFrom<usize>,
) -> Result<&'in_out mut [u8], error::Unspecified> {
fn open_within<'in_out>(
key: &UnboundKey,
nonce: Nonce,
aad: Aad<&[u8]>,
in_out: &'in_out mut [u8],
ciphertext_and_tag: RangeFrom<usize>,
) -> Result<&'in_out mut [u8], error::Unspecified> {
let in_prefix_len = ciphertext_and_tag.start;
let ciphertext_and_tag_len = in_out
.len()
.checked_sub(in_prefix_len)
.ok_or(error::Unspecified)?;
let ciphertext_len = ciphertext_and_tag_len
.checked_sub(TAG_LEN)
.ok_or(error::Unspecified)?;
check_per_nonce_max_bytes(key.algorithm, ciphertext_len)?;
let (in_out, received_tag) = in_out.split_at_mut(in_prefix_len + ciphertext_len);
let Tag(calculated_tag) = (key.algorithm.open)(
&key.inner,
nonce,
aad,
in_prefix_len,
in_out,
key.cpu_features,
);
if constant_time::verify_slices_are_equal(calculated_tag.as_ref(), received_tag).is_err() {
for b in &mut in_out[..ciphertext_len] {
*b = 0;
}
return Err(error::Unspecified);
}
Ok(&mut in_out[..ciphertext_len])
}
open_within(
key,
nonce,
Aad::from(aad.as_ref()),
in_out,
ciphertext_and_tag,
)
}
pub struct SealingKey<N: NonceSequence> {
key: UnboundKey,
nonce_sequence: N,
}
impl<N: NonceSequence> BoundKey<N> for SealingKey<N> {
fn new(key: UnboundKey, nonce_sequence: N) -> Self {
Self {
key,
nonce_sequence,
}
}
#[inline]
fn algorithm(&self) -> &'static Algorithm {
self.key.algorithm
}
}
impl<N: NonceSequence> core::fmt::Debug for SealingKey<N> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
f.debug_struct("SealingKey")
.field("algorithm", &self.algorithm())
.finish()
}
}
impl<N: NonceSequence> SealingKey<N> {
#[deprecated(note = "Renamed to `seal_in_place_append_tag`.")]
#[inline]
pub fn seal_in_place<A, InOut>(
&mut self,
aad: Aad<A>,
in_out: &mut InOut,
) -> Result<(), error::Unspecified>
where
A: AsRef<[u8]>,
InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
{
self.seal_in_place_append_tag(aad, in_out)
}
#[inline]
pub fn seal_in_place_append_tag<A, InOut>(
&mut self,
aad: Aad<A>,
in_out: &mut InOut,
) -> Result<(), error::Unspecified>
where
A: AsRef<[u8]>,
InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
{
self.seal_in_place_separate_tag(aad, in_out.as_mut())
.map(|tag| in_out.extend(tag.as_ref()))
}
#[inline]
pub fn seal_in_place_separate_tag<A>(
&mut self,
aad: Aad<A>,
in_out: &mut [u8],
) -> Result<Tag, error::Unspecified>
where
A: AsRef<[u8]>,
{
seal_in_place_separate_tag_(
&self.key,
self.nonce_sequence.advance()?,
Aad::from(aad.as_ref()),
in_out,
)
}
}
#[inline]
fn seal_in_place_separate_tag_(
key: &UnboundKey,
nonce: Nonce,
aad: Aad<&[u8]>,
in_out: &mut [u8],
) -> Result<Tag, error::Unspecified> {
check_per_nonce_max_bytes(key.algorithm, in_out.len())?;
Ok((key.algorithm.seal)(
&key.inner,
nonce,
aad,
in_out,
key.cpu_features,
))
}
pub struct Aad<A: AsRef<[u8]>>(A);
impl<A: AsRef<[u8]>> Aad<A> {
#[inline]
pub fn from(aad: A) -> Self {
Aad(aad)
}
}
impl<A> AsRef<[u8]> for Aad<A>
where
A: AsRef<[u8]>,
{
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl Aad<[u8; 0]> {
pub fn empty() -> Self {
Self::from([])
}
}
pub struct UnboundKey {
inner: KeyInner,
algorithm: &'static Algorithm,
cpu_features: cpu::Features,
}
impl core::fmt::Debug for UnboundKey {
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
f.debug_struct("UnboundKey")
.field("algorithm", &self.algorithm)
.finish()
}
}
#[allow(clippy::large_enum_variant, variant_size_differences)]
enum KeyInner {
AesGcm(aes_gcm::Key),
ChaCha20Poly1305(chacha20_poly1305::Key),
}
impl UnboundKey {
pub fn new(
algorithm: &'static Algorithm,
key_bytes: &[u8],
) -> Result<Self, error::Unspecified> {
let cpu_features = cpu::features();
Ok(Self {
inner: (algorithm.init)(key_bytes, cpu_features)?,
algorithm,
cpu_features,
})
}
#[inline]
pub fn algorithm(&self) -> &'static Algorithm {
self.algorithm
}
}
impl From<hkdf::Okm<'_, &'static Algorithm>> for UnboundKey {
fn from(okm: hkdf::Okm<&'static Algorithm>) -> Self {
let mut key_bytes = [0; MAX_KEY_LEN];
let key_bytes = &mut key_bytes[..okm.len().key_len];
let algorithm = *okm.len();
okm.fill(key_bytes).unwrap();
Self::new(algorithm, key_bytes).unwrap()
}
}
impl hkdf::KeyType for &'static Algorithm {
#[inline]
fn len(&self) -> usize {
self.key_len()
}
}
pub struct LessSafeKey {
key: UnboundKey,
}
impl LessSafeKey {
pub fn new(key: UnboundKey) -> Self {
Self { key }
}
#[inline]
pub fn open_in_place<'in_out, A>(
&self,
nonce: Nonce,
aad: Aad<A>,
in_out: &'in_out mut [u8],
) -> Result<&'in_out mut [u8], error::Unspecified>
where
A: AsRef<[u8]>,
{
self.open_within(nonce, aad, in_out, 0..)
}
#[inline]
pub fn open_within<'in_out, A>(
&self,
nonce: Nonce,
aad: Aad<A>,
in_out: &'in_out mut [u8],
ciphertext_and_tag: RangeFrom<usize>,
) -> Result<&'in_out mut [u8], error::Unspecified>
where
A: AsRef<[u8]>,
{
open_within_(&self.key, nonce, aad, in_out, ciphertext_and_tag)
}
#[deprecated(note = "Renamed to `seal_in_place_append_tag`.")]
#[inline]
pub fn seal_in_place<A, InOut>(
&self,
nonce: Nonce,
aad: Aad<A>,
in_out: &mut InOut,
) -> Result<(), error::Unspecified>
where
A: AsRef<[u8]>,
InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
{
self.seal_in_place_append_tag(nonce, aad, in_out)
}
#[inline]
pub fn seal_in_place_append_tag<A, InOut>(
&self,
nonce: Nonce,
aad: Aad<A>,
in_out: &mut InOut,
) -> Result<(), error::Unspecified>
where
A: AsRef<[u8]>,
InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
{
self.seal_in_place_separate_tag(nonce, aad, in_out.as_mut())
.map(|tag| in_out.extend(tag.as_ref()))
}
#[inline]
pub fn seal_in_place_separate_tag<A>(
&self,
nonce: Nonce,
aad: Aad<A>,
in_out: &mut [u8],
) -> Result<Tag, error::Unspecified>
where
A: AsRef<[u8]>,
{
seal_in_place_separate_tag_(&self.key, nonce, Aad::from(aad.as_ref()), in_out)
}
#[inline]
pub fn algorithm(&self) -> &'static Algorithm {
&self.key.algorithm
}
}
impl core::fmt::Debug for LessSafeKey {
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
f.debug_struct("LessSafeKey")
.field("algorithm", self.algorithm())
.finish()
}
}
pub struct Algorithm {
init: fn(key: &[u8], cpu_features: cpu::Features) -> Result<KeyInner, error::Unspecified>,
seal: fn(
key: &KeyInner,
nonce: Nonce,
aad: Aad<&[u8]>,
in_out: &mut [u8],
cpu_features: cpu::Features,
) -> Tag,
open: fn(
key: &KeyInner,
nonce: Nonce,
aad: Aad<&[u8]>,
in_prefix_len: usize,
in_out: &mut [u8],
cpu_features: cpu::Features,
) -> Tag,
key_len: usize,
id: AlgorithmID,
max_input_len: u64,
}
const fn max_input_len(block_len: usize, overhead_blocks_per_nonce: usize) -> u64 {
((1u64 << 32) - polyfill::u64_from_usize(overhead_blocks_per_nonce))
* polyfill::u64_from_usize(block_len)
}
impl Algorithm {
#[inline(always)]
pub fn key_len(&self) -> usize {
self.key_len
}
#[inline(always)]
pub fn tag_len(&self) -> usize {
TAG_LEN
}
#[inline(always)]
pub fn nonce_len(&self) -> usize {
NONCE_LEN
}
}
derive_debug_via_id!(Algorithm);
#[derive(Debug, Eq, PartialEq)]
enum AlgorithmID {
AES_128_GCM,
AES_256_GCM,
CHACHA20_POLY1305,
}
impl PartialEq for Algorithm {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for Algorithm {}
#[must_use]
#[repr(C)]
pub struct Tag([u8; TAG_LEN]);
impl AsRef<[u8]> for Tag {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
const MAX_KEY_LEN: usize = 32;
const TAG_LEN: usize = BLOCK_LEN;
pub const MAX_TAG_LEN: usize = TAG_LEN;
fn check_per_nonce_max_bytes(alg: &Algorithm, in_out_len: usize) -> Result<(), error::Unspecified> {
if polyfill::u64_from_usize(in_out_len) > alg.max_input_len {
return Err(error::Unspecified);
}
Ok(())
}
#[derive(Clone, Copy)]
enum Direction {
Opening { in_prefix_len: usize },
Sealing,
}
mod aes;
mod aes_gcm;
mod block;
mod chacha;
mod chacha20_poly1305;
pub mod chacha20_poly1305_openssh;
mod counter;
mod gcm;
mod iv;
mod nonce;
mod poly1305;
pub mod quic;
mod shift;