pub use aead::*;
#[cfg(feature = "aes-gcm")]
pub use aes_gcm::{Aes128Gcm, Aes256Gcm, Key as Aes256GcmKey, Nonce as Aes256GcmNonce};
#[cfg(feature = "transport")]
pub use aes_kw;
use crate::asn1::ObjectIdentifier;
use crate::crypto::common::typenum::Unsigned;
use crate::crypto::secret::SecretSlice;
use crate::der::oid::AssociatedOid;
#[cfg(not(feature = "std"))]
use alloc::{boxed::Box, vec::Vec};
#[cfg(feature = "aes-gcm")]
mod oid_wrappers {
crate::define_oid_wrapper!(
Aes128GcmOid,
"2.16.840.1.101.3.4.1.6"
);
crate::define_oid_wrapper!(
Aes256GcmOid,
"2.16.840.1.101.3.4.1.46"
);
}
#[cfg(feature = "aes-gcm")]
pub use oid_wrappers::*;
trait AeadOps: Send + Sync {
fn encrypt_bytes(&self, nonce: &[u8], plaintext: &[u8]) -> core::result::Result<Vec<u8>, aead::Error>;
fn decrypt_bytes(&self, nonce: &[u8], ciphertext: &[u8]) -> core::result::Result<Vec<u8>, aead::Error>;
fn nonce_size(&self) -> usize;
}
impl<A> AeadOps for A
where
A: Aead + Send + Sync,
{
fn encrypt_bytes(&self, nonce: &[u8], plaintext: &[u8]) -> core::result::Result<Vec<u8>, aead::Error> {
self.encrypt(nonce.into(), plaintext)
}
fn decrypt_bytes(&self, nonce: &[u8], ciphertext: &[u8]) -> core::result::Result<Vec<u8>, aead::Error> {
self.decrypt(nonce.into(), ciphertext)
}
fn nonce_size(&self) -> usize {
<A as AeadCore>::NonceSize::USIZE
}
}
pub struct RuntimeAead {
cipher: Box<dyn AeadOps>,
oid: ObjectIdentifier,
}
impl RuntimeAead {
pub fn new<A>(cipher: A, oid: ObjectIdentifier) -> Self
where
A: Aead + Send + Sync + 'static,
{
Self { cipher: Box::new(cipher), oid }
}
pub fn algorithm_oid(&self) -> ObjectIdentifier {
self.oid
}
pub fn nonce_size(&self) -> usize {
self.cipher.nonce_size()
}
}
#[inline]
fn build_encrypted_content_info(
ciphertext: Vec<u8>,
nonce: &[u8],
content_type: Option<ObjectIdentifier>,
algorithm_oid: ObjectIdentifier,
) -> crate::error::Result<crate::EncryptedContentInfo> {
let content_type = content_type.unwrap_or(crate::oids::DATA);
let nonce_octet_string = crate::der::asn1::OctetString::new(nonce)?;
let parameters = Some(crate::der::Any::encode_from(&nonce_octet_string)?);
let content_enc_alg = crate::AlgorithmIdentifier { oid: algorithm_oid, parameters };
let encrypted_content = Some(crate::der::asn1::OctetString::new(ciphertext)?);
Ok(crate::EncryptedContentInfo { content_type, content_enc_alg, encrypted_content })
}
#[inline]
fn extract_nonce_and_ciphertext(
info: &crate::EncryptedContentInfo,
expected_nonce_len: usize,
) -> crate::error::Result<(&[u8], &[u8])> {
let ciphertext = info
.encrypted_content
.as_ref()
.ok_or(crate::TightBeamError::MissingEncryptionInfo)?
.as_bytes();
let nonce_any = info
.content_enc_alg
.parameters
.as_ref()
.ok_or(crate::TightBeamError::MissingEncryptionInfo)?;
let nonce_octet_string: crate::der::asn1::OctetStringRef<'_> = nonce_any.decode_as()?;
let nonce = nonce_octet_string.as_bytes();
if nonce.len() != expected_nonce_len {
return Err(crate::TightBeamError::InvalidNonceLength(
(nonce.len(), expected_nonce_len).into(),
));
}
Ok((nonce, ciphertext))
}
pub trait Encryptor<C>
where
C: AssociatedOid,
{
fn encrypt_content(
&self,
data: impl AsRef<[u8]>,
nonce: impl AsRef<[u8]>,
content_type: Option<ObjectIdentifier>,
) -> crate::error::Result<crate::EncryptedContentInfo>;
}
pub trait Decryptor {
fn decrypt_content(&self, info: &crate::EncryptedContentInfo) -> crate::error::Result<SecretSlice<u8>>;
}
impl<C, A> Encryptor<C> for A
where
C: AssociatedOid,
A: Aead,
{
fn encrypt_content(
&self,
data: impl AsRef<[u8]>,
nonce: impl AsRef<[u8]>,
content_type: Option<ObjectIdentifier>,
) -> crate::error::Result<crate::EncryptedContentInfo> {
let nonce_bytes = nonce.as_ref();
let ciphertext = self.encrypt(nonce_bytes.into(), data.as_ref())?;
build_encrypted_content_info(ciphertext, nonce_bytes, content_type, C::OID)
}
}
impl<A> Decryptor for A
where
A: Aead,
{
fn decrypt_content(&self, info: &crate::EncryptedContentInfo) -> crate::error::Result<SecretSlice<u8>> {
let (nonce_bytes, ciphertext) = extract_nonce_and_ciphertext(info, <A as AeadCore>::NonceSize::USIZE)?;
let plaintext = self.decrypt(nonce_bytes.into(), ciphertext)?;
Ok(SecretSlice::from(plaintext))
}
}
impl RuntimeAead {
pub fn encrypt_content(
&self,
data: impl AsRef<[u8]>,
nonce: impl AsRef<[u8]>,
content_type: Option<ObjectIdentifier>,
) -> crate::error::Result<crate::EncryptedContentInfo> {
let nonce_bytes = nonce.as_ref();
let ciphertext = self.cipher.encrypt_bytes(nonce_bytes, data.as_ref())?;
build_encrypted_content_info(ciphertext, nonce_bytes, content_type, self.oid)
}
}
impl Decryptor for RuntimeAead {
fn decrypt_content(&self, info: &crate::EncryptedContentInfo) -> crate::error::Result<SecretSlice<u8>> {
if info.content_enc_alg.oid != self.oid {
return Err(crate::TightBeamError::UnexpectedAlgorithm(
(info.content_enc_alg.oid, self.oid).into(),
));
}
let (nonce_bytes, ciphertext) = extract_nonce_and_ciphertext(info, self.cipher.nonce_size())?;
let plaintext = self.cipher.decrypt_bytes(nonce_bytes, ciphertext)?;
Ok(SecretSlice::from(plaintext))
}
}
#[cfg(all(test, feature = "aes-gcm"))]
mod tests {
use super::*;
use crate::error::ReceivedExpectedError;
use crate::TightBeamError;
const NONCE: [u8; 12] = [0x24; 12];
const PLAINTEXT: &[u8] = b"aead round trip";
fn test_cipher() -> Aes256Gcm {
Aes256Gcm::new(&[0x42u8; 32].into())
}
fn encrypted_info() -> crate::EncryptedContentInfo {
Encryptor::<Aes256GcmOid>::encrypt_content(&test_cipher(), PLAINTEXT, NONCE, None).unwrap()
}
fn with_nonce_len(mut info: crate::EncryptedContentInfo, len: usize) -> crate::EncryptedContentInfo {
let nonce = crate::der::asn1::OctetString::new(vec![0x24; len]).unwrap();
info.content_enc_alg.parameters = Some(crate::der::Any::encode_from(&nonce).unwrap());
info
}
#[test]
fn decrypt_content_round_trips() {
let plaintext = test_cipher().decrypt_content(&encrypted_info()).unwrap();
assert!(plaintext.with(|p| p == PLAINTEXT).unwrap());
}
#[test]
fn decrypt_content_rejects_short_nonce() {
let info = with_nonce_len(encrypted_info(), 8);
let result = test_cipher().decrypt_content(&info);
assert!(matches!(
result,
Err(TightBeamError::InvalidNonceLength(ReceivedExpectedError {
received: 8,
expected: 12
}))
));
}
#[test]
fn decrypt_content_rejects_long_nonce() {
let info = with_nonce_len(encrypted_info(), 16);
let result = test_cipher().decrypt_content(&info);
assert!(matches!(
result,
Err(TightBeamError::InvalidNonceLength(ReceivedExpectedError {
received: 16,
expected: 12
}))
));
}
#[test]
fn runtime_aead_round_trips() {
let runtime = RuntimeAead::new(test_cipher(), crate::oids::AES_256_GCM);
let info = runtime.encrypt_content(PLAINTEXT, NONCE, None).unwrap();
let plaintext = runtime.decrypt_content(&info).unwrap();
assert!(plaintext.with(|p| p == PLAINTEXT).unwrap());
}
#[test]
fn runtime_aead_rejects_algorithm_oid_mismatch() {
let runtime = RuntimeAead::new(test_cipher(), crate::oids::AES_256_GCM);
let mut info = runtime.encrypt_content(PLAINTEXT, NONCE, None).unwrap();
info.content_enc_alg.oid = crate::oids::AES_128_GCM;
let result = runtime.decrypt_content(&info);
assert!(matches!(result, Err(TightBeamError::UnexpectedAlgorithm(_))));
}
#[test]
fn runtime_aead_rejects_wire_nonce_length() {
let runtime = RuntimeAead::new(test_cipher(), crate::oids::AES_256_GCM);
let info = runtime.encrypt_content(PLAINTEXT, NONCE, None).unwrap();
let info = with_nonce_len(info, 8);
let result = runtime.decrypt_content(&info);
assert!(matches!(result, Err(TightBeamError::InvalidNonceLength(_))));
}
}