Zip: add support for encrypted archive

This commit is contained in:
Remi Collet 2017-03-02 13:36:40 +01:00
parent 859a6505d3
commit 402eeb8598
8 changed files with 256 additions and 17 deletions

2
NEWS
View file

@ -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! >>>

View file

@ -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
========================================

View file

@ -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)

View file

@ -0,0 +1,49 @@
<?php
error_reporting(E_ALL);
if (!extension_loaded('zip')) {
dl('zip.so');
}
$name = __DIR__ . '/encrypted.zip';
$pass = 'secret';
$file = 'foo.php';
echo "== Create with per file password\n";
$zip = new ZipArchive;
$zip->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));

View file

@ -379,6 +379,20 @@ 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); \
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); \
add_ascii_assoc_long(return_value, "encryption_method", (zend_long) (sb)->encryption_method); \
}
#else
#define RETURN_SB(sb) \
{ \
array_init(return_value); \
@ -390,6 +404,7 @@ static int php_zip_parse_options(zval *options, zend_long *remove_all_path, char
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);

View file

@ -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)

View file

@ -0,0 +1,66 @@
--TEST--
ZipArchive::setEncryption*() functions
--SKIPIF--
<?php
/* $Id$ */
if (!extension_loaded('zip')) die('skip');
if (!method_exists('ZipArchive', 'setEncryptionName')) die('skip encrytion not supported');
?>
--FILE--
<?php
$name = __DIR__ . '/encrypted.zip';
$pass = 'secret';
echo "== Write\n";
$zip = new ZipArchive;
$r = $zip->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--
<?php
$name = __DIR__ . '/encrypted.zip';
@unlink($name);
?>
--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

View file

@ -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 = {