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 |