use crate::{derive_debug_via_id, hkdf};
use aes_gcm::aead_seal_separate;
use std::fmt::Debug;
use crate::error::Unspecified;
use aws_lc::{EVP_AEAD_CTX_open, EVP_AEAD_CTX_seal};
use key_inner::KeyInner;
use std::mem::MaybeUninit;
use std::ops::RangeFrom;
mod aes_gcm;
mod chacha;
pub mod chacha20_poly1305_openssh;
mod key_inner;
mod nonce;
pub mod nonce_sequence;
mod poly1305;
pub mod quic;
pub use self::{
aes_gcm::{AES_128_GCM, AES_256_GCM},
chacha::CHACHA20_POLY1305,
nonce::{Nonce, NONCE_LEN},
};
pub trait NonceSequence {
fn advance(&mut self) -> Result<Nonce, Unspecified>;
}
pub trait BoundKey<N: NonceSequence>: 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> 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], 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], 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], 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], Unspecified> {
let in_prefix_len = ciphertext_and_tag.start;
let ciphertext_and_tag_len = in_out.len().checked_sub(in_prefix_len).ok_or(Unspecified)?;
let ciphertext_len = ciphertext_and_tag_len
.checked_sub(TAG_LEN)
.ok_or(Unspecified)?;
check_per_nonce_max_bytes(key.algorithm, ciphertext_len)?;
let key_inner_ref = key.get_inner_key();
aead_open_combined(key_inner_ref, nonce, aad, &mut in_out[in_prefix_len..])?;
in_out.copy_within(in_prefix_len..in_prefix_len + ciphertext_len, 0);
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> 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<(), 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]
#[allow(clippy::needless_pass_by_value)]
pub fn seal_in_place_append_tag<A, InOut>(
&mut self,
aad: Aad<A>,
in_out: &mut InOut,
) -> Result<(), Unspecified>
where
A: AsRef<[u8]>,
InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
{
seal_in_place_append_tag_(
&self.key,
self.nonce_sequence.advance()?,
Aad::from(aad.as_ref()),
in_out,
)
}
#[inline]
#[allow(clippy::needless_pass_by_value)]
pub fn seal_in_place_separate_tag<A>(
&mut self,
aad: Aad<A>,
in_out: &mut [u8],
) -> Result<Tag, 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_append_tag_<InOut>(
key: &UnboundKey,
nonce: Nonce,
aad: Aad<&[u8]>,
in_out: &mut InOut,
) -> Result<(), Unspecified>
where
InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
{
check_per_nonce_max_bytes(key.algorithm, in_out.as_mut().len())?;
let key_inner_ref = key.get_inner_key();
aead_seal_combined(key_inner_ref, nonce, aad, in_out)
}
#[inline]
fn seal_in_place_separate_tag_(
key: &UnboundKey,
nonce: Nonce,
aad: Aad<&[u8]>,
in_out: &mut [u8],
) -> Result<Tag, Unspecified> {
check_per_nonce_max_bytes(key.algorithm, in_out.len())?;
let key_inner_ref = key.get_inner_key();
aead_seal_separate(key_inner_ref, nonce, aad, in_out)
}
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]> {
#[must_use]
pub fn empty() -> Self {
Self::from([])
}
}
pub struct UnboundKey {
inner: KeyInner,
algorithm: &'static Algorithm,
}
impl Debug for UnboundKey {
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
f.debug_struct("UnboundKey")
.field("algorithm", &self.algorithm)
.finish()
}
}
impl UnboundKey {
pub fn new(algorithm: &'static Algorithm, key_bytes: &[u8]) -> Result<Self, Unspecified> {
Ok(Self {
inner: (algorithm.init)(key_bytes)?,
algorithm,
})
}
#[inline]
fn get_inner_key(&self) -> &KeyInner {
&self.inner
}
#[inline]
#[must_use]
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 {
#[must_use]
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], 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], 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]
#[allow(clippy::missing_errors_doc)]
pub fn seal_in_place<A, InOut>(
&self,
nonce: Nonce,
aad: Aad<A>,
in_out: &mut InOut,
) -> Result<(), 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]
#[allow(clippy::needless_pass_by_value)]
pub fn seal_in_place_append_tag<A, InOut>(
&self,
nonce: Nonce,
aad: Aad<A>,
in_out: &mut InOut,
) -> Result<(), Unspecified>
where
A: AsRef<[u8]>,
InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
{
seal_in_place_append_tag_(&self.key, nonce, Aad::from(aad.as_ref()), in_out)
}
#[inline]
#[allow(clippy::needless_pass_by_value)]
pub fn seal_in_place_separate_tag<A>(
&self,
nonce: Nonce,
aad: Aad<A>,
in_out: &mut [u8],
) -> Result<Tag, Unspecified>
where
A: AsRef<[u8]>,
{
seal_in_place_separate_tag_(&self.key, nonce, Aad::from(aad.as_ref()), in_out)
}
#[inline]
#[must_use]
pub fn algorithm(&self) -> &'static Algorithm {
self.key.algorithm
}
}
impl 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]) -> Result<KeyInner, Unspecified>,
key_len: usize,
id: AlgorithmID,
max_input_len: u64,
}
impl Algorithm {
#[inline]
#[must_use]
pub fn key_len(&self) -> usize {
self.key_len
}
#[inline]
#[must_use]
pub fn tag_len(&self) -> usize {
TAG_LEN
}
#[inline]
#[must_use]
pub fn nonce_len(&self) -> usize {
NONCE_LEN
}
}
derive_debug_via_id!(Algorithm);
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
#[allow(non_camel_case_types)]
enum AlgorithmID {
AES_128_GCM,
AES_256_GCM,
CHACHA20_POLY1305,
}
impl PartialEq for Algorithm {
#[inline]
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()
}
}
#[allow(dead_code)]
const MAX_KEY_LEN: usize = 32;
const TAG_LEN: usize = 16;
pub const MAX_TAG_LEN: usize = TAG_LEN;
#[inline]
#[must_use]
const fn u64_from_usize(x: usize) -> u64 {
x as u64
}
#[inline]
fn check_per_nonce_max_bytes(alg: &Algorithm, in_out_len: usize) -> Result<(), Unspecified> {
if u64_from_usize(in_out_len) > alg.max_input_len {
return Err(Unspecified);
}
Ok(())
}
#[inline]
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn aead_seal_combined<InOut>(
key: &KeyInner,
nonce: Nonce,
aad: Aad<&[u8]>,
in_out: &mut InOut,
) -> Result<(), Unspecified>
where
InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
{
unsafe {
let aead_ctx = match key {
KeyInner::AES_128_GCM(.., aead_ctx)
| KeyInner::AES_256_GCM(.., aead_ctx)
| KeyInner::CHACHA20_POLY1305(.., aead_ctx) => aead_ctx,
};
let nonce = nonce.as_ref();
let plaintext_len = in_out.as_mut().len();
in_out.extend([0u8; TAG_LEN].iter());
let mut out_len = MaybeUninit::<usize>::uninit();
let mut_in_out = in_out.as_mut();
let add_str = aad.0;
if 1 != EVP_AEAD_CTX_seal(
aead_ctx,
mut_in_out.as_mut_ptr(),
out_len.as_mut_ptr(),
plaintext_len + TAG_LEN,
nonce.as_ptr(),
NONCE_LEN,
mut_in_out.as_ptr(),
plaintext_len,
add_str.as_ptr(),
add_str.len(),
) {
return Err(Unspecified);
}
Ok(())
}
}
#[inline]
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn aead_open_combined(
key: &KeyInner,
nonce: Nonce,
aad: Aad<&[u8]>,
in_out: &mut [u8],
) -> Result<(), Unspecified> {
unsafe {
let aead_ctx = match key {
KeyInner::AES_128_GCM(.., aead_ctx)
| KeyInner::AES_256_GCM(.., aead_ctx)
| KeyInner::CHACHA20_POLY1305(.., aead_ctx) => aead_ctx,
};
let nonce = nonce.as_ref();
let plaintext_len = in_out.len() - TAG_LEN;
let aad_str = aad.0;
let mut out_len = MaybeUninit::<usize>::uninit();
if 1 != EVP_AEAD_CTX_open(
aead_ctx,
in_out.as_mut_ptr(),
out_len.as_mut_ptr(),
plaintext_len,
nonce.as_ptr(),
NONCE_LEN,
in_out.as_ptr(),
plaintext_len + TAG_LEN,
aad_str.as_ptr(),
aad_str.len(),
) {
return Err(Unspecified);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::from_hex;
#[test]
fn test_aes_128() {
let key = from_hex("000102030405060708090a0b0c0d0e0f").unwrap();
let og_nonce = from_hex("5bf11a0951f0bfc7ea5c9e58").unwrap();
let plaintext = from_hex("00112233445566778899aabbccddeeff").unwrap();
let unbound_key = UnboundKey::new(&AES_128_GCM, &key).unwrap();
assert_eq!(&AES_128_GCM, unbound_key.algorithm());
assert_eq!(16, AES_128_GCM.tag_len());
assert_eq!(12, AES_128_GCM.nonce_len());
let less_safe_key = LessSafeKey::new(unbound_key);
let nonce: [u8; NONCE_LEN] = og_nonce.as_slice().try_into().unwrap();
let mut in_out = Vec::from(plaintext.as_slice());
#[allow(deprecated)]
less_safe_key
.seal_in_place(Nonce(nonce), Aad::empty(), &mut in_out)
.unwrap();
let mut in_out_clone = in_out.clone();
let nonce: [u8; NONCE_LEN] = og_nonce.as_slice().try_into().unwrap();
assert!(less_safe_key
.open_in_place(Nonce(nonce), Aad::from("test"), &mut in_out_clone)
.is_err());
let mut in_out_clone = in_out.clone();
let mut nonce: [u8; NONCE_LEN] = og_nonce.as_slice().try_into().unwrap();
nonce[0] = 0;
assert!(less_safe_key
.open_in_place(Nonce(nonce), Aad::empty(), &mut in_out_clone)
.is_err());
let nonce: [u8; NONCE_LEN] = og_nonce.as_slice().try_into().unwrap();
less_safe_key
.open_in_place(Nonce(nonce), Aad::empty(), &mut in_out)
.unwrap();
assert_eq!(plaintext, in_out[..plaintext.len()]);
}
}