Skip to main content

rar_stream/parsing/rar5/
encryption_header.rs

1//! RAR5 archive encryption header parser.
2//!
3//! The archive encryption header (type 4) appears in archives with encrypted headers.
4//! All headers after this one are encrypted with AES-256-CBC.
5
6use super::VintReader;
7use crate::crc32::crc32 as compute_crc32;
8use crate::error::{RarError, Result};
9
10/// Archive encryption header (type 4).
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct Rar5EncryptionHeader {
13    /// Encryption version (currently 0 for AES-256)
14    pub version: u8,
15    /// Encryption flags
16    pub flags: u8,
17    /// Log2 of PBKDF2 iteration count
18    pub lg2_count: u8,
19    /// 16-byte salt for key derivation
20    pub salt: [u8; 16],
21    /// Password check value (if FLAG_CHECK_PRESENT)
22    pub check_value: Option<[u8; 12]>,
23}
24
25impl Rar5EncryptionHeader {
26    /// Flag indicating password check value is present
27    pub const FLAG_CHECK_PRESENT: u8 = 0x01;
28}
29
30pub struct Rar5EncryptionHeaderParser;
31
32impl Rar5EncryptionHeaderParser {
33    /// Parse an encryption header.
34    /// Returns the header and number of bytes consumed.
35    pub fn parse(data: &[u8]) -> Result<(Rar5EncryptionHeader, usize)> {
36        if data.len() < 4 {
37            return Err(RarError::InvalidHeader);
38        }
39
40        let mut pos = 0;
41
42        // CRC32 (4 bytes)
43        let crc32 = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
44        pos += 4;
45
46        // Header size (vint)
47        let mut reader = VintReader::new(&data[pos..]);
48        let header_size = reader.read().ok_or(RarError::InvalidHeader)?;
49        let header_content_start = pos + reader.position();
50        pos += reader.position();
51
52        // Header type (vint)
53        let mut reader = VintReader::new(&data[pos..]);
54        let header_type = reader.read().ok_or(RarError::InvalidHeader)?;
55        pos += reader.position();
56
57        if header_type != 4 {
58            return Err(RarError::InvalidHeaderType(header_type as u8));
59        }
60
61        // Header flags (vint)
62        let mut reader = VintReader::new(&data[pos..]);
63        let _header_flags = reader.read().ok_or(RarError::InvalidHeader)?;
64        pos += reader.position();
65
66        // Encryption version (vint)
67        let mut reader = VintReader::new(&data[pos..]);
68        let version = reader.read().ok_or(RarError::InvalidHeader)? as u8;
69        pos += reader.position();
70
71        // Encryption flags (vint)
72        let mut reader = VintReader::new(&data[pos..]);
73        let flags = reader.read().ok_or(RarError::InvalidHeader)? as u8;
74        pos += reader.position();
75
76        // KDF count (1 byte)
77        if pos >= data.len() {
78            return Err(RarError::InvalidHeader);
79        }
80        let lg2_count = data[pos];
81        pos += 1;
82
83        // Salt (16 bytes)
84        if pos + 16 > data.len() {
85            return Err(RarError::InvalidHeader);
86        }
87        let mut salt = [0u8; 16];
88        salt.copy_from_slice(&data[pos..pos + 16]);
89        pos += 16;
90
91        // Check value (12 bytes, optional)
92        let check_value = if flags & Rar5EncryptionHeader::FLAG_CHECK_PRESENT != 0 {
93            if pos + 12 > data.len() {
94                return Err(RarError::InvalidHeader);
95            }
96            let mut check = [0u8; 12];
97            check.copy_from_slice(&data[pos..pos + 12]);
98            // pos += 12; // Not needed - last use of pos
99            Some(check)
100        } else {
101            None
102        };
103
104        let total_consumed = header_content_start
105            .checked_add(header_size as usize)
106            .ok_or(RarError::InvalidHeader)?;
107
108        // Validate CRC32
109        if total_consumed > 4 && total_consumed <= data.len() {
110            let actual_crc = compute_crc32(&data[4..total_consumed]);
111            if actual_crc != crc32 {
112                return Err(RarError::CrcMismatch {
113                    expected: crc32,
114                    actual: actual_crc,
115                });
116            }
117        }
118
119        Ok((
120            Rar5EncryptionHeader {
121                version,
122                flags,
123                lg2_count,
124                salt,
125                check_value,
126            },
127            total_consumed,
128        ))
129    }
130
131    /// Check if data starts with an encryption header.
132    pub fn is_encryption_header(data: &[u8]) -> bool {
133        if data.len() < 7 {
134            return false;
135        }
136
137        // Skip CRC32 (4 bytes) and header size (1-3 bytes typically)
138        let mut pos = 4;
139        let mut reader = VintReader::new(&data[pos..]);
140        if reader.read().is_none() {
141            return false;
142        }
143        pos += reader.position();
144
145        // Read header type
146        let mut reader = VintReader::new(&data[pos..]);
147        matches!(reader.read(), Some(4))
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn test_is_encryption_header() {
157        // Minimal encryption header: CRC + size(1) + type(4) + flags
158        let mut data = vec![0u8; 20];
159        data[4] = 5; // size = 5
160        data[5] = 4; // type = 4 (encryption header)
161
162        assert!(Rar5EncryptionHeaderParser::is_encryption_header(&data));
163    }
164
165    #[test]
166    fn test_is_not_encryption_header() {
167        // File header (type 2)
168        let mut data = vec![0u8; 20];
169        data[4] = 5; // size = 5
170        data[5] = 2; // type = 2 (file header)
171
172        assert!(!Rar5EncryptionHeaderParser::is_encryption_header(&data));
173    }
174}