aescrypt_rs/decryptor/
session.rs

1//! src/decryptor/session.rs
2//! Session data extraction — zero secret exposure, secure-gate v0.5.5+ gold standard
3//!
4//! Now with maximum overkill: even public-but-sensitive buffers auto-zeroed on drop
5//! Because in crypto, we wipe everything that ever touched the stack.
6
7use crate::aliases::{
8    Aes256Key, EncryptedSessionBlock48, Iv16, PrevCiphertextBlock16, SessionHmacTag32,
9};
10use crate::decryptor::read_exact_span;
11use crate::{crypto::hmac::HmacSha256, error::AescryptError, utils::xor_blocks};
12use aes::cipher::{BlockDecrypt, KeyInit};
13use aes::{Aes256Dec, Block as AesBlock};
14use hmac::Mac;
15use std::io::Read;
16
17/// Extract session IV + key — secure from first byte
18///
19/// - No raw buffers ever hold secrets
20/// - Zero exposure window
21/// - Full auto-zeroizing (even ciphertext & HMAC tags)
22/// - Maximum performance
23#[inline(always)]
24pub fn extract_session_data<R>(
25    reader: &mut R,
26    file_version: u8,
27    public_iv: &Iv16,
28    setup_key: &Aes256Key,
29    session_iv_out: &mut Iv16,
30    session_key_out: &mut Aes256Key,
31) -> Result<(), AescryptError>
32where
33    R: Read,
34{
35    // v0: direct secure copy — no encryption, no HMAC
36    if file_version == 0 {
37        *session_iv_out = Iv16::from(*public_iv.expose_secret());
38        *session_key_out = Aes256Key::from(*setup_key.expose_secret());
39        return Ok(());
40    }
41
42    // Read encrypted session block and HMAC tag — both wrapped for auto-zeroing
43    let encrypted_block: EncryptedSessionBlock48 =
44        EncryptedSessionBlock48::new(read_exact_span(reader)?);
45    let expected_hmac: SessionHmacTag32 = SessionHmacTag32::new(read_exact_span(reader)?);
46
47    // HMAC verification — exact same pattern as encryption side
48    let mut mac = <HmacSha256 as Mac>::new_from_slice(setup_key.expose_secret())
49        .expect("setup_key is always 32 bytes — valid HMAC-SHA256 key");
50
51    mac.update(encrypted_block.expose_secret());
52    if file_version >= 3 {
53        mac.update(&[file_version]); // v3 spec: version byte included in session HMAC
54    }
55
56    if mac.finalize().into_bytes().as_slice() != expected_hmac.expose_secret() {
57        return Err(AescryptError::Header(
58            "session data corrupted or tampered (HMAC mismatch)".into(),
59        ));
60    }
61
62    // Decrypt directly into secure output buffers
63    let cipher = Aes256Dec::new(setup_key.expose_secret().into());
64    let mut previous_block: PrevCiphertextBlock16 =
65        PrevCiphertextBlock16::new(*public_iv.expose_secret());
66
67    for (i, chunk) in encrypted_block.expose_secret().chunks_exact(16).enumerate() {
68        let mut block = *AesBlock::from_slice(chunk);
69        cipher.decrypt_block(&mut block);
70        let decrypted = block.as_slice();
71
72        let target = match i {
73            0 => session_iv_out.expose_secret_mut(),
74            1 => &mut session_key_out.expose_secret_mut()[0..16],
75            2 => &mut session_key_out.expose_secret_mut()[16..32],
76            _ => break,
77        };
78
79        xor_blocks(decrypted, previous_block.expose_secret(), target);
80
81        // Fixed: Convert &[u8] to [u8;16] correctly
82        let mut prev_array = [0u8; 16];
83        prev_array.copy_from_slice(chunk);
84        previous_block = PrevCiphertextBlock16::new(prev_array);
85    }
86
87    Ok(())
88}