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