aboutsummaryrefslogtreecommitdiff
path: root/src/zip
diff options
context:
space:
mode:
authorIgor Tolmachev <me@igorek.dev>2024-07-20 16:52:39 +0900
committerIgor Tolmachev <me@igorek.dev>2024-07-20 16:52:39 +0900
commit7bcdc3b4ca460aec2b98fb2dca6165788c562b05 (patch)
tree63f9616fc1b7f9ca6e414a4d32910720e155690c /src/zip
parent5f4ceda88c7299deb317f8d22a99ab2521c5a380 (diff)
downloadarchivator-7bcdc3b4ca460aec2b98fb2dca6165788c562b05.tar.gz
archivator-7bcdc3b4ca460aec2b98fb2dca6165788c562b05.zip
Partial aes implementation and others improvements
Diffstat (limited to 'src/zip')
-rw-r--r--src/zip/datetime.rs45
-rw-r--r--src/zip/driver.rs179
-rw-r--r--src/zip/encryption/aes.rs46
-rw-r--r--src/zip/encryption/mod.rs5
-rw-r--r--src/zip/encryption/weak.rs (renamed from src/zip/encryption.rs)62
-rw-r--r--src/zip/error.rs38
-rw-r--r--src/zip/file/info.rs43
-rw-r--r--src/zip/file/read.rs99
-rw-r--r--src/zip/mod.rs1
-rw-r--r--src/zip/structs.rs14
-rw-r--r--src/zip/tests.rs19
11 files changed, 391 insertions, 160 deletions
diff --git a/src/zip/datetime.rs b/src/zip/datetime.rs
new file mode 100644
index 0000000..a4b1b55
--- /dev/null
+++ b/src/zip/datetime.rs
@@ -0,0 +1,45 @@
1use crate::zip::{ZipError, ZipResult};
2use chrono::{DateTime, Datelike, NaiveDate, TimeZone, Timelike};
3
4pub trait DosDateTime<Tz>: Sized {
5 fn from_dos_date_time(date: u16, time: u16, tz: Tz) -> ZipResult<Self>;
6
7 #[allow(dead_code)]
8 fn to_dos_date(&self) -> u16;
9
10 fn to_dos_time(&self) -> u16;
11}
12
13impl<Tz: TimeZone> DosDateTime<Tz> for DateTime<Tz> {
14 #[inline]
15 fn from_dos_date_time(date: u16, time: u16, tz: Tz) -> ZipResult<Self> {
16 Ok(NaiveDate::from_ymd_opt(
17 (date as i32 >> 9 & 0x7F) + 1980,
18 date as u32 >> 5 & 0xF,
19 date as u32 & 0x1F,
20 )
21 .ok_or(ZipError::InvalidDate)?
22 .and_hms_opt(
23 (time as u32 >> 11) & 0x1F,
24 (time as u32 >> 5) & 0x3F,
25 (time as u32 & 0x1F) * 2,
26 )
27 .ok_or(ZipError::InvalidTime)?
28 .and_local_timezone(tz)
29 .unwrap())
30 }
31
32 #[inline]
33 fn to_dos_date(&self) -> u16 {
34 (self.year() as u16 - 1980 & 0x7F) << 9
35 | (self.month() as u16 & 0xF) << 5
36 | self.day() as u16 & 0x1F
37 }
38
39 #[inline]
40 fn to_dos_time(&self) -> u16 {
41 (self.hour() as u16 & 0x1F) << 11
42 | (self.minute() as u16 & 0x3F) << 5
43 | self.second() as u16 / 2 & 0x1F
44 }
45}
diff --git a/src/zip/driver.rs b/src/zip/driver.rs
index e7878cf..8dc902f 100644
--- a/src/zip/driver.rs
+++ b/src/zip/driver.rs
@@ -1,48 +1,51 @@
1use crate::driver::{ArchiveRead, ArchiveWrite, Driver}; 1use crate::driver::{ArchiveRead, ArchiveWrite, Driver};
2use crate::utils::ReadUtils; 2use crate::utils::ReadUtils;
3use crate::zip::cp437::FromCp437; 3use crate::zip::cp437::FromCp437;
4use crate::zip::structs::{deserialize, Cdr, Eocdr, Eocdr64, Eocdr64Locator, ExtraHeader}; 4use crate::zip::datetime::DosDateTime;
5use crate::zip::structs::{
6 deserialize, AesField, Cdr, Eocdr, Eocdr64, Eocdr64Locator, ExtraHeader, CDR_SIGNATURE,
7 EOCDR64_LOCATOR_SIGNATURE, EOCDR64_SIGNATURE, EOCDR_SIGNATURE,
8};
5use crate::zip::{ 9use crate::zip::{
6 BitFlag, CompressionMethod, EncryptionMethod, ZipError, ZipFileInfo, ZipFileReader, 10 BitFlag, CompressionMethod, EncryptionMethod, ZipError, ZipFileInfo, ZipFileReader,
7 ZipFileWriter, ZipResult, 11 ZipFileWriter, ZipResult,
8}; 12};
9use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime}; 13use chrono::{DateTime, Local};
10use std::collections::HashMap as Map; 14use std::collections::HashMap as Map;
11use std::fs::File; 15use std::fs::File;
12use std::io::{BufReader, Read, Seek, SeekFrom, Write}; 16use std::io::{BufReader, Read, Seek, SeekFrom, Write};
13 17
14fn dos_to_local(date: u16, time: u16) -> ZipResult<DateTime<Local>> { 18#[inline]
15 Ok(NaiveDateTime::new( 19fn split_fields(bytes: &[u8]) -> Option<Vec<(u16, &[u8])>> {
16 NaiveDate::from_ymd_opt( 20 let mut fields = Vec::new();
17 (date as i32 >> 9 & 0x7F) + 1980, 21
18 date as u32 >> 5 & 0xF, 22 let mut p = 0;
19 date as u32 & 0x1F, 23 while p < bytes.len() {
20 ) 24 let header: ExtraHeader = deserialize(bytes.get(p..p + 4)?).unwrap();
21 .ok_or(ZipError::InvalidDate)?, 25 p += 4;
22 NaiveTime::from_hms_opt( 26 let data = bytes.get(p..p + header.size as usize)?;
23 (time as u32 >> 11) & 0x1F, 27 p += header.size as usize;
24 (time as u32 >> 5) & 0x3F, 28
25 (time as u32 & 0x1F) * 2, 29 fields.push((header.id, data));
26 ) 30 }
27 .ok_or(ZipError::InvalidTime)?, 31
28 ) 32 Some(fields)
29 .and_local_timezone(Local)
30 .unwrap())
31} 33}
32 34
33fn ntfs_to_local(time: u64) -> ZipResult<DateTime<Local>> { 35#[inline]
34 Ok(DateTime::from_timestamp( 36fn ntfs_to_local(time: u64) -> Option<DateTime<Local>> {
35 (time / 10000000 - 11644473600) as i64, 37 Some(
36 (time % 10000000) as u32, 38 DateTime::from_timestamp(
39 (time / 10000000 - 11644473600) as i64,
40 (time % 10000000) as u32,
41 )?
42 .with_timezone(&Local),
37 ) 43 )
38 .ok_or(ZipError::InvalidTime)?
39 .with_timezone(&Local))
40} 44}
41 45
42fn timestamp_to_local(time: i32) -> ZipResult<DateTime<Local>> { 46#[inline]
43 Ok(DateTime::from_timestamp(time as i64, 0) 47fn timestamp_to_local(time: i32) -> Option<DateTime<Local>> {
44 .ok_or(ZipError::InvalidTime)? 48 Some(DateTime::from_timestamp(time as i64, 0)?.with_timezone(&Local))
45 .with_timezone(&Local))
46} 49}
47 50
48pub struct Zip<Io = File> { 51pub struct Zip<Io = File> {
@@ -74,7 +77,7 @@ impl<Io: Read + Seek> ArchiveRead for Zip<Io> {
74 .ok_or(ZipError::EocdrNotFound)?, 77 .ok_or(ZipError::EocdrNotFound)?,
75 )? 78 )?
76 .windows(4) 79 .windows(4)
77 .rposition(|v| u32::from_le_bytes(v.try_into().unwrap()) == 0x06054b50) 80 .rposition(|v| v == EOCDR_SIGNATURE)
78 .ok_or(ZipError::EocdrNotFound)? as u64; 81 .ok_or(ZipError::EocdrNotFound)? as u64;
79 82
80 // Read eocdr 83 // Read eocdr
@@ -88,13 +91,13 @@ impl<Io: Read + Seek> ArchiveRead for Zip<Io> {
88 let buf = io.read_arr::<20>()?; 91 let buf = io.read_arr::<20>()?;
89 let (cd_pointer, cd_size, cd_records) = 92 let (cd_pointer, cd_size, cd_records) =
90 // If locator found then read eocdr64 93 // If locator found then read eocdr64
91 if u32::from_le_bytes(buf[0..4].try_into().unwrap()) == 0x07064b50 { 94 if buf[0..4] == EOCDR64_LOCATOR_SIGNATURE {
92 let eocdr64locator: Eocdr64Locator = deserialize(&buf[4..]).unwrap(); 95 let eocdr64locator: Eocdr64Locator = deserialize(&buf[4..]).unwrap();
93 96
94 io.seek(SeekFrom::Start(eocdr64locator.eocdr64_pointer))?; 97 io.seek(SeekFrom::Start(eocdr64locator.eocdr64_pointer))?;
95 let buf = io.read_arr::<56>()?; 98 let buf = io.read_arr::<56>()?;
96 if u32::from_le_bytes(buf[0..4].try_into().unwrap()) != 0x06064b50 { 99 if buf[0..4] != EOCDR64_SIGNATURE {
97 return Err(ZipError::InvalidEOCDR64Signature.into()); 100 return Err(ZipError::InvalidEOCDR64Signature);
98 } 101 }
99 let eocdr64: Eocdr64 = deserialize(&buf[4..]).unwrap(); 102 let eocdr64: Eocdr64 = deserialize(&buf[4..]).unwrap();
100 103
@@ -116,8 +119,8 @@ impl<Io: Read + Seek> ArchiveRead for Zip<Io> {
116 for i in 0..cd_records as usize { 119 for i in 0..cd_records as usize {
117 let buf = buf_reader.read_arr::<46>()?; 120 let buf = buf_reader.read_arr::<46>()?;
118 121
119 if u32::from_le_bytes(buf[..4].try_into().unwrap()) != 0x02014b50 { 122 if buf[..4] != CDR_SIGNATURE {
120 return Err(ZipError::InvalidCDRSignature.into()); 123 return Err(ZipError::InvalidCDRSignature);
121 } 124 }
122 let cdr: Cdr = deserialize(&buf[4..46]).unwrap(); 125 let cdr: Cdr = deserialize(&buf[4..46]).unwrap();
123 let bit_flag = BitFlag::new(cdr.bit_flag); 126 let bit_flag = BitFlag::new(cdr.bit_flag);
@@ -138,91 +141,87 @@ impl<Io: Read + Seek> ArchiveRead for Zip<Io> {
138 String::from_cp437(comment) 141 String::from_cp437(comment)
139 }; 142 };
140 143
144 let mut compression_method = cdr.compression_method;
145 let mut encryption_method = if !bit_flag.is_encrypted() {
146 EncryptionMethod::None
147 } else if !bit_flag.is_strong_encryption() {
148 EncryptionMethod::Weak
149 } else {
150 EncryptionMethod::Unsupported
151 };
152
141 let mut compressed_size = cdr.compressed_size as u64; 153 let mut compressed_size = cdr.compressed_size as u64;
142 let mut size = cdr.size as u64; 154 let mut size = cdr.size as u64;
143 let mut header_pointer = cdr.header_pointer as u64; 155 let mut header_pointer = cdr.header_pointer as u64;
144 156
145 let mut mtime = dos_to_local(cdr.dos_date, cdr.dos_time)?; 157 let mut mtime = DateTime::from_dos_date_time(cdr.dos_date, cdr.dos_time, Local)?;
146 let mut atime = None; 158 let mut atime = None;
147 let mut ctime = None; 159 let mut ctime = None;
148 160
149 // Parse extensible data fields 161 // Parse extensible data fields
150 let mut ep: usize = 0; 162 for (id, mut data) in split_fields(&extra_fields).ok_or(ZipError::InvalidExtraFields)? {
151 while ep < cdr.extra_field_len as usize { 163 match id {
152 let header: ExtraHeader = deserialize(&extra_fields[ep..ep + 4]).unwrap();
153 ep += 4;
154
155 match header.id {
156 // Zip64 164 // Zip64
157 0x0001 => { 165 0x0001 => {
158 if size == 0xFFFFFFFF { 166 if size == 0xFFFFFFFF {
159 compressed_size = 167 size = u64::from_le_bytes(data.read_arr()?);
160 u64::from_le_bytes(extra_fields[ep..ep + 8].try_into().unwrap());
161 ep += 8;
162 } 168 }
163 if compressed_size == 0xFFFFFFFF { 169 if compressed_size == 0xFFFFFFFF {
164 size = u64::from_le_bytes(extra_fields[ep..ep + 8].try_into().unwrap()); 170 compressed_size = u64::from_le_bytes(data.read_arr()?);
165 ep += 8;
166 } 171 }
167 if header_pointer == 0xFFFFFFFF { 172 if header_pointer == 0xFFFFFFFF {
168 header_pointer = 173 header_pointer = u64::from_le_bytes(data.read_arr()?);
169 u64::from_le_bytes(extra_fields[ep..ep + 8].try_into().unwrap());
170 ep += 8;
171 }
172 if cdr.disk == 0xFFFF {
173 ep += 4
174 } 174 }
175 } 175 }
176 // NTFS 176 // NTFS
177 0x000a => { 177 0x000a => {
178 let mut tp = ep + 4; 178 for (id, mut data) in
179 ep += header.size as usize; 179 split_fields(&data).ok_or(ZipError::InvalidExtraFields)?
180 180 {
181 while tp < ep { 181 match id {
182 let header: ExtraHeader =
183 deserialize(&extra_fields[tp..tp + 4]).unwrap();
184 tp += 4;
185
186 match header.id {
187 0x0001 => { 182 0x0001 => {
188 mtime = ntfs_to_local(u64::from_le_bytes( 183 mtime = ntfs_to_local(u64::from_le_bytes(data.read_arr()?))
189 extra_fields[tp..tp + 8].try_into().unwrap(), 184 .unwrap_or(mtime);
190 ))?; 185 atime = ntfs_to_local(u64::from_le_bytes(data.read_arr()?));
191 tp += 8; 186 ctime = ntfs_to_local(u64::from_le_bytes(data.read_arr()?));
192 atime = Some(ntfs_to_local(u64::from_le_bytes(
193 extra_fields[tp..tp + 8].try_into().unwrap(),
194 ))?);
195 tp += 8;
196 ctime = Some(ntfs_to_local(u64::from_le_bytes(
197 extra_fields[tp..tp + 8].try_into().unwrap(),
198 ))?);
199 tp += 8;
200 }
201 _ => {
202 tp += header.size as usize;
203 } 187 }
188 _ => {}
204 } 189 }
205 } 190 }
206 } 191 }
207 // Unix 192 // Unix
208 0x000d => { 193 0x000d => {
209 atime = Some(timestamp_to_local(i32::from_le_bytes( 194 atime = timestamp_to_local(i32::from_le_bytes(data.read_arr()?));
210 extra_fields[ep..ep + 4].try_into().unwrap(), 195 mtime = timestamp_to_local(i32::from_le_bytes(data.read_arr()?))
211 ))?); 196 .unwrap_or(mtime);
212 mtime = timestamp_to_local(i32::from_le_bytes( 197 }
213 extra_fields[ep + 4..ep + 8].try_into().unwrap(), 198 // AES
214 ))?; 199 0x9901 => {
215 ep += header.size as usize 200 let aes: AesField = deserialize(&data.read_arr::<7>()?).unwrap();
201 if aes.id != 0x4541 {
202 return Err(ZipError::InvalidExtraFields);
203 }
204 encryption_method = match aes.strength {
205 0x01 => EncryptionMethod::Aes128,
206 0x02 => EncryptionMethod::Aes192,
207 0x03 => EncryptionMethod::Aes256,
208 _ => EncryptionMethod::Unsupported,
209 };
210 compression_method = aes.compression_method;
216 } 211 }
217 // Skip unrecognized header 212 // Skip unrecognized header
218 _ => ep += header.size as usize, 213 _ => {}
219 } 214 }
220 } 215 }
221 216
217 if compression_method == 99 {
218 return Err(ZipError::AesExtraFieldNotFound);
219 }
220
222 indexes.insert(name.clone(), i); 221 indexes.insert(name.clone(), i);
223 files.push(ZipFileInfo::new( 222 files.push(ZipFileInfo::new(
224 CompressionMethod::from_struct_id(cdr.compression_method)?, 223 CompressionMethod::from_struct_id(compression_method),
225 EncryptionMethod::from_bif_flag(bit_flag, cdr.crc, cdr.dos_time), 224 encryption_method,
226 bit_flag, 225 bit_flag,
227 mtime, 226 mtime,
228 atime, 227 atime,
@@ -248,15 +247,15 @@ impl<Io: Read + Seek> ArchiveRead for Zip<Io> {
248 &self.files 247 &self.files
249 } 248 }
250 249
251 fn get_file_index(&self, name: &str) -> crate::ArchiveResult<usize, Self::Error> { 250 fn get_file_index(&self, name: &str) -> ZipResult<usize> {
252 self.indexes 251 self.indexes
253 .get(name) 252 .get(name)
254 .ok_or(ZipError::FileNotFound.into()) 253 .ok_or(ZipError::FileNotFound)
255 .copied() 254 .copied()
256 } 255 }
257 256
258 fn get_file_info(&self, index: usize) -> ZipResult<&Self::FileInfo> { 257 fn get_file_info(&self, index: usize) -> ZipResult<&Self::FileInfo> {
259 self.files.get(index).ok_or(ZipError::FileNotFound.into()) 258 self.files.get(index).ok_or(ZipError::FileNotFound)
260 } 259 }
261 260
262 #[inline] 261 #[inline]
diff --git a/src/zip/encryption/aes.rs b/src/zip/encryption/aes.rs
new file mode 100644
index 0000000..6f41aaa
--- /dev/null
+++ b/src/zip/encryption/aes.rs
@@ -0,0 +1,46 @@
1use crate::utils::ReadUtils;
2use aes::cipher::generic_array::GenericArray;
3use aes::cipher::BlockEncrypt;
4use std::io::{Read, Result as IoResult};
5
6#[allow(dead_code)]
7pub struct AesDecoder<Io: Read, Aes: BlockEncrypt> {
8 io: Io,
9 aes: Aes,
10
11 counter: u128,
12 block: [u8; 16],
13 cursor: usize,
14}
15
16impl<Io: Read, Aes: BlockEncrypt> AesDecoder<Io, Aes> {
17 pub fn new(mut io: Io, aes: Aes) -> IoResult<Self> {
18 let block = io.read_arr::<16>()?;
19 let mut decoder = Self {
20 io,
21 aes,
22 counter: 1,
23 block,
24 cursor: 0,
25 };
26 decoder.decrypt_block();
27 Ok(decoder)
28 }
29
30 #[inline]
31 fn decrypt_block(&mut self) {
32 let mut mask = self.counter.to_le_bytes();
33 self.aes
34 .encrypt_block(GenericArray::from_mut_slice(&mut mask));
35 for (b, m) in self.block.iter_mut().zip(mask) {
36 *b ^= m
37 }
38 self.counter += 1;
39 }
40}
41
42impl<Io: Read, Aes: BlockEncrypt> Read for AesDecoder<Io, Aes> {
43 fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
44 todo!()
45 }
46}
diff --git a/src/zip/encryption/mod.rs b/src/zip/encryption/mod.rs
new file mode 100644
index 0000000..8cacaf9
--- /dev/null
+++ b/src/zip/encryption/mod.rs
@@ -0,0 +1,5 @@
1mod aes;
2mod weak;
3
4pub use aes::AesDecoder;
5pub use weak::{Keys, WeakDecoder};
diff --git a/src/zip/encryption.rs b/src/zip/encryption/weak.rs
index f317245..144cd53 100644
--- a/src/zip/encryption.rs
+++ b/src/zip/encryption/weak.rs
@@ -1,5 +1,3 @@
1use crate::utils::ReadUtils;
2use crate::zip::{ZipError, ZipResult};
3use std::io::{Read, Result as IoResult}; 1use std::io::{Read, Result as IoResult};
4 2
5const TABLE: [u32; 256] = generate_table(); 3const TABLE: [u32; 256] = generate_table();
@@ -32,35 +30,22 @@ fn crc32(byte: u8, crc: u32) -> u32 {
32 (crc >> 8) ^ TABLE[((crc & 0xFF) as u8 ^ byte) as usize] 30 (crc >> 8) ^ TABLE[((crc & 0xFF) as u8 ^ byte) as usize]
33} 31}
34 32
35pub struct WeakDecoder<Io: Read> { 33pub struct Keys {
36 key0: u32, 34 key0: u32,
37 key1: u32, 35 key1: u32,
38 key2: u32, 36 key2: u32,
39 io: Io,
40} 37}
41 38
42impl<Io: Read> WeakDecoder<Io> { 39impl Keys {
43 pub fn new(io: Io, check: u8, password: &[u8]) -> ZipResult<Self> { 40 pub fn new() -> Self {
44 let mut decoder = Self { 41 Self {
45 key0: 305419896, 42 key0: 305419896,
46 key1: 591751049, 43 key1: 591751049,
47 key2: 878082192, 44 key2: 878082192,
48 io,
49 };
50
51 for c in password {
52 decoder.update_keys(*c)
53 }
54
55 let buf = decoder.read_arr::<12>()?;
56 if check != buf[11] {
57 return Err(ZipError::IncorrectPassword.into());
58 } 45 }
59
60 Ok(decoder)
61 } 46 }
62 47
63 fn update_keys(&mut self, byte: u8) { 48 fn update(&mut self, byte: u8) {
64 self.key0 = crc32(byte, self.key0); 49 self.key0 = crc32(byte, self.key0);
65 self.key1 = self 50 self.key1 = self
66 .key1 51 .key1
@@ -70,19 +55,50 @@ impl<Io: Read> WeakDecoder<Io> {
70 self.key2 = crc32((self.key1 >> 24) as u8, self.key2); 55 self.key2 = crc32((self.key1 >> 24) as u8, self.key2);
71 } 56 }
72 57
73 fn decode_byte(&mut self, byte: u8) -> u8 { 58 pub fn set_password(&mut self, passwd: &[u8]) {
59 for b in passwd {
60 self.update(*b)
61 }
62 }
63
64 pub fn set_header(&mut self, header: [u8; 12]) -> u8 {
65 for b in &header[..11] {
66 self.decode_byte(*b);
67 }
68 self.decode_byte(header[11])
69 }
70
71 #[allow(dead_code)]
72 pub fn encode_bytes(&mut self, byte: u8) -> u8 {
73 let key = self.key2 | 2;
74 self.update(byte);
75 byte ^ ((key.wrapping_mul(key ^ 1)) >> 8) as u8
76 }
77
78 pub fn decode_byte(&mut self, byte: u8) -> u8 {
74 let key = self.key2 | 2; 79 let key = self.key2 | 2;
75 let byte = byte ^ ((key.wrapping_mul(key ^ 1)) >> 8) as u8; 80 let byte = byte ^ ((key.wrapping_mul(key ^ 1)) >> 8) as u8;
76 self.update_keys(byte); 81 self.update(byte);
77 byte 82 byte
78 } 83 }
79} 84}
80 85
86pub struct WeakDecoder<Io: Read> {
87 io: Io,
88 keys: Keys,
89}
90
91impl<Io: Read> WeakDecoder<Io> {
92 pub fn new(io: Io, keys: Keys) -> Self {
93 WeakDecoder { io, keys }
94 }
95}
96
81impl<Io: Read> Read for WeakDecoder<Io> { 97impl<Io: Read> Read for WeakDecoder<Io> {
82 fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> { 98 fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
83 let bytes = self.io.read(buf)?; 99 let bytes = self.io.read(buf)?;
84 for i in 0..bytes { 100 for i in 0..bytes {
85 buf[i] = self.decode_byte(buf[i]); 101 buf[i] = self.keys.decode_byte(buf[i]);
86 } 102 }
87 Ok(bytes) 103 Ok(bytes)
88 } 104 }
diff --git a/src/zip/error.rs b/src/zip/error.rs
index 5573cf8..5508177 100644
--- a/src/zip/error.rs
+++ b/src/zip/error.rs
@@ -1,22 +1,25 @@
1use crate::{ArchiveError, ArchiveResult};
2use std::error::Error; 1use std::error::Error;
3use std::fmt::Display; 2use std::fmt::Display;
3use std::io::Error as IoError;
4 4
5pub type ZipResult<T> = ArchiveResult<T, ZipError>; 5pub type ZipResult<T> = Result<T, ZipError>;
6 6
7#[derive(Debug, PartialEq, Eq)] 7#[derive(Debug)]
8pub enum ZipError { 8pub enum ZipError {
9 Io(IoError),
10
9 EocdrNotFound, 11 EocdrNotFound,
10 InvalidEOCDR64Signature, 12 InvalidEOCDR64Signature,
11 InvalidFileHeaderSignature, 13 InvalidFileHeaderSignature,
12 InvalidCDRSignature, 14 InvalidCDRSignature,
13 15
14 InvalidCompressionMethod(u16),
15 UnsupportedCompressionMethod(u16), 16 UnsupportedCompressionMethod(u16),
16 UnsupportedEncryptionMethod, 17 UnsupportedEncryptionMethod,
17 InvalidDate, 18 InvalidDate,
18 InvalidTime, 19 InvalidTime,
19 InvalidFileName, 20 InvalidFileName,
21 InvalidExtraFields,
22 AesExtraFieldNotFound,
20 InvalidFileComment, 23 InvalidFileComment,
21 24
22 FileNotFound, 25 FileNotFound,
@@ -26,18 +29,30 @@ pub enum ZipError {
26 EncryptedDataIsUnseekable, 29 EncryptedDataIsUnseekable,
27} 30}
28 31
29impl From<ZipError> for ArchiveError<ZipError> { 32impl From<IoError> for ZipError {
30 fn from(value: ZipError) -> Self { 33 fn from(value: IoError) -> Self {
31 Self::Archivator { 34 Self::Io(value)
32 module: "Zip", 35 }
33 error: value, 36}
37
38impl PartialEq for ZipError {
39 fn eq(&self, other: &Self) -> bool {
40 match (self, other) {
41 (Self::Io(l0), Self::Io(r0)) => l0.kind() == r0.kind(),
42 (Self::UnsupportedCompressionMethod(l0), Self::UnsupportedCompressionMethod(r0)) => {
43 l0 == r0
44 }
45 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
34 } 46 }
35 } 47 }
36} 48}
37 49
50impl Eq for ZipError {}
51
38impl Display for ZipError { 52impl Display for ZipError {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 match self { 54 match self {
55 Self::Io(error) => write!(f, "{}", error),
41 Self::EocdrNotFound => write!(f, "End of central directory record not found"), 56 Self::EocdrNotFound => write!(f, "End of central directory record not found"),
42 Self::InvalidEOCDR64Signature => { 57 Self::InvalidEOCDR64Signature => {
43 write!( 58 write!(
@@ -52,9 +67,6 @@ impl Display for ZipError {
52 write!(f, "Invalid signature of central directory record") 67 write!(f, "Invalid signature of central directory record")
53 } 68 }
54 69
55 Self::InvalidCompressionMethod(id) => {
56 writeln!(f, "Invalid compression method {}", id)
57 }
58 Self::UnsupportedCompressionMethod(id) => { 70 Self::UnsupportedCompressionMethod(id) => {
59 writeln!(f, "Unsupported compression method {}", id) 71 writeln!(f, "Unsupported compression method {}", id)
60 } 72 }
@@ -64,6 +76,8 @@ impl Display for ZipError {
64 Self::InvalidDate => write!(f, "Invalid date"), 76 Self::InvalidDate => write!(f, "Invalid date"),
65 Self::InvalidTime => write!(f, "Invalid time"), 77 Self::InvalidTime => write!(f, "Invalid time"),
66 Self::InvalidFileName => write!(f, "Invalid file name"), 78 Self::InvalidFileName => write!(f, "Invalid file name"),
79 Self::InvalidExtraFields => write!(f, "Invalid extra fields"),
80 Self::AesExtraFieldNotFound => write!(f, "Aes extra field not found"),
67 Self::InvalidFileComment => write!(f, "Invalid file comment"), 81 Self::InvalidFileComment => write!(f, "Invalid file comment"),
68 82
69 Self::FileNotFound => write!(f, "File not found"), 83 Self::FileNotFound => write!(f, "File not found"),
diff --git a/src/zip/file/info.rs b/src/zip/file/info.rs
index 599dcc3..38ea984 100644
--- a/src/zip/file/info.rs
+++ b/src/zip/file/info.rs
@@ -1,5 +1,5 @@
1use crate::driver::ArchiveFileInfo; 1use crate::driver::ArchiveFileInfo;
2use crate::zip::{ZipError, ZipResult}; 2use crate::zip::datetime::DosDateTime;
3use chrono::{DateTime, Local}; 3use chrono::{DateTime, Local};
4 4
5#[derive(Debug, PartialEq, Eq, Clone)] 5#[derive(Debug, PartialEq, Eq, Clone)]
@@ -14,42 +14,30 @@ pub enum CompressionMethod {
14} 14}
15 15
16impl CompressionMethod { 16impl CompressionMethod {
17 pub(crate) fn from_struct_id(id: u16) -> ZipResult<Self> { 17 #[inline]
18 Ok(match id { 18 pub(crate) fn from_struct_id(id: u16) -> Self {
19 match id {
19 0 => Self::Store, 20 0 => Self::Store,
20 8 => Self::Deflate, 21 8 => Self::Deflate,
21 12 => Self::BZip2, 22 12 => Self::BZip2,
22 14 => Self::Lzma, 23 14 => Self::Lzma,
23 93 => Self::Zstd, 24 93 => Self::Zstd,
24 95 => Self::Xz, 25 95 => Self::Xz,
25 1..=7 | 9..=11 | 13 | 15..=20 | 94 | 96..=99 => Self::Unsupported(id), 26 _ => Self::Unsupported(id),
26 21..=92 | 100.. => return Err(ZipError::InvalidCompressionMethod(id).into()), 27 }
27 })
28 } 28 }
29} 29}
30 30
31#[derive(Debug, PartialEq, Eq, Clone)] 31#[derive(Debug, PartialEq, Eq, Clone)]
32pub enum EncryptionMethod { 32pub enum EncryptionMethod {
33 None, 33 None,
34 Weak(u8), 34 Weak, // ZipCrypto
35 Aes128, // WinZip encryption
36 Aes192,
37 Aes256,
35 Unsupported, 38 Unsupported,
36} 39}
37 40
38impl EncryptionMethod {
39 pub(crate) fn from_bif_flag(bit_flag: BitFlag, crc: u32, dos_time: u16) -> EncryptionMethod {
40 match (bit_flag.is_encrypted(), bit_flag.is_strong_encryption()) {
41 (false, false) => EncryptionMethod::None,
42 (true, false) => EncryptionMethod::Weak(if bit_flag.is_has_data_descriptor() {
43 (dos_time >> 8) as u8 // Info-ZIP modification
44 } else {
45 (crc >> 24) as u8
46 }),
47 (true, true) => EncryptionMethod::Unsupported,
48 _ => panic!("impossible"),
49 }
50 }
51}
52
53#[derive(Debug, PartialEq, Eq, Clone, Copy)] 41#[derive(Debug, PartialEq, Eq, Clone, Copy)]
54pub struct BitFlag { 42pub struct BitFlag {
55 flag: u16, 43 flag: u16,
@@ -159,7 +147,7 @@ pub struct ZipFileInfo {
159} 147}
160 148
161impl ZipFileInfo { 149impl ZipFileInfo {
162 pub fn new( 150 pub(crate) fn new(
163 compression_method: CompressionMethod, 151 compression_method: CompressionMethod,
164 encryption_method: EncryptionMethod, 152 encryption_method: EncryptionMethod,
165 bit_flag: BitFlag, 153 bit_flag: BitFlag,
@@ -189,6 +177,15 @@ impl ZipFileInfo {
189 } 177 }
190 } 178 }
191 179
180 #[inline]
181 pub(crate) fn password_check(&self) -> u8 {
182 if self.bit_flag.is_has_data_descriptor() {
183 (self.mtime.to_dos_time() >> 8) as u8
184 } else {
185 (self.crc >> 24) as u8
186 }
187 }
188
192 pub fn is_dir(&self) -> bool { 189 pub fn is_dir(&self) -> bool {
193 self.name.ends_with("/") 190 self.name.ends_with("/")
194 } 191 }
diff --git a/src/zip/file/read.rs b/src/zip/file/read.rs
index c26b304..80bdcfb 100644
--- a/src/zip/file/read.rs
+++ b/src/zip/file/read.rs
@@ -1,11 +1,16 @@
1use crate::driver::FileDriver; 1use crate::driver::FileDriver;
2use crate::utils::{IoCursor, ReadUtils}; 2use crate::utils::{IoCursor, ReadUtils};
3use crate::zip::encryption::WeakDecoder; 3use crate::zip::encryption::{AesDecoder, Keys, WeakDecoder};
4use crate::zip::structs::FILE_HEADER_SIGNATURE;
4use crate::zip::{CompressionMethod, EncryptionMethod, ZipError, ZipFileInfo, ZipResult}; 5use crate::zip::{CompressionMethod, EncryptionMethod, ZipError, ZipFileInfo, ZipResult};
6use aes::cipher::KeyInit;
7use aes::{Aes128, Aes192, Aes256};
5use bzip2::read::BzDecoder; 8use bzip2::read::BzDecoder;
6use flate2::read::DeflateDecoder; 9use flate2::read::DeflateDecoder;
7use liblzma::read::XzDecoder; 10use liblzma::read::XzDecoder;
8use liblzma::stream::{Filters, LzmaOptions, Stream}; 11use liblzma::stream::{Filters, LzmaOptions, Stream};
12use pbkdf2::pbkdf2_hmac_array;
13use sha1::Sha1;
9use std::io::{ 14use std::io::{
10 BufReader, Error as IoError, ErrorKind as IoErrorKind, Read, Result as IoResult, Seek, SeekFrom, 15 BufReader, Error as IoError, ErrorKind as IoErrorKind, Read, Result as IoResult, Seek, SeekFrom,
11}; 16};
@@ -14,34 +19,101 @@ use zstd::stream::Decoder as ZstdDecoder;
14enum Encryption<Io: Read> { 19enum Encryption<Io: Read> {
15 None(Io), 20 None(Io),
16 Weak(WeakDecoder<Io>), 21 Weak(WeakDecoder<Io>),
22 Aes128(AesDecoder<Io, Aes128>),
23 Aes192(AesDecoder<Io, Aes192>),
24 Aes256(AesDecoder<Io, Aes256>),
17} 25}
18 26
19impl<Io: Read> Encryption<Io> { 27impl<Io: Read> Encryption<Io> {
20 pub fn new(io: Io, info: &ZipFileInfo, password: Option<&[u8]>) -> ZipResult<Self> { 28 #[inline]
29 pub fn new(mut io: Io, info: &ZipFileInfo, password: Option<&[u8]>) -> ZipResult<Self> {
21 Ok(match info.encryption_method { 30 Ok(match info.encryption_method {
22 EncryptionMethod::None => Self::None(io), 31 EncryptionMethod::None => Self::None(io),
23 EncryptionMethod::Weak(check) => Self::Weak(WeakDecoder::new( 32 EncryptionMethod::Weak => {
24 io, 33 let mut keys = Keys::new();
25 check, 34 keys.set_password(password.ok_or(ZipError::PasswordIsNotSpecified)?);
26 password.ok_or(ZipError::PasswordIsNotSpecified)?, 35
27 )?), 36 let check = keys.set_header(io.read_arr()?);
28 EncryptionMethod::Unsupported => { 37 if check != info.password_check() {
29 return Err(ZipError::UnsupportedEncryptionMethod.into()) 38 return Err(ZipError::IncorrectPassword);
39 }
40
41 Self::Weak(WeakDecoder::new(io, keys))
42 }
43 EncryptionMethod::Aes128 => {
44 let header = io.read_arr::<10>()?;
45 let salt = &header[..8];
46 let check = &header[8..];
47
48 let hash = pbkdf2_hmac_array::<Sha1, 34>(
49 password.ok_or(ZipError::PasswordIsNotSpecified)?,
50 salt,
51 1000,
52 );
53 let key = &hash[..16];
54
55 if check != &hash[32..] {
56 return Err(ZipError::IncorrectPassword);
57 }
58
59 Self::Aes128(AesDecoder::new(io, Aes128::new(key.into()))?)
60 }
61 EncryptionMethod::Aes192 => {
62 let header = io.read_arr::<14>()?;
63 let salt = &header[..12];
64 let check = &header[12..];
65
66 let hash = pbkdf2_hmac_array::<Sha1, 50>(
67 password.ok_or(ZipError::PasswordIsNotSpecified)?,
68 salt,
69 1000,
70 );
71 let key = &hash[..24];
72
73 if check != &hash[48..] {
74 return Err(ZipError::IncorrectPassword);
75 }
76
77 Self::Aes192(AesDecoder::new(io, Aes192::new(key.into()))?)
78 }
79 EncryptionMethod::Aes256 => {
80 let header = io.read_arr::<18>()?;
81 let salt = &header[..16];
82 let check = &header[16..];
83
84 let hash = pbkdf2_hmac_array::<Sha1, 66>(
85 password.ok_or(ZipError::PasswordIsNotSpecified)?,
86 salt,
87 1000,
88 );
89 let key = &hash[..32];
90
91 if check != &hash[64..] {
92 return Err(ZipError::IncorrectPassword);
93 }
94
95 Self::Aes256(AesDecoder::new(io, Aes256::new(key.into()))?)
30 } 96 }
97 EncryptionMethod::Unsupported => return Err(ZipError::UnsupportedEncryptionMethod),
31 }) 98 })
32 } 99 }
33} 100}
34 101
35impl<Io: Read> Read for Encryption<Io> { 102impl<Io: Read> Read for Encryption<Io> {
103 #[inline]
36 fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> { 104 fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
37 match self { 105 match self {
38 Self::None(io) => io.read(buf), 106 Self::None(io) => io.read(buf),
39 Self::Weak(io) => io.read(buf), 107 Self::Weak(io) => io.read(buf),
108 Self::Aes128(io) => io.read(buf),
109 Self::Aes192(io) => io.read(buf),
110 Self::Aes256(io) => io.read(buf),
40 } 111 }
41 } 112 }
42} 113}
43 114
44impl<Io: Read + Seek> Seek for Encryption<Io> { 115impl<Io: Read + Seek> Seek for Encryption<Io> {
116 #[inline]
45 fn seek(&mut self, pos: SeekFrom) -> IoResult<u64> { 117 fn seek(&mut self, pos: SeekFrom) -> IoResult<u64> {
46 match self { 118 match self {
47 Self::None(io) => io.seek(pos), 119 Self::None(io) => io.seek(pos),
@@ -62,6 +134,7 @@ enum Compression<Io: Read> {
62} 134}
63 135
64impl<Io: Read + Seek> Compression<Io> { 136impl<Io: Read + Seek> Compression<Io> {
137 #[inline]
65 pub fn new(mut io: Io, info: &ZipFileInfo) -> ZipResult<Self> { 138 pub fn new(mut io: Io, info: &ZipFileInfo) -> ZipResult<Self> {
66 Ok(match info.compression_method { 139 Ok(match info.compression_method {
67 CompressionMethod::Store => Self::Store(io), 140 CompressionMethod::Store => Self::Store(io),
@@ -88,13 +161,14 @@ impl<Io: Read + Seek> Compression<Io> {
88 CompressionMethod::Zstd => Self::Zstd(ZstdDecoder::new(io)?), 161 CompressionMethod::Zstd => Self::Zstd(ZstdDecoder::new(io)?),
89 CompressionMethod::Xz => Self::Xz(XzDecoder::new(io)), 162 CompressionMethod::Xz => Self::Xz(XzDecoder::new(io)),
90 CompressionMethod::Unsupported(id) => { 163 CompressionMethod::Unsupported(id) => {
91 return Err(ZipError::UnsupportedCompressionMethod(id).into()) 164 return Err(ZipError::UnsupportedCompressionMethod(id))
92 } 165 }
93 }) 166 })
94 } 167 }
95} 168}
96 169
97impl<Io: Read> Read for Compression<Io> { 170impl<Io: Read> Read for Compression<Io> {
171 #[inline]
98 fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> { 172 fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
99 match self { 173 match self {
100 Compression::Store(io) => io.read(buf), 174 Compression::Store(io) => io.read(buf),
@@ -107,6 +181,7 @@ impl<Io: Read> Read for Compression<Io> {
107} 181}
108 182
109impl<Io: Read + Seek> Seek for Compression<Io> { 183impl<Io: Read + Seek> Seek for Compression<Io> {
184 #[inline]
110 fn seek(&mut self, pos: SeekFrom) -> IoResult<u64> { 185 fn seek(&mut self, pos: SeekFrom) -> IoResult<u64> {
111 match self { 186 match self {
112 Compression::Store(io) => io.seek(pos), 187 Compression::Store(io) => io.seek(pos),
@@ -137,8 +212,8 @@ impl<'d, Io: Read + Seek> ZipFileReader<'d, Io> {
137 io.seek(SeekFrom::Start(info.header_pointer))?; 212 io.seek(SeekFrom::Start(info.header_pointer))?;
138 213
139 let buf = io.read_arr::<30>()?; 214 let buf = io.read_arr::<30>()?;
140 if u32::from_le_bytes(buf[..4].try_into().unwrap()) != 0x04034b50 { 215 if buf[..4] != FILE_HEADER_SIGNATURE {
141 return Err(ZipError::InvalidFileHeaderSignature.into()); 216 return Err(ZipError::InvalidFileHeaderSignature);
142 } 217 }
143 let data_pointer = info.header_pointer 218 let data_pointer = info.header_pointer
144 + 30 219 + 30
diff --git a/src/zip/mod.rs b/src/zip/mod.rs
index fcc6161..488c06d 100644
--- a/src/zip/mod.rs
+++ b/src/zip/mod.rs
@@ -1,5 +1,6 @@
1mod archive; 1mod archive;
2mod cp437; 2mod cp437;
3mod datetime;
3mod driver; 4mod driver;
4mod encryption; 5mod encryption;
5mod error; 6mod error;
diff --git a/src/zip/structs.rs b/src/zip/structs.rs
index ebecae7..8b25400 100644
--- a/src/zip/structs.rs
+++ b/src/zip/structs.rs
@@ -1,6 +1,9 @@
1use crate::structs::{ByteOrder, Settings, StructResult, VariantIndexType}; 1use crate::structs::{ByteOrder, Settings, StructResult, VariantIndexType};
2use serde::{Deserialize, Serialize}; 2use serde::{Deserialize, Serialize};
3 3
4pub const FILE_HEADER_SIGNATURE: [u8; 4] = [0x50, 0x4b, 0x03, 0x04];
5
6pub const EOCDR_SIGNATURE: [u8; 4] = [0x50, 0x4b, 0x05, 0x06];
4#[derive(Serialize, Deserialize)] 7#[derive(Serialize, Deserialize)]
5pub struct Eocdr { 8pub struct Eocdr {
6 pub eocdr_disk: u16, 9 pub eocdr_disk: u16,
@@ -12,6 +15,7 @@ pub struct Eocdr {
12 pub comment_len: u16, 15 pub comment_len: u16,
13} 16}
14 17
18pub const EOCDR64_LOCATOR_SIGNATURE: [u8; 4] = [0x50, 0x4b, 0x06, 0x07];
15#[derive(Serialize, Deserialize)] 19#[derive(Serialize, Deserialize)]
16pub struct Eocdr64Locator { 20pub struct Eocdr64Locator {
17 pub eocdr64_disk: u32, 21 pub eocdr64_disk: u32,
@@ -19,6 +23,7 @@ pub struct Eocdr64Locator {
19 pub disks: u32, 23 pub disks: u32,
20} 24}
21 25
26pub const EOCDR64_SIGNATURE: [u8; 4] = [0x50, 0x4b, 0x06, 0x06];
22#[derive(Serialize, Deserialize)] 27#[derive(Serialize, Deserialize)]
23pub struct Eocdr64 { 28pub struct Eocdr64 {
24 pub eocdr64_size: u64, 29 pub eocdr64_size: u64,
@@ -32,6 +37,7 @@ pub struct Eocdr64 {
32 pub cd_pointer: u64, 37 pub cd_pointer: u64,
33} 38}
34 39
40pub const CDR_SIGNATURE: [u8; 4] = [0x50, 0x4b, 0x01, 0x02];
35#[derive(Serialize, Deserialize)] 41#[derive(Serialize, Deserialize)]
36pub struct Cdr { 42pub struct Cdr {
37 pub version: u16, 43 pub version: u16,
@@ -58,6 +64,14 @@ pub struct ExtraHeader {
58 pub size: u16, 64 pub size: u16,
59} 65}
60 66
67#[derive(Serialize, Deserialize)]
68pub struct AesField {
69 pub version: u16,
70 pub id: u16,
71 pub strength: u8,
72 pub compression_method: u16,
73}
74
61#[inline] 75#[inline]
62#[allow(dead_code)] 76#[allow(dead_code)]
63pub fn serialize<T: Serialize>(object: &mut T) -> StructResult<Vec<u8>> { 77pub fn serialize<T: Serialize>(object: &mut T) -> StructResult<Vec<u8>> {
diff --git a/src/zip/tests.rs b/src/zip/tests.rs
index e24cdfe..a8ac634 100644
--- a/src/zip/tests.rs
+++ b/src/zip/tests.rs
@@ -1,5 +1,7 @@
1use crate::zip::cp437::{from_char, is_cp437, to_char, FromCp437}; 1use crate::zip::cp437::{from_char, is_cp437, to_char, FromCp437};
2use crate::zip::datetime::DosDateTime;
2use crate::zip::{bit::DeflateMode, BitFlag}; 3use crate::zip::{bit::DeflateMode, BitFlag};
4use chrono::{DateTime, Local, TimeZone};
3 5
4#[test] 6#[test]
5fn test_bit_flag() { 7fn test_bit_flag() {
@@ -74,3 +76,20 @@ fn test_cp437() {
74 "abcdefghijklmnopqrstuvwxyz" 76 "abcdefghijklmnopqrstuvwxyz"
75 ); 77 );
76} 78}
79
80#[test]
81fn test_dos_datetime() {
82 let datetime = Local.with_ymd_and_hms(2001, 2, 3, 4, 5, 6).unwrap();
83 assert_eq!(
84 DateTime::from_dos_date_time(datetime.to_dos_date(), datetime.to_dos_time(), Local)
85 .unwrap(),
86 datetime
87 );
88
89 let datetime = Local.with_ymd_and_hms(1999, 9, 9, 9, 9, 9).unwrap();
90 assert_eq!(
91 DateTime::from_dos_date_time(datetime.to_dos_date(), datetime.to_dos_time(), Local)
92 .unwrap(),
93 Local.with_ymd_and_hms(1999, 9, 9, 9, 9, 8).unwrap()
94 ); // Dos format stores seconds with an accuracy of 2s
95}