From: Michihiro NAKAJIMA Date: Tue, 9 Sep 2014 13:29:07 +0000 (+0900) Subject: Add support for WinZip AES encryption. X-Git-Tag: v3.1.900a~227 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ea4701e7e5a8624f878a8c22652b58e0b158467e;p=thirdparty%2Flibarchive.git Add support for WinZip AES encryption. --- diff --git a/libarchive/archive_cryptor.c b/libarchive/archive_cryptor.c index e204b94d6..03b598fa8 100644 --- a/libarchive/archive_cryptor.c +++ b/libarchive/archive_cryptor.c @@ -89,8 +89,7 @@ pbkdf2_sha1(const char *pw, size_t pw_len, const uint8_t *salt, #ifdef __APPLE__ static int -decrypto_aes_ctr_init(archive_crypto_ctx *ctx, const uint8_t *key, - size_t key_len) +aes_ctr_init(archive_crypto_ctx *ctx, const uint8_t *key, size_t key_len) { CCCryptorStatus r; @@ -118,7 +117,7 @@ aes_ctr_encrypt_counter(archive_crypto_ctx *ctx) } static int -decrypto_aes_ctr_release(archive_crypto_ctx *ctx) +aes_ctr_release(archive_crypto_ctx *ctx) { memset(ctx->key, 0, ctx->key_len); memset(ctx->nonce, 0, sizeof(ctx->nonce)); @@ -128,8 +127,7 @@ decrypto_aes_ctr_release(archive_crypto_ctx *ctx) #elif defined(HAVE_LIBNETTLE) static int -decrypto_aes_ctr_init(archive_crypto_ctx *ctx, const uint8_t *key, - size_t key_len) +aes_ctr_init(archive_crypto_ctx *ctx, const uint8_t *key, size_t key_len) { ctx->key_len = key_len; memcpy(ctx->key, key, key_len); @@ -148,7 +146,7 @@ aes_ctr_encrypt_counter(archive_crypto_ctx *ctx) } static int -decrypto_aes_ctr_release(archive_crypto_ctx *ctx) +aes_ctr_release(archive_crypto_ctx *ctx) { memset(ctx, 0, sizeof(*ctx)); return 0; @@ -157,8 +155,7 @@ decrypto_aes_ctr_release(archive_crypto_ctx *ctx) #elif defined(HAVE_LIBCRYPTO) static int -decrypto_aes_ctr_init(archive_crypto_ctx *ctx, const uint8_t *key, - size_t key_len) +aes_ctr_init(archive_crypto_ctx *ctx, const uint8_t *key, size_t key_len) { switch (key_len) { @@ -193,7 +190,7 @@ aes_ctr_encrypt_counter(archive_crypto_ctx *ctx) } static int -decrypto_aes_ctr_release(archive_crypto_ctx *ctx) +aes_ctr_release(archive_crypto_ctx *ctx) { EVP_CIPHER_CTX_cleanup(&ctx->ctx); memset(ctx->key, 0, ctx->key_len); @@ -205,8 +202,7 @@ decrypto_aes_ctr_release(archive_crypto_ctx *ctx) /* Stub */ static int -decrypto_aes_ctr_init(archive_crypto_ctx *ctx, const uint8_t *key, - size_t key_len) +aes_ctr_init(archive_crypto_ctx *ctx, const uint8_t *key, size_t key_len) { (void)ctx; /* UNUSED */ (void)key; /* UNUSED */ @@ -221,7 +217,7 @@ aes_ctr_encrypt_counter(archive_crypto_ctx *ctx) } static int -decrypto_aes_ctr_release(archive_crypto_ctx *ctx) +aes_ctr_release(archive_crypto_ctx *ctx) { (void)ctx; /* UNUSED */ return 0; @@ -242,7 +238,7 @@ aes_ctr_increase_counter(archive_crypto_ctx *ctx) } static int -decrypto_aes_ctr_update(archive_crypto_ctx *ctx, const uint8_t * const in, +aes_ctr_update(archive_crypto_ctx *ctx, const uint8_t * const in, size_t in_len, uint8_t * const out, size_t *out_len) { uint8_t *const ebuf = ctx->encr_buf; @@ -280,8 +276,10 @@ decrypto_aes_ctr_update(archive_crypto_ctx *ctx, const uint8_t * const in, const struct archive_cryptor __archive_cryptor = { &pbkdf2_sha1, - &decrypto_aes_ctr_init, - &decrypto_aes_ctr_update, - &decrypto_aes_ctr_release, - + &aes_ctr_init, + &aes_ctr_update, + &aes_ctr_release, + &aes_ctr_init, + &aes_ctr_update, + &aes_ctr_release, }; diff --git a/libarchive/archive_cryptor_private.h b/libarchive/archive_cryptor_private.h index 4278bc384..5bd550782 100644 --- a/libarchive/archive_cryptor_private.h +++ b/libarchive/archive_cryptor_private.h @@ -86,7 +86,14 @@ typedef struct { #define archive_decrypto_aes_ctr_release(ctx) \ __archive_cryptor.decrypto_aes_ctr_release(ctx) -/* Minimal interface to cryptography functionality for internal use in +#define archive_encrypto_aes_ctr_init(ctx, key, key_len) \ + __archive_cryptor.encrypto_aes_ctr_init(ctx, key, key_len) +#define archive_encrypto_aes_ctr_update(ctx, in, in_len, out, out_len) \ + __archive_cryptor.encrypto_aes_ctr_update(ctx, in, in_len, out, out_len) +#define archive_encrypto_aes_ctr_release(ctx) \ + __archive_cryptor.encrypto_aes_ctr_release(ctx) + +/* Minimal interface to cryptographic functionality for internal use in * libarchive */ struct archive_cryptor { @@ -94,11 +101,15 @@ struct archive_cryptor int (*pbkdf2sha1)(const char *pw, size_t pw_len, const uint8_t *salt, size_t salt_len, unsigned rounds, uint8_t *derived_key, size_t derived_key_len); - /* AES CTR mode */ + /* AES CTR mode(little endian version) */ int (*decrypto_aes_ctr_init)(archive_crypto_ctx *, const uint8_t *, size_t); int (*decrypto_aes_ctr_update)(archive_crypto_ctx *, const uint8_t *, size_t, uint8_t *, size_t *); int (*decrypto_aes_ctr_release)(archive_crypto_ctx *); + int (*encrypto_aes_ctr_init)(archive_crypto_ctx *, const uint8_t *, size_t); + int (*encrypto_aes_ctr_update)(archive_crypto_ctx *, const uint8_t *, + size_t, uint8_t *, size_t *); + int (*encrypto_aes_ctr_release)(archive_crypto_ctx *); }; extern const struct archive_cryptor __archive_cryptor; diff --git a/libarchive/archive_hmac_private.h b/libarchive/archive_hmac_private.h index 3b296375d..324deb6f3 100644 --- a/libarchive/archive_hmac_private.h +++ b/libarchive/archive_hmac_private.h @@ -55,6 +55,8 @@ typedef HMAC_CTX archive_hmac_sha1_ctx; __archive_hmac.__hmac_sha1_update(ctx, data, data_len) #define archive_hmac_sha1_final(ctx, out, out_len)\ __archive_hmac.__hmac_sha1_final(ctx, out, out_len) +#define archive_hmac_sha1_cleanup(ctx)\ + __archive_hmac.__hmac_sha1_cleanup(ctx) struct archive_hmac { diff --git a/libarchive/archive_read_support_format_zip.c b/libarchive/archive_read_support_format_zip.c index 68a5bb0fd..24af61d27 100644 --- a/libarchive/archive_read_support_format_zip.c +++ b/libarchive/archive_read_support_format_zip.c @@ -957,6 +957,43 @@ zip_read_local_file_header(struct archive_read *a, struct archive_entry *entry, 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 @@ -988,6 +1025,7 @@ zip_read_data_none(struct archive_read *a, const void **_buff, struct zip *zip; const char *buff; ssize_t bytes_avail; + int r; (void)offset; /* UNUSED */ @@ -995,10 +1033,13 @@ zip_read_data_none(struct archive_read *a, const void **_buff, 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 @@ -1011,10 +1052,14 @@ zip_read_data_none(struct archive_read *a, const void **_buff, /* 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 = @@ -1030,6 +1075,11 @@ zip_read_data_none(struct archive_read *a, const void **_buff, 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); } @@ -1046,6 +1096,8 @@ zip_read_data_none(struct archive_read *a, const void **_buff, 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; } } @@ -1053,6 +1105,11 @@ zip_read_data_none(struct archive_read *a, const void **_buff, } 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. */ @@ -1127,7 +1184,7 @@ zip_read_data_deflate(struct archive_read *a, const void **buff, { struct zip *zip; ssize_t bytes_avail; - const void *compressed_buff; + const void *compressed_buff, *sp; int r; (void)offset; /* UNUSED */ @@ -1156,7 +1213,7 @@ zip_read_data_deflate(struct archive_read *a, const void **buff, * 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; @@ -1198,9 +1255,8 @@ zip_read_data_deflate(struct archive_read *a, const void **buff, 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, @@ -1252,6 +1308,9 @@ zip_read_data_deflate(struct archive_read *a, const void **buff, 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; @@ -1260,6 +1319,12 @@ zip_read_data_deflate(struct archive_read *a, const void **buff, 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; @@ -1611,10 +1676,10 @@ init_WinZip_AES_decryption(struct archive_read *a) 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; @@ -1699,31 +1764,6 @@ archive_read_format_zip_read_data(struct archive_read *a, (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) { @@ -1790,6 +1830,8 @@ archive_read_format_zip_cleanup(struct archive_read *a) 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); @@ -1961,10 +2003,10 @@ archive_read_format_zip_streamable_read_header(struct archive_read *a, 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. */ @@ -2798,10 +2840,10 @@ archive_read_format_zip_seekable_read_header(struct archive_read *a, 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 diff --git a/libarchive/archive_write_set_format_zip.c b/libarchive/archive_write_set_format_zip.c index ea659eded..607438329 100644 --- a/libarchive/archive_write_set_format_zip.c +++ b/libarchive/archive_write_set_format_zip.c @@ -49,9 +49,11 @@ __FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_zip.c 201168 20 #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" @@ -84,7 +86,23 @@ enum encryption { }; #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; @@ -115,6 +133,11 @@ struct zip { 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; @@ -167,6 +190,10 @@ static struct archive_string_conv *get_sconv(struct archive_write *, struct zip 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) @@ -252,8 +279,34 @@ archive_write_zip_options(struct archive_write *a, const char *key, 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'", @@ -442,7 +495,7 @@ static int 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; @@ -499,7 +552,28 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) 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 @@ -589,13 +663,13 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) 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; @@ -604,12 +678,30 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) 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. */ @@ -625,7 +717,6 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *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; @@ -634,10 +725,18 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) } 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; + } } } @@ -646,7 +745,11 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) 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); @@ -681,7 +784,11 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) 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); @@ -736,7 +843,33 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) 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); @@ -848,51 +981,58 @@ archive_write_zip_data(struct archive_write *a, const void *buff, size_t s) 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); @@ -921,6 +1061,20 @@ archive_write_zip_data(struct archive_write *a, const void *buff, size_t s) 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); @@ -942,7 +1096,9 @@ archive_write_zip_data(struct archive_write *a, const void *buff, size_t s) } 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); } @@ -957,6 +1113,7 @@ archive_write_zip_finish_entry(struct archive_write *a) if (zip->entry_compression == COMPRESSION_DEFLATE) { for (;;) { size_t remainder; + ret = deflate(&zip->stream, Z_FINISH); if (ret == Z_STREAM_ERROR) return (ARCHIVE_FATAL); @@ -964,6 +1121,19 @@ archive_write_zip_finish_entry(struct archive_write *a) 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) @@ -978,12 +1148,26 @@ archive_write_zip_finish_entry(struct archive_write *a) 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); @@ -1037,7 +1221,10 @@ archive_write_zip_finish_entry(struct archive_write *a) } /* 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, @@ -1142,6 +1329,10 @@ archive_write_zip_free(struct archive_write *a) 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); @@ -1314,3 +1505,143 @@ trad_enc_init(struct trad_enc_ctx *ctx, const char *pw, size_t pw_len) 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); +} diff --git a/libarchive/test/test_write_format_zip.c b/libarchive/test/test_write_format_zip.c index d013bc85f..ece1d4004 100644 --- a/libarchive/test/test_write_format_zip.c +++ b/libarchive/test/test_write_format_zip.c @@ -742,3 +742,153 @@ DEFINE_TEST(test_write_format_zip_traditional_pkware_encryption) 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); +}