use super::format::{read_header, truncated, write_header, Extension, Header};
use super::types::{DerivedKey, Iterations, Limits, Password, PublicIv, SessionIv, SessionKey};
use crate::aes::Aes256;
use crate::cbc::{CbcDecryptor, CbcEncryptor};
use crate::detect::Version;
use crate::hmac::HmacSha256;
use crate::padding::pkcs7_unpad;
use crate::zeroize::Zeroizing;
use crate::{ct, Error, Limit, StreamError};
use std::io::{self, Read, Write};
const CHUNK_SIZE: usize = 64 * 1024;
#[derive(Clone, Copy)]
pub(crate) enum EncryptKey<'a> {
Password(&'a Password),
Derived(&'a DerivedKey),
}
#[derive(Clone, Copy)]
pub(crate) enum DecryptKey<'a> {
Password(&'a Password),
Derived(&'a DerivedKey),
Session(&'a SessionIv, &'a SessionKey),
}
pub(crate) struct EncryptParams<'a> {
pub(crate) version: Version,
pub(crate) key: EncryptKey<'a>,
pub(crate) iterations: Iterations,
pub(crate) extensions: &'a [Extension],
pub(crate) public_iv: PublicIv,
pub(crate) session_iv: SessionIv,
pub(crate) session_key: &'a SessionKey,
}
pub(crate) fn encrypt<R: Read, W: Write>(params: &EncryptParams<'_>, reader: R, mut writer: W) -> Result<u64, Error> {
if params.version == Version::V0 {
let mut out = Vec::new();
let (len, modulo) = encrypt_inner(params, reader, &mut out)?;
if let Some(reserved) = out.get_mut(4) {
*reserved = modulo;
}
writer.write_all(&out)?;
writer.flush()?;
return Ok(len);
}
let (len, _) = encrypt_inner(params, reader, &mut writer)?;
writer.flush()?;
Ok(len)
}
fn encrypt_inner<R: Read, W: Write>(params: &EncryptParams<'_>, mut reader: R, writer: &mut W) -> Result<(u64, u8), Error> {
let version = params.version;
let mut header = Vec::new();
write_header(&mut header, version, 0, params.extensions, params.iterations.get(), params.public_iv.as_bytes())?;
let derived = match params.key {
EncryptKey::Derived(key) => key.clone_secret(),
EncryptKey::Password(password) => DerivedKey::derive(version, password, ¶ms.public_iv, params.iterations)?,
};
let (bulk_key, bulk_iv) = if version == Version::V0 {
(Zeroizing::new(*derived.expose_secret()), *params.public_iv.as_bytes())
} else {
let mut block = Zeroizing::new([0u8; 48]);
let (iv_part, key_part) = block.split_at_mut(16);
iv_part.copy_from_slice(params.session_iv.as_bytes());
key_part.copy_from_slice(params.session_key.expose_secret());
CbcEncryptor::new(Aes256::new(derived.expose_secret()), params.public_iv.as_bytes())
.encrypt_in_place(&mut *block)?;
let mut mac = HmacSha256::new(derived.expose_secret());
mac.update(&*block);
if version >= Version::V3 {
mac.update(&[version.as_u8()]);
}
header.extend_from_slice(&*block);
header.extend_from_slice(&mac.finalize());
(Zeroizing::new(*params.session_key.expose_secret()), *params.session_iv.as_bytes())
};
writer.write_all(&header)?;
let mut encryptor = CbcEncryptor::new(Aes256::new(&bulk_key), &bulk_iv);
let mut mac = HmacSha256::new(&*bulk_key);
let mut buf = Zeroizing::new(vec![0u8; CHUNK_SIZE]);
let mut filled = 0;
let mut total = 0u64;
loop {
let n = read_some(&mut reader, buf.get_mut(filled..).unwrap_or_default())?;
if n == 0 {
break;
}
filled = filled.saturating_add(n);
total = total.saturating_add(n as u64);
if filled == buf.len() {
encryptor.encrypt_in_place(&mut buf)?;
mac.update(&buf);
writer.write_all(&buf)?;
filled = 0;
}
}
let rem = filled % 16;
let (pad, modulo) = if version >= Version::V3 {
(16usize.saturating_sub(rem), 0)
} else if rem > 0 {
(16usize.saturating_sub(rem), rem as u8)
} else {
(0, 0)
};
let tail = buf.get_mut(..filled.saturating_add(pad)).ok_or_else(internal_bounds)?;
if let Some(padding) = tail.get_mut(filled..) {
padding.fill(pad as u8);
}
encryptor.encrypt_in_place(tail)?;
mac.update(tail);
writer.write_all(tail)?;
if version == Version::V1 || version == Version::V2 {
writer.write_all(&[modulo])?;
}
writer.write_all(&mac.finalize())?;
Ok((total, modulo))
}
#[derive(Default)]
pub(crate) struct DecryptOptions {
pub(crate) limits: Limits,
pub(crate) verify: bool,
}
impl DecryptOptions {
pub(crate) fn verified(limits: Limits) -> Self {
DecryptOptions { limits, verify: true }
}
}
pub(crate) struct DecryptInfo {
pub(crate) header: Header,
pub(crate) plaintext_len: u64,
pub(crate) derived_key: Option<DerivedKey>,
pub(crate) session_iv: SessionIv,
pub(crate) session_key: SessionKey,
pub(crate) key_hmac_ok: Option<bool>,
pub(crate) message_hmac_ok: bool,
pub(crate) final_block_ok: Option<bool>,
}
pub(crate) fn decrypt<R: Read, W: Write>(
key: DecryptKey<'_>,
options: &DecryptOptions,
mut reader: R,
mut writer: W,
) -> Result<DecryptInfo, Error> {
let header = read_header(&mut reader, true, &options.limits)?;
let version = header.version;
let mut key_hmac_ok = None;
let (derived_key, bulk_key, bulk_iv) = match key {
DecryptKey::Session(iv, key) => {
if version != Version::V0 {
let mut block = [0u8; 80];
reader.read_exact(&mut block).map_err(|e| truncated(e, StreamError::TruncatedKeyBlock))?;
}
(None, key.clone_secret(), *iv)
}
DecryptKey::Derived(derived) => {
let (key, iv) = open_key_block(&header, derived, options.verify, &mut key_hmac_ok, &mut reader)?;
(Some(derived.clone_secret()), key, iv)
}
DecryptKey::Password(password) => {
let iterations = match header.iterations {
None => Iterations::DEFAULT, Some(0) => return Err(Error::InvalidStream(StreamError::ZeroIterations)),
Some(n) if n > options.limits.max_iterations => {
return Err(Error::LimitExceeded(Limit::Iterations {
found: n,
max: options.limits.max_iterations,
}))
}
Some(n) => Iterations::new_unbounded(n)?,
};
let derived = DerivedKey::derive(version, password, &PublicIv::from(header.iv), iterations)?;
let (key, iv) = open_key_block(&header, &derived, options.verify, &mut key_hmac_ok, &mut reader)?;
(Some(derived), key, iv)
}
};
let trailer_len = match version {
Version::V1 | Version::V2 => 33,
_ => 32,
};
let mut decryptor = CbcDecryptor::new(Aes256::new(bulk_key.expose_secret()), bulk_iv.as_bytes());
let mut mac = HmacSha256::new(bulk_key.expose_secret());
let mut buf = Zeroizing::new(Vec::with_capacity(CHUNK_SIZE.saturating_add(trailer_len).saturating_add(32)));
let mut chunk = Zeroizing::new(vec![0u8; CHUNK_SIZE]);
let mut written = 0u64;
let hold_back = trailer_len.saturating_add(16);
loop {
let n = read_some(&mut reader, &mut chunk)?;
if n == 0 {
break;
}
buf.extend_from_slice(chunk.get(..n).ok_or_else(internal_bounds)?);
let ready = buf.len().saturating_sub(hold_back) & !15; if ready > 0 {
let part = buf.get_mut(..ready).ok_or_else(internal_bounds)?;
mac.update(part);
decryptor.decrypt_in_place(part)?;
writer.write_all(part)?;
written = written.saturating_add(ready as u64);
buf.drain(..ready);
}
}
let ciphertext_len = buf.len().checked_sub(trailer_len).ok_or(Error::InvalidStream(StreamError::Truncated))?;
if ciphertext_len % 16 != 0 {
return Err(Error::InvalidStream(StreamError::UnalignedCiphertext));
}
let (last, trailer) = buf.split_at_mut(ciphertext_len);
mac.update(last);
let (modulo, tag) = match version {
Version::V1 | Version::V2 => match trailer.split_first() {
Some((modulo, tag)) => (*modulo, tag),
None => return Err(Error::InvalidStream(StreamError::Truncated)),
},
_ => (header.reserved, &trailer[..]),
};
let message_hmac_ok = ct::eq(&mac.finalize(), tag);
if !message_hmac_ok && options.verify {
return Err(if version == Version::V0 { Error::InvalidPassword } else { Error::AlteredMessage });
}
decryptor.decrypt_in_place(last)?;
let mut final_block_ok = None;
let plaintext: &[u8] = if version >= Version::V3 {
if written == 0 && last.is_empty() {
return Err(Error::InvalidStream(StreamError::MissingFinalBlock));
}
match pkcs7_unpad(last) {
Ok(plaintext) => plaintext,
Err(_) if !options.verify => last,
Err(_) => return Err(Error::InvalidStream(StreamError::InvalidPadding)),
}
} else {
let size = (modulo & 0x0f) as usize;
let (plaintext, padding_ok) = match (size, last.len()) {
(0, _) => (&last[..], true),
(_, 0) => (&last[..], false),
(n, _) => {
let (plaintext, padding) = last.split_at(n.min(last.len()));
let expected = 16usize.saturating_sub(n) as u8;
(plaintext, padding.iter().all(|&b| b == expected))
}
};
final_block_ok = Some(padding_ok && modulo & 0xf0 == 0);
plaintext
};
writer.write_all(plaintext)?;
writer.flush()?;
written = written.saturating_add(plaintext.len() as u64);
Ok(DecryptInfo {
header,
plaintext_len: written,
derived_key,
session_iv: bulk_iv,
session_key: bulk_key,
key_hmac_ok,
message_hmac_ok,
final_block_ok,
})
}
fn read_some<R: Read>(reader: &mut R, buf: &mut [u8]) -> Result<usize, Error> {
loop {
match reader.read(buf) {
Ok(n) if n <= buf.len() => return Ok(n),
Ok(_) => {
return Err(Error::Io(io::Error::new(
io::ErrorKind::InvalidData,
"reader returned more octets than the buffer holds",
)))
}
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e.into()),
}
}
}
fn internal_bounds() -> Error {
Error::Io(io::Error::new(io::ErrorKind::Other, "internal buffer bounds violated"))
}
fn open_key_block<R: Read>(
header: &Header,
derived: &DerivedKey,
verify: bool,
key_hmac_ok: &mut Option<bool>,
reader: &mut R,
) -> Result<(SessionKey, SessionIv), Error> {
let version = header.version;
if version == Version::V0 {
return Ok((SessionKey::from(*derived.expose_secret()), SessionIv::from(header.iv)));
}
let mut block = Zeroizing::new([0u8; 80]);
reader.read_exact(&mut *block).map_err(|e| truncated(e, StreamError::TruncatedKeyBlock))?;
let (encrypted, tag) = block.split_at_mut(48);
let mut mac = HmacSha256::new(derived.expose_secret());
mac.update(encrypted);
if version >= Version::V3 {
mac.update(&[version.as_u8()]);
}
let ok = ct::eq(&mac.finalize(), tag);
*key_hmac_ok = Some(ok);
if !ok && verify {
return Err(Error::InvalidPassword);
}
CbcDecryptor::new(Aes256::new(derived.expose_secret()), &header.iv).decrypt_in_place(encrypted)?;
let (iv, key) = encrypted.split_at(16);
Ok((SessionKey::try_from(key)?, SessionIv::try_from(iv)?))
}
#[cfg(test)]
mod tests {
use super::*;
const VERSIONS: [Version; 4] = [Version::V0, Version::V1, Version::V2, Version::V3];
fn session_key() -> SessionKey {
SessionKey::from([3; 32])
}
fn params<'a>(version: Version, key: EncryptKey<'a>, extensions: &'a [Extension], session_key: &'a SessionKey) -> EncryptParams<'a> {
EncryptParams {
version,
key,
iterations: Iterations::new(10).unwrap(),
extensions,
public_iv: PublicIv::from([1; 16]),
session_iv: SessionIv::from([2; 16]),
session_key,
}
}
fn write(p: &EncryptParams<'_>, data: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
assert_eq!(encrypt(p, data, &mut out).unwrap(), data.len() as u64);
out
}
fn read(key: DecryptKey<'_>, data: &[u8]) -> Result<(Vec<u8>, DecryptInfo), Error> {
let mut out = Vec::new();
let info = decrypt(key, &DecryptOptions::verified(Limits::DEFAULT), data, &mut out)?;
Ok((out, info))
}
fn text(p: &str) -> Password {
Password::new(p).unwrap()
}
#[test]
#[cfg_attr(miri, ignore = "too slow under Miri")]
fn every_version_roundtrips() {
let ext = [Extension::new("CREATED_BY", "test").unwrap()];
let sk = session_key();
let (right, wrong) = (text("pässword"), text("password"));
for version in VERSIONS {
let extensions: &[Extension] = if version >= Version::V2 { &ext } else { &[] };
for len in [0usize, 1, 15, 16, 17, 100] {
let data = vec![0xA5u8; len];
let stream = write(¶ms(version, EncryptKey::Password(&right), extensions, &sk), &data);
assert_eq!(crate::detect::from_bytes(&stream), Some(version));
if version == Version::V0 {
assert_eq!(stream[4] as usize, len % 16);
}
let (plain, info) = read(DecryptKey::Password(&right), &stream).unwrap();
assert_eq!(plain, data, "{} length {}", version, len);
assert_eq!(info.plaintext_len, len as u64);
assert!(info.message_hmac_ok);
assert_eq!(info.key_hmac_ok, if version == Version::V0 { None } else { Some(true) });
assert!(matches!(read(DecryptKey::Password(&wrong), &stream), Err(Error::InvalidPassword)));
}
}
}
#[test]
#[cfg_attr(miri, ignore = "too slow under Miri")]
fn deterministic_with_fixed_parameters() {
let (sk, pw) = (session_key(), text("pw"));
for version in VERSIONS {
let p = params(version, EncryptKey::Password(&pw), &[], &sk);
assert_eq!(write(&p, b"same"), write(&p, b"same"));
}
}
#[test]
#[cfg_attr(miri, ignore = "too slow under Miri")]
fn raw_passwords_match_text_encoding() {
let sk = session_key();
let pw = text("pässword");
for version in VERSIONS {
let raw = Password::from_raw_bytes(pw.encoded(version).expose_secret().clone());
let from_text = write(¶ms(version, EncryptKey::Password(&pw), &[], &sk), b"data");
let from_raw = write(¶ms(version, EncryptKey::Password(&raw), &[], &sk), b"data");
assert_eq!(from_text, from_raw, "{}", version);
let odd = Password::from_raw_bytes(vec![0xFF, 0x00, 0xD8]);
let stream = write(¶ms(version, EncryptKey::Password(&odd), &[], &sk), b"data");
assert_eq!(read(DecryptKey::Password(&odd), &stream).unwrap().0, b"data");
}
}
#[test]
#[cfg_attr(miri, ignore = "too slow under Miri")]
fn decrypts_with_recovered_keys() {
let (sk, pw) = (session_key(), text("pw"));
for version in VERSIONS {
let stream = write(¶ms(version, EncryptKey::Password(&pw), &[], &sk), b"secret data");
let (_, info) = read(DecryptKey::Password(&pw), &stream).unwrap();
let derived = info.derived_key.unwrap();
let (plain, _) = read(DecryptKey::Derived(&derived), &stream).unwrap();
assert_eq!(plain, b"secret data");
let (plain, session_info) = read(DecryptKey::Session(&info.session_iv, &info.session_key), &stream).unwrap();
assert_eq!(plain, b"secret data");
assert_eq!(session_info.key_hmac_ok, None);
assert!(session_info.header.extensions.is_empty());
if version != Version::V0 {
assert_eq!(info.session_iv, SessionIv::from([2; 16]));
assert_eq!(info.session_key, session_key());
}
}
}
#[test]
fn unverified_decryption_reports_failures() {
let (sk, pw) = (session_key(), text("pw"));
let mut stream = write(¶ms(Version::V3, EncryptKey::Password(&pw), &[], &sk), &[7u8; 40]);
let n = stream.len();
stream[n - 32 - 48] ^= 1;
assert!(matches!(read(DecryptKey::Password(&pw), &stream), Err(Error::AlteredMessage)));
let options = DecryptOptions { limits: Limits::DEFAULT, verify: false };
let mut out = Vec::new();
let info = decrypt(DecryptKey::Password(&pw), &options, &stream[..], &mut out).unwrap();
assert!(!info.message_hmac_ok);
assert_eq!(info.key_hmac_ok, Some(true));
assert_eq!(out.len(), 40);
assert_eq!(out[16], 7 ^ 1);
assert_eq!(&out[17..], &[7u8; 23][..]);
stream[n - 32 - 1] ^= 1;
let mut out = Vec::new();
decrypt(DecryptKey::Password(&pw), &options, &stream[..], &mut out).unwrap();
assert_eq!(out.len(), 48);
}
#[test]
fn derived_key_writes_the_same_stream() {
let (sk, pw) = (session_key(), Password::from_raw_bytes(b"pw".to_vec()));
let derived = DerivedKey::derive(Version::V3, &pw, &PublicIv::from([1; 16]), Iterations::new(10).unwrap()).unwrap();
let from_key = write(¶ms(Version::V3, EncryptKey::Derived(&derived), &[], &sk), b"x");
let from_pw = write(¶ms(Version::V3, EncryptKey::Password(&pw), &[], &sk), b"x");
assert_eq!(from_key, from_pw);
}
#[test]
fn iteration_limits() {
let (sk, pw) = (session_key(), text("pw"));
let mut p = params(Version::V3, EncryptKey::Password(&pw), &[], &sk);
p.iterations = Iterations::new(100).unwrap();
let stream = write(&p, b"x");
let strict = DecryptOptions::verified(Limits::DEFAULT.max_iterations(99));
let result = decrypt(DecryptKey::Password(&pw), &strict, &stream[..], Vec::new());
assert!(matches!(result, Err(Error::LimitExceeded(Limit::Iterations { found: 100, max: 99 }))));
let mut zero = stream.clone();
zero[7..11].copy_from_slice(&[0; 4]);
assert!(matches!(
read(DecryptKey::Password(&pw), &zero),
Err(Error::InvalidStream(StreamError::ZeroIterations))
));
}
}