use crate::aliases::HmacSha256;
use crate::aliases::{Aes256Key32, Iv16, Trailer32};
use crate::decryption::stream::context::DecryptionContext;
use crate::decryption::stream::trailer::{
extract_hmac_scattered, extract_hmac_simple, write_final_modulo, write_final_pkcs7,
};
use crate::error::AescryptError;
use aes::cipher::KeyInit;
use aes::Aes256Dec;
use hmac::Mac;
use secure_gate::{ConstantTimeEq, RevealSecret};
use std::io::{Read, Write};
fn verify_payload_hmac(hmac: HmacSha256, expected: &Trailer32) -> Result<(), AescryptError> {
let computed = hmac.finalize().into_bytes();
let computed_fixed =
Trailer32::try_from(computed.as_ref()).expect("computed hmac is 32 bytes");
if !computed_fixed.ct_eq(expected) {
return Err(AescryptError::Header("HMAC verification failed".into()));
}
Ok(())
}
#[derive(Clone, Copy)]
pub enum StreamConfig {
V0 {
reserved_modulo: u8,
},
V1,
V2,
V3,
}
#[inline(always)]
pub fn decrypt_ciphertext_stream<R, W>(
mut input_reader: R,
mut output_writer: W,
initial_vector: &Iv16,
encryption_key: &Aes256Key32,
config: StreamConfig,
) -> Result<(), AescryptError>
where
R: Read,
W: Write,
{
let cipher = encryption_key.with_secret(|key| Aes256Dec::new(key.into()));
let mut hmac = encryption_key.with_secret(|key| {
<HmacSha256 as Mac>::new_from_slice(key)
.expect("encryption_key is always 32 bytes — valid HMAC key")
});
let mut ctx = DecryptionContext::new_with_iv(initial_vector);
ctx.decrypt_cbc_loop(&mut input_reader, &mut output_writer, &cipher, &mut hmac)?;
ctx.advance_tail();
let remaining = ctx.remaining();
match config {
StreamConfig::V0 { reserved_modulo } => {
if remaining != 32 {
return Err(AescryptError::Header(
"v0: expected 32-byte HMAC trailer".into(),
));
}
let expected_hmac = extract_hmac_simple(&ctx);
verify_payload_hmac(hmac, &expected_hmac)?;
write_final_modulo(&ctx, &mut output_writer, reserved_modulo)?;
}
StreamConfig::V1 | StreamConfig::V2 => {
if remaining != 33 {
return Err(AescryptError::Header(
"v1/v2: expected 33-byte trailer".into(),
));
}
let (expected_hmac, modulo_byte) = extract_hmac_scattered(&ctx);
verify_payload_hmac(hmac, &expected_hmac)?;
write_final_modulo(&ctx, &mut output_writer, modulo_byte)?;
}
StreamConfig::V3 => {
if remaining != 32 {
return Err(AescryptError::Header(
"v3: expected 32-byte HMAC trailer".into(),
));
}
let expected_hmac = extract_hmac_simple(&ctx);
verify_payload_hmac(hmac, &expected_hmac)?;
write_final_pkcs7(&ctx, &mut output_writer)?;
}
}
Ok(())
}