summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore44
-rw-r--r--LICENSE68
-rw-r--r--README.md53
-rw-r--r--config.m463
-rw-r--r--php_xpass.h31
-rw-r--r--tests/sha512.phpt93
-rw-r--r--tests/xpass.phpt10
-rw-r--r--tests/yescrypt.phpt92
-rw-r--r--xpass.c188
9 files changed, 641 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..648b2b2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,44 @@
+*.lo
+*.la
+*.dep
+.libs
+acinclude.m4
+aclocal.m4
+autom4te.cache
+build
+config.guess
+config.h
+config.h.in
+config.h.in~
+config.log
+config.nice
+config.status
+config.sub
+configure
+configure~
+configure.ac
+configure.in
+include
+install-sh
+libtool
+ltmain.sh
+Makefile
+Makefile.fragments
+Makefile.global
+Makefile.objects
+missing
+mkinstalldirs
+modules
+php_test_results_*.txt
+phpt.*
+run-test-info.php
+run-tests.php
+tests/**/*.diff
+tests/**/*.out
+tests/**/*.php
+tests/**/*.exp
+tests/**/*.log
+tests/**/*.sh
+tests/**/*.db
+tests/**/*.mem
+tmp-php.ini
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9964e07
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,68 @@
+--------------------------------------------------------------------
+ The PHP License, version 3.01
+Copyright (c) 1999 - 2017 The PHP Group. All rights reserved.
+--------------------------------------------------------------------
+
+Redistribution and use in source and binary forms, with or without
+modification, is 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.
+
+ 3. The name "PHP" must not be used to endorse or promote products
+ derived from this software without prior written permission. For
+ written permission, please contact group@php.net.
+
+ 4. Products derived from this software may not be called "PHP", nor
+ may "PHP" appear in their name, without prior written permission
+ from group@php.net. You may indicate that your software works in
+ conjunction with PHP by saying "Foo for PHP" instead of calling
+ it "PHP Foo" or "phpfoo"
+
+ 5. The PHP Group may publish revised and/or new versions of the
+ license from time to time. Each version will be given a
+ distinguishing version number.
+ Once covered code has been published under a particular version
+ of the license, you may always continue to use it under the terms
+ of that version. You may also choose to use such covered code
+ under the terms of any subsequent version of the license
+ published by the PHP Group. No one other than the PHP Group has
+ the right to modify the terms applicable to covered code created
+ under this License.
+
+ 6. Redistributions of any form whatsoever must retain the following
+ acknowledgment:
+ "This product includes PHP software, freely available from
+ <http://www.php.net/software/>".
+
+THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND
+ANY EXPRESSED 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 PHP
+DEVELOPMENT TEAM OR ITS CONTRIBUTORS 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.
+
+--------------------------------------------------------------------
+
+This software consists of voluntary contributions made by many
+individuals on behalf of the PHP Group.
+
+The PHP Group can be contacted via Email at group@php.net.
+
+For more information on the PHP Group and the PHP project,
+please see <http://www.php.net>.
+
+PHP includes the Zend Engine, freely available at
+<http://www.zend.com>.
diff --git a/README.md b/README.md
index a99a53d..a33ed23 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,52 @@
-xpass PHP extension
+# xpass extension for PHP
+
+This extension provides password hashing algorithms used in Linux distributions.
+
+* **sha512** (`$6$`) provided for legacy as used on some old distributions (ex: RHEL-8)
+* **yescrypt** (`$y$`) used on modern distributions
+
+----
+
+# Sources
+
+* Official git repository: https://git.remirepo.net/cgit/tools/php-xpass.git/
+* Mirror on github for contributors: https://github.com/remicollet/php-xpass
+
+----
+
+# Build
+
+You need the Extended crypt library development files (libxcrypt-devel)
+
+From the sources tree
+
+ $ phpize
+ $ ./configure --enable-xpass
+ $ make
+ $ make test
+
+----
+
+# Usage
+
+## password hashing
+
+ $ php -a
+ php > var_dump($hash = password_hash("secret", PASSWORD_YESCRYPT));
+ string(73) "$y$j9T$X9Va6i3zHjyKGJAskYZPv.$i1m/WR1C6/tqhB7IdOsi9Ar1JF4Qr38vBx104ao1OS5"
+ php > var_dump(password_verify("secret", $hash));
+ bool(true)
+
+----
+
+# LICENSE
+
+Author: Remi Collet
+
+This extension is licensed under [The PHP License, version 3.01](http://www.php.net/license/3_01.txt)
+
+-----
+
+# History
+
+Created on user request
diff --git a/config.m4 b/config.m4
new file mode 100644
index 0000000..b473924
--- /dev/null
+++ b/config.m4
@@ -0,0 +1,63 @@
+dnl config.m4 for extension xpass
+
+PHP_ARG_ENABLE([xpass],
+ [whether to enable xpass support],
+ [AS_HELP_STRING([--enable-xpass],
+ [Enable xpass support])],
+ [no])
+
+if test "$PHP_XPASS" != "no"; then
+ PKG_CHECK_MODULES([LIBXCRYPT], [libxcrypt])
+ PHP_EVAL_INCLINE($LIBXCRYPT_CFLAGS)
+ PHP_EVAL_LIBLINE($LIBXCRYPT_LIBS, XPASS_SHARED_LIBADD)
+
+ old_CFLAGS=$CFLAGS; CFLAGS="$CFLAGS $LIBXCRYPT_CFLAGS"
+ old_LDFLAGS=$CFLAGS; LDFLAGS="$CFLAGS $LIBXCRYPT_LIBS"
+
+ AC_MSG_CHECKING(for yescrypt)
+ AC_RUN_IFELSE([AC_LANG_SOURCE([[
+#include <string.h>
+#include <unistd.h>
+#include <crypt.h>
+#include <stdlib.h>
+#include <string.h>
+
+int main(void) {
+ char salt[8];
+ salt[0]='$'; salt[1]='y'; salt[2]='$'; salt[3]=0;
+ return crypt_gensalt(salt, 0, NULL, 0) ? 0 : 1;
+}]])],[
+ AC_DEFINE(HAVE_CRYPT_YESCRYPT, 1, [ Have yescrypt hash support ])
+ AC_MSG_RESULT([available])
+ ], [
+ AC_MSG_RESULT([missing])
+ ])
+
+ AC_MSG_CHECKING(for sha512)
+ AC_RUN_IFELSE([AC_LANG_SOURCE([[
+#include <string.h>
+#include <unistd.h>
+#include <crypt.h>
+#include <stdlib.h>
+#include <string.h>
+
+int main(void) {
+ char salt[8];
+ salt[0]='$'; salt[1]='6'; salt[2]='$'; salt[3]=0;
+ return crypt_gensalt(salt, 0, NULL, 0) ? 0 : 1;
+}]])],[
+ AC_DEFINE(HAVE_CRYPT_SHA512, 1, [ Have sha512 hash support ])
+ AC_MSG_RESULT([available])
+ ], [
+ AC_MSG_RESULT([missing])
+ ])
+
+ CFLAGS=$old_CFLAGS
+ LDFLAGS=$old_LDFLAGS
+
+ PHP_SUBST(XPASS_SHARED_LIBADD)
+
+ AC_DEFINE(HAVE_XPASS, 1, [ Have xpass support ])
+
+ PHP_NEW_EXTENSION(xpass, xpass.c, $ext_shared)
+fi
diff --git a/php_xpass.h b/php_xpass.h
new file mode 100644
index 0000000..17e3d5a
--- /dev/null
+++ b/php_xpass.h
@@ -0,0 +1,31 @@
+/*
+ +----------------------------------------------------------------------+
+ | xpass extension for PHP |
+ +----------------------------------------------------------------------+
+ | Copyright (c) The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt. |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Author: Remi Collet <remi@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef PHP_XPASS_H
+# define PHP_XPASS_H
+
+extern zend_module_entry xpass_module_entry;
+# define phpext_xpass_ptr &xpass_module_entry
+
+# define PHP_XPASS_VERSION "1.0.0-dev"
+
+# if defined(ZTS) && defined(COMPILE_DL_XPASS)
+ZEND_TSRMLS_CACHE_EXTERN()
+# endif
+
+#endif /* PHP_XPASS_H */
diff --git a/tests/sha512.phpt b/tests/sha512.phpt
new file mode 100644
index 0000000..2a91456
--- /dev/null
+++ b/tests/sha512.phpt
@@ -0,0 +1,93 @@
+--TEST--
+Check if xpass is loaded
+--EXTENSIONS--
+xpass
+--SKIPIF--
+<?php
+if (!defined("PASSWORD_SHA512")) die("skip SHA512 missing");
+?>
+--FILE--
+<?php
+$data = [
+ 'mysecret' => '$6$1w/SLyhyEvGAul3q$W5VyKdQFPZaNOoIWMJTIi590Tu7ioejI90F0asQneP/Mn893X0m3aPIb2J7I4cPXtuN65t/vNwgMGHlfvf6hK/',
+ 'remicollet' => '$6$BG/h0.OlUBaXdi11$iJnP3HmoR3QicxajlNTgGPpLBEDAe/QTpcrNPPZJwcc.orIwvTPQK5E5IjPmIu2ArLj3mjjVGDUSRNgDb32jD.',
+];
+foreach($data as $pass => $hash) {
+ echo "-- $pass\n";
+ var_dump(password_verify($pass, $hash));
+ var_dump(password_get_info($hash));
+ var_dump(password_verify($pass."bad", $hash));
+ var_dump(password_verify($pass, $hash."bad"));
+ var_dump(password_needs_rehash($hash, PASSWORD_SHA512));
+}
+
+echo "-- no cost\n";
+$pass = 'secret';
+var_dump($hash = password_hash($pass, PASSWORD_SHA512));
+var_dump(password_get_info($hash));
+var_dump(password_verify($pass, $hash));
+foreach([0,4,8,99] as $cost) {
+ echo "-- cost=$cost\n";
+ try {
+ $pass = "secret$cost";
+ var_dump($hash = password_hash($pass, PASSWORD_SHA512, ['cost'=>$cost]));
+ var_dump(password_verify($pass, $hash));
+ } catch (ValueError $e) {
+ printf("EXCEPTION %s\n", $e->getMessage());
+ }
+}
+?>
+--EXPECTF--
+-- mysecret
+bool(true)
+array(3) {
+ ["algo"]=>
+ string(1) "6"
+ ["algoName"]=>
+ string(6) "sha512"
+ ["options"]=>
+ array(0) {
+ }
+}
+bool(false)
+bool(false)
+bool(false)
+-- remicollet
+bool(true)
+array(3) {
+ ["algo"]=>
+ string(1) "6"
+ ["algoName"]=>
+ string(6) "sha512"
+ ["options"]=>
+ array(0) {
+ }
+}
+bool(false)
+bool(false)
+bool(false)
+-- no cost
+string(106) "$6$%s"
+array(3) {
+ ["algo"]=>
+ string(1) "6"
+ ["algoName"]=>
+ string(6) "sha512"
+ ["options"]=>
+ array(0) {
+ }
+}
+bool(true)
+-- cost=0
+string(106) "$6$%s"
+bool(true)
+-- cost=4
+string(118) "$6$rounds=1000$%s"
+bool(true)
+-- cost=8
+string(118) "$6$rounds=1000$%s"
+bool(true)
+-- cost=99
+string(118) "$6$rounds=1000$%s"
+bool(true)
+
diff --git a/tests/xpass.phpt b/tests/xpass.phpt
new file mode 100644
index 0000000..e30ce54
--- /dev/null
+++ b/tests/xpass.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Check if xpass is loaded
+--EXTENSIONS--
+xpass
+--FILE--
+<?php
+echo 'The extension "xpass" is available';
+?>
+--EXPECT--
+The extension "xpass" is available
diff --git a/tests/yescrypt.phpt b/tests/yescrypt.phpt
new file mode 100644
index 0000000..38c84e2
--- /dev/null
+++ b/tests/yescrypt.phpt
@@ -0,0 +1,92 @@
+--TEST--
+Check if xpass is loaded
+--EXTENSIONS--
+xpass
+--SKIPIF--
+<?php
+if (!defined("PASSWORD_YESCRYPT")) die("skip YESCRYPT missing");
+?>
+--FILE--
+<?php
+$data = [
+ 'mysecret' => '$y$j9T$EWkxmhFdtlCH.UrDi8l6T1$65TpODO1HXFLut3PhZhxiFweWNWFpo7QHTQtMVanr2B',
+ 'remicollet' => '$y$j9T$XxuuhBKq0UT68HX8KXaXy0$p.PggRtVfQ6rO5TReD0TgMKFyfNEA2l3QQi/dW8fS63',
+];
+foreach($data as $pass => $hash) {
+ echo "-- $pass\n";
+ var_dump(password_verify($pass, $hash));
+ var_dump(password_get_info($hash));
+ var_dump(password_verify($pass."bad", $hash));
+ var_dump(password_verify($pass, $hash."bad"));
+ var_dump(password_needs_rehash($hash, PASSWORD_YESCRYPT));
+}
+
+echo "-- no cost\n";
+$pass = 'secret';
+var_dump($hash = password_hash($pass, PASSWORD_YESCRYPT));
+var_dump(password_get_info($hash));
+var_dump(password_verify($pass, $hash));
+foreach([0,4,8,99] as $cost) {
+ echo "-- cost=$cost\n";
+ try {
+ $pass = "secret$cost";
+ var_dump($hash = password_hash($pass, PASSWORD_YESCRYPT, ['cost'=>$cost]));
+ var_dump(password_verify($pass, $hash));
+ } catch (ValueError $e) {
+ printf("EXCEPTION %s\n", $e->getMessage());
+ }
+}
+?>
+--EXPECTF--
+-- mysecret
+bool(true)
+array(3) {
+ ["algo"]=>
+ string(1) "y"
+ ["algoName"]=>
+ string(8) "yescrypt"
+ ["options"]=>
+ array(0) {
+ }
+}
+bool(false)
+bool(false)
+bool(false)
+-- remicollet
+bool(true)
+array(3) {
+ ["algo"]=>
+ string(1) "y"
+ ["algoName"]=>
+ string(8) "yescrypt"
+ ["options"]=>
+ array(0) {
+ }
+}
+bool(false)
+bool(false)
+bool(false)
+-- no cost
+string(73) "$y$j9T$%s"
+array(3) {
+ ["algo"]=>
+ string(1) "y"
+ ["algoName"]=>
+ string(8) "yescrypt"
+ ["options"]=>
+ array(0) {
+ }
+}
+bool(true)
+-- cost=0
+string(73) "$y$j9T$%s"
+bool(true)
+-- cost=4
+string(73) "$y$j8T$%s"
+bool(true)
+-- cost=8
+string(73) "$y$jCT$%s"
+bool(true)
+-- cost=99
+EXCEPTION Bad password options
+
diff --git a/xpass.c b/xpass.c
new file mode 100644
index 0000000..b20e3ca
--- /dev/null
+++ b/xpass.c
@@ -0,0 +1,188 @@
+/*
+ +----------------------------------------------------------------------+
+ | xpass extension for PHP |
+ +----------------------------------------------------------------------+
+ | Copyright (c) The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt. |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Author: Remi Collet <remi@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "ext/standard/info.h"
+#include "ext/standard/php_password.h"
+#include "php_xpass.h"
+#include <crypt.h>
+
+/* {{{ PHP_RINIT_FUNCTION */
+PHP_RINIT_FUNCTION(xpass)
+{
+#if defined(ZTS) && defined(COMPILE_DL_XPASS)
+ ZEND_TSRMLS_CACHE_UPDATE();
+#endif
+
+ return SUCCESS;
+}
+/* }}} */
+
+/* {{{ PHP_MINFO_FUNCTION */
+PHP_MINFO_FUNCTION(xpass)
+{
+ php_info_print_table_start();
+ php_info_print_table_header(2, "xpass support", "enabled");
+ php_info_print_table_row(2, "extension version", PHP_XPASS_VERSION);
+#ifdef HAVE_CRYPT_SHA512
+ php_info_print_table_row(2, "sha512 hash", "yes");
+#else
+ php_info_print_table_row(2, "sha512 hash", "no");
+#endif
+#ifdef HAVE_CRYPT_YESCRYPT
+ php_info_print_table_row(2, "yescrypt hash", "yes");
+#else
+ php_info_print_table_row(2, "yescrypt hash", "no");
+#endif
+ php_info_print_table_end();
+}
+/* }}} */
+
+static bool get_options(zend_array *options, zend_ulong *cost) {
+ zval *opt;
+
+ *cost = 0;
+ if (!options) {
+ return true;
+ }
+ if ((opt = zend_hash_str_find(options, "cost", strlen("cost")))) {
+ *cost = zval_get_long(opt);
+ }
+ return true;
+}
+
+
+static zend_string *php_xpass_hash(const zend_string *password, zend_array *options, const char *algo) {
+ struct crypt_data data;
+ zend_ulong cost;
+
+ memset(&data, 0, sizeof(data));
+
+ if (!get_options(options, &cost)) {
+ return NULL;
+ }
+ if ((ZSTR_LEN(password) >= CRYPT_MAX_PASSPHRASE_SIZE)) {
+ zend_value_error("Password is too long");
+ return NULL;
+ }
+ if (!crypt_gensalt_rn(algo, cost, NULL, 0, data.setting, sizeof(data.setting))) {
+ zend_value_error("Bad password options");
+ return NULL;
+ }
+ if (!crypt_r(ZSTR_VAL(password), data.setting, &data)) {
+ zend_value_error("Unexpected failure hashing password");
+ return NULL;
+ }
+ return zend_string_init(data.output, strlen(data.output), 0);
+}
+
+static zend_string *php_xpass_yescrypt_hash(const zend_string *password, zend_array *options) {
+ return php_xpass_hash(password, options, "$y$");
+}
+
+static zend_string *php_xpass_sha512_hash(const zend_string *password, zend_array *options) {
+ return php_xpass_hash(password, options, "$6$");
+}
+
+static bool php_xpass_verify(const zend_string *password, const zend_string *hash) {
+ struct crypt_data data;
+
+ memset(&data, 0, sizeof(data));
+
+ if ((ZSTR_LEN(password) >= CRYPT_MAX_PASSPHRASE_SIZE) || (ZSTR_LEN(hash) >= CRYPT_OUTPUT_SIZE)) {
+ return false;
+ }
+ if (!crypt_r(ZSTR_VAL(password), ZSTR_VAL(hash), &data)) {
+ return false;
+ }
+ if (strcmp(data.output, ZSTR_VAL(hash))) {
+ return false;
+ }
+ return true;
+}
+
+static bool php_xpass_needs_rehash(const zend_string *hash, zend_array *options) {
+
+ if (crypt_checksalt(ZSTR_VAL(hash))) {
+ return 1;
+ }
+ return 0;
+}
+
+static const php_password_algo xpass_algo_sha512 = {
+ "sha512",
+ php_xpass_sha512_hash,
+ php_xpass_verify,
+ php_xpass_needs_rehash,
+ NULL, // php_xpass_yescrypt_get_info,
+ NULL,
+};
+
+static const php_password_algo xpass_algo_yescrypt = {
+ "yescrypt",
+ php_xpass_yescrypt_hash,
+ php_xpass_verify,
+ php_xpass_needs_rehash,
+ NULL, // php_xpass_yescrypt_get_info,
+ NULL,
+};
+
+PHP_MINIT_FUNCTION(xpass) /* {{{ */ {
+
+#ifdef HAVE_CRYPT_SHA512
+ if (FAILURE == php_password_algo_register("6", &xpass_algo_sha512)) {
+ return FAILURE;
+ }
+ REGISTER_STRING_CONSTANT("PASSWORD_SHA512", "6", CONST_CS | CONST_PERSISTENT);
+#endif
+
+#ifdef HAVE_CRYPT_YESCRYPT
+ if (FAILURE == php_password_algo_register("y", &xpass_algo_yescrypt)) {
+ return FAILURE;
+ }
+ REGISTER_STRING_CONSTANT("PASSWORD_YESCRYPT", "y", CONST_CS | CONST_PERSISTENT);
+#endif
+
+ return SUCCESS;
+}
+
+/* {{{ xpass_module_entry */
+zend_module_entry xpass_module_entry = {
+ STANDARD_MODULE_HEADER,
+ "xpass", /* Extension name */
+ NULL, /* zend_function_entry */
+ PHP_MINIT(xpass), /* PHP_MINIT - Module initialization */
+ NULL, /* PHP_MSHUTDOWN - Module shutdown */
+ PHP_RINIT(xpass), /* PHP_RINIT - Request initialization */
+ NULL, /* PHP_RSHUTDOWN - Request shutdown */
+ PHP_MINFO(xpass), /* PHP_MINFO - Module info */
+ PHP_XPASS_VERSION, /* Version */
+ STANDARD_MODULE_PROPERTIES
+};
+/* }}} */
+
+#ifdef COMPILE_DL_XPASS
+# ifdef ZTS
+ZEND_TSRMLS_CACHE_DEFINE()
+# endif
+ZEND_GET_MODULE(xpass)
+#endif