diff options
| author | Remi Collet <remi@remirepo.net> | 2024-08-27 10:50:21 +0200 | 
|---|---|---|
| committer | Remi Collet <remi@php.net> | 2024-08-27 10:50:21 +0200 | 
| commit | d4ff70e6cd95010bbe47f9a5a81a19bdae52e241 (patch) | |
| tree | 3271a8fee984854524f79987cdcf21e3d6289fe6 | |
| parent | 1c8bfa97ff1976f212b07e3dc30b78eac60b253d (diff) | |
start development
| -rw-r--r-- | .gitignore | 44 | ||||
| -rw-r--r-- | LICENSE | 68 | ||||
| -rw-r--r-- | README.md | 53 | ||||
| -rw-r--r-- | config.m4 | 63 | ||||
| -rw-r--r-- | php_xpass.h | 31 | ||||
| -rw-r--r-- | tests/sha512.phpt | 93 | ||||
| -rw-r--r-- | tests/xpass.phpt | 10 | ||||
| -rw-r--r-- | tests/yescrypt.phpt | 92 | ||||
| -rw-r--r-- | xpass.c | 188 | 
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 @@ -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>. @@ -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 + @@ -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  | 
