From de0a204ecce9ce2d1348b8748b67c568e97f9771 Mon Sep 17 00:00:00 2001 From: Michihiro NAKAJIMA Date: Sun, 7 Sep 2014 20:28:59 +0900 Subject: [PATCH] Add support for traditional PKWARE encryption. --- CMakeLists.txt | 2 + Makefile.am | 2 + build/cmake/config.h.in | 6 + configure.ac | 4 +- libarchive/CMakeLists.txt | 1 + libarchive/archive_random.c | 266 ++++++++++++++++++++++ libarchive/archive_random_private.h | 36 +++ libarchive/archive_write_set_format_zip.c | 207 ++++++++++++++++- libarchive/test/test_write_format_zip.c | 58 ++++- 9 files changed, 568 insertions(+), 14 deletions(-) create mode 100644 libarchive/archive_random.c create mode 100644 libarchive/archive_random_private.h diff --git a/CMakeLists.txt b/CMakeLists.txt index db245e435..24ff970fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -538,6 +538,7 @@ LA_CHECK_INCLUDE_FILE("memory.h" HAVE_MEMORY_H) LA_CHECK_INCLUDE_FILE("paths.h" HAVE_PATHS_H) LA_CHECK_INCLUDE_FILE("poll.h" HAVE_POLL_H) LA_CHECK_INCLUDE_FILE("process.h" HAVE_PROCESS_H) +LA_CHECK_INCLUDE_FILE("pthread.h" HAVE_PTHREAD_H) LA_CHECK_INCLUDE_FILE("pwd.h" HAVE_PWD_H) LA_CHECK_INCLUDE_FILE("regex.h" HAVE_REGEX_H) LA_CHECK_INCLUDE_FILE("signal.h" HAVE_SIGNAL_H) @@ -1112,6 +1113,7 @@ IF ("CMAKE_C_COMPILER_ID" MATCHES "^GNU$") SET(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -fno-builtin") ENDIF ("CMAKE_C_COMPILER_ID" MATCHES "^GNU$") CHECK_SYMBOL_EXISTS(_CrtSetReportMode "crtdbg.h" HAVE__CrtSetReportMode) +CHECK_FUNCTION_EXISTS_GLIBC(arc4random_buf HAVE_CHFLAGS) CHECK_FUNCTION_EXISTS_GLIBC(chflags HAVE_CHFLAGS) CHECK_FUNCTION_EXISTS_GLIBC(chown HAVE_CHOWN) CHECK_FUNCTION_EXISTS_GLIBC(chroot HAVE_CHROOT) diff --git a/Makefile.am b/Makefile.am index 2758367d3..c45dfe598 100644 --- a/Makefile.am +++ b/Makefile.am @@ -126,6 +126,8 @@ libarchive_la_SOURCES= \ libarchive/archive_ppmd7.c \ libarchive/archive_ppmd7_private.h \ libarchive/archive_private.h \ + libarchive/archive_random.c \ + libarchive/archive_random_private.h \ libarchive/archive_rb.c \ libarchive/archive_rb.h \ libarchive/archive_read.c \ diff --git a/build/cmake/config.h.in b/build/cmake/config.h.in index 3b82b24fd..b66bb5433 100644 --- a/build/cmake/config.h.in +++ b/build/cmake/config.h.in @@ -328,6 +328,9 @@ typedef uint64_t uintmax_t; /* True for systems with POSIX ACL support */ #cmakedefine HAVE_ACL_USER 1 +/* Define to 1 if you have the `arc4random_buf' function. */ +#cmakedefine HAVE_ARC4RANDOM_BUF 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_ATTR_XATTR_H 1 @@ -757,6 +760,9 @@ typedef uint64_t uintmax_t; /* Define to 1 if you have the header file. */ #cmakedefine HAVE_PROCESS_H 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_PTHREAD_H 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_PWD_H 1 diff --git a/configure.ac b/configure.ac index 8c1d8a97a..46311b5d3 100644 --- a/configure.ac +++ b/configure.ac @@ -267,7 +267,7 @@ AS_VAR_IF([ac_cv_have_decl_EXT2_IOC_GETFLAGS], [yes], AC_CHECK_HEADERS([inttypes.h io.h langinfo.h limits.h]) AC_CHECK_HEADERS([linux/fiemap.h linux/fs.h linux/magic.h linux/types.h]) -AC_CHECK_HEADERS([locale.h paths.h poll.h pwd.h signal.h spawn.h]) +AC_CHECK_HEADERS([locale.h paths.h poll.h pthread.h pwd.h signal.h spawn.h]) AC_CHECK_HEADERS([stdarg.h stdint.h stdlib.h string.h]) AC_CHECK_HEADERS([sys/acl.h sys/cdefs.h sys/extattr.h]) AC_CHECK_HEADERS([sys/ioctl.h sys/mkdev.h sys/mount.h]) @@ -569,7 +569,7 @@ AC_FUNC_VPRINTF # To avoid necessity for including windows.h or special forward declaration # workarounds, we use 'void *' for 'struct SECURITY_ATTRIBUTES *' AC_CHECK_STDCALL_FUNC([CreateHardLinkA],[const char *, const char *, void *]) -AC_CHECK_FUNCS([chflags chown chroot ctime_r dirfd]) +AC_CHECK_FUNCS([arc4random_buf chflags chown chroot ctime_r dirfd]) AC_CHECK_FUNCS([fchdir fchflags fchmod fchown fcntl fdopendir fork]) AC_CHECK_FUNCS([fstat fstatat fstatfs fstatvfs ftruncate]) AC_CHECK_FUNCS([futimens futimes futimesat]) diff --git a/libarchive/CMakeLists.txt b/libarchive/CMakeLists.txt index 03508fbee..10036e5f6 100644 --- a/libarchive/CMakeLists.txt +++ b/libarchive/CMakeLists.txt @@ -33,6 +33,7 @@ SET(libarchive_SOURCES archive_entry_xattr.c archive_getdate.c archive_hmac.c + archive_hmac_private.c archive_match.c archive_options.c archive_options_private.h diff --git a/libarchive/archive_random.c b/libarchive/archive_random.c new file mode 100644 index 000000000..7786323dc --- /dev/null +++ b/libarchive/archive_random.c @@ -0,0 +1,266 @@ +/*- + * Copyright (c) 2014 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#ifdef HAVE_STDLIB_H +#include +#endif + +#if !defined(HAVE_ARC4RANDOM_BUF) && (!defined(_WIN32) || defined(__CYGWIN__)) + +#ifdef HAVE_FCNTL +#include +#endif +#ifdef HAVE_LIMITS_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_TIME_H +#include +#endif +#ifdef HAVE_PTHREAD_H +#include +#endif + +#if defined(HAVE_WINCRYPT_H) && !defined(__CYGWIN__) +#include +#endif + +static void arc4random_buf(void *, size_t); + +#endif /* HAVE_ARC4RANDOM_BUF */ + +#include "archive.h" +#include "archive_random_private.h" + +/* + * Random number generator function. + * This simply calls arc4random_buf function if the platform provides it. + */ + +int +archive_random(void *buf, size_t nbytes) +{ +#if defined(_WIN32) && !defined(__CYGWIN__) + HCRYPTPROV hProv; + BOOL success; + + success = CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT); + if (!success && GetLastError() == NTE_BAD_KEYSET) { + success = CryptAcquireContext(&hProv, NULL, NULL, + PROV_RSA_FULL, CRYPT_NEWKEYSET); + } + if (success) { + success = CryptGenRandom(hProv, (DWORD)nbytes, (BYTE*)buf)) + CryptReleaseContext(hProv, 0); + if (success) + return ARCHIVE_OK; + } + /* TODO: Does this case really happen? */ + return ARCHIVE_FAILED; +#else + arc4random_buf(buf, nbytes); + return ARCHIVE_OK; +#endif +} + +#if !defined(HAVE_ARC4RANDOM_BUF) && (!defined(_WIN32) || defined(__CYGWIN__)) + +/* $OpenBSD: arc4random.c,v 1.24 2013/06/11 16:59:50 deraadt Exp $ */ +/* + * Copyright (c) 1996, David Mazieres + * Copyright (c) 2008, Damien Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Arc4 random number generator for OpenBSD. + * + * This code is derived from section 17.1 of Applied Cryptography, + * second edition, which describes a stream cipher allegedly + * compatible with RSA Labs "RC4" cipher (the actual description of + * which is a trade secret). The same algorithm is used as a stream + * cipher called "arcfour" in Tatu Ylonen's ssh package. + * + * RC4 is a registered trademark of RSA Laboratories. + */ + +#ifdef __GNUC__ +#define inline __inline +#else /* !__GNUC__ */ +#define inline +#endif /* !__GNUC__ */ + +struct arc4_stream { + u_int8_t i; + u_int8_t j; + u_int8_t s[256]; +}; + +static pthread_mutex_t arc4random_mtx = PTHREAD_MUTEX_INITIALIZER; + +#define RANDOMDEV "/dev/urandom" +#define KEYSIZE 128 +#ifdef HAVE_PTHREAD_H +#define _ARC4_LOCK() pthread_mutex_lock(&arc4random_mtx); +#define _ARC4_UNLOCK() pthread_mutex_unlock(&arc4random_mtx); +#else +#define _ARC4_LOCK() +#define _ARC4_UNLOCK() +#endif + +static int rs_initialized; +static struct arc4_stream rs; +static pid_t arc4_stir_pid; +static int arc4_count; + +static inline u_int8_t arc4_getbyte(void); +static void arc4_stir(void); + +static inline void +arc4_init(void) +{ + int n; + + for (n = 0; n < 256; n++) + rs.s[n] = n; + rs.i = 0; + rs.j = 0; +} + +static inline void +arc4_addrandom(u_char *dat, int datlen) +{ + int n; + u_int8_t si; + + rs.i--; + for (n = 0; n < 256; n++) { + rs.i = (rs.i + 1); + si = rs.s[rs.i]; + rs.j = (rs.j + si + dat[n % datlen]); + rs.s[rs.i] = rs.s[rs.j]; + rs.s[rs.j] = si; + } + rs.j = rs.i; +} + +static void +arc4_stir(void) +{ + int done, fd, i; + struct { + struct timeval tv; + pid_t pid; + u_char rnd[KEYSIZE]; + } rdat; + + if (!rs_initialized) { + arc4_init(); + rs_initialized = 1; + } + done = 0; + fd = open(RANDOMDEV, O_RDONLY | O_CLOEXEC, 0); + if (fd >= 0) { + if (read(fd, &rdat, KEYSIZE) == KEYSIZE) + done = 1; + (void)close(fd); + } + if (!done) { + (void)gettimeofday(&rdat.tv, NULL); + rdat.pid = getpid(); + /* We'll just take whatever was on the stack too... */ + } + + arc4_addrandom((u_char *)&rdat, KEYSIZE); + + /* + * Discard early keystream, as per recommendations in: + * "(Not So) Random Shuffles of RC4" by Ilya Mironov. + */ + for (i = 0; i < 1024; i++) + (void)arc4_getbyte(); + arc4_count = 1600000; +} + +static void +arc4_stir_if_needed(void) +{ + pid_t pid = getpid(); + + if (arc4_count <= 0 || !rs_initialized || arc4_stir_pid != pid) { + arc4_stir_pid = pid; + arc4_stir(); + } +} + +static inline u_int8_t +arc4_getbyte(void) +{ + u_int8_t si, sj; + + rs.i = (rs.i + 1); + si = rs.s[rs.i]; + rs.j = (rs.j + si); + sj = rs.s[rs.j]; + rs.s[rs.i] = sj; + rs.s[rs.j] = si; + return (rs.s[(si + sj) & 0xff]); +} + +static void +arc4random_buf(void *_buf, size_t n) +{ + u_char *buf = (u_char *)_buf; + _ARC4_LOCK(); + arc4_stir_if_needed(); + while (n--) { + if (--arc4_count <= 0) + arc4_stir(); + buf[n] = arc4_getbyte(); + } + _ARC4_UNLOCK(); +} + +#endif /* !HAVE_ARC4RANDOM_BUF */ diff --git a/libarchive/archive_random_private.h b/libarchive/archive_random_private.h new file mode 100644 index 000000000..c414779f8 --- /dev/null +++ b/libarchive/archive_random_private.h @@ -0,0 +1,36 @@ +/*- + * Copyright (c) 2014 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __LIBARCHIVE_BUILD +#error This header is only to be used internally to libarchive. +#endif + +#ifndef ARCHIVE_RANDOM_PRIVATE_H_INCLUDED +#define ARCHIVE_RANDOM_PRIVATE_H_INCLUDED + +/* Random number generator. */ +int archive_random(void *buf, size_t nbytes); + +#endif /* ARCHIVE_RANDOM_PRIVATE_H_INCLUDED */ diff --git a/libarchive/archive_write_set_format_zip.c b/libarchive/archive_write_set_format_zip.c index 883da7c51..ea659eded 100644 --- a/libarchive/archive_write_set_format_zip.c +++ b/libarchive/archive_write_set_format_zip.c @@ -1,7 +1,7 @@ /*- * Copyright (c) 2008 Anselm Strauss * Copyright (c) 2009 Joerg Sonnenberger - * Copyright (c) 2011-2012 Michihiro NAKAJIMA + * Copyright (c) 2011-2012,2014 Michihiro NAKAJIMA * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -53,16 +53,17 @@ __FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_zip.c 201168 20 #include "archive_entry.h" #include "archive_entry_locale.h" #include "archive_private.h" +#include "archive_random_private.h" #include "archive_write_private.h" #ifndef HAVE_ZLIB_H #include "archive_crc32.h" #endif +#define ZIP_ENTRY_FLAG_ENCRYPTED (1<<0) #define ZIP_ENTRY_FLAG_LENGTH_AT_END (1<<3) #define ZIP_ENTRY_FLAG_UTF8_NAME (1 << 11) - enum compression { COMPRESSION_UNSPECIFIED = -1, COMPRESSION_STORE = 0, @@ -75,6 +76,16 @@ enum compression { #define COMPRESSION_DEFAULT COMPRESSION_STORE #endif +enum encryption { + ENCRYPTION_NONE = 0, + ENCRYPTION_TRADITIONAL, /* Traditional PKWARE encryption. */ + ENCRYPTION_WINZIP_AES128, /* WinZIP AES-128 encryption. */ + ENCRYPTION_WINZIP_AES256, /* WinZIP AES-256 encryption. */ +}; + +#define TRAD_HEADER_SIZE 12 + + struct cd_segment { struct cd_segment *next; size_t buff_size; @@ -82,6 +93,10 @@ struct cd_segment { unsigned char *p; }; +struct trad_enc_ctx { + uint32_t keys[3]; +}; + struct zip { int64_t entry_offset; @@ -93,9 +108,13 @@ struct zip { struct archive_entry *entry; uint32_t entry_crc32; enum compression entry_compression; + enum encryption entry_encryption; int entry_flags; int entry_uses_zip64; int experiments; + struct trad_enc_ctx tctx; + char tctx_valid; + unsigned char trad_chkdat; unsigned char *file_header; size_t file_header_extra_offset; @@ -112,6 +131,8 @@ struct zip { struct archive_string_conv *sconv_default; enum compression requested_compression; int init_default_conversion; + enum encryption encryption_type; + struct archive_string password; #define ZIP_FLAG_AVOID_ZIP64 1 #define ZIP_FLAG_FORCE_ZIP64 2 @@ -143,6 +164,9 @@ static size_t path_length(struct archive_entry *); static int write_path(struct archive_entry *, struct archive_write *); static void copy_path(struct archive_entry *, unsigned char *); 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 unsigned char * cd_alloc(struct zip *zip, size_t length) @@ -223,6 +247,19 @@ archive_write_zip_options(struct archive_write *a, const char *key, ret = ARCHIVE_OK; } return (ret); + } else if (strcmp(key, "encryption") == 0) { + if (val == NULL) { + 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; + } else { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "%s: unknown encryption '%s'", + a->format_name, val); + } + return (ret); } else if (strcmp(key, "experimental") == 0) { if (val == NULL || val[0] == 0) { zip->flags &= ~ ZIP_FLAG_EXPERIMENT_xl; @@ -258,6 +295,16 @@ archive_write_zip_options(struct archive_write *a, const char *key, ret = ARCHIVE_FATAL; } return (ret); + } else if (strcmp(key, "password") == 0) { + if (val == NULL || val[0] == 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "%s: password option needs its value", + a->format_name); + } else { + archive_strcpy(&zip->password, val); + ret = ARCHIVE_OK; + } + return (ret); } else if (strcmp(key, "zip64") == 0) { /* * Bias decisions about Zip64: force them to be @@ -355,7 +402,7 @@ archive_write_set_format_zip(struct archive *_a) zip->requested_compression = COMPRESSION_UNSPECIFIED; zip->crc32func = real_crc32; -#ifdef HAVE_ZLIB_H + /* A buffer used for both compression and encryption. */ zip->len_buf = 65536; zip->buf = malloc(zip->len_buf); if (zip->buf == NULL) { @@ -364,7 +411,6 @@ archive_write_set_format_zip(struct archive *_a) "Can't allocate compression buffer"); return (ARCHIVE_FATAL); } -#endif a->format_data = zip; a->format_name = "zip"; @@ -448,10 +494,12 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) zip->entry_flags = 0; zip->entry_uses_zip64 = 0; zip->entry_crc32 = zip->crc32func(0, NULL, 0); + zip->entry_encryption = 0; if (zip->entry != NULL) { archive_entry_free(zip->entry); zip->entry = NULL; } + zip->tctx_valid = 0; #if defined(_WIN32) && !defined(__CYGWIN__) /* Make sure the path separators in pahtname, hardlink and symlink @@ -546,6 +594,8 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) 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; @@ -554,6 +604,12 @@ 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_compression == COMPRESSION_STORE) + zip->entry_compressed_size += TRAD_HEADER_SIZE; + version_needed = 20; + zip->entry_flags |= ZIP_ENTRY_FLAG_ENCRYPTED; + } if ((zip->flags & ZIP_FLAG_FORCE_ZIP64) /* User asked. */ || (zip->entry_uncompressed_size > 0xffffffffLL)) { /* Large entry. */ @@ -569,6 +625,7 @@ 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; @@ -577,6 +634,11 @@ 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; + } } /* Format the local header. */ @@ -602,6 +664,13 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) } archive_le16enc(local_header + 26, filename_length); + if (zip->entry_encryption == ENCRYPTION_TRADITIONAL) { + if (zip->entry_flags & ZIP_ENTRY_FLAG_LENGTH_AT_END) + zip->trad_chkdat = local_header[11]; + else + zip->trad_chkdat = local_header[17]; + } + /* Format as much of central directory file header as we can: */ zip->file_header = cd_alloc(zip, 46); /* If (zip->file_header == NULL) XXXX */ @@ -779,13 +848,65 @@ archive_write_zip_data(struct archive_write *a, const void *buff, size_t s) if (s == 0) return 0; - switch (zip->entry_compression) { - case COMPRESSION_STORE: - ret = __archive_write_output(a, buff, s); + 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; + } + 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 += s; - zip->entry_compressed_written += s; + 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) { + 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); + ret = __archive_write_output(a, zip->buf, l); + if (ret != ARCHIVE_OK) + return (ret); + zip->entry_compressed_written += l; + zip->written_bytes += l; + rb += l; + } + } else { + ret = __archive_write_output(a, buff, s); + if (ret != ARCHIVE_OK) + return (ret); + zip->written_bytes += s; + zip->entry_compressed_written += s; + } break; #if HAVE_ZLIB_H case COMPRESSION_DEFLATE: @@ -796,6 +917,11 @@ archive_write_zip_data(struct archive_write *a, const void *buff, size_t s) if (ret == Z_STREAM_ERROR) return (ARCHIVE_FATAL); if (zip->stream.avail_out == 0) { + if (zip->tctx_valid) { + trad_enc_encrypt_update(&zip->tctx, + zip->buf, zip->len_buf, + zip->buf, zip->len_buf); + } ret = __archive_write_output(a, zip->buf, zip->len_buf); if (ret != ARCHIVE_OK) @@ -835,6 +961,10 @@ archive_write_zip_finish_entry(struct archive_write *a) if (ret == Z_STREAM_ERROR) return (ARCHIVE_FATAL); remainder = zip->len_buf - zip->stream.avail_out; + if (zip->tctx_valid) { + trad_enc_encrypt_update(&zip->tctx, + zip->buf, remainder, zip->buf, remainder); + } ret = __archive_write_output(a, zip->buf, remainder); if (ret != ARCHIVE_OK) return (ret); @@ -1006,10 +1136,12 @@ archive_write_zip_free(struct archive_write *a) free(segment->buff); free(segment); } -#ifdef HAVE_ZLIB_H free(zip->buf); -#endif archive_entry_free(zip->entry); + if (zip->password.s != NULL) { + memset(zip->password.s, 0, archive_strlen(&zip->password)); + archive_string_free(&zip->password); + } /* TODO: Free opt_sconv, sconv_default */ free(zip); @@ -1129,3 +1261,56 @@ get_sconv(struct archive_write *a, struct zip *zip) } return (zip->sconv_default); } + +/* + Traditional PKWARE Decryption functions. + */ + +static void +trad_enc_update_keys(struct trad_enc_ctx *ctx, uint8_t c) +{ + uint8_t t; +#define CRC32(c, b) (crc32(c ^ 0xffffffffUL, &b, 1) ^ 0xffffffffUL) + + ctx->keys[0] = CRC32(ctx->keys[0], c); + ctx->keys[1] = (ctx->keys[1] + (ctx->keys[0] & 0xff)) * 134775813L + 1; + t = (ctx->keys[1] >> 24) & 0xff; + ctx->keys[2] = CRC32(ctx->keys[2], t); +#undef CRC32 +} + +static uint8_t +trad_enc_decypt_byte(struct trad_enc_ctx *ctx) +{ + unsigned temp = ctx->keys[2] | 2; + return (uint8_t)((temp * (temp ^ 1)) >> 8) & 0xff; +} + +static unsigned +trad_enc_encrypt_update(struct trad_enc_ctx *ctx, const uint8_t *in, + size_t in_len, uint8_t *out, size_t out_len) +{ + unsigned i, max; + + max = (unsigned)((in_len < out_len)? in_len: out_len); + + for (i = 0; i < max; i++) { + uint8_t t = in[i]; + out[i] = t ^ trad_enc_decypt_byte(ctx); + trad_enc_update_keys(ctx, t); + } + return i; +} + +static int +trad_enc_init(struct trad_enc_ctx *ctx, const char *pw, size_t pw_len) +{ + + ctx->keys[0] = 305419896L; + ctx->keys[1] = 591751049L; + ctx->keys[2] = 878082192L; + + for (;pw_len; --pw_len) + trad_enc_update_keys(ctx, *pw++); + return 0; +} diff --git a/libarchive/test/test_write_format_zip.c b/libarchive/test/test_write_format_zip.c index 4c94e123b..48abe494f 100644 --- a/libarchive/test/test_write_format_zip.c +++ b/libarchive/test/test_write_format_zip.c @@ -585,12 +585,62 @@ DEFINE_TEST(test_write_format_zip64) archive_write_set_options(a, "zip:zip64")); 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("constructed64.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_open_memory(a, buff, used)); + verify_contents(a, 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, read_open_memory(a, buff, used, 7)); + /* Streaming reader doesn't see mode information from Central Directory. */ + verify_contents(a, 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, read_open_memory_seek(a, buff, used, 7)); + verify_contents(a, 1); + + free(buff); +} + +DEFINE_TEST(test_write_format_zip_traditional_pkware_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=traditional")); + 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("constructed64.zip", buff, used); + dumpfile("constructed.zip", buff, used); /* * Now, read the data back. @@ -599,6 +649,8 @@ DEFINE_TEST(test_write_format_zip64) 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); @@ -606,6 +658,8 @@ DEFINE_TEST(test_write_format_zip64) 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); @@ -614,6 +668,8 @@ DEFINE_TEST(test_write_format_zip64) 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); -- 2.47.2