use super::types::Limits;
use crate::detect::Version;
use crate::{Error, ExtensionError, Limit, StreamError};
use std::io::{self, Read};
#[derive(Clone, PartialEq, Eq)]
pub struct Extension {
raw: Vec<u8>,
}
pub const MAX_EXTENSION_LEN: usize = 65535;
pub const DEFAULT_CONTAINER_LEN: usize = 128;
impl Extension {
pub fn new(identifier: &str, value: impl AsRef<[u8]>) -> Result<Self, Error> {
let value = value.as_ref();
if identifier.is_empty() {
return Err(Error::InvalidExtension(ExtensionError::EmptyIdentifier));
}
if identifier.contains('\0') {
return Err(Error::InvalidExtension(ExtensionError::NulInIdentifier));
}
let len = identifier.len().saturating_add(1).saturating_add(value.len());
if len > MAX_EXTENSION_LEN {
return Err(Error::InvalidExtension(ExtensionError::InvalidLength));
}
let mut raw = Vec::with_capacity(len);
raw.extend_from_slice(identifier.as_bytes());
raw.push(0);
raw.extend_from_slice(value);
Ok(Extension { raw })
}
pub fn container(len: usize) -> Result<Self, Error> {
if len == 0 || len > MAX_EXTENSION_LEN {
return Err(Error::InvalidExtension(ExtensionError::InvalidLength));
}
Ok(Extension { raw: vec![0u8; len] })
}
pub fn from_bytes(raw: impl Into<Vec<u8>>) -> Result<Self, Error> {
let raw = raw.into();
if raw.is_empty() || raw.len() > MAX_EXTENSION_LEN {
return Err(Error::InvalidExtension(ExtensionError::InvalidLength));
}
Ok(Extension { raw })
}
pub(crate) fn from_raw(raw: Vec<u8>) -> Self {
Extension { raw }
}
fn parts(&self) -> (&[u8], &[u8]) {
let mut parts = self.raw.splitn(2, |&b| b == 0);
let identifier = parts.next().unwrap_or_default();
let value = parts.next().unwrap_or_default();
(identifier, value)
}
pub fn identifier(&self) -> &[u8] {
self.parts().0
}
pub fn identifier_str(&self) -> Option<&str> {
core::str::from_utf8(self.identifier()).ok()
}
pub fn value(&self) -> &[u8] {
self.parts().1
}
pub fn is_container(&self) -> bool {
self.parts().0.is_empty()
}
pub fn as_bytes(&self) -> &[u8] {
&self.raw
}
}
impl core::fmt::Debug for Extension {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.is_container() {
return write!(f, "Extension::container({})", self.raw.len());
}
f.debug_struct("Extension")
.field("identifier", &String::from_utf8_lossy(self.identifier()))
.field("value", &String::from_utf8_lossy(self.value()))
.finish()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Header {
pub(crate) version: Version,
pub(crate) reserved: u8,
pub(crate) extensions: Vec<Extension>,
pub(crate) iterations: Option<u32>,
pub(crate) iv: [u8; 16],
pub(crate) len: usize,
}
impl Header {
pub fn version(&self) -> Version {
self.version
}
pub fn reserved(&self) -> u8 {
self.reserved
}
pub fn extensions(&self) -> &[Extension] {
&self.extensions
}
pub fn extension(&self, identifier: &str) -> Option<&[u8]> {
self.extensions
.iter()
.find(|e| e.identifier() == identifier.as_bytes())
.map(Extension::value)
}
pub fn iterations(&self) -> Option<u32> {
self.iterations
}
pub fn iv(&self) -> &[u8; 16] {
&self.iv
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
false
}
}
pub(crate) fn truncated(e: io::Error, what: StreamError) -> Error {
if e.kind() == io::ErrorKind::UnexpectedEof {
Error::InvalidStream(what)
} else {
Error::Io(e)
}
}
pub(crate) fn read_header<R: Read>(reader: &mut R, keep_extensions: bool, limits: &Limits) -> Result<Header, Error> {
let mut start = [0u8; 5];
reader.read_exact(&mut start).map_err(|e| truncated(e, StreamError::TruncatedHeader))?;
if &start[..3] != crate::detect::MAGIC {
return Err(Error::NotAesCrypt);
}
let version = Version::from_u8(start[3]).ok_or(Error::UnsupportedVersion(start[3]))?;
let mut len: usize = 5;
let tail_len: usize = if version >= Version::V3 { 20 } else { 16 };
let header_too_long = Error::LimitExceeded(Limit::HeaderLength { max: limits.max_header_len });
let mut extensions = Vec::new();
let mut count = 0usize;
if version >= Version::V2 {
loop {
let mut ext_len = [0u8; 2];
reader.read_exact(&mut ext_len).map_err(|e| truncated(e, StreamError::TruncatedExtensions))?;
len = len.saturating_add(2);
let ext_len = u16::from_be_bytes(ext_len) as usize;
if ext_len == 0 {
break;
}
count = count.saturating_add(1);
if count > limits.max_extensions {
return Err(Error::LimitExceeded(Limit::Extensions { max: limits.max_extensions }));
}
if len.saturating_add(ext_len).saturating_add(2).saturating_add(tail_len) > limits.max_header_len {
return Err(header_too_long);
}
let mut raw = vec![0u8; ext_len];
reader.read_exact(&mut raw).map_err(|e| truncated(e, StreamError::TruncatedExtensions))?;
len = len.saturating_add(ext_len);
if keep_extensions {
extensions.push(Extension::from_raw(raw));
}
}
}
if len.saturating_add(tail_len) > limits.max_header_len {
return Err(header_too_long);
}
let mut iterations = None;
if version >= Version::V3 {
let mut n = [0u8; 4];
reader.read_exact(&mut n).map_err(|e| truncated(e, StreamError::TruncatedHeader))?;
iterations = Some(u32::from_be_bytes(n));
len = len.saturating_add(4);
}
let mut iv = [0u8; 16];
reader.read_exact(&mut iv).map_err(|e| truncated(e, StreamError::TruncatedHeader))?;
len = len.saturating_add(16);
Ok(Header { version, reserved: start[4], extensions, iterations, iv, len })
}
pub(crate) fn write_header(
out: &mut Vec<u8>,
version: Version,
reserved: u8,
extensions: &[Extension],
iterations: u32,
iv: &[u8; 16],
) -> Result<(), Error> {
out.extend_from_slice(crate::detect::MAGIC);
out.push(version.as_u8());
out.push(reserved);
if version >= Version::V2 {
for ext in extensions {
let len = ext.raw.len();
if len == 0 || len > MAX_EXTENSION_LEN {
return Err(Error::InvalidExtension(ExtensionError::InvalidLength));
}
out.extend_from_slice(&(len as u16).to_be_bytes());
out.extend_from_slice(&ext.raw);
}
out.extend_from_slice(&[0, 0]);
} else if !extensions.is_empty() {
return Err(Error::InvalidExtension(ExtensionError::UnsupportedVersion));
}
if version >= Version::V3 {
out.extend_from_slice(&iterations.to_be_bytes());
}
out.extend_from_slice(iv);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extension_accessors() {
let ext = Extension::new("CREATED_BY", "aescry").unwrap();
assert_eq!(ext.identifier(), b"CREATED_BY");
assert_eq!(ext.identifier_str(), Some("CREATED_BY"));
assert_eq!(ext.value(), b"aescry");
assert_eq!(ext.as_bytes(), b"CREATED_BY\0aescry");
assert!(!ext.is_container());
let container = Extension::container(128).unwrap();
assert!(container.is_container());
assert_eq!(container.as_bytes().len(), 128);
let raw = Extension::from_raw(b"NO_TERMINATOR".to_vec());
assert_eq!(raw.identifier(), b"NO_TERMINATOR");
assert_eq!(raw.value(), b"");
}
#[test]
fn extension_validation() {
assert!(Extension::new("", "x").is_err());
assert!(Extension::new("A\0B", "x").is_err());
assert!(Extension::new("ID", vec![0u8; 65535 - 3]).is_ok());
assert!(Extension::new("ID", vec![0u8; 65535 - 2]).is_err());
assert!(Extension::container(0).is_err());
assert!(Extension::container(65536).is_err());
}
struct EndlessExtensions {
pos: usize,
}
impl Read for EndlessExtensions {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
const HEADER: &[u8] = b"AES\x03\x00";
const EXT: &[u8] = b"\x00\x04ABC\x00";
for b in buf.iter_mut() {
*b = if self.pos < HEADER.len() {
HEADER[self.pos]
} else {
EXT[(self.pos - HEADER.len()) % EXT.len()]
};
self.pos += 1;
}
Ok(buf.len())
}
}
#[test]
fn limits_bound_hostile_headers() {
let result = read_header(&mut EndlessExtensions { pos: 0 }, true, &Limits::DEFAULT);
assert!(matches!(result, Err(Error::LimitExceeded(Limit::Extensions { max: 256 }))));
let small = Limits::DEFAULT.max_header_len(64).max_extensions(1_000_000);
let result = read_header(&mut EndlessExtensions { pos: 0 }, false, &small);
assert!(matches!(result, Err(Error::LimitExceeded(Limit::HeaderLength { max: 64 }))));
let mut big = b"AES\x02\x00\xff\xff".to_vec();
big.extend(vec![b'A'; 65535]);
big.extend([0u8; 18]);
assert!(read_header(&mut &big[..], true, &Limits::DEFAULT).is_ok());
let tight = Limits::DEFAULT.max_header_len(1000);
assert!(matches!(read_header(&mut &big[..], true, &tight), Err(Error::LimitExceeded(_))));
}
#[test]
fn header_roundtrip() {
let exts = vec![Extension::new("CREATED_BY", "test").unwrap(), Extension::container(128).unwrap()];
for version in [Version::V0, Version::V1, Version::V2, Version::V3] {
let e: &[Extension] = if version >= Version::V2 { &exts } else { &[] };
let mut out = Vec::new();
write_header(&mut out, version, 7, e, 1234, &[9u8; 16]).unwrap();
let header = read_header(&mut &out[..], true, &Limits::DEFAULT).unwrap();
assert_eq!(header.version(), version);
assert_eq!(header.reserved(), 7);
assert_eq!(header.extensions(), e);
assert_eq!(header.iterations(), if version == Version::V3 { Some(1234) } else { None });
assert_eq!(header.iv(), &[9u8; 16]);
assert_eq!(header.len(), out.len());
}
}
#[test]
fn header_errors() {
let limits = Limits::DEFAULT;
assert!(matches!(read_header(&mut &b"XYZ\x03\x00"[..], true, &limits), Err(Error::NotAesCrypt)));
assert!(matches!(read_header(&mut &b"AES\x07\x00"[..], true, &limits), Err(Error::UnsupportedVersion(7))));
assert!(matches!(
read_header(&mut &b"AES\x02"[..], true, &limits),
Err(Error::InvalidStream(StreamError::TruncatedHeader))
));
assert!(matches!(
read_header(&mut &b"AES\x02\x00\x00\x05ab"[..], true, &limits),
Err(Error::InvalidStream(StreamError::TruncatedExtensions))
));
assert!(write_header(&mut Vec::new(), Version::V1, 0, &[Extension::container(4).unwrap()], 0, &[0; 16]).is_err());
}
}