unsigned char compression;
unsigned char system; /* From "version written by" */
unsigned char flags; /* Our extra markers. */
+ unsigned char decdat;/* Used for Decryption check */
+};
+
+struct trad_enc_ctx {
+ uint32_t keys[3];
};
/* Bits used in zip_flags. */
struct archive_string_conv *sconv_utf8;
int init_default_conversion;
int process_mac_extensions;
+
+ struct archive_string password;
+ struct trad_enc_ctx tctx;
+ char tctx_valid;
+ unsigned char *decrypted_buffer;
+ size_t decrypted_buffer_size;
+ size_t decrypted_bytes_remaining;
};
/* Many systems define min or MIN, but not all. */
/* ------------------------------------------------------------------------ */
+/*
+ Traditional PKWARE Decryption functions.
+ */
+
+static void
+trad_enc_update_keys(struct trad_enc_ctx *ctx, uint8_t c)
+{
+ uint8_t t;
+#define CRC32(c, b) (crc32(c ^ 0xffffffffUL, &b, 1) ^ 0xffffffffUL)
+
+ ctx->keys[0] = CRC32(ctx->keys[0], c);
+ ctx->keys[1] = (ctx->keys[1] + (ctx->keys[0] & 0xff)) * 134775813L + 1;
+ t = (ctx->keys[1] >> 24) & 0xff;
+ ctx->keys[2] = CRC32(ctx->keys[2], t);
+#undef CRC32
+}
+
+static void
+trad_enc_init(struct trad_enc_ctx *ctx, const char *p, size_t l)
+{
+ ctx->keys[0] = 305419896L;
+ ctx->keys[1] = 591751049L;
+ ctx->keys[2] = 878082192L;
+
+ for (;l; --l)
+ trad_enc_update_keys(ctx, *p++);
+}
+
+static uint8_t
+trad_enc_decypt_byte(struct trad_enc_ctx *ctx)
+{
+ unsigned temp = ctx->keys[2] | 2;
+ return (uint8_t)((temp * (temp ^ 1)) >> 8) & 0xff;
+}
+
+static void
+trad_enc_decrypt(struct trad_enc_ctx *ctx, uint8_t *p, size_t l)
+{
+
+ for (; l; --l) {
+ uint8_t t = *p ^ trad_enc_decypt_byte(ctx);
+ *p++ = t;
+ trad_enc_update_keys(ctx, t);
+ }
+}
+
+static unsigned int
+trad_enc_read_encyption_header(struct trad_enc_ctx *ctx, const uint8_t *p,
+ size_t l)
+{
+ uint8_t header[12];
+
+ if (l < 12)
+ return -1;
+
+ memcpy(header, p, 12);
+ trad_enc_decrypt(ctx, header, 12);
+ /* Return the last byte for CRC check. */
+ return header[11];
+}
+
/*
* Common code for streaming or seeking modes.
*
zip_entry->compression = (char)archive_le16dec(p + 8);
zip_entry->mtime = zip_time(p + 10);
zip_entry->crc32 = archive_le32dec(p + 14);
+ if (zip_entry->zip_flags & ZIP_LENGTH_AT_END)
+ zip_entry->decdat = p[11];
+ else
+ zip_entry->decdat = p[17];
zip_entry->compressed_size = archive_le32dec(p + 18);
zip_entry->uncompressed_size = archive_le32dec(p + 22);
filename_length = archive_le16dec(p + 26);
if (bytes_avail > zip->entry_bytes_remaining)
bytes_avail = (ssize_t)zip->entry_bytes_remaining;
}
+ if (zip->tctx_valid) {
+ size_t dec_size = bytes_avail;
+
+ if (dec_size > zip->decrypted_buffer_size)
+ dec_size = zip->decrypted_buffer_size;
+ memcpy(zip->decrypted_buffer, buff, dec_size);
+ trad_enc_decrypt(&zip->tctx, zip->decrypted_buffer, dec_size);
+ bytes_avail = dec_size;
+ buff = (const char *)zip->decrypted_buffer;
+ }
*size = bytes_avail;
zip->entry_bytes_remaining -= bytes_avail;
zip->entry_uncompressed_bytes_read += bytes_avail;
return (ARCHIVE_FATAL);
}
+ if (zip->tctx_valid) {
+ size_t buff_remaining =
+ zip->decrypted_buffer_size - zip->decrypted_bytes_remaining;
+
+ if (buff_remaining > (size_t)bytes_avail)
+ buff_remaining = (size_t)bytes_avail;
+ if ((int64_t)(zip->decrypted_bytes_remaining + buff_remaining)
+ > zip->entry_bytes_remaining)
+ buff_remaining = zip->entry_bytes_remaining
+ - zip->decrypted_bytes_remaining;
+ memcpy(zip->decrypted_buffer + zip->decrypted_bytes_remaining,
+ compressed_buff, buff_remaining);
+ __archive_read_consume(a, buff_remaining);
+ trad_enc_decrypt(&zip->tctx,
+ zip->decrypted_buffer + zip->decrypted_bytes_remaining,
+ buff_remaining);
+ zip->decrypted_bytes_remaining += buff_remaining;
+ bytes_avail = zip->decrypted_bytes_remaining;
+ compressed_buff = (const char *)zip->decrypted_buffer;
+ }
+
/*
* A bug in zlib.h: stream.next_in should be marked 'const'
* but isn't (the library never alters data through the
/* Consume as much as the compressor actually used. */
bytes_avail = zip->stream.total_in;
- __archive_read_consume(a, bytes_avail);
+ if (zip->tctx_valid)
+ zip->decrypted_bytes_remaining -= bytes_avail;
+ else
+ __archive_read_consume(a, bytes_avail);
zip->entry_bytes_remaining -= bytes_avail;
zip->entry_compressed_bytes_read += bytes_avail;
if (AE_IFREG != (zip->entry->mode & AE_IFMT))
return (ARCHIVE_EOF);
- if (zip->entry->zip_flags & (ZIP_ENCRYPTED | ZIP_STRONG_ENCRYPTED)) {
- zip->has_encrypted_entries = 1;
- archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
- "Encrypted file is unsupported");
- return (ARCHIVE_FAILED);
- }
-
__archive_read_consume(a, zip->unconsumed);
zip->unconsumed = 0;
+ if (!zip->tctx_valid &&
+ zip->entry->zip_flags & (ZIP_ENCRYPTED | ZIP_STRONG_ENCRYPTED)) {
+ zip->has_encrypted_entries = 1;
+ if (zip->entry->zip_flags & ZIP_STRONG_ENCRYPTED) {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_FILE_FORMAT,
+ "Encrypted file is unsupported");
+ return (ARCHIVE_FAILED);
+ } else if (archive_strlen(&zip->password) == 0) {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_MISC,
+ "This is encrypted file and passowrd is needed");
+ return (ARCHIVE_FAILED);
+ }
+
+ /*
+ * Initialize ctx for Traditional PKWARE Decyption.
+ */
+ if (!zip->tctx_valid) {
+ const void *p;
+ unsigned int crcchk;
+
+ trad_enc_init(&zip->tctx, zip->password.s,
+ archive_strlen(&zip->password));
+
+ /*
+ Read the 12 bytes encryption header stored at
+ the start of the data area.
+ */
+#define ENC_HEADER_SIZE 12
+ p = __archive_read_ahead(a, ENC_HEADER_SIZE, NULL);
+ if (p == NULL) {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_FILE_FORMAT,
+ "Truncated ZIP file data");
+ return (ARCHIVE_FATAL);
+ }
+ crcchk = trad_enc_read_encyption_header(&zip->tctx, p,
+ ENC_HEADER_SIZE);
+ if (crcchk != zip->entry->decdat) {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_MISC, "Incorrect passowrd");
+ return (ARCHIVE_FAILED);
+ }
+ __archive_read_consume(a, ENC_HEADER_SIZE);
+ zip->tctx_valid = 1;
+ zip->entry_bytes_remaining -= ENC_HEADER_SIZE;
+ //zip->entry_uncompressed_bytes_read += ENC_HEADER_SIZE;
+ zip->entry_compressed_bytes_read += ENC_HEADER_SIZE;
+ if (zip->decrypted_buffer == NULL) {
+ size_t bs = 256 * 1024;
+ zip->decrypted_buffer_size = bs;
+ zip->decrypted_buffer = malloc(bs);
+ if (zip->decrypted_buffer == NULL) {
+ archive_set_error(&a->archive, ENOMEM,
+ "No memory for ZIP decryption");
+ return (ARCHIVE_FATAL);
+ }
+ }
+ zip->decrypted_bytes_remaining = 0;
+#undef ENC_HEADER_SIZE
+ }
+ }
+
switch(zip->entry->compression) {
case 0: /* No compression. */
r = zip_read_data_none(a, buff, size, offset);
zip_entry = next_zip_entry;
}
}
+ archive_string_free(&zip->password);
+ free(zip->decrypted_buffer);
free(zip);
(a->format->data) = NULL;
return (ARCHIVE_OK);
} else if (strcmp(key, "mac-ext") == 0) {
zip->process_mac_extensions = (val != NULL && val[0] != 0);
return (ARCHIVE_OK);
+ } else if (strcmp(key, "password") == 0) {
+ if (val != NULL)
+ archive_strcpy(&zip->password, val);
+ else
+ archive_string_empty(&zip->password);
+ return (ARCHIVE_OK);
}
/* Note: The "warn" return is just to inform the options
}
zip->entry = zip->zip_entries;
memset(zip->entry, 0, sizeof(struct zip_entry));
+ zip->tctx_valid = 0;
/* Search ahead for the next local file header. */
__archive_read_consume(a, zip->unconsumed);
archive_check_magic(_a, ARCHIVE_READ_MAGIC,
ARCHIVE_STATE_NEW, "archive_read_support_format_zip");
- zip = (struct zip *)malloc(sizeof(*zip));
+ zip = (struct zip *)calloc(1, sizeof(*zip));
if (zip == NULL) {
archive_set_error(&a->archive, ENOMEM,
"Can't allocate zip data");
return (ARCHIVE_FATAL);
}
- memset(zip, 0, sizeof(*zip));
/* Streamable reader doesn't support mac extensions. */
zip->process_mac_extensions = 0;
zip_entry->compression = (char)archive_le16dec(p + 10);
zip_entry->mtime = zip_time(p + 12);
zip_entry->crc32 = archive_le32dec(p + 16);
+ if (zip_entry->zip_flags & ZIP_LENGTH_AT_END)
+ zip_entry->decdat = p[13];
+ else
+ zip_entry->decdat = p[19];
zip_entry->compressed_size = archive_le32dec(p + 20);
zip_entry->uncompressed_size = archive_le32dec(p + 24);
filename_length = archive_le16dec(p + 28);
else
rsrc = NULL;
+ zip->tctx_valid = 0;
+
/* File entries are sorted by the header offset, we should mostly
* use __archive_read_consume to advance a read point to avoid redundant
* data reading. */
archive_check_magic(_a, ARCHIVE_READ_MAGIC,
ARCHIVE_STATE_NEW, "archive_read_support_format_zip_seekable");
- zip = (struct zip *)malloc(sizeof(*zip));
+ zip = (struct zip *)calloc(1, sizeof(*zip));
if (zip == NULL) {
archive_set_error(&a->archive, ENOMEM,
"Can't allocate zip data");
return (ARCHIVE_FATAL);
}
- memset(zip, 0, sizeof(*zip));
#ifdef HAVE_COPYFILE_H
/* Set this by default on Mac OS. */
--- /dev/null
+/*-
+ * Copyright (c) 2013 Konrad Kleine
+ * Copyright (c) 2014 Michihiro NAKAJIMA
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+__FBSDID("$FreeBSD$");
+
+DEFINE_TEST(test_read_format_zip_traditional_encryption_data)
+{
+ /* This file is password protected (Traditional PKWARE Enctypted).
+ The headers are NOT encrypted. Password is "12345678". */
+ const char *refname =
+ "test_read_format_zip_traditional_encryption_data.zip";
+ struct archive_entry *ae;
+ struct archive *a;
+ char buff[512];
+
+ extract_reference_file(refname);
+
+ /*
+ * Extract a zip file without password.
+ */
+ assert((a = archive_read_new()) != NULL);
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a));
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
+ assertEqualIntA(a, ARCHIVE_OK,
+ archive_read_open_filename(a, refname, 10240));
+
+ assertEqualIntA(a, ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW,
+ archive_read_has_encrypted_entries(a));
+
+ /* Verify encrypted file "bar.txt" */
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+ assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae));
+ assertEqualString("bar.txt", archive_entry_pathname(ae));
+ assertEqualInt(495, archive_entry_size(ae));
+ assertEqualInt(1, archive_entry_is_data_encrypted(ae));
+ assertEqualInt(0, archive_entry_is_metadata_encrypted(ae));
+ assertEqualIntA(a, 1, archive_read_has_encrypted_entries(a));
+ assertEqualInt(ARCHIVE_FAILED, archive_read_data(a, buff, sizeof(buff)));
+
+ /* Verify encrypted file "foo.txt" */
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+ assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae));
+ assertEqualString("foo.txt", archive_entry_pathname(ae));
+ assertEqualInt(495, archive_entry_size(ae));
+ assertEqualInt(1, archive_entry_is_data_encrypted(ae));
+ assertEqualInt(0, archive_entry_is_metadata_encrypted(ae));
+ assertEqualIntA(a, 1, archive_read_has_encrypted_entries(a));
+ assertEqualInt(ARCHIVE_FAILED, archive_read_data(a, buff, sizeof(buff)));
+
+ assertEqualInt(2, archive_file_count(a));
+
+ /* End of archive. */
+ assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae));
+
+ /* Verify archive format. */
+ assertEqualIntA(a, ARCHIVE_FILTER_NONE, archive_filter_code(a, 0));
+ assertEqualIntA(a, ARCHIVE_FORMAT_ZIP, archive_format(a));
+
+ /* Close the archive. */
+ assertEqualInt(ARCHIVE_OK, archive_read_close(a));
+ assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+
+
+ /*
+ * Extract a zip file with password.
+ */
+ assert((a = archive_read_new()) != NULL);
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a));
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
+ assertEqualIntA(a, ARCHIVE_OK,
+ archive_read_set_options(a, "zip:password=12345678"));
+ assertEqualIntA(a, ARCHIVE_OK,
+ archive_read_open_filename(a, refname, 10240));
+
+ assertEqualIntA(a, ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW,
+ archive_read_has_encrypted_entries(a));
+
+ /* Verify encrypted file "bar.txt" */
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+ assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae));
+ assertEqualString("bar.txt", archive_entry_pathname(ae));
+ assertEqualInt(495, archive_entry_size(ae));
+ assertEqualInt(1, archive_entry_is_data_encrypted(ae));
+ assertEqualInt(0, archive_entry_is_metadata_encrypted(ae));
+ assertEqualIntA(a, 1, archive_read_has_encrypted_entries(a));
+ assertEqualInt(495, archive_read_data(a, buff, sizeof(buff)));
+
+ /* Verify encrypted file "foo.txt" */
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+ assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae));
+ assertEqualString("foo.txt", archive_entry_pathname(ae));
+ assertEqualInt(495, archive_entry_size(ae));
+ assertEqualInt(1, archive_entry_is_data_encrypted(ae));
+ assertEqualInt(0, archive_entry_is_metadata_encrypted(ae));
+ assertEqualIntA(a, 1, archive_read_has_encrypted_entries(a));
+ assertEqualInt(495, archive_read_data(a, buff, sizeof(buff)));
+
+ assertEqualInt(2, archive_file_count(a));
+
+ /* End of archive. */
+ assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae));
+
+ /* Verify archive format. */
+ assertEqualIntA(a, ARCHIVE_FILTER_NONE, archive_filter_code(a, 0));
+ assertEqualIntA(a, ARCHIVE_FORMAT_ZIP, archive_format(a));
+
+ /* Close the archive. */
+ assertEqualInt(ARCHIVE_OK, archive_read_close(a));
+ assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+}
+