Merge pull request #2865 from GeorgH93/read_zipx_encryption

Add support for reading encrypted zipx formats (bzip2, lzma, ppmd, xz, zstd)
This commit is contained in:
Dustin L. Howett authored and GitHub committed 2026-06-12 10:28:45 -05:00
commit b66d136b70
10 files changed
+9469 -231

No files matched your search

+6
View File
@@ -601,6 +601,7 @@ libarchive_test_SOURCES= \
libarchive/test/test_read_format_zip_winzip_aes.c \
libarchive/test/test_read_format_zip_winzip_aes_large.c \
libarchive/test/test_read_format_zip_zip64.c \
libarchive/test/test_read_format_zip_zipx_encrypted.c \
libarchive/test/test_read_format_zip_zipx_lzma_oom.c \
libarchive/test/test_read_format_zip_with_invalid_traditional_eocd.c \
libarchive/test/test_read_large.c \
@@ -1122,6 +1123,11 @@ libarchive_test_EXTRA_DIST=\
libarchive/test/test_read_format_zip_winzip_aes128.zip.uu \
libarchive/test/test_read_format_zip_winzip_aes256.zip.uu \
libarchive/test/test_read_format_zip_winzip_aes256_large.zip.uu \
libarchive/test/test_read_format_zip_winzip_aes256_large_bzip2.zip.uu \
libarchive/test/test_read_format_zip_winzip_aes256_large_lzma.zip.uu \
libarchive/test/test_read_format_zip_winzip_aes256_large_ppmd.zip.uu \
libarchive/test/test_read_format_zip_winzip_aes256_large_xz.zip.uu \
libarchive/test/test_read_format_zip_winzip_aes256_large_zstd.zip.uu \
libarchive/test/test_read_format_zip_winzip_aes256_stored.zip.uu \
libarchive/test/test_read_format_zip_with_invalid_traditional_eocd.zip.uu \
libarchive/test/test_read_format_zip_xz_multi.zipx.uu \
+286 -72
View File
@@ -272,6 +272,10 @@ zip_read_data_zipx_lzma_alone(struct archive_read *a, const void **buff,
size_t *size, int64_t *offset);
#endif
static void
trad_enc_decrypt_update(struct trad_enc_ctx *, const uint8_t *, size_t,
uint8_t *, size_t);
/* This function is used by Ppmd8_DecodeSymbol during decompression of Ppmd8
* streams inside ZIP files. It has 2 purposes: one is to fetch the next
* compressed byte from the stream, second one is to increase the counter how
@@ -290,6 +294,24 @@ ppmd_read(void* p) {
return 0;
}
if (zip->tctx_valid || zip->cctx_valid) {
uint8_t val;
if (zip->tctx_valid) {
trad_enc_decrypt_update(&zip->tctx,
data, 1, &val, 1);
} else {
size_t dsize = 1;
archive_decrypto_aes_ctr_update(&zip->cctx,
data, 1, &val, &dsize);
}
if (zip->hctx_valid)
archive_hmac_sha1_update(&zip->hctx, data, 1);
__archive_read_consume(a, 1);
++zip->zipx_ppmd_read_compressed;
return val;
}
__archive_read_consume(a, 1);
/* Increment the counter. */
@@ -401,6 +423,146 @@ crypt_derive_key_sha1(const void *p, int size, unsigned char *key,
}
#endif
/* Read and decrypt bytes for zipx init headers.
* Used by format-specific init functions (lzma, ppmd) that need to
* read a small header from the compressed stream. When encryption is
* active the bytes are decrypted in-place into the decryption buffer. */
static int
zipx_read_header_and_decrypt(struct archive_read *a, const void **buf, size_t in_len,
size_t *out_len, size_t *consumed)
{
struct zip *zip = (struct zip *)(a->format->data);
const void *raw;
ssize_t bytes_avail;
size_t to_decrypt;
raw = __archive_read_ahead(a, in_len, &bytes_avail);
if (raw == NULL || bytes_avail < (ssize_t)in_len) {
archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
"Truncated ZIP file data");
return (ARCHIVE_FATAL);
}
if (zip->tctx_valid || zip->cctx_valid) {
to_decrypt = in_len;
if (to_decrypt > zip->decrypted_buffer_size)
to_decrypt = zip->decrypted_buffer_size;
if (zip->tctx_valid) {
trad_enc_decrypt_update(&zip->tctx,
raw, to_decrypt,
zip->decrypted_buffer, to_decrypt);
} else {
size_t dsize = to_decrypt;
archive_decrypto_aes_ctr_update(&zip->cctx,
raw, to_decrypt,
zip->decrypted_buffer, &dsize);
}
if (zip->hctx_valid)
archive_hmac_sha1_update(&zip->hctx,
raw, to_decrypt);
*buf = zip->decrypted_buffer;
*out_len = to_decrypt;
*consumed = to_decrypt;
} else {
*buf = raw;
*out_len = in_len;
*consumed = in_len;
}
return (ARCHIVE_OK);
}
/* Decrypt bulk compressed data for zipx decompression.
* Manages the decryption buffer, handles partial fills, and returns decrypted
* data pointer + length. `sp` is set to the raw pointer for HMAC accounting. */
static void
zip_read_decrypt(struct zip *zip, const void *compressed_buff,
ssize_t bytes_avail, const void **result_buff, ssize_t *result_avail,
const void **sp)
{
*sp = compressed_buff;
if (zip->tctx_valid || zip->cctx_valid) {
if (zip->decrypted_bytes_remaining < (size_t)bytes_avail) {
size_t buff_remaining =
(zip->decrypted_buffer +
zip->decrypted_buffer_size)
- (zip->decrypted_ptr +
zip->decrypted_bytes_remaining);
/* The new bytes to decrypt start after decrypted_bytes_remaining
* in the raw stream: those leading bytes were already
* decrypted on a previous call but have not yet been consumed. */
size_t new_bytes =
(size_t)bytes_avail
- zip->decrypted_bytes_remaining;
if (buff_remaining > new_bytes)
buff_remaining = new_bytes;
if (0 == (zip->entry->zip_flags & ZIP_LENGTH_AT_END) &&
zip->entry_bytes_remaining > 0) {
if ((int64_t)(zip->decrypted_bytes_remaining
+ buff_remaining)
> zip->entry_bytes_remaining) {
if (zip->entry_bytes_remaining <
(int64_t)zip->decrypted_bytes_remaining)
buff_remaining = 0;
else
buff_remaining =
(size_t)zip->entry_bytes_remaining
- zip->decrypted_bytes_remaining;
}
}
if (buff_remaining > 0) {
if (zip->tctx_valid) {
trad_enc_decrypt_update(&zip->tctx,
(const uint8_t *)compressed_buff
+ zip->decrypted_bytes_remaining,
buff_remaining,
zip->decrypted_ptr
+ zip->decrypted_bytes_remaining,
buff_remaining);
} else {
size_t dsize = buff_remaining;
archive_decrypto_aes_ctr_update(
&zip->cctx,
(const uint8_t *)compressed_buff
+ zip->decrypted_bytes_remaining,
buff_remaining,
zip->decrypted_ptr
+ zip->decrypted_bytes_remaining,
&dsize);
}
zip->decrypted_bytes_remaining +=
buff_remaining;
}
}
*result_avail = zip->decrypted_bytes_remaining;
*result_buff = (const char *)zip->decrypted_ptr;
} else {
*result_buff = compressed_buff;
*result_avail = bytes_avail;
}
}
/* Post-decompression decrypt state update.
* Updates decrypt buffer pointers and HMAC after the decompressor
* has consumed `to_consume` bytes. */
static void
zip_read_decrypt_update(struct zip *zip, ssize_t to_consume, const void *sp)
{
if (zip->tctx_valid || zip->cctx_valid) {
zip->decrypted_bytes_remaining -= to_consume;
if (zip->decrypted_bytes_remaining == 0)
zip->decrypted_ptr = zip->decrypted_buffer;
else
zip->decrypted_ptr += to_consume;
}
if (zip->hctx_valid)
archive_hmac_sha1_update(&zip->hctx, sp, to_consume);
}
/*
* Common code for streaming or seeking modes.
*
@@ -1825,12 +1987,32 @@ zipx_lzma_alone_init(struct archive_read *a, struct zip *zip)
*/
/* Read magic1,magic2,lzma_params from the ZIPX stream. */
if(zip->entry_bytes_remaining < 9 || (p = __archive_read_ahead(a, 9, NULL)) == NULL) {
if(zip->entry_bytes_remaining < 9) {
archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
"Truncated lzma data");
return (ARCHIVE_FATAL);
}
if (zip->tctx_valid || zip->cctx_valid) {
const void *decrypted;
size_t out_len;
size_t consumed;
int ret;
ret = zipx_read_header_and_decrypt(a, &decrypted, 9, &out_len, &consumed);
if (ret != ARCHIVE_OK)
return ret;
p = decrypted;
} else {
p = __archive_read_ahead(a, 9, NULL);
if (p == NULL) {
archive_set_error(&a->archive,
ARCHIVE_ERRNO_FILE_FORMAT,
"Truncated lzma data");
return (ARCHIVE_FATAL);
}
}
if(p[2] != 0x05 || p[3] != 0x00) {
archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
"Invalid lzma data");
@@ -1894,6 +2076,7 @@ zip_read_data_zipx_xz(struct archive_read *a, const void **buff,
int ret;
lzma_ret lz_ret;
const void* compressed_buf;
const void* sp;
ssize_t bytes_avail, in_bytes, to_consume = 0;
(void) offset; /* UNUSED */
@@ -1905,7 +2088,7 @@ zip_read_data_zipx_xz(struct archive_read *a, const void **buff,
return (ret);
}
compressed_buf = __archive_read_ahead(a, 1, &bytes_avail);
compressed_buf = sp = __archive_read_ahead(a, 1, &bytes_avail);
if (bytes_avail < 0) {
archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
"Truncated xz file body");
@@ -1913,6 +2096,10 @@ zip_read_data_zipx_xz(struct archive_read *a, const void **buff,
}
in_bytes = (ssize_t)zipmin(zip->entry_bytes_remaining, bytes_avail);
zip_read_decrypt(zip, compressed_buf, in_bytes,
&compressed_buf, &in_bytes, &sp);
zip->zipx_lzma_stream.next_in = compressed_buf;
zip->zipx_lzma_stream.avail_in = in_bytes;
zip->zipx_lzma_stream.total_in = 0;
@@ -1961,6 +2148,16 @@ zip_read_data_zipx_xz(struct archive_read *a, const void **buff,
zip->entry_compressed_bytes_read += to_consume;
zip->entry_uncompressed_bytes_read += zip->zipx_lzma_stream.total_out;
zip_read_decrypt_update(zip, to_consume, sp);
if (zip->end_of_entry) {
if (zip->hctx_valid) {
ret = check_authentication_code(a, NULL);
if (ret != ARCHIVE_OK)
return ret;
}
}
*size = (size_t)zip->zipx_lzma_stream.total_out;
*buff = zip->uncompressed_buffer;
@@ -1975,6 +2172,7 @@ zip_read_data_zipx_lzma_alone(struct archive_read *a, const void **buff,
int ret;
lzma_ret lz_ret;
const void* compressed_buf;
const void* sp;
ssize_t bytes_avail, in_bytes, to_consume;
(void) offset; /* UNUSED */
@@ -2004,6 +2202,9 @@ zip_read_data_zipx_lzma_alone(struct archive_read *a, const void **buff,
/* Set decompressor parameters. */
in_bytes = (ssize_t)zipmin(zip->entry_bytes_remaining, bytes_avail);
zip_read_decrypt(zip, compressed_buf, in_bytes,
&compressed_buf, &in_bytes, &sp);
zip->zipx_lzma_stream.next_in = compressed_buf;
zip->zipx_lzma_stream.avail_in = in_bytes;
zip->zipx_lzma_stream.total_in = 0;
@@ -2043,6 +2244,12 @@ zip_read_data_zipx_lzma_alone(struct archive_read *a, const void **buff,
case LZMA_OK:
break;
case LZMA_BUF_ERROR:
if (zip->zipx_lzma_stream.avail_out == 0) {
zip->end_of_entry = 1;
break;
}
/* FALL THROUGH */
default:
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
"lzma unknown error (%d)", (int) lz_ret);
@@ -2057,16 +2264,37 @@ zip_read_data_zipx_lzma_alone(struct archive_read *a, const void **buff,
zip->entry_compressed_bytes_read += to_consume;
zip->entry_uncompressed_bytes_read += zip->zipx_lzma_stream.total_out;
zip_read_decrypt_update(zip, to_consume, sp);
if(zip->entry_bytes_remaining == 0) {
zip->end_of_entry = 1;
}
if(zip->end_of_entry && zip->entry_bytes_remaining > 0) {
ssize_t remaining = (ssize_t)zip->entry_bytes_remaining;
const void *p = __archive_read_ahead(a, remaining, NULL);
if (p != NULL) {
if (zip->hctx_valid)
archive_hmac_sha1_update(&zip->hctx,
p, remaining);
__archive_read_consume(a, remaining);
zip->entry_compressed_bytes_read += remaining;
zip->entry_bytes_remaining = 0;
}
}
/* Free lzma decoder handle because we'll no longer need it. */
/* This cannot be folded into LZMA_STREAM_END handling above
* because the stream end marker is not required in this format. */
if(zip->end_of_entry) {
lzma_end(&zip->zipx_lzma_stream);
zip->zipx_lzma_valid = 0;
if (zip->hctx_valid) {
ret = check_authentication_code(a, NULL);
if (ret != ARCHIVE_OK)
return ret;
}
}
/* Return values. */
@@ -2108,11 +2336,22 @@ zipx_ppmd8_init(struct archive_read *a, struct zip *zip)
zip->zipx_ppmd_read_compressed = 0;
/* Read Ppmd8 header (2 bytes). */
p = __archive_read_ahead(a, 2, NULL);
if(!p) {
archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
"Truncated file data in PPMd8 stream");
return (ARCHIVE_FATAL);
if (zip->tctx_valid || zip->cctx_valid) {
size_t out_len;
size_t consumed;
int ret;
ret = zipx_read_header_and_decrypt(a, &p, 2, &out_len, &consumed);
if (ret != ARCHIVE_OK)
return ret;
} else {
p = __archive_read_ahead(a, 2, NULL);
if(!p) {
archive_set_error(&a->archive,
ARCHIVE_ERRNO_FILE_FORMAT,
"Truncated file data in PPMd8 stream");
return (ARCHIVE_FATAL);
}
}
__archive_read_consume(a, 2);
@@ -2238,6 +2477,12 @@ zip_read_data_zipx_ppmd(struct archive_read *a, const void **buff,
if(zip->end_of_entry) {
__archive_ppmd8_functions.Ppmd8_Free(&zip->ppmd8);
zip->ppmd8_valid = 0;
if (zip->hctx_valid) {
int r = check_authentication_code(a, NULL);
if (r != ARCHIVE_OK)
return (r);
}
}
/* Update pointers for libarchive. */
@@ -2297,6 +2542,7 @@ zip_read_data_zipx_bzip2(struct archive_read *a, const void **buff,
struct zip *zip = (struct zip *)(a->format->data);
ssize_t bytes_avail = 0, in_bytes, to_consume;
const void *compressed_buff;
const void *sp;
int r;
uint64_t total_out;
@@ -2329,6 +2575,9 @@ zip_read_data_zipx_bzip2(struct archive_read *a, const void **buff,
return (ARCHIVE_FATAL);
}
zip_read_decrypt(zip, compressed_buff, in_bytes,
&compressed_buff, &in_bytes, &sp);
/* Setup buffer boundaries. */
zip->bzstream.next_in = (char*)(uintptr_t) compressed_buff;
zip->bzstream.avail_in = (uint32_t)in_bytes;
@@ -2379,6 +2628,14 @@ zip_read_data_zipx_bzip2(struct archive_read *a, const void **buff,
zip->entry_compressed_bytes_read += to_consume;
zip->entry_uncompressed_bytes_read += total_out;
zip_read_decrypt_update(zip, to_consume, sp);
if (zip->end_of_entry && zip->hctx_valid) {
r = check_authentication_code(a, NULL);
if (r != ARCHIVE_OK)
return r;
}
/* Give libarchive its due. */
*size = (size_t)total_out;
*buff = zip->uncompressed_buffer;
@@ -2440,6 +2697,7 @@ zip_read_data_zipx_zstd(struct archive_read *a, const void **buff,
struct zip *zip = (struct zip *)(a->format->data);
ssize_t bytes_avail = 0, in_bytes, to_consume;
const void *compressed_buff;
const void *sp;
int r;
size_t ret;
uint64_t total_out;
@@ -2456,7 +2714,7 @@ zip_read_data_zipx_zstd(struct archive_read *a, const void **buff,
}
/* Fetch more compressed bytes */
compressed_buff = __archive_read_ahead(a, 1, &bytes_avail);
compressed_buff = sp = __archive_read_ahead(a, 1, &bytes_avail);
if(bytes_avail < 0) {
archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
"Truncated zstd file body");
@@ -2474,6 +2732,9 @@ zip_read_data_zipx_zstd(struct archive_read *a, const void **buff,
return (ARCHIVE_FATAL);
}
zip_read_decrypt(zip, compressed_buff, in_bytes,
&compressed_buff, &in_bytes, &sp);
/* Setup buffer boundaries */
in.src = compressed_buff;
in.size = in_bytes;
@@ -2508,6 +2769,14 @@ zip_read_data_zipx_zstd(struct archive_read *a, const void **buff,
zip->entry_compressed_bytes_read += to_consume;
zip->entry_uncompressed_bytes_read += total_out;
zip_read_decrypt_update(zip, to_consume, sp);
if (zip->end_of_entry && zip->hctx_valid) {
r = check_authentication_code(a, NULL);
if (r != ARCHIVE_OK)
return r;
}
/* Give libarchive its due. */
*size = (size_t)total_out;
*buff = zip->uncompressed_buffer;
@@ -2548,7 +2817,8 @@ zip_read_data_deflate(struct archive_read *a, const void **buff,
{
struct zip *zip;
ssize_t bytes_avail, to_consume = 0;
const void *compressed_buff, *sp;
const void *compressed_buff;
const void *sp;
int r;
(void)offset; /* UNUSED */
@@ -2588,54 +2858,8 @@ zip_read_data_deflate(struct archive_read *a, const void **buff,
return (ARCHIVE_FATAL);
}
if (zip->tctx_valid || zip->cctx_valid) {
if (zip->decrypted_bytes_remaining < (size_t)bytes_avail) {
size_t buff_remaining =
(zip->decrypted_buffer +
zip->decrypted_buffer_size)
- (zip->decrypted_ptr +
zip->decrypted_bytes_remaining);
if (buff_remaining > (size_t)bytes_avail)
buff_remaining = (size_t)bytes_avail;
if (0 == (zip->entry->zip_flags & ZIP_LENGTH_AT_END) &&
zip->entry_bytes_remaining > 0) {
if ((int64_t)(zip->decrypted_bytes_remaining
+ buff_remaining)
> zip->entry_bytes_remaining) {
if (zip->entry_bytes_remaining <
(int64_t)zip->decrypted_bytes_remaining)
buff_remaining = 0;
else
buff_remaining =
(size_t)zip->entry_bytes_remaining
- zip->decrypted_bytes_remaining;
}
}
if (buff_remaining > 0) {
if (zip->tctx_valid) {
trad_enc_decrypt_update(&zip->tctx,
compressed_buff, buff_remaining,
zip->decrypted_ptr
+ zip->decrypted_bytes_remaining,
buff_remaining);
} else {
size_t dsize = buff_remaining;
archive_decrypto_aes_ctr_update(
&zip->cctx,
compressed_buff, buff_remaining,
zip->decrypted_ptr
+ zip->decrypted_bytes_remaining,
&dsize);
}
zip->decrypted_bytes_remaining +=
buff_remaining;
}
}
bytes_avail = zip->decrypted_bytes_remaining;
compressed_buff = (const char *)zip->decrypted_ptr;
}
zip_read_decrypt(zip, compressed_buff, bytes_avail,
&compressed_buff, &bytes_avail, &sp);
/*
* A bug in zlib.h: stream.next_in should be marked 'const'
@@ -2674,22 +2898,12 @@ zip_read_data_deflate(struct archive_read *a, const void **buff,
zip->entry_compressed_bytes_read += to_consume;
zip->entry_uncompressed_bytes_read += zip->stream.total_out;
if (zip->tctx_valid || zip->cctx_valid) {
zip->decrypted_bytes_remaining -= to_consume;
if (zip->decrypted_bytes_remaining == 0)
zip->decrypted_ptr = zip->decrypted_buffer;
else
zip->decrypted_ptr += to_consume;
}
if (zip->hctx_valid)
archive_hmac_sha1_update(&zip->hctx, sp, to_consume);
zip_read_decrypt_update(zip, to_consume, sp);
if (zip->end_of_entry) {
if (zip->hctx_valid) {
r = check_authentication_code(a, NULL);
if (r != ARCHIVE_OK) {
return (r);
}
if (zip->end_of_entry && zip->hctx_valid) {
r = check_authentication_code(a, NULL);
if (r != ARCHIVE_OK) {
return r;
}
}
+1
View File
@@ -233,6 +233,7 @@ IF(ENABLE_TEST)
test_read_format_zip_winzip_aes.c
test_read_format_zip_winzip_aes_large.c
test_read_format_zip_zip64.c
test_read_format_zip_zipx_encrypted.c
test_read_format_zip_zipx_lzma_oom.c
test_read_format_zip_with_invalid_traditional_eocd.c
test_read_large.c
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -25,182 +25,80 @@
*/
#include "test.h"
DEFINE_TEST(test_read_format_zip_winzip_aes256_large)
#define FILE_COUNT 4
static const char *FILE_NAMES[FILE_COUNT] = { "Makefile", "NEWS", "README", "config.h" };
static const size_t FILE_SIZE[FILE_COUNT] = { 1456747, 29357, 6818, 32667 };
static void
test_file_with_bad_password(struct archive *a, int file_id, __LA_MODE_T file_permissions)
{
const char *refname = "test_read_format_zip_winzip_aes256_large.zip";
struct archive_entry *ae;
struct archive *a;
char buff[512];
assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
assertEqualInt((AE_IFREG | file_permissions), archive_entry_mode(ae));
assertEqualString(FILE_NAMES[file_id], archive_entry_pathname(ae));
assertEqualInt(FILE_SIZE[file_id], 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)));
}
/* Check if running system has cryptographic functionality. */
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));
if (ARCHIVE_OK != archive_write_set_options(a,
"zip:encryption=aes256")) {
skipping("This system does not have cryptographic library");
archive_write_free(a);
return;
static void
test_file_with_good_password(struct archive *a, int file_id, __LA_MODE_T file_permissions)
{
struct archive_entry *ae;
char buff[512];
assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
assertEqualInt((AE_IFREG | file_permissions), archive_entry_mode(ae));
assertEqualString(FILE_NAMES[file_id], archive_entry_pathname(ae));
assertEqualInt(FILE_SIZE[file_id], 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));
if (archive_zlib_version() != NULL) {
assertEqualInt(512, archive_read_data(a, buff, sizeof(buff)));
} else {
assertEqualInt(ARCHIVE_FAILED,
archive_read_data(a, buff, sizeof(buff)));
assertEqualString(archive_error_string(a),
"Unsupported ZIP compression method (8: deflation)");
assert(archive_errno(a) != 0);
}
archive_write_free(a);
}
static void
test_archive(const char *refname, const char *password, int good_password, const int file_order[FILE_COUNT], __LA_MODE_T file_permissions)
{
struct archive *a;
struct archive_entry *ae;
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 "Makefile" */
assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae));
assertEqualString("Makefile", archive_entry_pathname(ae));
assertEqualInt(1456747, 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 "NEWS" */
assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae));
assertEqualString("NEWS", archive_entry_pathname(ae));
assertEqualInt(29357, 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 "README" */
assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae));
assertEqualString("README", archive_entry_pathname(ae));
assertEqualInt(6818, 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 "config.h" */
assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae));
assertEqualString("config.h", archive_entry_pathname(ae));
assertEqualInt(32667, 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(4, 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. */
assertEqualIntA(a, 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));
if (password != NULL) {
assertEqualIntA(a, ARCHIVE_OK,
archive_read_add_passphrase(a, password));
}
assertEqualIntA(a, ARCHIVE_OK,
archive_read_add_passphrase(a, "password"));
assertEqualIntA(a, ARCHIVE_OK,
archive_read_open_filename(a, refname, 10240));
archive_read_open_filename(a, refname, 10240));
assertEqualIntA(a, ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW,
archive_read_has_encrypted_entries(a));
archive_read_has_encrypted_entries(a));
/* Verify encrypted file "Makefile" */
assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae));
assertEqualString("Makefile", archive_entry_pathname(ae));
assertEqualInt(1456747, 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));
if (archive_zlib_version() != NULL) {
assertEqualInt(512, archive_read_data(a, buff, sizeof(buff)));
} else {
assertEqualInt(ARCHIVE_FAILED,
archive_read_data(a, buff, sizeof(buff)));
assertEqualString(archive_error_string(a),
"Unsupported ZIP compression method (8: deflation)");
assert(archive_errno(a) != 0);
/* Verify encrypted files */
for(int i = 0; i < FILE_COUNT; ++i) {
if (good_password) {
test_file_with_good_password(a, file_order[i], file_permissions);
} else {
test_file_with_bad_password(a, file_order[i], file_permissions);
}
}
/* Verify encrypted file "NEWS" */
assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae));
assertEqualString("NEWS", archive_entry_pathname(ae));
assertEqualInt(29357, 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));
if (archive_zlib_version() != NULL) {
assertEqualInt(512, archive_read_data(a, buff, sizeof(buff)));
} else {
assertEqualInt(ARCHIVE_FAILED,
archive_read_data(a, buff, sizeof(buff)));
assertEqualString(archive_error_string(a),
"Unsupported ZIP compression method (8: deflation)");
assert(archive_errno(a) != 0);
}
/* Verify encrypted file "README" */
assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae));
assertEqualString("README", archive_entry_pathname(ae));
assertEqualInt(6818, 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));
if (archive_zlib_version() != NULL) {
assertEqualInt(512, archive_read_data(a, buff, sizeof(buff)));
} else {
assertEqualInt(ARCHIVE_FAILED,
archive_read_data(a, buff, sizeof(buff)));
assertEqualString(archive_error_string(a),
"Unsupported ZIP compression method (8: deflation)");
assert(archive_errno(a) != 0);
}
/* Verify encrypted file "config.h" */
assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae));
assertEqualString("config.h", archive_entry_pathname(ae));
assertEqualInt(32667, 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));
if (archive_zlib_version() != NULL) {
assertEqualInt(512, archive_read_data(a, buff, sizeof(buff)));
} else {
assertEqualInt(ARCHIVE_FAILED,
archive_read_data(a, buff, sizeof(buff)));
assertEqualString(archive_error_string(a),
"Unsupported ZIP compression method (8: deflation)");
assert(archive_errno(a) != 0);
}
assertEqualInt(4, archive_file_count(a));
assertEqualInt(FILE_COUNT, archive_file_count(a));
/* End of archive. */
assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae));
@@ -214,3 +112,81 @@ DEFINE_TEST(test_read_format_zip_winzip_aes256_large)
assertEqualInt(ARCHIVE_OK, archive_read_free(a));
}
static void
test_winzip_aes_large_with_file_order(const char *refname, const char *compression_name, __LA_MODE_T file_permissions, const int file_order[FILE_COUNT])
{
struct archive *a;
/* Check if running system has cryptographic functionality. */
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));
if (ARCHIVE_OK != archive_write_set_options(a,
"zip:encryption=aes256")) {
skipping("This system does not have cryptographic library");
archive_write_free(a);
return;
}
/* Check if running system supports compression format used */
if (compression_name && ARCHIVE_OK != archive_write_set_options(a, compression_name)) {
skipping("%s is not supported on this platform", compression_name);
archive_write_free(a);
return;
}
archive_write_free(a);
extract_reference_file(refname);
/* Extract a zip file without password. */
test_archive(refname, NULL, 0, file_order, file_permissions);
/* Extract a zip file with bad password. */
test_archive(refname, "not_the_password", 0, file_order, file_permissions);
/* Extract a zip file with good password. */
test_archive(refname, "password", 1, file_order, file_permissions);
}
static void
test_winzip_aes_large(const char *refname, const char *compression_name)
{
int file_order[FILE_COUNT] = { 0, 1, 2, 3 };
test_winzip_aes_large_with_file_order(refname, compression_name, ((__LA_MODE_T)0644), file_order);
}
DEFINE_TEST(test_read_format_zip_winzip_aes256_large)
{
// Deflate is always supported, no need to test for it
test_winzip_aes_large("test_read_format_zip_winzip_aes256_large.zip", NULL);
}
DEFINE_TEST(test_read_format_zip_winzip_aes256_large_bzip2)
{
test_winzip_aes_large("test_read_format_zip_winzip_aes256_large_bzip2.zip", "zip:compression=bzip2");
}
DEFINE_TEST(test_read_format_zip_winzip_aes256_large_lzma)
{
test_winzip_aes_large("test_read_format_zip_winzip_aes256_large_lzma.zip", "zip:compression=lzma");
}
DEFINE_TEST(test_read_format_zip_winzip_aes256_large_ppmd)
{
test_winzip_aes_large("test_read_format_zip_winzip_aes256_large_ppmd.zip", NULL);
}
DEFINE_TEST(test_read_format_zip_winzip_aes256_large_xz)
{
test_winzip_aes_large("test_read_format_zip_winzip_aes256_large_xz.zip", "zip:compression=xz");
}
DEFINE_TEST(test_read_format_zip_winzip_aes256_large_zstd)
{
int file_order[FILE_COUNT] = { 3, 0, 1, 2 };
test_winzip_aes_large_with_file_order("test_read_format_zip_winzip_aes256_large_zstd.zip",
"zip:compression=zstd", ((__LA_MODE_T)0664), file_order);
}
@@ -0,0 +1,182 @@
/*-
* Copyright (c) 2026 GeorgH93
* 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"
static const char *password = "test_password_zipx";
/* 512 bytes of test data — large enough to exercise decompression. */
static const char file_data[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz\n"
"The quick brown fox jumps over the lazy dog. Pack my box with five\n"
"dozen liquor jugs. How vexingly quick daft zebras jump! Bright\n"
"vixens jump; dozy fowl quack. Jackdaws love my big sphinx of\n"
"quartz. The five boxing wizards jump quickly. Amazingly few\n"
"discotheques provide jukeboxes. Crazy Frederick bought many very\n"
"exquisite opal jewels. We promptly judged antique ivory buckles\n"
"for the next prize. Sixty zippers were quickly picked from the bag.\n";
static void
validate_entry_read(struct archive *a, const char* msg)
{
struct archive_entry *ae;
size_t total_read;
ssize_t r;
char readbuf[1024];
assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
assertEqualString("encrypted_file.txt", archive_entry_pathname(ae));
assertEqualInt((int)(sizeof(file_data) - 1),
archive_entry_size(ae));
assertEqualInt(1, archive_entry_is_data_encrypted(ae));
total_read = 0;
for (;;) {
r = archive_read_data(a, readbuf, sizeof(readbuf));
if (r <= 0) {
if (r < 0) {
failure(msg, (int) r, archive_error_string(a));
assertEqualInt(r > 0, 1);
}
break;
}
assertEqualMem(readbuf, file_data + total_read, (size_t)r);
total_read += (size_t)r;
}
assertEqualInt((int)total_read, (int)(sizeof(file_data) - 1));
}
static void
test_encrypted_zipx(const char *compression_name)
{
struct archive *a;
struct archive_entry *ae;
size_t used;
size_t buffsize = 1000000;
char *buff;
char readbuf[1024];
buff = malloc(buffsize);
assert(buff != NULL);
/* ---- Write encrypted zipx archive ---- */
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));
if (ARCHIVE_OK != archive_write_set_options(a,
"zip:encryption=aes256")) {
skipping("This system does not have cryptographic library");
archive_write_free(a);
free(buff);
return;
}
if (ARCHIVE_OK != archive_write_set_options(a, compression_name)) {
skipping("%s is not supported on this platform", compression_name);
archive_write_free(a);
free(buff);
return;
}
assertEqualIntA(a, ARCHIVE_OK, archive_write_set_passphrase(a, password));
assertEqualIntA(a, ARCHIVE_OK, archive_write_set_options(a, "zip:experimental"));
assertEqualIntA(a, ARCHIVE_OK, archive_write_open_memory(a, buff, buffsize, &used));
assert((ae = archive_entry_new()) != NULL);
archive_entry_set_mtime(ae, 1, 0);
archive_entry_copy_pathname(ae, "encrypted_file.txt");
archive_entry_set_mode(ae, AE_IFREG | 0644);
archive_entry_set_size(ae, sizeof(file_data) - 1);
assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
assertEqualInt(sizeof(file_data) - 1, archive_write_data(a, file_data, sizeof(file_data) - 1));
archive_entry_free(ae);
assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
assertEqualIntA(a, ARCHIVE_OK, archive_write_free(a));
/* ---- Read back without password — should fail ---- */
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_open_memory(a, buff, used));
assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
assertEqualString("encrypted_file.txt", archive_entry_pathname(ae));
assertEqualInt(1, archive_entry_is_data_encrypted(ae));
assertEqualInt(0, archive_entry_is_metadata_encrypted(ae));
assertEqualInt(ARCHIVE_FAILED, archive_read_data(a, readbuf, sizeof(readbuf)));
assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a));
assertEqualInt(ARCHIVE_OK, archive_read_free(a));
/* ---- Read back with correct password ---- */
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_add_passphrase(a, password));
assertEqualIntA(a, ARCHIVE_OK, archive_read_open_memory(a, buff, used));
validate_entry_read(a, "archive_read_data returned %d: %s");
assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a));
assertEqualInt(ARCHIVE_OK, archive_read_free(a));
/* ---- Seeking reader with correct password ---- */
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_add_passphrase(a, password));
assertEqualIntA(a, ARCHIVE_OK, read_open_memory_seek(a, buff, used, 7));
validate_entry_read(a, "seek: archive_read_data returned %d: %s");
assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a));
assertEqualInt(ARCHIVE_OK, archive_read_free(a));
free(buff);
}
DEFINE_TEST(test_read_format_zip_deflate_encrypted)
{
test_encrypted_zipx("zip:compression=deflate");
}
DEFINE_TEST(test_read_format_zip_zipx_bzip2_encrypted)
{
test_encrypted_zipx("zip:compression=bzip2");
}
DEFINE_TEST(test_read_format_zip_zipx_lzma_encrypted)
{
test_encrypted_zipx("zip:compression=lzma");
}
DEFINE_TEST(test_read_format_zip_zipx_xz_encrypted)
{
test_encrypted_zipx("zip:compression=xz");
}
DEFINE_TEST(test_read_format_zip_zipx_zstd_encrypted)
{
test_encrypted_zipx("zip:compression=zstd");
}