return (ret);
}
+static int
+check_authentication_code(struct archive_read *a, const void *_p)
+{
+ struct zip *zip = (struct zip *)(a->format->data);
+
+ /* Check authentication code. */
+ if (zip->hctx_valid) {
+ const void *p;
+ uint8_t hmac[20];
+ size_t hmac_len = 20;
+ int cmp;
+
+ archive_hmac_sha1_final(&zip->hctx, hmac, &hmac_len);
+ if (_p == NULL) {
+ /* Read authentication code. */
+ p = __archive_read_ahead(a, AUTH_CODE_SIZE, NULL);
+ if (p == NULL) {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_FILE_FORMAT,
+ "Truncated ZIP file data");
+ return (ARCHIVE_FATAL);
+ }
+ } else {
+ p = _p;
+ }
+ cmp = memcmp(hmac, p, AUTH_CODE_SIZE);
+ __archive_read_consume(a, AUTH_CODE_SIZE);
+ if (cmp != 0) {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_MISC,
+ "ZIP bad Authentication code");
+ return (ARCHIVE_WARN);
+ }
+ }
+ return (ARCHIVE_OK);
+}
+
/*
* Read "uncompressed" data. There are three cases:
* 1) We know the size of the data. This is always true for the
struct zip *zip;
const char *buff;
ssize_t bytes_avail;
+ int r;
(void)offset; /* UNUSED */
if (zip->entry->zip_flags & ZIP_LENGTH_AT_END) {
const char *p;
+ ssize_t grabbing_bytes = 24;
+ if (zip->hctx_valid)
+ grabbing_bytes += AUTH_CODE_SIZE;
/* Grab at least 24 bytes. */
- buff = __archive_read_ahead(a, 24, &bytes_avail);
- if (bytes_avail < 24) {
+ buff = __archive_read_ahead(a, grabbing_bytes, &bytes_avail);
+ if (bytes_avail < grabbing_bytes) {
/* Zip archives have end-of-archive markers
that are longer than this, so a failure to get at
least 24 bytes really does indicate a truncated
/* Check for a complete PK\007\010 signature, followed
* by the correct 4-byte CRC. */
p = buff;
+ if (zip->hctx_valid)
+ p += AUTH_CODE_SIZE;
if (p[0] == 'P' && p[1] == 'K'
&& p[2] == '\007' && p[3] == '\010'
&& (archive_le32dec(p + 4) == zip->entry_crc32
- || zip->ignore_crc32)) {
+ || zip->ignore_crc32
+ || (zip->hctx_valid
+ && zip->entry->aes_extra.vendor == AES_VENDOR_AE_2))) {
if (zip->entry->flags & LA_USED_ZIP64) {
zip->entry->crc32 = archive_le32dec(p + 4);
zip->entry->compressed_size =
archive_le32dec(p + 12);
zip->unconsumed = 16;
}
+ if (zip->hctx_valid) {
+ r = check_authentication_code(a, buff);
+ if (r != ARCHIVE_OK)
+ return (r);
+ }
zip->end_of_entry = 1;
return (ARCHIVE_OK);
}
else if (p[3] == '\007') { p += 1; }
else if (p[3] == '\010' && p[2] == '\007'
&& p[1] == 'K' && p[0] == 'P') {
+ if (zip->hctx_valid)
+ p -= AUTH_CODE_SIZE;
break;
} else { p += 4; }
}
} else {
if (zip->entry_bytes_remaining == 0) {
zip->end_of_entry = 1;
+ if (zip->hctx_valid) {
+ r = check_authentication_code(a, NULL);
+ if (r != ARCHIVE_OK)
+ return (r);
+ }
return (ARCHIVE_OK);
}
/* Grab a bunch of bytes. */
{
struct zip *zip;
ssize_t bytes_avail;
- const void *compressed_buff;
+ const void *compressed_buff, *sp;
int r;
(void)offset; /* UNUSED */
* available bytes; asking for more than that forces the
* decompressor to combine reads by copying data.
*/
- compressed_buff = __archive_read_ahead(a, 1, &bytes_avail);
+ compressed_buff = sp = __archive_read_ahead(a, 1, &bytes_avail);
if (0 == (zip->entry->zip_flags & ZIP_LENGTH_AT_END)
&& bytes_avail > zip->entry_bytes_remaining) {
bytes_avail = (ssize_t)zip->entry_bytes_remaining;
buff_remaining);
} else {
size_t dsize = buff_remaining;
- archive_hmac_sha1_update(&zip->hctx,
- compressed_buff, buff_remaining);
- archive_decrypto_aes_ctr_update(&zip->cctx,
+ archive_decrypto_aes_ctr_update(
+ &zip->cctx,
compressed_buff, buff_remaining,
zip->decrypted_ptr
+ zip->decrypted_bytes_remaining,
else
zip->decrypted_ptr += bytes_avail;
}
+ /* Calculate compressed data as much as we used.*/
+ if (zip->hctx_valid)
+ archive_hmac_sha1_update(&zip->hctx, sp, bytes_avail);
__archive_read_consume(a, bytes_avail);
zip->entry_bytes_remaining -= bytes_avail;
zip->entry_compressed_bytes_read += bytes_avail;
zip->entry_uncompressed_bytes_read += zip->stream.total_out;
*buff = zip->uncompressed_buffer;
+ if (zip->end_of_entry && zip->hctx_valid) {
+ r = check_authentication_code(a, NULL);
+ if (r != ARCHIVE_OK)
+ return (r);
+ }
+
if (zip->end_of_entry && (zip->entry->zip_flags & ZIP_LENGTH_AT_END)) {
const char *p;
return (ARCHIVE_FAILED);
}
zip->cctx_valid = zip->hctx_valid = 1;
-
__archive_read_consume(a, salt_len + 2);
zip->entry_bytes_remaining -= salt_len + 2 + AUTH_CODE_SIZE;
- if (zip->entry_bytes_remaining < 0)
+ if (0 == (zip->entry->zip_flags & ZIP_LENGTH_AT_END)
+ && zip->entry_bytes_remaining < 0)
goto corrupted;
zip->entry_compressed_bytes_read += salt_len + 2 + AUTH_CODE_SIZE;
zip->decrypted_bytes_remaining = 0;
(unsigned)*size);
/* If we hit the end, swallow any end-of-data marker. */
if (zip->end_of_entry) {
- /* Check authentication code. */
- if (zip->hctx_valid) {
- const void *p;
- uint8_t hmac[20];
- size_t hmac_len = 20;
- int cmp;
-
- archive_hmac_sha1_final(&zip->hctx, hmac, &hmac_len);
- /* Read authentication code. */
- p = __archive_read_ahead(a, AUTH_CODE_SIZE, NULL);
- if (p == NULL) {
- archive_set_error(&a->archive,
- ARCHIVE_ERRNO_FILE_FORMAT,
- "Truncated ZIP file data");
- return (ARCHIVE_FATAL);
- }
- cmp = memcmp(hmac, p, AUTH_CODE_SIZE);
- __archive_read_consume(a, AUTH_CODE_SIZE);
- if (cmp != 0) {
- archive_set_error(&a->archive,
- ARCHIVE_ERRNO_MISC,
- "ZIP bad Authentication code");
- return (ARCHIVE_WARN);
- }
- }
/* Check file size, CRC against these values. */
if (zip->entry->compressed_size !=
zip->entry_compressed_bytes_read) {
free(zip->decrypted_buffer);
if (zip->cctx_valid)
archive_decrypto_aes_ctr_release(&zip->cctx);
+ if (zip->hctx_valid)
+ archive_hmac_sha1_cleanup(&zip->hctx);
free(zip->iv);
free(zip->erd);
free(zip->v_data);
zip->entry = zip->zip_entries;
memset(zip->entry, 0, sizeof(struct zip_entry));
- if (zip->cctx_valid) {
+ if (zip->cctx_valid)
archive_decrypto_aes_ctr_release(&zip->cctx);
- zip->cctx_valid = 0;
- }
+ if (zip->hctx_valid)
+ archive_hmac_sha1_cleanup(&zip->hctx);
zip->tctx_valid = zip->cctx_valid = zip->hctx_valid = 0;
/* Search ahead for the next local file header. */
else
rsrc = NULL;
- if (zip->cctx_valid) {
+ if (zip->cctx_valid)
archive_decrypto_aes_ctr_release(&zip->cctx);
- zip->cctx_valid = 0;
- }
+ if (zip->hctx_valid)
+ archive_hmac_sha1_cleanup(&zip->hctx);
zip->tctx_valid = zip->cctx_valid = zip->hctx_valid = 0;
/* File entries are sorted by the header offset, we should mostly
#endif
#include "archive.h"
+#include "archive_cryptor_private.h"
#include "archive_endian.h"
#include "archive_entry.h"
#include "archive_entry_locale.h"
+#include "archive_hmac_private.h"
#include "archive_private.h"
#include "archive_random_private.h"
#include "archive_write_private.h"
};
#define TRAD_HEADER_SIZE 12
-
+/*
+ * See "WinZip - AES Encryption Information"
+ * http://www.winzip.com/aes_info.htm
+ */
+/* Value used in compression method. */
+#define WINZIP_AES_ENCRYPTION 99
+/* A WinZip AES header size which is stored at the beginning of
+ * file contents. */
+#define WINZIP_AES128_HEADER_SIZE (8 + 2)
+#define WINZIP_AES256_HEADER_SIZE (16 + 2)
+/* AES vendor version. */
+#define AES_VENDOR_AE_1 0x0001
+#define AES_VENDOR_AE_2 0x0002
+/* Authentication code size. */
+#define AUTH_CODE_SIZE 10
+/**/
+#define MAX_DERIVED_KEY_BUF_SIZE (AES_MAX_KEY_SIZE * 2 + 2)
struct cd_segment {
struct cd_segment *next;
struct trad_enc_ctx tctx;
char tctx_valid;
unsigned char trad_chkdat;
+ unsigned aes_vendor;
+ archive_crypto_ctx cctx;
+ char cctx_valid;
+ archive_hmac_sha1_ctx hctx;
+ char hctx_valid;
unsigned char *file_header;
size_t file_header_extra_offset;
static int trad_enc_init(struct trad_enc_ctx *, const char *, size_t);
static unsigned trad_enc_encrypt_update(struct trad_enc_ctx *, const uint8_t *,
size_t, uint8_t *, size_t);
+static int init_traditional_pkware_encryption(struct archive_write *);
+static int is_traditional_pkware_encryption_supported(void);
+static int init_winzip_aes_encryption(struct archive_write *);
+static int is_winzip_aes_encryption_supported(int encryption);
static unsigned char *
cd_alloc(struct zip *zip, size_t length)
zip->encryption_type = ENCRYPTION_NONE;
ret = ARCHIVE_OK;
} else if (val[0] == '1' || strcmp(val, "traditional") == 0) {
- zip->encryption_type = ENCRYPTION_TRADITIONAL;
- ret = ARCHIVE_OK;
+ if (is_traditional_pkware_encryption_supported()) {
+ zip->encryption_type = ENCRYPTION_TRADITIONAL;
+ ret = ARCHIVE_OK;
+ } else {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_MISC,
+ "encryption not supported");
+ }
+ } else if (strcmp(val, "aes128") == 0) {
+ if (is_winzip_aes_encryption_supported(
+ ENCRYPTION_WINZIP_AES128)) {
+ zip->encryption_type = ENCRYPTION_WINZIP_AES128;
+ ret = ARCHIVE_OK;
+ } else {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_MISC,
+ "encryption not supported");
+ }
+ } else if (strcmp(val, "aes256") == 0) {
+ if (is_winzip_aes_encryption_supported(
+ ENCRYPTION_WINZIP_AES256)) {
+ zip->encryption_type = ENCRYPTION_WINZIP_AES256;
+ ret = ARCHIVE_OK;
+ } else {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_MISC,
+ "encryption not supported");
+ }
} else {
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
"%s: unknown encryption '%s'",
archive_write_zip_header(struct archive_write *a, struct archive_entry *entry)
{
unsigned char local_header[32];
- unsigned char local_extra[128];
+ unsigned char local_extra[144];
struct zip *zip = a->format_data;
unsigned char *e;
unsigned char *cd_extra;
archive_entry_free(zip->entry);
zip->entry = NULL;
}
- zip->tctx_valid = 0;
+
+ if (zip->cctx_valid)
+ archive_encrypto_aes_ctr_release(&zip->cctx);
+ if (zip->hctx_valid)
+ archive_hmac_sha1_cleanup(&zip->hctx);
+ zip->tctx_valid = zip->cctx_valid = zip->hctx_valid = 0;
+
+ if (type == AE_IFREG
+ &&(!archive_entry_size_is_set(entry)
+ || archive_entry_size(entry) > 0)) {
+ switch (zip->encryption_type) {
+ case ENCRYPTION_TRADITIONAL:
+ case ENCRYPTION_WINZIP_AES128:
+ case ENCRYPTION_WINZIP_AES256:
+ zip->entry_flags |= ZIP_ENTRY_FLAG_ENCRYPTED;
+ zip->entry_encryption = zip->encryption_type;
+ break;
+ default:
+ break;
+ }
+ }
+
#if defined(_WIN32) && !defined(__CYGWIN__)
/* Make sure the path separators in pahtname, hardlink and symlink
version_needed = 20;
} else if (archive_entry_size_is_set(zip->entry)) {
int64_t size = archive_entry_size(zip->entry);
+ int64_t additional_size = 0;
+
zip->entry_uncompressed_limit = size;
zip->entry_compression = zip->requested_compression;
if (zip->entry_compression == COMPRESSION_UNSPECIFIED) {
zip->entry_compression = COMPRESSION_DEFAULT;
}
- if (archive_entry_size(zip->entry) > 0)
- zip->entry_encryption = zip->encryption_type;
if (zip->entry_compression == COMPRESSION_STORE) {
zip->entry_compressed_size = size;
zip->entry_uncompressed_size = size;
zip->entry_uncompressed_size = size;
version_needed = 20;
}
- if (zip->entry_encryption == ENCRYPTION_TRADITIONAL) {
+
+ if (zip->entry_flags | ZIP_ENTRY_FLAG_ENCRYPTED) {
+ switch (zip->entry_encryption) {
+ case ENCRYPTION_TRADITIONAL:
+ additional_size = TRAD_HEADER_SIZE;
+ version_needed = 20;
+ break;
+ case ENCRYPTION_WINZIP_AES128:
+ additional_size = WINZIP_AES128_HEADER_SIZE
+ + AUTH_CODE_SIZE;
+ version_needed = 20;
+ break;
+ case ENCRYPTION_WINZIP_AES256:
+ additional_size = WINZIP_AES256_HEADER_SIZE
+ + AUTH_CODE_SIZE;
+ version_needed = 20;
+ break;
+ default:
+ break;
+ }
if (zip->entry_compression == COMPRESSION_STORE)
- zip->entry_compressed_size += TRAD_HEADER_SIZE;
- version_needed = 20;
- zip->entry_flags |= ZIP_ENTRY_FLAG_ENCRYPTED;
+ zip->entry_compressed_size += additional_size;
}
+
if ((zip->flags & ZIP_FLAG_FORCE_ZIP64) /* User asked. */
|| (zip->entry_uncompressed_size > 0xffffffffLL)) {
/* Large entry. */
* length-at-end more reliable. */
zip->entry_compression = COMPRESSION_DEFAULT;
zip->entry_flags |= ZIP_ENTRY_FLAG_LENGTH_AT_END;
- zip->entry_encryption = zip->encryption_type;
if ((zip->flags & ZIP_FLAG_AVOID_ZIP64) == 0) {
zip->entry_uses_zip64 = 1;
version_needed = 45;
} else {
version_needed = 20;
}
- if (zip->entry_encryption == ENCRYPTION_TRADITIONAL) {
- zip->entry_flags |= ZIP_ENTRY_FLAG_ENCRYPTED;
- if (version_needed < 20)
- version_needed = 20;
+
+ if (zip->entry_flags | ZIP_ENTRY_FLAG_ENCRYPTED) {
+ switch (zip->entry_encryption) {
+ case ENCRYPTION_TRADITIONAL:
+ case ENCRYPTION_WINZIP_AES128:
+ case ENCRYPTION_WINZIP_AES256:
+ if (version_needed < 20)
+ version_needed = 20;
+ break;
+ default:
+ break;
+ }
}
}
memcpy(local_header, "PK\003\004", 4);
archive_le16enc(local_header + 4, version_needed);
archive_le16enc(local_header + 6, zip->entry_flags);
- archive_le16enc(local_header + 8, zip->entry_compression);
+ if (zip->entry_encryption == ENCRYPTION_WINZIP_AES128
+ || zip->entry_encryption == ENCRYPTION_WINZIP_AES256)
+ archive_le16enc(local_header + 8, WINZIP_AES_ENCRYPTION);
+ else
+ archive_le16enc(local_header + 8, zip->entry_compression);
archive_le32enc(local_header + 10,
dos_time(archive_entry_mtime(zip->entry)));
archive_le32enc(local_header + 14, zip->entry_crc32);
archive_le16enc(zip->file_header + 4, 3 * 256 + version_needed);
archive_le16enc(zip->file_header + 6, version_needed);
archive_le16enc(zip->file_header + 8, zip->entry_flags);
- archive_le16enc(zip->file_header + 10, zip->entry_compression);
+ if (zip->entry_encryption == ENCRYPTION_WINZIP_AES128
+ || zip->entry_encryption == ENCRYPTION_WINZIP_AES256)
+ archive_le16enc(zip->file_header + 10, WINZIP_AES_ENCRYPTION);
+ else
+ archive_le16enc(zip->file_header + 10, zip->entry_compression);
archive_le32enc(zip->file_header + 12,
dos_time(archive_entry_mtime(zip->entry)));
archive_le16enc(zip->file_header + 28, filename_length);
archive_le32enc(e, (uint32_t)archive_entry_gid(entry));
e += 4;
- /* Copy UT and ux into central directory as well. */
+ /* AES extra data field: WinZIP AES information, ID=0x9901 */
+ if ((zip->entry_flags | ZIP_ENTRY_FLAG_ENCRYPTED)
+ && (zip->entry_encryption == ENCRYPTION_WINZIP_AES128
+ || zip->entry_encryption == ENCRYPTION_WINZIP_AES256)) {
+
+ memcpy(e, "\001\231\007\000\001\000AE", 8);
+ /* AES vendoer version AE-2 does not store a CRC.
+ * WinZip 11 uses AE-1, which does store the CRC,
+ * but it does not store the CRC when the file size
+ * is less than 20 bytes. So we simulate what
+ * WinZip 11 does.
+ * NOTE: WinZip 9.0 and 10.0 uses AE-2 by default. */
+ if (archive_entry_size_is_set(zip->entry)
+ && archive_entry_size(zip->entry) < 20) {
+ archive_le16enc(e+4, AES_VENDOR_AE_2);
+ zip->aes_vendor = AES_VENDOR_AE_2;/* no CRC. */
+ } else
+ zip->aes_vendor = AES_VENDOR_AE_1;
+ e += 8;
+ /* AES encryption strength. */
+ *e++ = (zip->entry_encryption == ENCRYPTION_WINZIP_AES128)?1:3;
+ /* Actual compression method. */
+ archive_le16enc(e, zip->entry_compression);
+ e += 2;
+ }
+
+ /* Copy UT ,ux, and AES-extra into central directory as well. */
zip->file_header_extra_offset = zip->central_directory_bytes;
cd_extra = cd_alloc(zip, e - local_extra);
memcpy(cd_extra, local_extra, e - local_extra);
if (s == 0) return 0;
- if (zip->entry_encryption == ENCRYPTION_TRADITIONAL
- && zip->tctx_valid == 0) {
- /* Initialize traditoinal PKWARE encryption context. */
- uint8_t key[TRAD_HEADER_SIZE];
- uint8_t key_encrypted[TRAD_HEADER_SIZE];
-
- if (zip->password.s == NULL
- || archive_strlen(&zip->password) == 0) {
- archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
- "Encryption needs password");
- return ARCHIVE_FAILED;
- }
- if (archive_random(key, sizeof(key)-1) != ARCHIVE_OK) {
- archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
- "Can't generate random number for encryption");
- return ARCHIVE_FATAL;
+ if (zip->entry_flags | ZIP_ENTRY_FLAG_ENCRYPTED) {
+ switch (zip->entry_encryption) {
+ case ENCRYPTION_TRADITIONAL:
+ /* Initialize traditoinal PKWARE encryption context. */
+ if (!zip->tctx_valid) {
+ ret = init_traditional_pkware_encryption(a);
+ if (ret != ARCHIVE_OK)
+ return (ret);
+ zip->tctx_valid = 1;
+ }
+ break;
+ case ENCRYPTION_WINZIP_AES128:
+ case ENCRYPTION_WINZIP_AES256:
+ if (!zip->cctx_valid) {
+ ret = init_winzip_aes_encryption(a);
+ if (ret != ARCHIVE_OK)
+ return (ret);
+ zip->cctx_valid = zip->hctx_valid = 1;
+ }
+ break;
+ default:
+ break;
}
- trad_enc_init(&zip->tctx, zip->password.s,
- archive_strlen(&zip->password));
- /* Set the last key code which will be used as a check code
- * for ferifying password in decryption. */
- key[TRAD_HEADER_SIZE-1] = zip->trad_chkdat;
- trad_enc_encrypt_update(&zip->tctx, key, TRAD_HEADER_SIZE,
- key_encrypted, TRAD_HEADER_SIZE);
- /* Write encrypted keys in the top of the file content. */
- ret = __archive_write_output(a, key_encrypted,
- TRAD_HEADER_SIZE);
- if (ret != ARCHIVE_OK)
- return (ret);
- zip->written_bytes += TRAD_HEADER_SIZE;
- zip->entry_compressed_written += TRAD_HEADER_SIZE;
- zip->tctx_valid = 1;
}
switch (zip->entry_compression) {
case COMPRESSION_STORE:
- if (zip->tctx_valid) {
+ if (zip->tctx_valid || zip->cctx_valid) {
const uint8_t *rb = (const uint8_t *)buff;
const uint8_t * const re = rb + s;
while (rb < re) {
size_t l;
- l = trad_enc_encrypt_update(&zip->tctx,
- rb, re - rb, zip->buf, zip->len_buf);
+ if (zip->tctx_valid) {
+ l = trad_enc_encrypt_update(&zip->tctx,
+ rb, re - rb,
+ zip->buf, zip->len_buf);
+ } else {
+ l = zip->len_buf;
+ ret = archive_encrypto_aes_ctr_update(
+ &zip->cctx,
+ rb, re - rb, zip->buf, &l);
+ if (ret < 0) {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_MISC,
+ "Failed to encrypt file");
+ return (ARCHIVE_FAILED);
+ }
+ archive_hmac_sha1_update(&zip->hctx,
+ zip->buf, l);
+ }
ret = __archive_write_output(a, zip->buf, l);
if (ret != ARCHIVE_OK)
return (ret);
trad_enc_encrypt_update(&zip->tctx,
zip->buf, zip->len_buf,
zip->buf, zip->len_buf);
+ } else if (zip->cctx_valid) {
+ size_t outl = zip->len_buf;
+ ret = archive_encrypto_aes_ctr_update(
+ &zip->cctx,
+ zip->buf, zip->len_buf,
+ zip->buf, &outl);
+ if (ret < 0) {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_MISC,
+ "Failed to encrypt file");
+ return (ARCHIVE_FAILED);
+ }
+ archive_hmac_sha1_update(&zip->hctx,
+ zip->buf, zip->len_buf);
}
ret = __archive_write_output(a, zip->buf,
zip->len_buf);
}
zip->entry_uncompressed_limit -= s;
- zip->entry_crc32 = zip->crc32func(zip->entry_crc32, buff, (unsigned)s);
+ if (!zip->cctx_valid || zip->aes_vendor != AES_VENDOR_AE_2)
+ zip->entry_crc32 =
+ zip->crc32func(zip->entry_crc32, buff, (unsigned)s);
return (s);
}
if (zip->entry_compression == COMPRESSION_DEFLATE) {
for (;;) {
size_t remainder;
+
ret = deflate(&zip->stream, Z_FINISH);
if (ret == Z_STREAM_ERROR)
return (ARCHIVE_FATAL);
if (zip->tctx_valid) {
trad_enc_encrypt_update(&zip->tctx,
zip->buf, remainder, zip->buf, remainder);
+ } else if (zip->cctx_valid) {
+ size_t outl = remainder;
+ ret = archive_encrypto_aes_ctr_update(
+ &zip->cctx, zip->buf, remainder,
+ zip->buf, &outl);
+ if (ret < 0) {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_MISC,
+ "Failed to encrypt file");
+ return (ARCHIVE_FAILED);
+ }
+ archive_hmac_sha1_update(&zip->hctx,
+ zip->buf, remainder);
}
ret = __archive_write_output(a, zip->buf, remainder);
if (ret != ARCHIVE_OK)
deflateEnd(&zip->stream);
}
#endif
+ if (zip->hctx_valid) {
+ uint8_t hmac[20];
+ size_t hmac_len = 20;
+
+ archive_hmac_sha1_final(&zip->hctx, hmac, &hmac_len);
+ ret = __archive_write_output(a, hmac, AUTH_CODE_SIZE);
+ if (ret != ARCHIVE_OK)
+ return (ret);
+ zip->entry_compressed_written += AUTH_CODE_SIZE;
+ zip->written_bytes += AUTH_CODE_SIZE;
+ }
/* Write trailing data descriptor. */
if ((zip->entry_flags & ZIP_ENTRY_FLAG_LENGTH_AT_END) != 0) {
char d[24];
memcpy(d, "PK\007\010", 4);
- archive_le32enc(d + 4, zip->entry_crc32);
+ if (zip->cctx_valid && zip->aes_vendor == AES_VENDOR_AE_2)
+ archive_le32enc(d + 4, 0);/* no CRC.*/
+ else
+ archive_le32enc(d + 4, zip->entry_crc32);
if (zip->entry_uses_zip64) {
archive_le64enc(d + 8,
(uint64_t)zip->entry_compressed_written);
}
/* Fix up central directory file header. */
- archive_le32enc(zip->file_header + 16, zip->entry_crc32);
+ if (zip->cctx_valid && zip->aes_vendor == AES_VENDOR_AE_2)
+ archive_le32enc(zip->file_header + 16, 0);/* no CRC.*/
+ else
+ archive_le32enc(zip->file_header + 16, zip->entry_crc32);
archive_le32enc(zip->file_header + 20,
zipmin(zip->entry_compressed_written, 0xffffffffLL));
archive_le32enc(zip->file_header + 24,
memset(zip->password.s, 0, archive_strlen(&zip->password));
archive_string_free(&zip->password);
}
+ if (zip->cctx_valid)
+ archive_encrypto_aes_ctr_release(&zip->cctx);
+ if (zip->hctx_valid)
+ archive_hmac_sha1_cleanup(&zip->hctx);
/* TODO: Free opt_sconv, sconv_default */
free(zip);
trad_enc_update_keys(ctx, *pw++);
return 0;
}
+
+static int
+is_traditional_pkware_encryption_supported(void)
+{
+ uint8_t key[TRAD_HEADER_SIZE];
+
+ if (archive_random(key, sizeof(key)-1) != ARCHIVE_OK)
+ return (0);
+ return (1);
+}
+
+static int
+init_traditional_pkware_encryption(struct archive_write *a)
+{
+ struct zip *zip = a->format_data;
+ uint8_t key[TRAD_HEADER_SIZE];
+ uint8_t key_encrypted[TRAD_HEADER_SIZE];
+ int ret;
+
+ if (zip->password.s == NULL
+ || archive_strlen(&zip->password) == 0) {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Encryption needs password");
+ return ARCHIVE_FAILED;
+ }
+ if (archive_random(key, sizeof(key)-1) != ARCHIVE_OK) {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Can't generate random number for encryption");
+ return ARCHIVE_FATAL;
+ }
+ trad_enc_init(&zip->tctx, zip->password.s,
+ archive_strlen(&zip->password));
+ /* Set the last key code which will be used as a check code
+ * for ferifying password in decryption. */
+ key[TRAD_HEADER_SIZE-1] = zip->trad_chkdat;
+ trad_enc_encrypt_update(&zip->tctx, key, TRAD_HEADER_SIZE,
+ key_encrypted, TRAD_HEADER_SIZE);
+ /* Write encrypted keys in the top of the file content. */
+ ret = __archive_write_output(a, key_encrypted, TRAD_HEADER_SIZE);
+ if (ret != ARCHIVE_OK)
+ return (ret);
+ zip->written_bytes += TRAD_HEADER_SIZE;
+ zip->entry_compressed_written += TRAD_HEADER_SIZE;
+ return (ret);
+}
+
+static int
+init_winzip_aes_encryption(struct archive_write *a)
+{
+ struct zip *zip = a->format_data;
+ size_t key_len, salt_len;
+ uint8_t salt[16 + 2];
+ uint8_t derived_key[MAX_DERIVED_KEY_BUF_SIZE];
+ int ret;
+
+ if (zip->password.s == NULL
+ || archive_strlen(&zip->password) == 0) {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Encryption needs password");
+ return (ARCHIVE_FAILED);
+ }
+ if (zip->entry_encryption == ENCRYPTION_WINZIP_AES128) {
+ salt_len = 8;
+ key_len = 16;
+ } else {
+ /* AES 256 */
+ salt_len = 16;
+ key_len = 32;
+ }
+ if (archive_random(salt, salt_len) != ARCHIVE_OK) {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Can't generate random number for encryption");
+ return (ARCHIVE_FATAL);
+ }
+ archive_pbkdf2_sha1(zip->password.s, archive_strlen(&zip->password),
+ salt, salt_len, 1000, derived_key, key_len * 2 + 2);
+
+ ret = archive_encrypto_aes_ctr_init(&zip->cctx, derived_key, key_len);
+ if (ret != 0) {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Decryption is unsupported due to lack of crypto library");
+ return (ARCHIVE_FAILED);
+ }
+ ret = archive_hmac_sha1_init(&zip->hctx, derived_key + key_len,
+ key_len);
+ if (ret != 0) {
+ archive_encrypto_aes_ctr_release(&zip->cctx);
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Failed to initialize HMAC-SHA1");
+ return (ARCHIVE_FAILED);
+ }
+
+ /* Set a passowrd verification value after the 'salt'. */
+ salt[salt_len] = derived_key[key_len * 2];
+ salt[salt_len + 1] = derived_key[key_len * 2 + 1];
+
+ /* Write encrypted keys in the top of the file content. */
+ ret = __archive_write_output(a, salt, salt_len + 2);
+ if (ret != ARCHIVE_OK)
+ return (ret);
+ zip->written_bytes += salt_len + 2;
+ zip->entry_compressed_written += salt_len + 2;
+
+ return (ARCHIVE_OK);
+}
+
+static int
+is_winzip_aes_encryption_supported(int encryption)
+{
+ size_t key_len, salt_len;
+ uint8_t salt[16 + 2];
+ uint8_t derived_key[MAX_DERIVED_KEY_BUF_SIZE];
+ archive_crypto_ctx cctx;
+ archive_hmac_sha1_ctx hctx;
+ int ret;
+
+ if (encryption == ENCRYPTION_WINZIP_AES128) {
+ salt_len = 8;
+ key_len = 16;
+ } else {
+ /* AES 256 */
+ salt_len = 16;
+ key_len = 32;
+ }
+ if (archive_random(salt, salt_len) != ARCHIVE_OK)
+ return (0);
+ archive_pbkdf2_sha1("p", 1, salt, salt_len, 1000,
+ derived_key, key_len * 2 + 2);
+
+ ret = archive_encrypto_aes_ctr_init(&cctx, derived_key, key_len);
+ if (ret != 0)
+ return (0);
+ ret = archive_hmac_sha1_init(&hctx, derived_key + key_len,
+ key_len);
+ archive_encrypto_aes_ctr_release(&cctx);
+ if (ret != 0)
+ return (0);
+ archive_hmac_sha1_cleanup(&hctx);
+ return (1);
+}
free(buff);
}
+
+DEFINE_TEST(test_write_format_zip_winzip_aes128_encryption)
+{
+ struct archive *a;
+ size_t used;
+ size_t buffsize = 1000000;
+ char *buff;
+
+ buff = malloc(buffsize);
+
+ /* Create a new archive in memory. */
+ assert((a = archive_write_new()) != NULL);
+ assertEqualIntA(a, ARCHIVE_OK, archive_write_set_format_zip(a));
+ assertEqualIntA(a, ARCHIVE_OK, archive_write_add_filter_none(a));
+ assertEqualIntA(a, ARCHIVE_OK,
+ archive_write_set_options(a, "zip:encryption=aes128"));
+ assertEqualIntA(a, ARCHIVE_OK,
+ archive_write_set_options(a, "zip:password=password1234"));
+ assertEqualIntA(a, ARCHIVE_OK,
+ archive_write_set_options(a, "zip:experimental"));
+ assertEqualIntA(a, ARCHIVE_OK,
+ archive_write_open_memory(a, buff, buffsize, &used));
+ write_contents(a);
+ dumpfile("constructed.zip", buff, used);
+
+ /*
+ * Now, read the data back.
+ */
+ /* With the standard memory reader. */
+ assert((a = archive_read_new()) != NULL);
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a));
+ assertEqualIntA(a, ARCHIVE_OK,
+ archive_read_set_options(a, "zip:password=password1234"));
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_open_memory(a, buff, used));
+ verify_contents(a, 1, 1);
+
+ /* With the test memory reader -- streaming mode. */
+ assert((a = archive_read_new()) != NULL);
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a));
+ assertEqualIntA(a, ARCHIVE_OK,
+ archive_read_set_options(a, "zip:password=password1234"));
+ assertEqualIntA(a, ARCHIVE_OK, read_open_memory(a, buff, used, 7));
+ /* Streaming reader doesn't see mode information from Central Directory. */
+ verify_contents(a, 0, 1);
+
+ assert((a = archive_read_new()) != NULL);
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a));
+ assertEqualIntA(a, ARCHIVE_OK,
+ archive_read_set_options(a, "zip:password=password1234"));
+ assertEqualIntA(a, ARCHIVE_OK, read_open_memory(a, buff, used, 7));
+ /* Streaming reader doesn't see mode information from Central Directory. */
+ verify_contents(a, 0, 0);
+
+ /* With the test memory reader -- seeking mode. */
+ assert((a = archive_read_new()) != NULL);
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a));
+ assertEqualIntA(a, ARCHIVE_OK,
+ archive_read_set_options(a, "zip:password=password1234"));
+ assertEqualIntA(a, ARCHIVE_OK, read_open_memory_seek(a, buff, used, 7));
+ verify_contents(a, 1, 1);
+
+ assert((a = archive_read_new()) != NULL);
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a));
+ assertEqualIntA(a, ARCHIVE_OK,
+ archive_read_set_options(a, "zip:password=password1234"));
+ assertEqualIntA(a, ARCHIVE_OK, read_open_memory_seek(a, buff, used, 7));
+ verify_contents(a, 1, 0);
+
+ free(buff);
+}
+
+DEFINE_TEST(test_write_format_zip_winzip_aes256_encryption)
+{
+ struct archive *a;
+ size_t used;
+ size_t buffsize = 1000000;
+ char *buff;
+
+ buff = malloc(buffsize);
+
+ /* Create a new archive in memory. */
+ assert((a = archive_write_new()) != NULL);
+ assertEqualIntA(a, ARCHIVE_OK, archive_write_set_format_zip(a));
+ assertEqualIntA(a, ARCHIVE_OK, archive_write_add_filter_none(a));
+ assertEqualIntA(a, ARCHIVE_OK,
+ archive_write_set_options(a, "zip:encryption=aes256"));
+ assertEqualIntA(a, ARCHIVE_OK,
+ archive_write_set_options(a, "zip:password=password1234"));
+ assertEqualIntA(a, ARCHIVE_OK,
+ archive_write_set_options(a, "zip:experimental"));
+ assertEqualIntA(a, ARCHIVE_OK,
+ archive_write_open_memory(a, buff, buffsize, &used));
+ write_contents(a);
+ dumpfile("constructed.zip", buff, used);
+
+ /*
+ * Now, read the data back.
+ */
+ /* With the standard memory reader. */
+ assert((a = archive_read_new()) != NULL);
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a));
+ assertEqualIntA(a, ARCHIVE_OK,
+ archive_read_set_options(a, "zip:password=password1234"));
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_open_memory(a, buff, used));
+ verify_contents(a, 1, 1);
+
+ /* With the test memory reader -- streaming mode. */
+ assert((a = archive_read_new()) != NULL);
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a));
+ assertEqualIntA(a, ARCHIVE_OK,
+ archive_read_set_options(a, "zip:password=password1234"));
+ assertEqualIntA(a, ARCHIVE_OK, read_open_memory(a, buff, used, 7));
+ /* Streaming reader doesn't see mode information from Central Directory. */
+ verify_contents(a, 0, 1);
+
+ assert((a = archive_read_new()) != NULL);
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a));
+ assertEqualIntA(a, ARCHIVE_OK,
+ archive_read_set_options(a, "zip:password=password1234"));
+ assertEqualIntA(a, ARCHIVE_OK, read_open_memory(a, buff, used, 7));
+ /* Streaming reader doesn't see mode information from Central Directory. */
+ verify_contents(a, 0, 0);
+
+ /* With the test memory reader -- seeking mode. */
+ assert((a = archive_read_new()) != NULL);
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a));
+ assertEqualIntA(a, ARCHIVE_OK,
+ archive_read_set_options(a, "zip:password=password1234"));
+ assertEqualIntA(a, ARCHIVE_OK, read_open_memory_seek(a, buff, used, 7));
+ verify_contents(a, 1, 1);
+
+ assert((a = archive_read_new()) != NULL);
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a));
+ assertEqualIntA(a, ARCHIVE_OK,
+ archive_read_set_options(a, "zip:password=password1234"));
+ assertEqualIntA(a, ARCHIVE_OK, read_open_memory_seek(a, buff, used, 7));
+ verify_contents(a, 1, 0);
+
+ free(buff);
+}