Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
nielsdos committed Oct 13, 2024
1 parent 41c55d1 commit e41d311
Show file tree
Hide file tree
Showing 16 changed files with 3,759 additions and 5 deletions.
1 change: 1 addition & 0 deletions README.REDIST.BINS
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
19. xxHash (ext/hash/xxhash)
20. Lexbor (ext/dom/lexbor/lexbor) see ext/dom/lexbor/LICENSE
21. Portions of libcperciva (ext/hash/hash_sha_{ni,sse2}.c) see the header in the source file
22. yescrypt (ext/standard/yescrypt) see the header in the source files

3. pcre2lib (ext/pcre)

Expand Down
9 changes: 7 additions & 2 deletions ext/standard/config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ AS_VAR_IF([PHP_EXTERNAL_LIBCRYPT], [no], [
crypt_sha256.c
crypt_sha512.c
php_crypt_r.c
yescrypt/yescrypt-opt.c
yescrypt/yescrypt-common.c
yescrypt/sha256.c
"])
], [
AC_SEARCH_LIBS([crypt], [crypt],
Expand Down Expand Up @@ -143,8 +146,8 @@ int main(void) {
return !encrypted || strcmp(encrypted,"_J9..rasmBYk8r9AiWNc");
}])],
[ac_cv_crypt_ext_des=yes],
[ac_cv_crypt_ext_des=no],
[ac_cv_crypt_ext_des=no])])
[ac_cv_crypt_ext_des=yes],
[ac_cv_crypt_ext_des=yes])])
AC_CACHE_CHECK([for MD5 crypt], [ac_cv_crypt_md5],
[AC_RUN_IFELSE([AC_LANG_SOURCE([[
Expand Down Expand Up @@ -396,6 +399,7 @@ PHP_NEW_EXTENSION([standard], m4_normalize([
crc32.c
credits.c
crypt.c
yescrypt/yescrypt-config.c
css.c
datetime.c
dir.c
Expand Down Expand Up @@ -456,6 +460,7 @@ PHP_NEW_EXTENSION([standard], m4_normalize([
[-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1])

PHP_ADD_BUILD_DIR([$ext_builddir/libavifinfo])
PHP_ADD_BUILD_DIR([$ext_builddir/yescrypt])

PHP_ADD_MAKEFILE_FRAGMENT
PHP_INSTALL_HEADERS([ext/standard/])
28 changes: 28 additions & 0 deletions ext/standard/crypt.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#if PHP_USE_PHP_CRYPT_R
# include "php_crypt_r.h"
# include "crypt_freesec.h"
# include "yescrypt/yescrypt.h"
#else
# ifdef HAVE_CRYPT_H
# if defined(CRYPT_R_GNU_SOURCE) && !defined(_GNU_SOURCE)
Expand Down Expand Up @@ -138,6 +139,33 @@ PHPAPI zend_string *php_crypt(const char *password, const int pass_len, const ch
ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN + 1);
return result;
}
} else if (salt[0] == '$' && (salt[1] == 'y' || salt[1] == '7') && salt[2] == '$') {
yescrypt_local_t local;
uint8_t buf[PREFIX_LEN + 1 + HASH_LEN + 1]; /* prefix, '$', hash, NUL */

if (yescrypt_init_local(&local)) {
return NULL;
}

uint8_t *hash = yescrypt_r(
NULL,
&local,
(const uint8_t *) password,
(size_t) pass_len,
(const uint8_t *) salt,
NULL /* no key */,
buf,
sizeof(buf)
);

if (yescrypt_free_local(&local) || !hash) {
ZEND_SECURE_ZERO(buf, sizeof(buf));
return NULL;
}

result = zend_string_init((const char *) hash, strlen((const char *) hash), false);
ZEND_SECURE_ZERO(buf, sizeof(buf));
return result;
} else if (salt[0] == '_'
|| (IS_VALID_SALT_CHARACTER(salt[0]) && IS_VALID_SALT_CHARACTER(salt[1]))) {
/* DES Fallback */
Expand Down
202 changes: 200 additions & 2 deletions ext/standard/password.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#ifdef HAVE_ARGON2LIB
#include "argon2.h"
#endif
#include "yescrypt/yescrypt.h"

#ifdef PHP_WIN32
#include "win32/winutil.h"
Expand Down Expand Up @@ -151,7 +152,8 @@ static bool php_password_bcrypt_needs_rehash(const zend_string *hash, zend_array
return old_cost != new_cost;
}

static bool php_password_bcrypt_verify(const zend_string *password, const zend_string *hash) {
/* Password verification using the crypt() API, works for both bcrypt and yescrypt. */
static bool php_password_crypt_verify(const zend_string *password, const zend_string *hash) {
int status = 0;
zend_string *ret = php_crypt(ZSTR_VAL(password), (int)ZSTR_LEN(password), ZSTR_VAL(hash), (int)ZSTR_LEN(hash), 1);

Expand Down Expand Up @@ -224,12 +226,204 @@ static zend_string* php_password_bcrypt_hash(const zend_string *password, zend_a
const php_password_algo php_password_algo_bcrypt = {
"bcrypt",
php_password_bcrypt_hash,
php_password_bcrypt_verify,
php_password_crypt_verify,
php_password_bcrypt_needs_rehash,
php_password_bcrypt_get_info,
php_password_bcrypt_valid,
};

/* yescrypt implementation */

static zend_string *php_password_yescrypt_hash(const zend_string *password, zend_array *options) {
zend_long block_count = PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT;
zend_long block_size = PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE;
zend_long parallelism = PHP_PASSWORD_YESCRYPT_DEFAULT_PARALLELISM;
zend_long time = PHP_PASSWORD_YESCRYPT_DEFAULT_TIME;

if (UNEXPECTED(ZEND_LONG_INT_OVFL(ZSTR_LEN(password)))) {
zend_value_error("Password is too long");
return NULL;
}

if (options) {
bool failed;
const zval *option;

option = zend_hash_str_find(options, ZEND_STRL("block_count"));
if (option) {
block_count = zval_try_get_long(option, &failed);
if (UNEXPECTED(failed)) {
return NULL;
}

if (block_count < 4 || block_count > UINT32_MAX) {
zend_value_error("Parameter \"block_count\" must be between 4 and %u", UINT32_MAX);
return NULL;
}
}

option = zend_hash_str_find(options, ZEND_STRL("block_size"));
if (option) {
block_size = zval_try_get_long(option, &failed);
if (UNEXPECTED(failed)) {
return NULL;
}

if (block_size < 1) {
zend_value_error("Parameter \"block_size\" must be greater than 0");
return NULL;
}
}

option = zend_hash_str_find(options, ZEND_STRL("parallelism"));
if (option) {
parallelism = zval_try_get_long(option, &failed);
if (UNEXPECTED(failed)) {
return NULL;
}

if (parallelism < 1) {
zend_value_error("Parameter \"parallelism\" must be greater than 0");
return NULL;
}
}

option = zend_hash_str_find(options, ZEND_STRL("time"));
if (option) {
time = zval_try_get_long(option, &failed);
if (UNEXPECTED(failed)) {
return NULL;
}

if (time < 0) {
zend_value_error("Parameter \"time\" must be greater than or equal to 0");
return NULL;
}
}

if ((uint64_t) block_size * (uint64_t) parallelism >= (1U << 30)) {
zend_value_error("Parameter \"block_size\" * parameter \"parallelism\" must be less than 2**30");
return NULL;
}
}

zend_string *salt = php_password_get_salt(NULL, Z_UL(22), options);
if (UNEXPECTED(!salt)) {
return NULL;
}
ZSTR_VAL(salt)[ZSTR_LEN(salt)] = 0;

uint8_t prefix_buffer[PREFIX_LEN + 1];
yescrypt_params_t params = {
.flags = YESCRYPT_DEFAULTS,
.N = block_count, .r = block_size, .p = parallelism, .t = time,
.g = 0, .NROM = 0
};
uint8_t *prefix = yescrypt_encode_params_r(
&params,
(const uint8_t *) ZSTR_VAL(salt),
ZSTR_LEN(salt),
prefix_buffer,
sizeof(prefix_buffer)
);

zend_string_release_ex(salt, false);

if (UNEXPECTED(prefix == NULL)) {
return NULL;
}

return php_crypt(
ZSTR_VAL(password),
/* This cast is safe because we check that the password length fits in an int at the start. */
(int) ZSTR_LEN(password),
(const char *) prefix_buffer,
/* The following cast is safe because the prefix buffer size is always below INT_MAX. */
(int) strlen((const char *) prefix_buffer),
true
);
}

static bool php_password_yescrypt_valid(const zend_string *hash) {
const char *h = ZSTR_VAL(hash);
return (ZSTR_LEN(hash) >= 3 /* "$y$" */ + 3 /* 3 parameters that must be encoded */ + 2 /* $salt$ */ + HASH_LEN
&& ZSTR_LEN(hash) <= PREFIX_LEN + 1 + HASH_LEN)
&& (h[0] == '$') && (h[1] == 'y' || h[1] == '7') && (h[2] == '$');
}

static bool php_password_yescrypt_needs_rehash(const zend_string *hash, zend_array *options) {
zend_long block_count = PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT;
zend_long block_size = PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE;
zend_long parallelism = PHP_PASSWORD_YESCRYPT_DEFAULT_PARALLELISM;
zend_long time = PHP_PASSWORD_YESCRYPT_DEFAULT_TIME;

if (!php_password_yescrypt_valid(hash)) {
/* Should never get called this way. */
return true;
}

yescrypt_params_t params = { .p = 1 };
const uint8_t *src = yescrypt_parse_settings((const uint8_t *) ZSTR_VAL(hash), &params, NULL);
if (!src) {
return true;
}

if (options) {
const zval *option;

option = zend_hash_str_find(options, ZEND_STRL("block_count"));
if (option) {
block_count = zval_get_long(option);
}

option = zend_hash_str_find(options, ZEND_STRL("block_size"));
if (option) {
block_size = zval_get_long(option);
}

option = zend_hash_str_find(options, ZEND_STRL("parallelism"));
if (option) {
parallelism = zval_get_long(option);
}

option = zend_hash_str_find(options, ZEND_STRL("time"));
if (option) {
time = zval_get_long(option);
}
}

return block_count != params.N || block_size != params.r || parallelism != params.p || time != params.t;
}

static int php_password_yescrypt_get_info(zval *return_value, const zend_string *hash) {
if (!php_password_yescrypt_valid(hash)) {
/* Should never get called this way. */
return FAILURE;
}

yescrypt_params_t params = { .p = 1 };
const uint8_t *src = yescrypt_parse_settings((const uint8_t *) ZSTR_VAL(hash), &params, NULL);
if (!src) {
return FAILURE;
}

add_assoc_long(return_value, "block_count", (zend_long) params.N);
add_assoc_long(return_value, "block_size", (zend_long) params.r);
add_assoc_long(return_value, "parallelism", (zend_long) params.p);
add_assoc_long(return_value, "time", (zend_long) params.t);

return SUCCESS;
}

const php_password_algo php_password_algo_yescrypt = {
"yescrypt",
php_password_yescrypt_hash,
php_password_crypt_verify,
php_password_yescrypt_needs_rehash,
php_password_yescrypt_get_info,
php_password_yescrypt_valid,
};


#ifdef HAVE_ARGON2LIB
/* argon2i/argon2id shared implementation */
Expand Down Expand Up @@ -427,6 +621,10 @@ PHP_MINIT_FUNCTION(password) /* {{{ */
return FAILURE;
}

if (FAILURE == php_password_algo_register("y", &php_password_algo_yescrypt)) {
return FAILURE;
}

#ifdef HAVE_ARGON2LIB
if (FAILURE == php_password_algo_register("argon2i", &php_password_algo_argon2i)) {
return FAILURE;
Expand Down
20 changes: 20 additions & 0 deletions ext/standard/password.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,32 @@
* @var string
*/
const PASSWORD_BCRYPT = "2y";
/**
* @var string
*/
const PASSWORD_YESCRYPT = "y";
/**
* @var int
* @cvalue PHP_PASSWORD_BCRYPT_COST
*/
const PASSWORD_BCRYPT_DEFAULT_COST = UNKNOWN;

/**
* @var int
* @cvalue PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT
*/
const PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT = UNKNOWN;
/**
* @var int
* @cvalue PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE
*/
const PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE = UNKNOWN;
/**
* @var int
* @cvalue PHP_PASSWORD_YESCRYPT_DEFAULT_PARALLELISM
*/
const PASSWORD_YESCRYPT_DEFAULT_PARALLELISM = UNKNOWN;

#ifdef HAVE_ARGON2LIB
/**
* @var string
Expand Down
6 changes: 5 additions & 1 deletion ext/standard/password_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions ext/standard/php_password.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ PHP_MSHUTDOWN_FUNCTION(password);
#define PHP_PASSWORD_DEFAULT PHP_PASSWORD_BCRYPT
#define PHP_PASSWORD_BCRYPT_COST 12

#define PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT 4096
#define PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE 32
#define PHP_PASSWORD_YESCRYPT_DEFAULT_PARALLELISM 1
#define PHP_PASSWORD_YESCRYPT_DEFAULT_TIME 0

#ifdef HAVE_ARGON2LIB
/**
* When updating these values, synchronize values in
Expand Down
Loading

0 comments on commit e41d311

Please sign in to comment.