Merge pull request #115 from BenjaminRi/pkzip-cipher

Add ZipCrypto reading support
This commit is contained in:
Plecra 2020-06-23 21:50:15 +01:00 committed by GitHub
commit f99cdd0fb4
Signed by: DevComp
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 412 additions and 85 deletions

View file

@ -15,3 +15,4 @@ pub mod result;
mod spec;
mod types;
pub mod write;
mod zipcrypto;

View file

@ -4,6 +4,8 @@ use crate::compression::CompressionMethod;
use crate::crc32::Crc32Reader;
use crate::result::{ZipError, ZipResult};
use crate::spec;
use crate::zipcrypto::ZipCryptoReader;
use crate::zipcrypto::ZipCryptoReaderValid;
use std::borrow::Cow;
use std::collections::HashMap;
use std::io;
@ -59,6 +61,121 @@ pub struct ZipArchive<R: Read + io::Seek> {
comment: Vec<u8>,
}
enum CryptoReader<'a> {
Plaintext(io::Take<&'a mut dyn Read>),
ZipCrypto(ZipCryptoReaderValid<io::Take<&'a mut dyn Read>>),
}
impl<'a> Read for CryptoReader<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self {
CryptoReader::Plaintext(r) => r.read(buf),
CryptoReader::ZipCrypto(r) => r.read(buf),
}
}
}
impl<'a> CryptoReader<'a> {
/// Consumes this decoder, returning the underlying reader.
pub fn into_inner(self) -> io::Take<&'a mut dyn Read> {
match self {
CryptoReader::Plaintext(r) => r,
CryptoReader::ZipCrypto(r) => r.into_inner(),
}
}
}
enum ZipFileReader<'a> {
NoReader,
Stored(Crc32Reader<CryptoReader<'a>>),
#[cfg(any(
feature = "deflate",
feature = "deflate-miniz",
feature = "deflate-zlib"
))]
Deflated(Crc32Reader<flate2::read::DeflateDecoder<CryptoReader<'a>>>),
#[cfg(feature = "bzip2")]
Bzip2(Crc32Reader<BzDecoder<CryptoReader<'a>>>),
}
impl<'a> Read for ZipFileReader<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self {
ZipFileReader::NoReader => panic!("ZipFileReader was in an invalid state"),
ZipFileReader::Stored(r) => r.read(buf),
#[cfg(any(
feature = "deflate",
feature = "deflate-miniz",
feature = "deflate-zlib"
))]
ZipFileReader::Deflated(r) => r.read(buf),
#[cfg(feature = "bzip2")]
ZipFileReader::Bzip2(r) => r.read(buf),
}
}
}
impl<'a> ZipFileReader<'a> {
/// Consumes this decoder, returning the underlying reader.
pub fn into_inner(self) -> io::Take<&'a mut dyn Read> {
match self {
ZipFileReader::NoReader => panic!("ZipFileReader was in an invalid state"),
ZipFileReader::Stored(r) => r.into_inner().into_inner(),
#[cfg(any(
feature = "deflate",
feature = "deflate-miniz",
feature = "deflate-zlib"
))]
ZipFileReader::Deflated(r) => r.into_inner().into_inner().into_inner(),
#[cfg(feature = "bzip2")]
ZipFileReader::Bzip2(r) => r.into_inner().into_inner().into_inner(),
}
}
}
/// A struct for reading a zip file
pub struct ZipFile<'a> {
data: Cow<'a, ZipFileData>,
reader: ZipFileReader<'a>,
}
fn make_reader<'a>(
compression_method: crate::compression::CompressionMethod,
crc32: u32,
reader: io::Take<&'a mut dyn io::Read>,
password: Option<&[u8]>,
) -> ZipResult<ZipFileReader<'a>> {
let reader = match password {
None => CryptoReader::Plaintext(reader),
Some(password) => match ZipCryptoReader::new(reader, password).validate(crc32)? {
None => return Err(ZipError::InvalidPassword),
Some(r) => CryptoReader::ZipCrypto(r),
},
};
match compression_method {
CompressionMethod::Stored => Ok(ZipFileReader::Stored(Crc32Reader::new(reader, crc32))),
#[cfg(any(
feature = "deflate",
feature = "deflate-miniz",
feature = "deflate-zlib"
))]
CompressionMethod::Deflated => {
let deflate_reader = DeflateDecoder::new(reader);
Ok(ZipFileReader::Deflated(Crc32Reader::new(
deflate_reader,
crc32,
)))
}
#[cfg(feature = "bzip2")]
CompressionMethod::Bzip2 => {
let bzip2_reader = BzDecoder::new(reader);
Ok(ZipFileReader::Bzip2(Crc32Reader::new(bzip2_reader, crc32)))
}
_ => unsupported_zip_error("Compression method not supported"),
}
}
impl<R: Read + io::Seek> ZipArchive<R> {
/// Get the directory start offset and number of files. This is done in a
/// separate function to ease the control flow design.
@ -227,26 +344,62 @@ impl<R: Read + io::Seek> ZipArchive<R> {
self.names_map.keys().map(|s| s.as_str())
}
/// Search for a file entry by name, decrypt with given password
pub fn by_name_decrypt<'a>(
&'a mut self,
name: &str,
password: &[u8],
) -> ZipResult<ZipFile<'a>> {
self.by_name_with_optional_password(name, Some(password))
}
/// Search for a file entry by name
pub fn by_name<'a>(&'a mut self, name: &str) -> ZipResult<ZipFile<'a>> {
self.by_name_with_optional_password(name, None)
}
fn by_name_with_optional_password<'a>(
&'a mut self,
name: &str,
password: Option<&[u8]>,
) -> ZipResult<ZipFile<'a>> {
let index = match self.names_map.get(name) {
Some(index) => *index,
None => {
return Err(ZipError::FileNotFound);
}
};
self.by_index(index)
self.by_index_with_optional_password(index, password)
}
/// Get a contained file by index, decrypt with given password
pub fn by_index_decrypt<'a>(
&'a mut self,
file_number: usize,
password: &[u8],
) -> ZipResult<ZipFile<'a>> {
self.by_index_with_optional_password(file_number, Some(password))
}
/// Get a contained file by index
pub fn by_index<'a>(&'a mut self, file_number: usize) -> ZipResult<ZipFile<'a>> {
self.by_index_with_optional_password(file_number, None)
}
fn by_index_with_optional_password<'a>(
&'a mut self,
file_number: usize,
mut password: Option<&[u8]>,
) -> ZipResult<ZipFile<'a>> {
if file_number >= self.files.len() {
return Err(ZipError::FileNotFound);
}
let data = &mut self.files[file_number];
if data.encrypted {
return unsupported_zip_error("Encrypted files are not supported");
match (password, data.encrypted) {
(None, true) => return Err(ZipError::PasswordRequired),
(Some(_), false) => password = None, //Password supplied, but none needed! Discard.
_ => {}
}
// Parse local header
@ -267,7 +420,7 @@ impl<R: Read + io::Seek> ZipArchive<R> {
let limit_reader = (self.reader.by_ref() as &mut dyn Read).take(data.compressed_size);
Ok(ZipFile {
reader: make_reader(data.compression_method, data.crc32, limit_reader)?,
reader: make_reader(data.compression_method, data.crc32, limit_reader, password)?,
data: Cow::Borrowed(data),
})
}
@ -280,51 +433,10 @@ impl<R: Read + io::Seek> ZipArchive<R> {
}
}
enum ZipFileReader<'a> {
NoReader,
Stored(Crc32Reader<io::Take<&'a mut dyn Read>>),
#[cfg(any(
feature = "deflate",
feature = "deflate-miniz",
feature = "deflate-zlib"
))]
Deflated(Crc32Reader<flate2::read::DeflateDecoder<io::Take<&'a mut dyn Read>>>),
#[cfg(feature = "bzip2")]
Bzip2(Crc32Reader<BzDecoder<io::Take<&'a mut dyn Read>>>),
}
fn unsupported_zip_error<T>(detail: &'static str) -> ZipResult<T> {
Err(ZipError::UnsupportedArchive(detail))
}
fn make_reader<'a>(
compression_method: crate::compression::CompressionMethod,
crc32: u32,
reader: io::Take<&'a mut dyn io::Read>,
) -> ZipResult<ZipFileReader<'a>> {
match compression_method {
CompressionMethod::Stored => Ok(ZipFileReader::Stored(Crc32Reader::new(reader, crc32))),
#[cfg(any(
feature = "deflate",
feature = "deflate-miniz",
feature = "deflate-zlib"
))]
CompressionMethod::Deflated => {
let deflate_reader = DeflateDecoder::new(reader);
Ok(ZipFileReader::Deflated(Crc32Reader::new(
deflate_reader,
crc32,
)))
}
#[cfg(feature = "bzip2")]
CompressionMethod::Bzip2 => {
let bzip2_reader = BzDecoder::new(reader);
Ok(ZipFileReader::Bzip2(Crc32Reader::new(bzip2_reader, crc32)))
}
_ => unsupported_zip_error("Compression method not supported"),
}
}
fn central_header_to_zip_file<R: Read + io::Seek>(
reader: &mut R,
archive_offset: u64,
@ -434,33 +546,8 @@ fn parse_extra_field(file: &mut ZipFileData, data: &[u8]) -> ZipResult<()> {
Ok(())
}
fn get_reader<'a>(reader: &'a mut ZipFileReader<'_>) -> &'a mut dyn Read {
match *reader {
ZipFileReader::NoReader => panic!("ZipFileReader was in an invalid state"),
ZipFileReader::Stored(ref mut r) => r as &mut dyn Read,
#[cfg(any(
feature = "deflate",
feature = "deflate-miniz",
feature = "deflate-zlib"
))]
ZipFileReader::Deflated(ref mut r) => r as &mut dyn Read,
#[cfg(feature = "bzip2")]
ZipFileReader::Bzip2(ref mut r) => r as &mut dyn Read,
}
}
/// A struct for reading a zip file
pub struct ZipFile<'a> {
data: Cow<'a, ZipFileData>,
reader: ZipFileReader<'a>,
}
/// Methods for retrieving information on zip files
impl<'a> ZipFile<'a> {
fn get_reader(&mut self) -> &mut dyn Read {
get_reader(&mut self.reader)
}
/// Get the version of the file
pub fn version_made_by(&self) -> (u8, u8) {
(
@ -566,7 +653,7 @@ impl<'a> ZipFile<'a> {
impl<'a> Read for ZipFile<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.get_reader().read(buf)
self.reader.read(buf)
}
}
@ -577,20 +664,9 @@ impl<'a> Drop for ZipFile<'a> {
if let Cow::Owned(_) = self.data {
let mut buffer = [0; 1 << 16];
// Get the inner `Take` reader so all decompression and CRC calculation is skipped.
// Get the inner `Take` reader so all decryption, decompression and CRC calculation is skipped.
let innerreader = ::std::mem::replace(&mut self.reader, ZipFileReader::NoReader);
let mut reader = match innerreader {
ZipFileReader::NoReader => panic!("ZipFileReader was in an invalid state"),
ZipFileReader::Stored(crcreader) => crcreader.into_inner(),
#[cfg(any(
feature = "deflate",
feature = "deflate-miniz",
feature = "deflate-zlib"
))]
ZipFileReader::Deflated(crcreader) => crcreader.into_inner().into_inner(),
#[cfg(feature = "bzip2")]
ZipFileReader::Bzip2(crcreader) => crcreader.into_inner().into_inner(),
};
let mut reader: std::io::Take<&mut dyn std::io::Read> = innerreader.into_inner();
loop {
match reader.read(&mut buffer) {
@ -698,7 +774,7 @@ pub fn read_zipfile_from_stream<'a, R: io::Read>(
let result_compression_method = result.compression_method;
Ok(Some(ZipFile {
data: Cow::Owned(result),
reader: make_reader(result_compression_method, result_crc32, limit_reader)?,
reader: make_reader(result_compression_method, result_crc32, limit_reader, None)?,
}))
}

View file

@ -25,4 +25,12 @@ pub enum ZipError {
/// The requested file could not be found in the archive
#[error("specified file not found in archive")]
FileNotFound,
/// No password was given but the data is encrypted
#[error("missing password, file in archive is encrypted")]
PasswordRequired,
/// The given password is wrong
#[error("invalid password for file in archive")]
InvalidPassword,
}

162
src/zipcrypto.rs Normal file
View file

@ -0,0 +1,162 @@
//! Implementation of the ZipCrypto algorithm
//!
//! The following paper was used to implement the ZipCrypto algorithm:
//! [https://courses.cs.ut.ee/MTAT.07.022/2015_fall/uploads/Main/dmitri-report-f15-16.pdf](https://courses.cs.ut.ee/MTAT.07.022/2015_fall/uploads/Main/dmitri-report-f15-16.pdf)
use std::num::Wrapping;
/// A container to hold the current key state
struct ZipCryptoKeys {
key_0: Wrapping<u32>,
key_1: Wrapping<u32>,
key_2: Wrapping<u32>,
}
impl ZipCryptoKeys {
fn new() -> ZipCryptoKeys {
ZipCryptoKeys {
key_0: Wrapping(0x12345678),
key_1: Wrapping(0x23456789),
key_2: Wrapping(0x34567890),
}
}
fn update(&mut self, input: u8) {
self.key_0 = ZipCryptoKeys::crc32(self.key_0, input);
self.key_1 =
(self.key_1 + (self.key_0 & Wrapping(0xff))) * Wrapping(0x08088405) + Wrapping(1);
self.key_2 = ZipCryptoKeys::crc32(self.key_2, (self.key_1 >> 24).0 as u8);
}
fn stream_byte(&mut self) -> u8 {
let temp: Wrapping<u16> = Wrapping(self.key_2.0 as u16) | Wrapping(3);
((temp * (temp ^ Wrapping(1))) >> 8).0 as u8
}
fn decrypt_byte(&mut self, cipher_byte: u8) -> u8 {
let plain_byte: u8 = self.stream_byte() ^ cipher_byte;
self.update(plain_byte);
plain_byte
}
#[allow(dead_code)]
fn encrypt_byte(&mut self, plain_byte: u8) -> u8 {
let cipher_byte: u8 = self.stream_byte() ^ plain_byte;
self.update(plain_byte);
cipher_byte
}
fn crc32(crc: Wrapping<u32>, input: u8) -> Wrapping<u32> {
return (crc >> 8) ^ Wrapping(CRCTABLE[((crc & Wrapping(0xff)).0 as u8 ^ input) as usize]);
}
}
/// A ZipCrypto reader with unverified password
pub struct ZipCryptoReader<R> {
file: R,
keys: ZipCryptoKeys,
}
impl<R: std::io::Read> ZipCryptoReader<R> {
/// Note: The password is `&[u8]` and not `&str` because the
/// [zip specification](https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.3.3.TXT)
/// does not specify password encoding (see function `update_keys` in the specification).
/// Therefore, if `&str` was used, the password would be UTF-8 and it
/// would be impossible to decrypt files that were encrypted with a
/// password byte sequence that is unrepresentable in UTF-8.
pub fn new(file: R, password: &[u8]) -> ZipCryptoReader<R> {
let mut result = ZipCryptoReader {
file: file,
keys: ZipCryptoKeys::new(),
};
// Key the cipher by updating the keys with the password.
for byte in password.iter() {
result.keys.update(*byte);
}
result
}
/// Read the ZipCrypto header bytes and validate the password.
pub fn validate(
mut self,
crc32_plaintext: u32,
) -> Result<Option<ZipCryptoReaderValid<R>>, std::io::Error> {
// ZipCrypto prefixes a file with a 12 byte header
let mut header_buf = [0u8; 12];
self.file.read_exact(&mut header_buf)?;
for byte in header_buf.iter_mut() {
*byte = self.keys.decrypt_byte(*byte);
}
// PKZIP before 2.0 used 2 byte CRC check.
// PKZIP 2.0+ used 1 byte CRC check. It's more secure.
// We also use 1 byte CRC.
if (crc32_plaintext >> 24) as u8 != header_buf[11] {
return Ok(None); // Wrong password
}
Ok(Some(ZipCryptoReaderValid { reader: self }))
}
}
/// A ZipCrypto reader with verified password
pub struct ZipCryptoReaderValid<R> {
reader: ZipCryptoReader<R>,
}
impl<R: std::io::Read> std::io::Read for ZipCryptoReaderValid<R> {
fn read(&mut self, mut buf: &mut [u8]) -> std::io::Result<usize> {
// Note: There might be potential for optimization. Inspiration can be found at:
// https://github.com/kornelski/7z/blob/master/CPP/7zip/Crypto/ZipCrypto.cpp
let result = self.reader.file.read(&mut buf);
for byte in buf.iter_mut() {
*byte = self.reader.keys.decrypt_byte(*byte);
}
result
}
}
impl<R: std::io::Read> ZipCryptoReaderValid<R> {
/// Consumes this decoder, returning the underlying reader.
pub fn into_inner(self) -> R {
self.reader.file
}
}
static CRCTABLE: [u32; 256] = [
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
];

80
tests/zip_crypto.rs Normal file
View file

@ -0,0 +1,80 @@
// The following is a hexdump of a zip file containing the following
// ZipCrypto encrypted file:
// test.txt: 35 bytes, contents: `abcdefghijklmnopqrstuvwxyz123456789`, password: `test`
//
// 00000000 50 4b 03 04 14 00 01 00 00 00 54 bd b5 50 2f 20 |PK........T..P/ |
// 00000010 79 55 2f 00 00 00 23 00 00 00 08 00 00 00 74 65 |yU/...#.......te|
// 00000020 73 74 2e 74 78 74 ca 2d 1d 27 19 19 63 43 77 9a |st.txt.-.'..cCw.|
// 00000030 71 76 c9 ec d1 6f d9 f5 22 67 b3 8f 52 b5 41 bc |qv...o.."g..R.A.|
// 00000040 5c 36 f2 1d 84 c3 c0 28 3b fd e1 70 c2 cc 0c 11 |\6.....(;..p....|
// 00000050 0c c5 95 2f a4 50 4b 01 02 3f 00 14 00 01 00 00 |.../.PK..?......|
// 00000060 00 54 bd b5 50 2f 20 79 55 2f 00 00 00 23 00 00 |.T..P/ yU/...#..|
// 00000070 00 08 00 24 00 00 00 00 00 00 00 20 00 00 00 00 |...$....... ....|
// 00000080 00 00 00 74 65 73 74 2e 74 78 74 0a 00 20 00 00 |...test.txt.. ..|
// 00000090 00 00 00 01 00 18 00 31 b2 3b bf b8 2f d6 01 31 |.......1.;../..1|
// 000000a0 b2 3b bf b8 2f d6 01 a8 c4 45 bd b8 2f d6 01 50 |.;../....E../..P|
// 000000b0 4b 05 06 00 00 00 00 01 00 01 00 5a 00 00 00 55 |K..........Z...U|
// 000000c0 00 00 00 00 00 |.....|
// 000000c5
use std::io::Cursor;
use std::io::Read;
#[test]
fn encrypted_file() {
let zip_file_bytes = &mut Cursor::new(vec![
0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x54, 0xbd, 0xb5, 0x50, 0x2f,
0x20, 0x79, 0x55, 0x2f, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x74, 0x65, 0x73, 0x74, 0x2e, 0x74, 0x78, 0x74, 0xca, 0x2d, 0x1d, 0x27, 0x19, 0x19, 0x63,
0x43, 0x77, 0x9a, 0x71, 0x76, 0xc9, 0xec, 0xd1, 0x6f, 0xd9, 0xf5, 0x22, 0x67, 0xb3, 0x8f,
0x52, 0xb5, 0x41, 0xbc, 0x5c, 0x36, 0xf2, 0x1d, 0x84, 0xc3, 0xc0, 0x28, 0x3b, 0xfd, 0xe1,
0x70, 0xc2, 0xcc, 0x0c, 0x11, 0x0c, 0xc5, 0x95, 0x2f, 0xa4, 0x50, 0x4b, 0x01, 0x02, 0x3f,
0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x54, 0xbd, 0xb5, 0x50, 0x2f, 0x20, 0x79, 0x55,
0x2f, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x08, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74,
0x2e, 0x74, 0x78, 0x74, 0x0a, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x18,
0x00, 0x31, 0xb2, 0x3b, 0xbf, 0xb8, 0x2f, 0xd6, 0x01, 0x31, 0xb2, 0x3b, 0xbf, 0xb8, 0x2f,
0xd6, 0x01, 0xa8, 0xc4, 0x45, 0xbd, 0xb8, 0x2f, 0xd6, 0x01, 0x50, 0x4b, 0x05, 0x06, 0x00,
0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00,
0x00, 0x00,
]);
let mut archive = zip::ZipArchive::new(zip_file_bytes).unwrap();
assert_eq!(archive.len(), 1); //Only one file inside archive: `test.txt`
{
// No password
let file = archive.by_index(0);
match file {
Err(zip::result::ZipError::PasswordRequired) => (),
Err(_) => panic!(
"Expected PasswordRequired error when opening encrypted file without password"
),
Ok(_) => panic!("Error: Successfully opened encrypted file without password?!"),
}
}
{
// Wrong password
let file = archive.by_index_decrypt(0, b"wrong password");
match file {
Err(zip::result::ZipError::InvalidPassword) => (),
Err(_) => panic!(
"Expected InvalidPassword error when opening encrypted file with wrong password"
),
Ok(_) => panic!("Error: Successfully opened encrypted file with wrong password?!"),
}
}
{
// Correct password, read contents
let mut file = archive.by_index_decrypt(0, "test".as_bytes()).unwrap();
let file_name = file.sanitized_name();
assert_eq!(file_name, std::path::PathBuf::from("test.txt"));
let mut data = Vec::new();
file.read_to_end(&mut data).unwrap();
assert_eq!(data, "abcdefghijklmnopqrstuvwxyz123456789".as_bytes());
}
}