From 402eeb85988be91d90a92eff94635b6e9ef6160e Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Thu, 2 Mar 2017 13:36:40 +0100 Subject: [PATCH] Zip: add support for encrypted archive --- NEWS | 2 +- UPGRADING | 12 +++ ext/zip/config.m4 | 9 +++ ext/zip/examples/encryption.php | 49 ++++++++++++ ext/zip/php_zip.c | 123 +++++++++++++++++++++++++++---- ext/zip/php_zip.h | 2 +- ext/zip/tests/oo_encryption.phpt | 66 +++++++++++++++++ ext/zip/zip_stream.c | 10 ++- 8 files changed, 256 insertions(+), 17 deletions(-) create mode 100644 ext/zip/examples/encryption.php create mode 100644 ext/zip/tests/oo_encryption.phpt diff --git a/NEWS b/NEWS index f465d7dbfd..ea88623979 100644 --- a/NEWS +++ b/NEWS @@ -160,6 +160,6 @@ PHP NEWS . Use Zend MM for allocation in bundled libxmlrpc (Joe) - ZIP: - . Fixed bug #70103 (ZipArchive::addGlob ignores remove_all_path option). (cmb) + . Add support for encrypted archives. (Remi) <<< NOTE: Insert NEWS from last stable release here prior to actual release! >>> diff --git a/UPGRADING b/UPGRADING index da884dcc4f..6121b9d70d 100644 --- a/UPGRADING +++ b/UPGRADING @@ -100,6 +100,18 @@ PHP 7.2 UPGRADE NOTES (https://wiki.php.net/rfc/argon2_password_hash). . proc_nice() is now supported on Windows platforms. +- Zip: + . read/write encrypted archive, relying on libzip 1.2.0, + using new methods: + ZipArchive::setEncryptionName($name, $method [, $password]); + ZipArchive::setEncryptionIndex($index, $method [, $password]); + and new constants: + ZipArchive::EM_NONE + ZipArchive::EM_AES_128 + ZipArchive::EM_AES_192 + ZipArchive::EM_AES_256 + . accept 'password' from zip stream context + ======================================== 3. Changes in SAPI modules ======================================== diff --git a/ext/zip/config.m4 b/ext/zip/config.m4 index bb653c2068..83798ee792 100644 --- a/ext/zip/config.m4 +++ b/ext/zip/config.m4 @@ -99,6 +99,15 @@ if test "$PHP_ZIP" != "no"; then -L$LIBZIP_LIBDIR ]) + PHP_CHECK_LIBRARY(zip, zip_file_set_encryption, + [ + PHP_ADD_LIBRARY_WITH_PATH(zip, $LIBZIP_LIBDIR, ZIP_SHARED_LIBADD) + AC_DEFINE(HAVE_ENCRYPTION, 1, [Libzip >= 1.2.0 with encryption support]) + ], [ + ], [ + -L$LIBZIP_LIBDIR + ]) + AC_DEFINE(HAVE_ZIP,1,[ ]) PHP_NEW_EXTENSION(zip, php_zip.c zip_stream.c, $ext_shared,, $LIBZIP_CFLAGS) PHP_SUBST(ZIP_SHARED_LIBADD) diff --git a/ext/zip/examples/encryption.php b/ext/zip/examples/encryption.php new file mode 100644 index 0000000000..69888170a9 --- /dev/null +++ b/ext/zip/examples/encryption.php @@ -0,0 +1,49 @@ +open($name, ZIPARCHIVE::CREATE | ZipArchive::OVERWRITE); +$zip->addFile(__FILE__, $file); +$zip->setEncryptionName($file, ZipArchive::EM_AES_256, $pass); +$zip->close(); + +echo "== Create with global password\n"; + +$zip = new ZipArchive; +$zip->open($name, ZIPARCHIVE::CREATE | ZipArchive::OVERWRITE); +$zip->setPassword($pass); +$zip->addFile(__FILE__, $file); +$zip->setEncryptionName($file, ZipArchive::EM_AES_256); +$zip->close(); + +echo "== Stat\n"; + +$zip->open($name); +print_r($zip->statName($file)); + +echo "== Read\n"; + +$zip->setPassword($pass); +$text = $zip->getFromName($file); +printf("Size = %d\n", strlen($text)); +$zip->close(); + +echo "== Stream with context\n"; + +$ctx = stream_context_create(array( + 'zip' => array( + 'password' => $pass + ) +)); +$text = file_get_contents("zip://$name#$file", false, $ctx); +printf("Size = %d\n", strlen($text)); + diff --git a/ext/zip/php_zip.c b/ext/zip/php_zip.c index 28527cbee1..03f2184399 100644 --- a/ext/zip/php_zip.c +++ b/ext/zip/php_zip.c @@ -379,6 +379,7 @@ static int php_zip_parse_options(zval *options, zend_long *remove_all_path, char /* }}} */ /* {{{ RETURN_SB(sb) */ +#ifdef HAVE_ENCRYPTION #define RETURN_SB(sb) \ { \ array_init(return_value); \ @@ -389,7 +390,21 @@ static int php_zip_parse_options(zval *options, zend_long *remove_all_path, char add_ascii_assoc_long(return_value, "mtime", (zend_long) (sb)->mtime); \ add_ascii_assoc_long(return_value, "comp_size", (zend_long) (sb)->comp_size); \ add_ascii_assoc_long(return_value, "comp_method", (zend_long) (sb)->comp_method); \ + add_ascii_assoc_long(return_value, "encryption_method", (zend_long) (sb)->encryption_method); \ } +#else +#define RETURN_SB(sb) \ + { \ + array_init(return_value); \ + add_ascii_assoc_string(return_value, "name", (char *)(sb)->name); \ + add_ascii_assoc_long(return_value, "index", (zend_long) (sb)->index); \ + add_ascii_assoc_long(return_value, "crc", (zend_long) (sb)->crc); \ + add_ascii_assoc_long(return_value, "size", (zend_long) (sb)->size); \ + add_ascii_assoc_long(return_value, "mtime", (zend_long) (sb)->mtime); \ + add_ascii_assoc_long(return_value, "comp_size", (zend_long) (sb)->comp_size); \ + add_ascii_assoc_long(return_value, "comp_method", (zend_long) (sb)->comp_method); \ + } +#endif /* }}} */ static int php_zip_status(struct zip *za) /* {{{ */ @@ -2222,6 +2237,74 @@ static ZIPARCHIVE_METHOD(getExternalAttributesIndex) /* }}} */ #endif /* ifdef ZIP_OPSYS_DEFAULT */ +#ifdef HAVE_ENCRYPTION +/* {{{ proto bool ZipArchive::setEncryptionName(string name, int method, [string password]) +Set encryption method for file in zip, using its name */ +static ZIPARCHIVE_METHOD(setEncryptionName) +{ + struct zip *intern; + zval *self = getThis(); + zend_long method; + zip_int64_t idx; + char *name, *password = NULL; + size_t name_len, password_len; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sl|s", + &name, &name_len, &method, &password, &password_len) == FAILURE) { + return; + } + + if (name_len < 1) { + php_error_docref(NULL, E_NOTICE, "Empty string as entry name"); + } + + idx = zip_name_locate(intern, name, 0); + if (idx < 0) { + RETURN_FALSE; + } + + if (zip_file_set_encryption(intern, idx, (zip_uint16_t)method, password)) { + RETURN_FALSE; + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool ZipArchive::setEncryptionIndex(int index, int method, [string password]) +Set encryption method for file in zip, using its index */ +static ZIPARCHIVE_METHOD(setEncryptionIndex) +{ + struct zip *intern; + zval *self = getThis(); + zend_long index, method; + char *password = NULL; + size_t password_len; + + if (!self) { + RETURN_FALSE; + } + + ZIP_FROM_OBJECT(intern, self); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll|s", + &index, &method, &password, &password_len) == FAILURE) { + return; + } + + if (zip_file_set_encryption(intern, index, (zip_uint16_t)method, password)) { + RETURN_FALSE; + } + RETURN_TRUE; +} +/* }}} */ +#endif + /* {{{ proto string ZipArchive::getCommentName(string name[, int flags]) Returns the comment of an entry using its name */ static ZIPARCHIVE_METHOD(getCommentName) @@ -2952,6 +3035,20 @@ ZEND_END_ARG_INFO() #endif /* ifdef ZIP_OPSYS_DEFAULT */ /* }}} */ +#ifdef HAVE_ENCRYPTION +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_setencryption_name, 0, 0, 2) + ZEND_ARG_INFO(0, name) + ZEND_ARG_INFO(0, method) + ZEND_ARG_INFO(0, password) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_setencryption_index, 0, 0, 2) + ZEND_ARG_INFO(0, index) + ZEND_ARG_INFO(0, method) + ZEND_ARG_INFO(0, password) +ZEND_END_ARG_INFO() +#endif + ZEND_BEGIN_ARG_INFO_EX(arginfo_ziparchive_setcompname, 0, 0, 2) ZEND_ARG_INFO(0, name) ZEND_ARG_INFO(0, method) @@ -3003,6 +3100,10 @@ static const zend_function_entry zip_class_functions[] = { ZIPARCHIVE_ME(getExternalAttributesIndex, arginfo_ziparchive_getextattrindex, ZEND_ACC_PUBLIC) ZIPARCHIVE_ME(setCompressionName, arginfo_ziparchive_setcompname, ZEND_ACC_PUBLIC) ZIPARCHIVE_ME(setCompressionIndex, arginfo_ziparchive_setcompindex, ZEND_ACC_PUBLIC) +#ifdef HAVE_ENCRYPTION + ZIPARCHIVE_ME(setEncryptionName, arginfo_ziparchive_setencryption_name, ZEND_ACC_PUBLIC) + ZIPARCHIVE_ME(setEncryptionIndex, arginfo_ziparchive_setencryption_index, ZEND_ACC_PUBLIC) +#endif PHP_FE_END }; /* }}} */ @@ -3047,6 +3148,7 @@ static PHP_MINIT_FUNCTION(zip) REGISTER_ZIP_CLASS_CONST_LONG("FL_NODIR", ZIP_FL_NODIR); REGISTER_ZIP_CLASS_CONST_LONG("FL_COMPRESSED", ZIP_FL_COMPRESSED); REGISTER_ZIP_CLASS_CONST_LONG("FL_UNCHANGED", ZIP_FL_UNCHANGED); + #ifdef ZIP_FL_ENC_GUESS /* Default filename encoding policy. */ REGISTER_ZIP_CLASS_CONST_LONG("FL_ENC_GUESS", ZIP_FL_ENC_GUESS); @@ -3064,20 +3166,6 @@ static PHP_MINIT_FUNCTION(zip) REGISTER_ZIP_CLASS_CONST_LONG("FL_ENC_CP437", ZIP_FL_ENC_CP437); #endif -/* XXX The below are rather not implemented or to check whether makes sense to expose. */ -/*#ifdef ZIP_FL_RECOMPRESS - REGISTER_ZIP_CLASS_CONST_LONG("FL_RECOMPRESS", ZIP_FL_RECOMPRESS); -#endif -#ifdef ZIP_FL_ENCRYPTED - REGISTER_ZIP_CLASS_CONST_LONG("FL_ENCRYPTED", ZIP_FL_ENCRYPTED); -#endif -#ifdef ZIP_FL_LOCAL - REGISTER_ZIP_CLASS_CONST_LONG("FL_LOCAL", ZIP_FL_LOCAL); -#endif -#ifdef ZIP_FL_CENTRAL - REGISTER_ZIP_CLASS_CONST_LONG("FL_CENTRAL", ZIP_FL_CENTRAL); -#endif */ - REGISTER_ZIP_CLASS_CONST_LONG("CM_DEFAULT", ZIP_CM_DEFAULT); REGISTER_ZIP_CLASS_CONST_LONG("CM_STORE", ZIP_CM_STORE); REGISTER_ZIP_CLASS_CONST_LONG("CM_SHRINK", ZIP_CM_SHRINK); @@ -3147,6 +3235,13 @@ static PHP_MINIT_FUNCTION(zip) REGISTER_ZIP_CLASS_CONST_LONG("OPSYS_DEFAULT", ZIP_OPSYS_DEFAULT); #endif /* ifdef ZIP_OPSYS_DEFAULT */ +#ifdef HAVE_ENCRYPTION + REGISTER_ZIP_CLASS_CONST_LONG("EM_NONE", ZIP_EM_NONE); + REGISTER_ZIP_CLASS_CONST_LONG("EM_AES_128", ZIP_EM_AES_128); + REGISTER_ZIP_CLASS_CONST_LONG("EM_AES_192", ZIP_EM_AES_192); + REGISTER_ZIP_CLASS_CONST_LONG("EM_AES_256", ZIP_EM_AES_256); +#endif + php_register_url_stream_wrapper("zip", &php_stream_zip_wrapper); le_zip_dir = zend_register_list_destructors_ex(php_zip_free_dir, NULL, le_zip_dir_name, module_number); diff --git a/ext/zip/php_zip.h b/ext/zip/php_zip.h index f886da3400..327ad54262 100644 --- a/ext/zip/php_zip.h +++ b/ext/zip/php_zip.h @@ -37,7 +37,7 @@ extern zend_module_entry zip_module_entry; #define ZIP_OVERWRITE ZIP_TRUNCATE #endif -#define PHP_ZIP_VERSION "1.13.5" +#define PHP_ZIP_VERSION "1.14.0-dev" #define ZIP_OPENBASEDIR_CHECKPATH(filename) php_check_open_basedir(filename) diff --git a/ext/zip/tests/oo_encryption.phpt b/ext/zip/tests/oo_encryption.phpt new file mode 100644 index 0000000000..b703611667 --- /dev/null +++ b/ext/zip/tests/oo_encryption.phpt @@ -0,0 +1,66 @@ +--TEST-- +ZipArchive::setEncryption*() functions +--SKIPIF-- + +--FILE-- +open($name, ZIPARCHIVE::CREATE); +// Clear +$zip->addFromString('foo.txt', 'foo'); +// Encrypted +$zip->addFromString('bar.txt', 'bar'); +var_dump($zip->setEncryptionName('bar.txt', 9999, $pass)); // Fails +var_dump($zip->setEncryptionName('bar.txt', ZipArchive::EM_AES_256, $pass)); +$zip->close(); + +echo "== Read\n"; +$r = $zip->open($name); +$s = $zip->statName('foo.txt'); +var_dump($s['encryption_method'] === ZipArchive::EM_NONE); +$s = $zip->statName('bar.txt'); +var_dump($s['encryption_method'] === ZipArchive::EM_AES_256); +var_dump($zip->getFromName('foo.txt')); // Clear, ok +var_dump($zip->getFromName('bar.txt')); // Encrypted, fails +$zip->setPassword($pass); +var_dump($zip->getFromName('bar.txt')); // Ecnrypted, ok +$zip->close(); + +echo "== Stream\n"; +var_dump(file_get_contents("zip://$name#foo.txt")); // Clear, ok +var_dump(file_get_contents("zip://$name#bar.txt")); // Encrypted, fails +$ctx = stream_context_create(array('zip' => array('password' => $pass))); +var_dump(file_get_contents("zip://$name#bar.txt", false, $ctx)); // Ecnrypted, ok +?> +== Done +--CLEAN-- + +--EXPECTF-- +== Write +bool(false) +bool(true) +== Read +bool(true) +bool(true) +string(3) "foo" +bool(false) +string(3) "bar" +== Stream +string(3) "foo" + +Warning: file_get_contents(%s): failed to open stream: operation failed in %s on line %d +bool(false) +string(3) "bar" +== Done diff --git a/ext/zip/zip_stream.c b/ext/zip/zip_stream.c index 2fa828a572..2ed584021d 100644 --- a/ext/zip/zip_stream.c +++ b/ext/zip/zip_stream.c @@ -309,6 +309,14 @@ php_stream *php_stream_zip_opener(php_stream_wrapper *wrapper, za = zip_open(file_dirname, ZIP_CREATE, &err); if (za) { + zval *tmpzval; + + if (NULL != (tmpzval = php_stream_context_get_option(context, "zip", "password"))) { + if (Z_TYPE_P(tmpzval) != IS_STRING || zip_set_default_password(za, Z_STRVAL_P(tmpzval))) { + php_error_docref(NULL, E_WARNING, "Can't set zip password"); + } + } + zf = zip_fopen(za, fragment, 0); if (zf) { self = emalloc(sizeof(*self)); @@ -348,7 +356,7 @@ static php_stream_wrapper_ops zip_stream_wops = { NULL, /* rename */ NULL, /* mkdir */ NULL, /* rmdir */ - NULL + NULL /* metadata */ }; php_stream_wrapper php_stream_zip_wrapper = { -- 2.40.0