#![forbid(unsafe_code)]
extern crate alloc;
pub mod aes_gcm_siv;
pub mod ccm;
pub mod deoxys;
pub(crate) mod deoxys_bc;
pub mod keywrap;
pub mod nonce_seq;
pub mod ocb3_impl;
pub mod sealed_box;
pub mod stream;
pub mod xchacha20;
pub use aes_gcm_siv::{AesGcmSiv128, AesGcmSiv256};
pub use ccm::{Aes128Ccm, Aes256Ccm};
pub use deoxys::Deoxys2_128;
pub use keywrap::{aes128_key_unwrap, aes128_key_wrap, aes256_key_unwrap, aes256_key_wrap};
pub use nonce_seq::{Nonce12, Nonce24, NonceSequence};
pub use ocb3_impl::{Aes128Ocb3, Aes256Ocb3};
pub use sealed_box::{open_box, seal_box};
pub use stream::{Aes256GcmStream, ChaCha20Poly1305Stream};
pub use xchacha20::XChaCha20Poly1305;
pub fn seal_with_random_nonce(
aead: &dyn oxicrypto_core::Aead,
key: &[u8],
aad: &[u8],
plaintext: &[u8],
rng: &mut dyn oxicrypto_core::Rng,
) -> Result<(alloc::vec::Vec<u8>, alloc::vec::Vec<u8>), oxicrypto_core::CryptoError> {
let nonce_len = aead.nonce_len();
let mut nonce = alloc::vec![0u8; nonce_len];
rng.fill(&mut nonce)?;
let ct = aead.seal_to_vec(key, &nonce, aad, plaintext)?;
Ok((nonce, ct))
}
use aead::{AeadInPlace, KeyInit, KeySizeUser};
use oxicrypto_core::{Aead, CryptoError};
struct AeadParams {
key_len: usize,
nonce_len: usize,
tag_len: usize,
}
fn seal_in_place<C: AeadInPlace + KeyInit>(
key: &[u8],
nonce: &[u8],
aad: &[u8],
pt: &[u8],
ct_out: &mut [u8],
params: AeadParams,
) -> Result<usize, CryptoError> {
if key.len() != params.key_len {
return Err(CryptoError::InvalidKey);
}
if nonce.len() != params.nonce_len {
return Err(CryptoError::InvalidNonce);
}
let required = pt
.len()
.checked_add(params.tag_len)
.ok_or(CryptoError::BadInput)?;
if ct_out.len() < required {
return Err(CryptoError::BufferTooSmall);
}
ct_out[..pt.len()].copy_from_slice(pt);
let cipher = C::new_from_slice(key).map_err(|_| CryptoError::InvalidKey)?;
let nonce_arr = aead::generic_array::GenericArray::from_slice(nonce);
let tag = cipher
.encrypt_in_place_detached(nonce_arr, aad, &mut ct_out[..pt.len()])
.map_err(|_| CryptoError::Internal("AEAD encrypt failed"))?;
ct_out[pt.len()..required].copy_from_slice(&tag);
Ok(required)
}
fn open_in_place<C: AeadInPlace + KeyInit>(
key: &[u8],
nonce: &[u8],
aad: &[u8],
ct: &[u8],
pt_out: &mut [u8],
params: AeadParams,
) -> Result<usize, CryptoError> {
if key.len() != params.key_len {
return Err(CryptoError::InvalidKey);
}
if nonce.len() != params.nonce_len {
return Err(CryptoError::InvalidNonce);
}
if ct.len() < params.tag_len {
return Err(CryptoError::BadInput);
}
let pt_len = ct.len() - params.tag_len;
if pt_out.len() < pt_len {
return Err(CryptoError::BufferTooSmall);
}
pt_out[..pt_len].copy_from_slice(&ct[..pt_len]);
let cipher = C::new_from_slice(key).map_err(|_| CryptoError::InvalidKey)?;
let nonce_arr = aead::generic_array::GenericArray::from_slice(nonce);
let tag_bytes = &ct[pt_len..];
if tag_bytes.len() != params.tag_len {
return Err(CryptoError::BadInput);
}
let tag = aead::Tag::<C>::clone_from_slice(tag_bytes);
cipher
.decrypt_in_place_detached(nonce_arr, aad, &mut pt_out[..pt_len], &tag)
.map_err(|_| CryptoError::InvalidTag)?;
Ok(pt_len)
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Aes128Gcm;
impl Aead for Aes128Gcm {
fn name(&self) -> &'static str {
"AES-128-GCM"
}
fn key_len(&self) -> usize {
aes_gcm::Aes128Gcm::key_size()
}
fn nonce_len(&self) -> usize {
12
}
fn tag_len(&self) -> usize {
16
}
fn seal(
&self,
key: &[u8],
nonce: &[u8],
aad: &[u8],
pt: &[u8],
ct_out: &mut [u8],
) -> Result<usize, CryptoError> {
seal_in_place::<aes_gcm::Aes128Gcm>(
key,
nonce,
aad,
pt,
ct_out,
AeadParams {
key_len: 16,
nonce_len: 12,
tag_len: 16,
},
)
}
fn open(
&self,
key: &[u8],
nonce: &[u8],
aad: &[u8],
ct: &[u8],
pt_out: &mut [u8],
) -> Result<usize, CryptoError> {
open_in_place::<aes_gcm::Aes128Gcm>(
key,
nonce,
aad,
ct,
pt_out,
AeadParams {
key_len: 16,
nonce_len: 12,
tag_len: 16,
},
)
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Aes256Gcm;
impl Aead for Aes256Gcm {
fn name(&self) -> &'static str {
"AES-256-GCM"
}
fn key_len(&self) -> usize {
aes_gcm::Aes256Gcm::key_size()
}
fn nonce_len(&self) -> usize {
12
}
fn tag_len(&self) -> usize {
16
}
fn seal(
&self,
key: &[u8],
nonce: &[u8],
aad: &[u8],
pt: &[u8],
ct_out: &mut [u8],
) -> Result<usize, CryptoError> {
seal_in_place::<aes_gcm::Aes256Gcm>(
key,
nonce,
aad,
pt,
ct_out,
AeadParams {
key_len: 32,
nonce_len: 12,
tag_len: 16,
},
)
}
fn open(
&self,
key: &[u8],
nonce: &[u8],
aad: &[u8],
ct: &[u8],
pt_out: &mut [u8],
) -> Result<usize, CryptoError> {
open_in_place::<aes_gcm::Aes256Gcm>(
key,
nonce,
aad,
ct,
pt_out,
AeadParams {
key_len: 32,
nonce_len: 12,
tag_len: 16,
},
)
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct ChaCha20Poly1305;
impl Aead for ChaCha20Poly1305 {
fn name(&self) -> &'static str {
"ChaCha20-Poly1305"
}
fn key_len(&self) -> usize {
chacha20poly1305::ChaCha20Poly1305::key_size()
}
fn nonce_len(&self) -> usize {
12
}
fn tag_len(&self) -> usize {
16
}
fn seal(
&self,
key: &[u8],
nonce: &[u8],
aad: &[u8],
pt: &[u8],
ct_out: &mut [u8],
) -> Result<usize, CryptoError> {
seal_in_place::<chacha20poly1305::ChaCha20Poly1305>(
key,
nonce,
aad,
pt,
ct_out,
AeadParams {
key_len: 32,
nonce_len: 12,
tag_len: 16,
},
)
}
fn open(
&self,
key: &[u8],
nonce: &[u8],
aad: &[u8],
ct: &[u8],
pt_out: &mut [u8],
) -> Result<usize, CryptoError> {
open_in_place::<chacha20poly1305::ChaCha20Poly1305>(
key,
nonce,
aad,
ct,
pt_out,
AeadParams {
key_len: 32,
nonce_len: 12,
tag_len: 16,
},
)
}
}
#[cfg(test)]
mod tests {
use super::*;
const KEY_128: [u8; 16] = [0x42u8; 16];
const KEY_256: [u8; 32] = [0x42u8; 32];
const NONCE_12: [u8; 12] = [0x24u8; 12];
const AAD: &[u8] = b"additional authenticated data";
const PLAINTEXT: &[u8] = b"hello, oxicrypto!";
fn round_trip<A: Aead>(aead: &A, key: &[u8]) {
let mut ct = vec![0u8; PLAINTEXT.len() + aead.tag_len()];
let written = aead
.seal(key, &NONCE_12, AAD, PLAINTEXT, &mut ct)
.expect("seal failed");
assert_eq!(written, PLAINTEXT.len() + aead.tag_len());
let mut pt = vec![0u8; PLAINTEXT.len()];
let recovered = aead
.open(key, &NONCE_12, AAD, &ct[..written], &mut pt)
.expect("open failed");
assert_eq!(recovered, PLAINTEXT.len());
assert_eq!(&pt[..recovered], PLAINTEXT);
}
fn wrong_key_fails<A: Aead>(aead: &A, good_key: &[u8], wrong_key: &[u8]) {
let mut ct = vec![0u8; PLAINTEXT.len() + aead.tag_len()];
let written = aead
.seal(good_key, &NONCE_12, AAD, PLAINTEXT, &mut ct)
.unwrap();
let mut pt = vec![0u8; PLAINTEXT.len()];
let result = aead.open(wrong_key, &NONCE_12, AAD, &ct[..written], &mut pt);
assert_eq!(result, Err(CryptoError::InvalidTag));
}
#[test]
fn aes128gcm_round_trip() {
round_trip(&Aes128Gcm, &KEY_128);
}
#[test]
fn aes256gcm_round_trip() {
round_trip(&Aes256Gcm, &KEY_256);
}
#[test]
fn chacha20poly1305_round_trip() {
round_trip(&ChaCha20Poly1305, &KEY_256);
}
#[test]
fn aes128gcm_wrong_key_fails() {
wrong_key_fails(&Aes128Gcm, &KEY_128, &[0x00u8; 16]);
}
#[test]
fn aes256gcm_wrong_key_fails() {
wrong_key_fails(&Aes256Gcm, &KEY_256, &[0x00u8; 32]);
}
#[test]
fn chacha20poly1305_wrong_key_fails() {
wrong_key_fails(&ChaCha20Poly1305, &KEY_256, &[0x00u8; 32]);
}
#[test]
fn invalid_key_length() {
let aead = Aes256Gcm;
let mut ct = vec![0u8; PLAINTEXT.len() + 16];
let result = aead.seal(&[0u8; 16], &NONCE_12, AAD, PLAINTEXT, &mut ct);
assert_eq!(result, Err(CryptoError::InvalidKey));
}
struct CounterRng {
counter: u8,
}
impl CounterRng {
fn new() -> Self {
Self { counter: 0x11 }
}
}
impl oxicrypto_core::Rng for CounterRng {
fn fill(&mut self, dst: &mut [u8]) -> Result<(), CryptoError> {
for b in dst.iter_mut() {
*b = self.counter;
self.counter = self.counter.wrapping_add(1);
}
Ok(())
}
}
#[test]
fn seal_with_random_nonce_aes128gcm_round_trip() {
let aead = Aes128Gcm;
let mut rng = CounterRng::new();
let (nonce, ct) = seal_with_random_nonce(&aead, &KEY_128, AAD, PLAINTEXT, &mut rng)
.expect("seal_with_random_nonce failed");
assert_eq!(
nonce.len(),
aead.nonce_len(),
"nonce length must match aead.nonce_len()"
);
assert_eq!(
ct.len(),
PLAINTEXT.len() + aead.tag_len(),
"ct length must be pt+tag"
);
let expected_nonce: alloc::vec::Vec<u8> =
(0u8..12).map(|i| 0x11_u8.wrapping_add(i)).collect();
assert_eq!(nonce, expected_nonce, "nonce must match RNG output");
let recovered = aead
.open_to_vec(&KEY_128, &nonce, AAD, &ct)
.expect("open_to_vec after seal_with_random_nonce failed");
assert_eq!(
recovered.as_slice(),
PLAINTEXT,
"round-trip must recover plaintext"
);
}
#[test]
fn seal_with_random_nonce_returns_separate_nonce_and_ct() {
let aead = Aes256Gcm;
let mut rng = CounterRng::new();
let (nonce, ct) = seal_with_random_nonce(&aead, &KEY_256, AAD, PLAINTEXT, &mut rng)
.expect("seal_with_random_nonce failed");
assert_eq!(ct.len(), PLAINTEXT.len() + aead.tag_len());
assert_eq!(nonce.len(), aead.nonce_len());
assert_ne!(
nonce.as_slice(),
&ct[..nonce.len()],
"nonce must not be embedded in ct"
);
}
#[test]
fn seal_with_random_nonce_rng_failure_propagates() {
struct AlwaysFailRng;
impl oxicrypto_core::Rng for AlwaysFailRng {
fn fill(&mut self, _dst: &mut [u8]) -> Result<(), CryptoError> {
Err(CryptoError::Rng)
}
}
let aead = Aes128Gcm;
let result = seal_with_random_nonce(&aead, &KEY_128, AAD, PLAINTEXT, &mut AlwaysFailRng);
assert_eq!(result, Err(CryptoError::Rng), "RNG failure must propagate");
}
}