]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Add support for traditional PKWARE encryption.
authorMichihiro NAKAJIMA <ggcueroad@gmail.com>
Sun, 7 Sep 2014 11:28:59 +0000 (20:28 +0900)
committerMichihiro NAKAJIMA <ggcueroad@gmail.com>
Mon, 8 Sep 2014 10:41:34 +0000 (19:41 +0900)
CMakeLists.txt
Makefile.am
build/cmake/config.h.in
configure.ac
libarchive/CMakeLists.txt
libarchive/archive_random.c [new file with mode: 0644]
libarchive/archive_random_private.h [new file with mode: 0644]
libarchive/archive_write_set_format_zip.c
libarchive/test/test_write_format_zip.c

index db245e4354e30a55cb62d5f87eb1680b7ab897ba..24ff970fcf97b771d3a97309d919dc7373fe7970 100644 (file)
@@ -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)
index 2758367d349094ec142cdefd2bd6a362e6fb3162..c45dfe598c88f68e360deae6e1fe63e000d87a5b 100644 (file)
@@ -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 \
index 3b82b24fd8e740d3bfacd2aa27b2828c968a3c48..b66bb5433bcc33824acda8209a200ed4f4cfec43 100644 (file)
@@ -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 <attr/xattr.h> header file. */
 #cmakedefine HAVE_ATTR_XATTR_H 1
 
@@ -757,6 +760,9 @@ typedef uint64_t uintmax_t;
 /* Define to 1 if you have the <process.h> header file. */
 #cmakedefine HAVE_PROCESS_H 1
 
+/* Define to 1 if you have the <pthread.h> header file. */
+#cmakedefine HAVE_PTHREAD_H 1
+
 /* Define to 1 if you have the <pwd.h> header file. */
 #cmakedefine HAVE_PWD_H 1
 
index 8c1d8a97a2d56580c40728286559acc30718b8a3..46311b5d3e2a5f02496aa72d6a4a49a2de163c96 100644 (file)
@@ -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])
index 03508fbeebd860ad31a4632eb295f64439c6d404..10036e5f6f0aeda2edec9dcdb1d24dc87dc59871 100644 (file)
@@ -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 (file)
index 0000000..7786323
--- /dev/null
@@ -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 <stdlib.h>
+#endif
+
+#if !defined(HAVE_ARC4RANDOM_BUF) && (!defined(_WIN32) || defined(__CYGWIN__))
+
+#ifdef HAVE_FCNTL
+#include <fcntl.h>
+#endif
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#ifdef HAVE_PTHREAD_H
+#include <pthread.h>
+#endif
+
+#if defined(HAVE_WINCRYPT_H) && !defined(__CYGWIN__)
+#include <wincrypt.h>
+#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 <dm@uun.org>
+ * Copyright (c) 2008, Damien Miller <djm@openbsd.org>
+ *
+ * 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 (file)
index 0000000..c414779
--- /dev/null
@@ -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 */
index 883da7c51c34dfec6d1cda5e33b4c6fcd31882bc..ea659eded1da714269efae830ae08e381d98f2f8 100644 (file)
@@ -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;
+}
index 4c94e123bb8ab6d3b7b9b5839812f9a88b5a045f..48abe494f9de81e87d0a8c2cc33ad07ccbc71317 100644 (file)
@@ -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);