Source code
Revision control
Copy as Markdown
Other Tools
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::fmt;
use super::{Mode, RecordProtectionOps, split_tag};
use crate::{Cipher, Error, Res, SymKey, Version, err::sec::SEC_ERROR_BAD_DATA};
pub const AEAD_NULL_TAG: &[u8] = &[0x0a; 16];
pub struct RecordProtection {}
impl RecordProtection {
fn decrypt_check(_count: u64, _aad: &[u8], input: &[u8]) -> Res<usize> {
let (len_encrypted, tag) = split_tag(input)?;
// Check that:
// 1) expansion is all zeros and
// 2) if the encrypted data is also supplied that at least some values are no zero
// (otherwise padding will be interpreted as a valid packet)
if tag.as_slice() == AEAD_NULL_TAG
&& (len_encrypted == 0 || input[..len_encrypted].iter().any(|x| *x != 0x0))
{
Ok(len_encrypted)
} else {
Err(Error::from(SEC_ERROR_BAD_DATA))
}
}
/// Create a new AEAD instance.
///
/// # Errors
///
/// Returns `Error` when the underlying crypto operations fail.
#[expect(
clippy::unnecessary_wraps,
reason = "uniform interface with other backends"
)]
pub const fn new(
_version: Version,
_cipher: Cipher,
_secret: &SymKey,
_prefix: &str,
_mode: Mode,
) -> Res<Self> {
Ok(Self {})
}
}
impl RecordProtectionOps for RecordProtection {
fn expansion(&self) -> usize {
AEAD_NULL_TAG.len()
}
fn encrypt<'a>(
&self,
_count: u64,
_aad: &[u8],
input: &[u8],
output: &'a mut [u8],
) -> Res<&'a [u8]> {
let l = input.len();
let total = l
.checked_add(self.expansion())
.ok_or(Error::IntegerOverflow)?;
if output.len() < total {
return Err(Error::from(SEC_ERROR_BAD_DATA));
}
output[..l].copy_from_slice(input);
output[l..total].copy_from_slice(AEAD_NULL_TAG);
Ok(&output[..total])
}
fn encrypt_in_place(&self, _count: u64, _aad: &[u8], data: &mut [u8]) -> Res<usize> {
if data.len() < self.expansion() {
return Err(Error::from(SEC_ERROR_BAD_DATA));
}
let pos = data.len() - self.expansion();
data[pos..].copy_from_slice(AEAD_NULL_TAG);
Ok(data.len())
}
fn decrypt<'a>(
&self,
count: u64,
aad: &[u8],
input: &[u8],
output: &'a mut [u8],
) -> Res<&'a [u8]> {
Self::decrypt_check(count, aad, input).map(|len| {
output[..len].copy_from_slice(&input[..len]);
&output[..len]
})
}
fn decrypt_in_place(&self, count: u64, aad: &[u8], data: &mut [u8]) -> Res<usize> {
Self::decrypt_check(count, aad, data)
}
}
impl fmt::Debug for RecordProtection {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[NULL AEAD]")
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::{AEAD_NULL_TAG, RecordProtection, RecordProtectionOps as _};
fn aead() -> RecordProtection {
RecordProtection {}
}
#[test]
fn expansion() {
assert_eq!(aead().expansion(), AEAD_NULL_TAG.len());
}
#[test]
fn debug() {
assert_eq!(format!("{:?}", aead()), "[NULL AEAD]");
}
#[test]
fn encrypt_decrypt_roundtrip() {
let a = aead();
let plaintext = b"hello world";
let mut out = vec![0u8; plaintext.len() + a.expansion()];
let encrypted = a.encrypt(0, b"aad", plaintext, &mut out).unwrap();
assert_eq!(encrypted.len(), plaintext.len() + a.expansion());
assert_eq!(&encrypted[..plaintext.len()], plaintext);
assert_eq!(&encrypted[plaintext.len()..], AEAD_NULL_TAG);
let mut dec_out = vec![0u8; plaintext.len()];
let decrypted = a.decrypt(0, b"aad", encrypted, &mut dec_out).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn encrypt_in_place_roundtrip() {
let a = aead();
let plaintext = b"hello";
let mut buf = plaintext.to_vec();
buf.resize(plaintext.len() + a.expansion(), 0);
let len = a.encrypt_in_place(0, b"", &mut buf).unwrap();
assert_eq!(len, buf.len());
assert_eq!(&buf[plaintext.len()..], AEAD_NULL_TAG);
let dec_len = a.decrypt_in_place(0, b"", &mut buf).unwrap();
assert_eq!(dec_len, plaintext.len());
assert_eq!(&buf[..dec_len], plaintext);
}
#[test]
fn decrypt_empty_plaintext() {
// Zero-length plaintext (just the tag) is valid.
let a = aead();
let mut out = vec![0u8; a.expansion()];
a.encrypt(0, b"", b"", &mut out).unwrap();
let mut dec = vec![];
let res = a.decrypt(0, b"", &out, &mut dec).unwrap();
assert_eq!(res, b"");
}
#[test]
fn decrypt_fails_too_short() {
let a = aead();
let short = &AEAD_NULL_TAG[..a.expansion() - 1];
assert!(a.decrypt(0, b"", short, &mut []).is_err());
}
#[test]
fn decrypt_fails_bad_tag() {
let a = aead();
let plaintext = b"test";
let mut buf = vec![0u8; plaintext.len() + a.expansion()];
a.encrypt(0, b"", plaintext, &mut buf).unwrap();
// Corrupt the tag.
let tag_start = plaintext.len();
buf[tag_start] ^= 0xff;
assert!(a.decrypt(0, b"", &buf, &mut []).is_err());
}
#[test]
fn decrypt_rejects_all_zero_data_bytes() {
// All-zero plaintext with correct tag should fail (looks like padding).
let a = aead();
let mut buf = vec![0u8; 4 + a.expansion()];
buf[4..].copy_from_slice(AEAD_NULL_TAG);
assert!(a.decrypt(0, b"", &buf, &mut []).is_err());
}
}