From 3c04dfc8dee22473d03f3fb88be28eb3b3a7d74c Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Fri, 8 Mar 2024 11:03:55 +0100 Subject: [PATCH 1/9] Implement GH-13514 PASSWORD_ARGON2 from OpenSSL 3.2 --- ext/openssl/config0.m4 | 2 +- ext/openssl/openssl.c | 22 + ext/openssl/openssl_pwhash.c | 413 ++++++++++++++++++ ext/openssl/openssl_pwhash.stub.php | 37 ++ ext/openssl/openssl_pwhash_arginfo.h | 66 +++ ext/openssl/php_openssl.h | 34 +- ext/openssl/tests/openssl_password.phpt | 42 ++ .../tests/openssl_password_compat.phpt | 52 +++ .../tests/openssl_password_compat2.phpt | 52 +++ ext/standard/php_password.h | 6 +- 10 files changed, 722 insertions(+), 4 deletions(-) create mode 100644 ext/openssl/openssl_pwhash.c create mode 100644 ext/openssl/openssl_pwhash.stub.php create mode 100644 ext/openssl/openssl_pwhash_arginfo.h create mode 100644 ext/openssl/tests/openssl_password.phpt create mode 100644 ext/openssl/tests/openssl_password_compat.phpt create mode 100644 ext/openssl/tests/openssl_password_compat2.phpt diff --git a/ext/openssl/config0.m4 b/ext/openssl/config0.m4 index e80281110ffe0..6770337a09f46 100644 --- a/ext/openssl/config0.m4 +++ b/ext/openssl/config0.m4 @@ -18,7 +18,7 @@ PHP_ARG_WITH([openssl-legacy-provider], [no]) if test "$PHP_OPENSSL" != "no"; then - PHP_NEW_EXTENSION([openssl], [openssl.c xp_ssl.c], [$ext_shared]) + PHP_NEW_EXTENSION([openssl], [openssl.c openssl_pwhash.c xp_ssl.c], [$ext_shared]) PHP_SUBST([OPENSSL_SHARED_LIBADD]) PHP_SETUP_OPENSSL([OPENSSL_SHARED_LIBADD], [AC_DEFINE([HAVE_OPENSSL_EXT], [1], diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 4c16279d81856..e5a1cf7483cda 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -270,9 +270,20 @@ static void php_openssl_pkey_free_obj(zend_object *object) zend_object_std_dtor(&key_object->std); } +#if PHP_OPENSSL_API_VERSION >= 0x30200 +static const zend_module_dep openssl_deps[] = { + ZEND_MOD_REQUIRED("standard") + ZEND_MOD_END +}; +#endif /* {{{ openssl_module_entry */ zend_module_entry openssl_module_entry = { +#if PHP_OPENSSL_API_VERSION >= 0x30200 + STANDARD_MODULE_HEADER_EX, NULL, + openssl_deps, +#else STANDARD_MODULE_HEADER, +#endif "openssl", ext_functions, PHP_MINIT(openssl), @@ -1330,6 +1341,12 @@ PHP_MINIT_FUNCTION(openssl) REGISTER_INI_ENTRIES(); +#if PHP_OPENSSL_API_VERSION >= 0x30200 + if (FAILURE == PHP_MINIT(openssl_pwhash)(INIT_FUNC_ARGS_PASSTHRU)) { + return FAILURE; + } +#endif + return SUCCESS; } /* }}} */ @@ -1402,6 +1419,11 @@ PHP_MSHUTDOWN_FUNCTION(openssl) php_stream_xport_unregister("tlsv1.2"); php_stream_xport_unregister("tlsv1.3"); +#if PHP_OPENSSL_API_VERSION >= 0x30200 + if (FAILURE == PHP_MSHUTDOWN(openssl_pwhash)(SHUTDOWN_FUNC_ARGS_PASSTHRU)) { + return FAILURE; + } +#endif /* reinstate the default tcp handler */ php_stream_xport_register("tcp", php_stream_generic_socket_factory); diff --git a/ext/openssl/openssl_pwhash.c b/ext/openssl/openssl_pwhash.c new file mode 100644 index 0000000000000..3a05cfee2da1b --- /dev/null +++ b/ext/openssl/openssl_pwhash.c @@ -0,0 +1,413 @@ +/* + +----------------------------------------------------------------------+ + | 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: | + | https://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. | + +----------------------------------------------------------------------+ + | Authors: Remi Collet | + +----------------------------------------------------------------------+ +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "php.h" +#include "ext/standard/php_password.h" +#include "php_openssl.h" + +#if PHP_OPENSSL_API_VERSION >= 0x30200 +#include "Zend/zend_attributes.h" +#include "openssl_pwhash_arginfo.h" +#include +#include "ext/random/php_random_csprng.h" +#include +#include +#include +#include + +#define MEMLIMIT_MIN 8u +#define MEMLIMIT_MAX UINT32_MAX +#define OPSLIMIT_MIN 1u +#define OPSLIMIT_MAX UINT32_MAX +#define THREADS_MIN 1u +#define THREADS_MAX UINT32_MAX + +#define ARGON_VERSION 0x13 + +#define SALT_SIZE 16 +#define HASH_SIZE 32 +#define DIGEST_SIZE 128 + +static inline int get_options(zend_array *options, uint32_t *memlimit, uint32_t *opslimit, uint32_t *threads) +{ + zval *opt; + + *opslimit = PHP_OPENSSL_PWHASH_OPSLIMIT; + *memlimit = PHP_OPENSSL_PWHASH_MEMLIMIT; + *threads = PHP_OPENSSL_PWHASH_THREADS; + + if (!options) { + return SUCCESS; + } + if ((opt = zend_hash_str_find(options, "memory_cost", strlen("memory_cost")))) { + zend_long smemlimit = zval_get_long(opt); + + if ((smemlimit < 0) || (smemlimit < MEMLIMIT_MIN) || (smemlimit > (MEMLIMIT_MAX))) { + zend_value_error("Memory cost is outside of allowed memory range"); + return FAILURE; + } + *memlimit = smemlimit; + } + if ((opt = zend_hash_str_find(options, "time_cost", strlen("time_cost")))) { + zend_long sopslimit = zval_get_long(opt); + if ((sopslimit < OPSLIMIT_MIN) || (sopslimit > OPSLIMIT_MAX)) { + zend_value_error("Time cost is outside of allowed time range"); + return FAILURE; + } + *opslimit = sopslimit; + } + if ((opt = zend_hash_str_find(options, "threads", strlen("threads"))) && (zval_get_long(opt) != 1)) { + zend_long sthreads = zval_get_long(opt); + if ((sthreads < THREADS_MIN) || (sthreads > THREADS_MAX)) { + zend_value_error("Invalid number of threads"); + return FAILURE; + } + *threads = sthreads; + } + return SUCCESS; +} + +static bool php_openssl_argon2_compute_hash( + const char *algo, + uint32_t version, uint32_t memlimit, uint32_t opslimit, uint32_t threads, + const char *pass, size_t pass_len, + const unsigned char *salt, size_t salt_len, + unsigned char *hash, size_t hash_len) +{ + OSSL_PARAM params[7], *p = params; + EVP_KDF *kdf = NULL; + EVP_KDF_CTX *kctx = NULL; + bool ret = false; + + + if (threads > 1) { + if (OSSL_set_max_threads(NULL, threads) != 1) { + goto fail; + } + } + p = params; + *p++ = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_THREADS, + &threads); + *p++ = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_LANES, + &threads); + *p++= OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ITER, + &opslimit); + *p++ = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, + &memlimit); + *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, + (void *)salt, salt_len); + *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_PASSWORD, + (void *)pass, pass_len); + *p++ = OSSL_PARAM_construct_end(); + + if ((kdf = EVP_KDF_fetch(NULL, algo, NULL)) == NULL) { + goto fail; + } + if ((kctx = EVP_KDF_CTX_new(kdf)) == NULL) { + goto fail; + } + if (EVP_KDF_derive(kctx, hash, hash_len, params) != 1) { + zend_value_error("Unexpected failure hashing password"); + goto fail; + } + + ret = true; + +fail: + EVP_KDF_free(kdf); + EVP_KDF_CTX_free(kctx); + + if (threads > 1) { + OSSL_set_max_threads(NULL, 0); + } + return ret; +} + +static zend_string *php_openssl_argon2_hash(const zend_string *password, zend_array *options, const char *algo) +{ + uint32_t opslimit, memlimit, threads, version = ARGON_VERSION; + zend_string *digest = NULL, *salt64 = NULL, *hash64 = NULL; + unsigned char hash[HASH_SIZE+1], salt[SALT_SIZE+1]; + + if ((ZSTR_LEN(password) >= UINT32_MAX)) { + zend_value_error("Password is too long"); + return NULL; + } + if (get_options(options, &memlimit, &opslimit, &threads) == FAILURE) { + return NULL; + } + if (FAILURE == php_random_bytes_throw(salt, SALT_SIZE)) { + return NULL; + } + + if (!php_openssl_argon2_compute_hash(algo, version, memlimit, opslimit, threads, + ZSTR_VAL(password), ZSTR_LEN(password), salt, SALT_SIZE, hash, HASH_SIZE)) { + return NULL; + } + + hash64 = php_base64_encode(hash, HASH_SIZE); + /* No padding utsing 32 *4 / 3 = 42.6 (43 + 1 padding char) */ + ZEND_ASSERT(ZSTR_LEN(hash64)==44 && ZSTR_VAL(hash64)[43]=='='); + ZSTR_VAL(hash64)[43] = 0; + ZSTR_LEN(hash64) = 43; + + salt64 = php_base64_encode(salt, SALT_SIZE); + /* No padding using 16 *4 / 3 = 21.3 (22 + 2 padding char) */ + ZEND_ASSERT(ZSTR_LEN(salt64)==24 && ZSTR_VAL(salt64)[22]=='=' && ZSTR_VAL(salt64)[23]=='='); + ZSTR_VAL(salt64)[22] = 0; + ZSTR_LEN(salt64) = 22; + + digest = zend_string_alloc(DIGEST_SIZE, 0); + ZSTR_LEN(digest) = snprintf(ZSTR_VAL(digest), ZSTR_LEN(digest), "$%s$v=%d$m=%u,t=%u,p=%u$%s$%s", + algo, version, memlimit, opslimit, threads, ZSTR_VAL(salt64), ZSTR_VAL(hash64)); + + zend_string_release(salt64); + zend_string_release(hash64); + + return digest; +} + +static int php_openssl_argon2_extract( + const zend_string *digest, uint32_t *version, uint32_t *memlimit, uint32_t *opslimit, + uint32_t *threads, zend_string **salt, zend_string **hash) +{ + const char *p; + char *hash64, *salt64; + + if (!digest || (ZSTR_LEN(digest) < sizeof("$argon2id$"))) { + return FAILURE; + } + p = ZSTR_VAL(digest); + if (!memcmp(p, "$argon2i$", strlen("$argon2i$"))) { + p += strlen("$argon2i$"); + } else if (!memcmp(p, "$argon2id$", strlen("$argon2id$"))) { + p += strlen("$argon2id$"); + } else { + return FAILURE; + } + if (sscanf(p, "v=%" PRIu32 "$m=%" PRIu32 ",t=%" PRIu32 ",p=%" PRIu32, + version, memlimit, opslimit, threads) != 4) { + return FAILURE; + } + if (salt && hash) { + /* start of param */ + p = strchr(p, '$'); + if (!p) { + return FAILURE; + } + /* start of salt */ + p = strchr(p+1, '$'); + if (!p) { + return FAILURE; + } + salt64 = estrdup(p+1); + /* start of hash */ + hash64 = strchr(salt64, '$'); + if (!hash64) { + efree(salt64); + return FAILURE; + } + *hash64++ = 0; + *salt = php_base64_decode((unsigned char *)salt64, strlen(salt64)); + *hash = php_base64_decode((unsigned char *)hash64, strlen(hash64)); + efree(salt64); + } + return SUCCESS; +} + +static bool php_openssl_argon2_verify(const zend_string *password, const zend_string *digest, const char *algo) +{ + uint32_t version, opslimit, memlimit, threads; + zend_string *salt, *hash, *new; + bool ret = false; + + if ((ZSTR_LEN(password) >= UINT32_MAX) || (ZSTR_LEN(digest) >= UINT32_MAX)) { + return false; + } + if (FAILURE == php_openssl_argon2_extract(digest, &version, &memlimit, &opslimit, &threads, &salt, &hash)) { + return false; + } + + new = zend_string_alloc(ZSTR_LEN(hash), 0); + if (php_openssl_argon2_compute_hash(algo, version, memlimit, opslimit, threads, + ZSTR_VAL(password), ZSTR_LEN(password), (unsigned char *)ZSTR_VAL(salt), + ZSTR_LEN(salt), (unsigned char *)ZSTR_VAL(new), ZSTR_LEN(new))) { + ret = (php_safe_bcmp(hash, new) == 0); + } + + zend_string_release(new); + zend_string_release(salt); + zend_string_release(hash); + + return ret; +} + +static bool php_openssl_argon2i_verify(const zend_string *password, const zend_string *digest) +{ + return php_openssl_argon2_verify(password, digest, "argon2i"); +} + +static bool php_openssl_argon2id_verify(const zend_string *password, const zend_string *digest) +{ + return php_openssl_argon2_verify(password, digest, "argon2id"); +} + +static bool php_openssl_argon2_needs_rehash(const zend_string *hash, zend_array *options) +{ + uint32_t version, opslimit, memlimit, threads; + uint32_t new_version = ARGON_VERSION, new_opslimit, new_memlimit, new_threads; + + if (FAILURE == get_options(options, &new_memlimit, &new_opslimit, &new_threads)) { + return true; + } + if (FAILURE == php_openssl_argon2_extract(hash, &version, &memlimit, &opslimit, &threads, NULL, NULL)) { + return true; + } + + // Algo already checked in pasword_needs_rehash implementation + return (version != new_version) || + (opslimit != new_opslimit) || + (memlimit != new_memlimit) || + (threads != new_threads); +} + +static int php_openssl_argon2_get_info(zval *return_value, const zend_string *hash) +{ + uint32_t v, threads; + uint32_t memory_cost; + uint32_t time_cost; + + if (FAILURE == php_openssl_argon2_extract(hash, &v, &memory_cost, &time_cost, &threads, NULL, NULL)) { + return FAILURE; + } + add_assoc_long(return_value, "memory_cost", memory_cost); + add_assoc_long(return_value, "time_cost", time_cost); + add_assoc_long(return_value, "threads", threads); + + return SUCCESS; +} + + +static zend_string *php_openssl_argon2i_hash(const zend_string *password, zend_array *options) +{ + return php_openssl_argon2_hash(password, options, "argon2i"); +} + +static const php_password_algo openssl_algo_argon2i = { + "argon2i", + php_openssl_argon2i_hash, + php_openssl_argon2i_verify, + php_openssl_argon2_needs_rehash, + php_openssl_argon2_get_info, + NULL, +}; + +static zend_string *php_openssl_argon2id_hash(const zend_string *password, zend_array *options) +{ + return php_openssl_argon2_hash(password, options, "argon2id"); +} + +static const php_password_algo openssl_algo_argon2id = { + "argon2id", + php_openssl_argon2id_hash, + php_openssl_argon2id_verify, + php_openssl_argon2_needs_rehash, + php_openssl_argon2_get_info, + NULL, +}; + +PHP_FUNCTION(openssl_password_hash) +{ + zend_string *password, *algo, *digest; + zend_array *options = NULL; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(algo) + Z_PARAM_STR(password) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_HT(options) + ZEND_PARSE_PARAMETERS_END(); + + if (strcmp(ZSTR_VAL(algo), "argon2i") && strcmp(ZSTR_VAL(algo), "argon2id")) { + zend_argument_value_error(1, "must be a valid password openssl hashing algorithm"); + RETURN_THROWS(); + } + + digest = php_openssl_argon2_hash(password, options, ZSTR_VAL(algo)); + if (!digest) { + if (!EG(exception)) { + zend_throw_error(NULL, "Password hashing failed for unknown reason"); + } + RETURN_THROWS(); + } + + RETURN_NEW_STR(digest); +} + +PHP_FUNCTION(openssl_password_verify) +{ + zend_string *password, *algo, *digest; + + ZEND_PARSE_PARAMETERS_START(3, 3) + Z_PARAM_STR(algo) + Z_PARAM_STR(password) + Z_PARAM_STR(digest) + ZEND_PARSE_PARAMETERS_END(); + + if (strcmp(ZSTR_VAL(algo), "argon2i") && strcmp(ZSTR_VAL(algo), "argon2id")) { + zend_argument_value_error(1, "must be a valid password openssl hashing algorithm"); + RETURN_THROWS(); + } + + RETURN_BOOL(php_openssl_argon2_verify(password, digest, ZSTR_VAL(algo))); +} + +PHP_MINIT_FUNCTION(openssl_pwhash) +{ + zend_string *argon2i = ZSTR_INIT_LITERAL("argon2i", 1); + + zend_register_functions(NULL, ext_functions, NULL, type); + + if (php_password_algo_find(argon2i)) { + /* Nothing to do. Core or sodium has registered these algorithms for us. */ + zend_string_release(argon2i); + return SUCCESS; + } + zend_string_release(argon2i); + + register_openssl_pwhash_symbols(module_number); + + if (FAILURE == php_password_algo_register("argon2i", &openssl_algo_argon2i)) { + return FAILURE; + } + if (FAILURE == php_password_algo_register("argon2id", &openssl_algo_argon2id)) { + return FAILURE; + } + + return SUCCESS; +} + +PHP_MSHUTDOWN_FUNCTION(openssl_pwhash) +{ + zend_unregister_functions(ext_functions, -1, NULL); + + return SUCCESS; +} +#endif /* PHP_OPENSSL_API_VERSION >= 0x30200 */ diff --git a/ext/openssl/openssl_pwhash.stub.php b/ext/openssl/openssl_pwhash.stub.php new file mode 100644 index 0000000000000..a0b0a3860f5b2 --- /dev/null +++ b/ext/openssl/openssl_pwhash.stub.php @@ -0,0 +1,37 @@ += 0x30200 +/** + * @var string + */ +const PASSWORD_ARGON2I = "argon2i"; +/** + * @var string + */ +const PASSWORD_ARGON2ID = "argon2id"; +/** + * @var int + * @cvalue PHP_OPENSSL_PWHASH_MEMLIMIT + */ +const PASSWORD_ARGON2_DEFAULT_MEMORY_COST = UNKNOWN; +/** + * @var int + * @cvalue PHP_OPENSSL_PWHASH_OPSLIMIT + */ +const PASSWORD_ARGON2_DEFAULT_TIME_COST = UNKNOWN; +/** + * @var int + * @cvalue PHP_OPENSSL_PWHASH_THREADS + */ +const PASSWORD_ARGON2_DEFAULT_THREADS = UNKNOWN; +/** + * @var string + */ +const PASSWORD_ARGON2_PROVIDER = "openssl"; + +function openssl_password_hash(string $algo, #[\SensitiveParameter] string $password, array $options = []): string {} +function openssl_password_verify(string $algo, #[\SensitiveParameter] string $password, string $hash): bool {} +#endif + diff --git a/ext/openssl/openssl_pwhash_arginfo.h b/ext/openssl/openssl_pwhash_arginfo.h new file mode 100644 index 0000000000000..3680f1b6a335e --- /dev/null +++ b/ext/openssl/openssl_pwhash_arginfo.h @@ -0,0 +1,66 @@ +/* This is a generated file, edit the .stub.php file instead. + * Stub hash: 6781103bb88a5cb7c4e776301f0c8689582a29ab */ + +#if PHP_OPENSSL_API_VERSION >= 0x30200 +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_password_hash, 0, 2, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, algo, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, password, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 0, "[]") +ZEND_END_ARG_INFO() +#endif + +#if PHP_OPENSSL_API_VERSION >= 0x30200 +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_password_verify, 0, 3, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, algo, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, password, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, hash, IS_STRING, 0) +ZEND_END_ARG_INFO() +#endif + +#if PHP_OPENSSL_API_VERSION >= 0x30200 +ZEND_FUNCTION(openssl_password_hash); +#endif +#if PHP_OPENSSL_API_VERSION >= 0x30200 +ZEND_FUNCTION(openssl_password_verify); +#endif + +static const zend_function_entry ext_functions[] = { +#if PHP_OPENSSL_API_VERSION >= 0x30200 + ZEND_FE(openssl_password_hash, arginfo_openssl_password_hash) +#endif +#if PHP_OPENSSL_API_VERSION >= 0x30200 + ZEND_FE(openssl_password_verify, arginfo_openssl_password_verify) +#endif + ZEND_FE_END +}; + +static void register_openssl_pwhash_symbols(int module_number) +{ +#if PHP_OPENSSL_API_VERSION >= 0x30200 + REGISTER_STRING_CONSTANT("PASSWORD_ARGON2I", "argon2i", CONST_PERSISTENT); +#endif +#if PHP_OPENSSL_API_VERSION >= 0x30200 + REGISTER_STRING_CONSTANT("PASSWORD_ARGON2ID", "argon2id", CONST_PERSISTENT); +#endif +#if PHP_OPENSSL_API_VERSION >= 0x30200 + REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_MEMORY_COST", PHP_OPENSSL_PWHASH_MEMLIMIT, CONST_PERSISTENT); +#endif +#if PHP_OPENSSL_API_VERSION >= 0x30200 + REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_TIME_COST", PHP_OPENSSL_PWHASH_OPSLIMIT, CONST_PERSISTENT); +#endif +#if PHP_OPENSSL_API_VERSION >= 0x30200 + REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_THREADS", PHP_OPENSSL_PWHASH_THREADS, CONST_PERSISTENT); +#endif +#if PHP_OPENSSL_API_VERSION >= 0x30200 + REGISTER_STRING_CONSTANT("PASSWORD_ARGON2_PROVIDER", "openssl", CONST_PERSISTENT); +#endif + +#if PHP_OPENSSL_API_VERSION >= 0x30200 + + zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "openssl_password_hash", sizeof("openssl_password_hash") - 1), 1, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); +#endif +#if PHP_OPENSSL_API_VERSION >= 0x30200 + + zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "openssl_password_verify", sizeof("openssl_password_verify") - 1), 1, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); +#endif +} diff --git a/ext/openssl/php_openssl.h b/ext/openssl/php_openssl.h index 3eeb7eec35238..fe059368ce0f8 100644 --- a/ext/openssl/php_openssl.h +++ b/ext/openssl/php_openssl.h @@ -37,8 +37,10 @@ extern zend_module_entry openssl_module_entry; /* OpenSSL version check */ #if OPENSSL_VERSION_NUMBER < 0x30000000L #define PHP_OPENSSL_API_VERSION 0x10100 -#else +#elif OPENSSL_VERSION_NUMBER < 0x30200000L #define PHP_OPENSSL_API_VERSION 0x30000 +#else +#define PHP_OPENSSL_API_VERSION 0x30200 #endif #endif @@ -156,11 +158,41 @@ static inline php_openssl_certificate_object *php_openssl_certificate_from_obj(z #define Z_OPENSSL_CERTIFICATE_P(zv) php_openssl_certificate_from_obj(Z_OBJ_P(zv)) +#if PHP_OPENSSL_API_VERSION >= 0x30200 + +/** + * MEMLIMIT is normalized to KB even though sodium uses Bytes in order to + * present a consistent user-facing API. + * + * When updating these values, synchronize ext/standard/php_password.h values. + */ +#if defined(PHP_PASSWORD_ARGON2_MEMORY_COST) +#define PHP_OPENSSL_PWHASH_MEMLIMIT PHP_PASSWORD_ARGON2_MEMORY_COST +#else +#define PHP_OPENSSL_PWHASH_MEMLIMIT (64 << 10) +#endif +#if defined(PHP_PASSWORD_ARGON2_TIME_COST) +#define PHP_OPENSSL_PWHASH_OPSLIMIT PHP_PASSWORD_ARGON2_TIME_COST +#else +#define PHP_OPENSSL_PWHASH_OPSLIMIT 4 +#endif +#if defined(PHP_PASSWORD_ARGON2_THREADS) +#define PHP_OPENSSL_PWHASH_THREADS PHP_PASSWORD_ARGON2_THREADS +#else +#define PHP_OPENSSL_PWHASH_THREADS 1 +#endif + +#endif + PHP_MINIT_FUNCTION(openssl); PHP_MSHUTDOWN_FUNCTION(openssl); PHP_MINFO_FUNCTION(openssl); PHP_GINIT_FUNCTION(openssl); PHP_GSHUTDOWN_FUNCTION(openssl); +#if PHP_OPENSSL_API_VERSION >= 0x30200 +PHP_MINIT_FUNCTION(openssl_pwhash); +PHP_MSHUTDOWN_FUNCTION(openssl_pwhash); +#endif #ifdef PHP_WIN32 #define PHP_OPENSSL_BIO_MODE_R(flags) (((flags) & PKCS7_BINARY) ? "rb" : "r") diff --git a/ext/openssl/tests/openssl_password.phpt b/ext/openssl/tests/openssl_password.phpt new file mode 100644 index 0000000000000..78818030388e1 --- /dev/null +++ b/ext/openssl/tests/openssl_password.phpt @@ -0,0 +1,42 @@ +--TEST-- +Basic features of password_hash +--EXTENSIONS-- +openssl +--SKIPIF-- + +--FILE-- + PASSWORD_ARGON2_DEFAULT_MEMORY_COST / $mem, + 'time_cost' => PASSWORD_ARGON2_DEFAULT_TIME_COST / $time, + 'threads' => PASSWORD_ARGON2_DEFAULT_THREADS, + ]; + foreach(['argon2i', 'argon2id'] as $algo) { + $pass = "secret$mem$time$algo"; + $hash = openssl_password_hash($algo, $pass, $opts); + var_dump(openssl_password_verify($algo, $pass, $hash)); + } + } +} +?> +--EXPECTF-- +Argon2 provider: string(%d) "%s" +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) + diff --git a/ext/openssl/tests/openssl_password_compat.phpt b/ext/openssl/tests/openssl_password_compat.phpt new file mode 100644 index 0000000000000..0de683616aee8 --- /dev/null +++ b/ext/openssl/tests/openssl_password_compat.phpt @@ -0,0 +1,52 @@ +--TEST-- +Compatibility of password_hash (libsodium / openssl) +--EXTENSIONS-- +openssl +sodium +--SKIPIF-- + +--FILE-- + PASSWORD_ARGON2_DEFAULT_MEMORY_COST / $mem, + 'time_cost' => PASSWORD_ARGON2_DEFAULT_TIME_COST / $time, + 'threads' => PASSWORD_ARGON2_DEFAULT_THREADS, + ]; + $algo = 'argon2id'; + $pass = "secret$mem$time$algo"; + + /* hash with libsodium / verify with openssl */ + $hash = sodium_crypto_pwhash_str($pass, PASSWORD_ARGON2_DEFAULT_TIME_COST / $time, PASSWORD_ARGON2_DEFAULT_MEMORY_COST / $mem); + var_dump(openssl_password_verify($algo, $pass, $hash)); + + /* hash with openssl / verify with libsodium */ + $hash = openssl_password_hash($algo, $pass, $opts); + var_dump(sodium_crypto_pwhash_str_verify($hash, $pass)); + } +} +?> +--EXPECTF-- +Argon2 provider: string(%d) "%s" +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) + diff --git a/ext/openssl/tests/openssl_password_compat2.phpt b/ext/openssl/tests/openssl_password_compat2.phpt new file mode 100644 index 0000000000000..42cf8682fd70c --- /dev/null +++ b/ext/openssl/tests/openssl_password_compat2.phpt @@ -0,0 +1,52 @@ +--TEST-- +Compatibility of password_hash (libargon2 / openssl) +--EXTENSIONS-- +openssl +sodium +--SKIPIF-- + +--FILE-- + PASSWORD_ARGON2_DEFAULT_MEMORY_COST / $mem, + 'time_cost' => PASSWORD_ARGON2_DEFAULT_TIME_COST / $time, + 'threads' => PASSWORD_ARGON2_DEFAULT_THREADS, + ]; + $algo = 'argon2id'; + $pass = "secret$mem$time$algo"; + + /* hash with libargon2 / verify with openssl */ + $hash = password_hash($pass, PASSWORD_ARGON2ID, $opts); + var_dump(openssl_password_verify($algo, $pass, $hash)); + + /* hash with openssl / verify with libargon2 */ + $hash = openssl_password_hash($algo, $pass, $opts); + var_dump(password_verify($pass, $hash)); + } +} +?> +--EXPECT-- +Argon2 provider: string(8) "standard" +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) + diff --git a/ext/standard/php_password.h b/ext/standard/php_password.h index aa74b1a58f0dd..5a6f3b2af5c9e 100644 --- a/ext/standard/php_password.h +++ b/ext/standard/php_password.h @@ -26,8 +26,10 @@ PHP_MSHUTDOWN_FUNCTION(password); #ifdef HAVE_ARGON2LIB /** - * When updating these values, synchronize ext/sodium/sodium_pwhash.c values. - * Note that libargon expresses memlimit in KB, while libsoidum uses bytes. + * When updating these values, synchronize values in + * ext/sodium/php_libsodium.h + * ext/openssl/php_openssl.h + * Note that libargon/openssl express memlimit in KB, while libsodium uses bytes. */ #define PHP_PASSWORD_ARGON2_MEMORY_COST (64 << 10) #define PHP_PASSWORD_ARGON2_TIME_COST 4 From 7893c820e9a0e5d94c392b32720935d94bd95b44 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Tue, 12 Mar 2024 14:47:55 +0100 Subject: [PATCH 2/9] simplify init/shutdown --- ext/openssl/openssl.c | 5 ---- ext/openssl/openssl.stub.php | 5 ++++ ext/openssl/openssl_arginfo.h | 38 +++++++++++++++++++++++- ext/openssl/openssl_pwhash.c | 9 ------ ext/openssl/openssl_pwhash.stub.php | 3 -- ext/openssl/openssl_pwhash_arginfo.h | 44 +--------------------------- ext/openssl/php_openssl.h | 1 - 7 files changed, 43 insertions(+), 62 deletions(-) diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index e5a1cf7483cda..1891ad2b83606 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -1419,11 +1419,6 @@ PHP_MSHUTDOWN_FUNCTION(openssl) php_stream_xport_unregister("tlsv1.2"); php_stream_xport_unregister("tlsv1.3"); -#if PHP_OPENSSL_API_VERSION >= 0x30200 - if (FAILURE == PHP_MSHUTDOWN(openssl_pwhash)(SHUTDOWN_FUNC_ARGS_PASSTHRU)) { - return FAILURE; - } -#endif /* reinstate the default tcp handler */ php_stream_xport_register("tcp", php_stream_generic_socket_factory); diff --git a/ext/openssl/openssl.stub.php b/ext/openssl/openssl.stub.php index 3109230f3a2bd..f45933e498242 100644 --- a/ext/openssl/openssl.stub.php +++ b/ext/openssl/openssl.stub.php @@ -678,3 +678,8 @@ function openssl_spki_export_challenge(string $spki): string|false {} * @refcount 1 */ function openssl_get_cert_locations(): array {} + +#if PHP_OPENSSL_API_VERSION >= 0x30200 +function openssl_password_hash(string $algo, #[\SensitiveParameter] string $password, array $options = []): string {} +function openssl_password_verify(string $algo, #[\SensitiveParameter] string $password, string $hash): bool {} +#endif diff --git a/ext/openssl/openssl_arginfo.h b/ext/openssl/openssl_arginfo.h index 45a0593cf966e..da5031c31fc5d 100644 --- a/ext/openssl/openssl_arginfo.h +++ b/ext/openssl/openssl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: b2af57dc7fedc4305cbae2664331376fe390873f */ + * Stub hash: c99b8b1efdc32b8d4315c11dd1fe55212445bff0 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_x509_export_to_file, 0, 2, _IS_BOOL, 0) ZEND_ARG_OBJ_TYPE_MASK(0, certificate, OpenSSLCertificate, MAY_BE_STRING, NULL) @@ -388,6 +388,22 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_get_cert_locations, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() +#if PHP_OPENSSL_API_VERSION >= 0x30200 +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_password_hash, 0, 2, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, algo, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, password, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 0, "[]") +ZEND_END_ARG_INFO() +#endif + +#if PHP_OPENSSL_API_VERSION >= 0x30200 +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_password_verify, 0, 3, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, algo, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, password, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, hash, IS_STRING, 0) +ZEND_END_ARG_INFO() +#endif + ZEND_FUNCTION(openssl_x509_export_to_file); ZEND_FUNCTION(openssl_x509_export); ZEND_FUNCTION(openssl_x509_fingerprint); @@ -451,6 +467,12 @@ ZEND_FUNCTION(openssl_spki_verify); ZEND_FUNCTION(openssl_spki_export); ZEND_FUNCTION(openssl_spki_export_challenge); ZEND_FUNCTION(openssl_get_cert_locations); +#if PHP_OPENSSL_API_VERSION >= 0x30200 +ZEND_FUNCTION(openssl_password_hash); +#endif +#if PHP_OPENSSL_API_VERSION >= 0x30200 +ZEND_FUNCTION(openssl_password_verify); +#endif static const zend_function_entry ext_functions[] = { ZEND_FE(openssl_x509_export_to_file, arginfo_openssl_x509_export_to_file) @@ -519,6 +541,12 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(openssl_spki_export, arginfo_openssl_spki_export) ZEND_FE(openssl_spki_export_challenge, arginfo_openssl_spki_export_challenge) ZEND_FE(openssl_get_cert_locations, arginfo_openssl_get_cert_locations) +#if PHP_OPENSSL_API_VERSION >= 0x30200 + ZEND_FE(openssl_password_hash, arginfo_openssl_password_hash) +#endif +#if PHP_OPENSSL_API_VERSION >= 0x30200 + ZEND_FE(openssl_password_verify, arginfo_openssl_password_verify) +#endif ZEND_FE_END }; @@ -759,6 +787,14 @@ static void register_openssl_symbols(int module_number) zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "openssl_pkey_derive", sizeof("openssl_pkey_derive") - 1), 1, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "openssl_spki_new", sizeof("openssl_spki_new") - 1), 0, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); +#if PHP_OPENSSL_API_VERSION >= 0x30200 + + zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "openssl_password_hash", sizeof("openssl_password_hash") - 1), 1, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); +#endif +#if PHP_OPENSSL_API_VERSION >= 0x30200 + + zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "openssl_password_verify", sizeof("openssl_password_verify") - 1), 1, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); +#endif } static zend_class_entry *register_class_OpenSSLCertificate(void) diff --git a/ext/openssl/openssl_pwhash.c b/ext/openssl/openssl_pwhash.c index 3a05cfee2da1b..508f58ccba7bc 100644 --- a/ext/openssl/openssl_pwhash.c +++ b/ext/openssl/openssl_pwhash.c @@ -383,8 +383,6 @@ PHP_MINIT_FUNCTION(openssl_pwhash) { zend_string *argon2i = ZSTR_INIT_LITERAL("argon2i", 1); - zend_register_functions(NULL, ext_functions, NULL, type); - if (php_password_algo_find(argon2i)) { /* Nothing to do. Core or sodium has registered these algorithms for us. */ zend_string_release(argon2i); @@ -403,11 +401,4 @@ PHP_MINIT_FUNCTION(openssl_pwhash) return SUCCESS; } - -PHP_MSHUTDOWN_FUNCTION(openssl_pwhash) -{ - zend_unregister_functions(ext_functions, -1, NULL); - - return SUCCESS; -} #endif /* PHP_OPENSSL_API_VERSION >= 0x30200 */ diff --git a/ext/openssl/openssl_pwhash.stub.php b/ext/openssl/openssl_pwhash.stub.php index a0b0a3860f5b2..9f7d7b6992b00 100644 --- a/ext/openssl/openssl_pwhash.stub.php +++ b/ext/openssl/openssl_pwhash.stub.php @@ -30,8 +30,5 @@ * @var string */ const PASSWORD_ARGON2_PROVIDER = "openssl"; - -function openssl_password_hash(string $algo, #[\SensitiveParameter] string $password, array $options = []): string {} -function openssl_password_verify(string $algo, #[\SensitiveParameter] string $password, string $hash): bool {} #endif diff --git a/ext/openssl/openssl_pwhash_arginfo.h b/ext/openssl/openssl_pwhash_arginfo.h index 3680f1b6a335e..5a785ee01cb34 100644 --- a/ext/openssl/openssl_pwhash_arginfo.h +++ b/ext/openssl/openssl_pwhash_arginfo.h @@ -1,38 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 6781103bb88a5cb7c4e776301f0c8689582a29ab */ - -#if PHP_OPENSSL_API_VERSION >= 0x30200 -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_password_hash, 0, 2, IS_STRING, 0) - ZEND_ARG_TYPE_INFO(0, algo, IS_STRING, 0) - ZEND_ARG_TYPE_INFO(0, password, IS_STRING, 0) - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 0, "[]") -ZEND_END_ARG_INFO() -#endif - -#if PHP_OPENSSL_API_VERSION >= 0x30200 -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_password_verify, 0, 3, _IS_BOOL, 0) - ZEND_ARG_TYPE_INFO(0, algo, IS_STRING, 0) - ZEND_ARG_TYPE_INFO(0, password, IS_STRING, 0) - ZEND_ARG_TYPE_INFO(0, hash, IS_STRING, 0) -ZEND_END_ARG_INFO() -#endif - -#if PHP_OPENSSL_API_VERSION >= 0x30200 -ZEND_FUNCTION(openssl_password_hash); -#endif -#if PHP_OPENSSL_API_VERSION >= 0x30200 -ZEND_FUNCTION(openssl_password_verify); -#endif - -static const zend_function_entry ext_functions[] = { -#if PHP_OPENSSL_API_VERSION >= 0x30200 - ZEND_FE(openssl_password_hash, arginfo_openssl_password_hash) -#endif -#if PHP_OPENSSL_API_VERSION >= 0x30200 - ZEND_FE(openssl_password_verify, arginfo_openssl_password_verify) -#endif - ZEND_FE_END -}; + * Stub hash: 6d501df8b3a9098e1656e5128d39603a38a07eca */ static void register_openssl_pwhash_symbols(int module_number) { @@ -54,13 +21,4 @@ static void register_openssl_pwhash_symbols(int module_number) #if PHP_OPENSSL_API_VERSION >= 0x30200 REGISTER_STRING_CONSTANT("PASSWORD_ARGON2_PROVIDER", "openssl", CONST_PERSISTENT); #endif - -#if PHP_OPENSSL_API_VERSION >= 0x30200 - - zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "openssl_password_hash", sizeof("openssl_password_hash") - 1), 1, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); -#endif -#if PHP_OPENSSL_API_VERSION >= 0x30200 - - zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "openssl_password_verify", sizeof("openssl_password_verify") - 1), 1, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); -#endif } diff --git a/ext/openssl/php_openssl.h b/ext/openssl/php_openssl.h index fe059368ce0f8..20b60a2f044fe 100644 --- a/ext/openssl/php_openssl.h +++ b/ext/openssl/php_openssl.h @@ -191,7 +191,6 @@ PHP_GINIT_FUNCTION(openssl); PHP_GSHUTDOWN_FUNCTION(openssl); #if PHP_OPENSSL_API_VERSION >= 0x30200 PHP_MINIT_FUNCTION(openssl_pwhash); -PHP_MSHUTDOWN_FUNCTION(openssl_pwhash); #endif #ifdef PHP_WIN32 From 2ac0b0482a57def6ac19c36ecd6281c6aaf2f1e9 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Tue, 26 Mar 2024 13:57:11 +0100 Subject: [PATCH 3/9] use php_base64_encode_ex --- ext/openssl/openssl_pwhash.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/ext/openssl/openssl_pwhash.c b/ext/openssl/openssl_pwhash.c index 508f58ccba7bc..e415960bcb26c 100644 --- a/ext/openssl/openssl_pwhash.c +++ b/ext/openssl/openssl_pwhash.c @@ -162,17 +162,9 @@ static zend_string *php_openssl_argon2_hash(const zend_string *password, zend_ar return NULL; } - hash64 = php_base64_encode(hash, HASH_SIZE); - /* No padding utsing 32 *4 / 3 = 42.6 (43 + 1 padding char) */ - ZEND_ASSERT(ZSTR_LEN(hash64)==44 && ZSTR_VAL(hash64)[43]=='='); - ZSTR_VAL(hash64)[43] = 0; - ZSTR_LEN(hash64) = 43; - - salt64 = php_base64_encode(salt, SALT_SIZE); - /* No padding using 16 *4 / 3 = 21.3 (22 + 2 padding char) */ - ZEND_ASSERT(ZSTR_LEN(salt64)==24 && ZSTR_VAL(salt64)[22]=='=' && ZSTR_VAL(salt64)[23]=='='); - ZSTR_VAL(salt64)[22] = 0; - ZSTR_LEN(salt64) = 22; + hash64 = php_base64_encode_ex(hash, HASH_SIZE, PHP_BASE64_NO_PADDING); + + salt64 = php_base64_encode_ex(salt, SALT_SIZE, PHP_BASE64_NO_PADDING); digest = zend_string_alloc(DIGEST_SIZE, 0); ZSTR_LEN(digest) = snprintf(ZSTR_VAL(digest), ZSTR_LEN(digest), "$%s$v=%d$m=%u,t=%u,p=%u$%s$%s", From 0460a1e1d055565acf70d0bc28de135bb2d43f68 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Tue, 2 Apr 2024 08:48:15 +0200 Subject: [PATCH 4/9] - rename macros - use openssl RAND_bytes - CS --- ext/openssl/openssl_pwhash.c | 105 ++++++++++++++------------- ext/openssl/openssl_pwhash.stub.php | 2 +- ext/openssl/openssl_pwhash_arginfo.h | 4 +- ext/openssl/php_openssl.h | 4 +- 4 files changed, 60 insertions(+), 55 deletions(-) diff --git a/ext/openssl/openssl_pwhash.c b/ext/openssl/openssl_pwhash.c index e415960bcb26c..b7c94252c8654 100644 --- a/ext/openssl/openssl_pwhash.c +++ b/ext/openssl/openssl_pwhash.c @@ -31,27 +31,28 @@ #include #include #include +#include -#define MEMLIMIT_MIN 8u -#define MEMLIMIT_MAX UINT32_MAX -#define OPSLIMIT_MIN 1u -#define OPSLIMIT_MAX UINT32_MAX -#define THREADS_MIN 1u -#define THREADS_MAX UINT32_MAX +#define PHP_OPENSSL_MEMLIMIT_MIN 8u +#define PHP_OPENSSL_MEMLIMIT_MAX UINT32_MAX +#define PHP_OPENSSL_ITERLIMIT_MIN 1u +#define PHP_OPENSSL_ITERLIMIT_MAX UINT32_MAX +#define PHP_OPENSSL_THREADS_MIN 1u +#define PHP_OPENSSL_THREADS_MAX UINT32_MAX -#define ARGON_VERSION 0x13 +#define PHP_OPENSSL_ARGON_VERSION 0x13 -#define SALT_SIZE 16 -#define HASH_SIZE 32 -#define DIGEST_SIZE 128 +#define PHP_OPENSSL_SALT_SIZE 16 +#define PHP_OPENSSL_HASH_SIZE 32 +#define PHP_OPENSSL_DIGEST_SIZE 128 -static inline int get_options(zend_array *options, uint32_t *memlimit, uint32_t *opslimit, uint32_t *threads) +static inline zend_result get_options(zend_array *options, uint32_t *memlimit, uint32_t *iterlimit, uint32_t *threads) { zval *opt; - *opslimit = PHP_OPENSSL_PWHASH_OPSLIMIT; - *memlimit = PHP_OPENSSL_PWHASH_MEMLIMIT; - *threads = PHP_OPENSSL_PWHASH_THREADS; + *iterlimit = PHP_OPENSSL_PWHASH_ITERLIMIT; + *memlimit = PHP_OPENSSL_PWHASH_MEMLIMIT; + *threads = PHP_OPENSSL_PWHASH_THREADS; if (!options) { return SUCCESS; @@ -59,23 +60,23 @@ static inline int get_options(zend_array *options, uint32_t *memlimit, uint32_t if ((opt = zend_hash_str_find(options, "memory_cost", strlen("memory_cost")))) { zend_long smemlimit = zval_get_long(opt); - if ((smemlimit < 0) || (smemlimit < MEMLIMIT_MIN) || (smemlimit > (MEMLIMIT_MAX))) { + if ((smemlimit < 0) || (smemlimit < PHP_OPENSSL_MEMLIMIT_MIN) || (smemlimit > (PHP_OPENSSL_MEMLIMIT_MAX))) { zend_value_error("Memory cost is outside of allowed memory range"); return FAILURE; } *memlimit = smemlimit; } if ((opt = zend_hash_str_find(options, "time_cost", strlen("time_cost")))) { - zend_long sopslimit = zval_get_long(opt); - if ((sopslimit < OPSLIMIT_MIN) || (sopslimit > OPSLIMIT_MAX)) { + zend_long siterlimit = zval_get_long(opt); + if ((siterlimit < PHP_OPENSSL_ITERLIMIT_MIN) || (siterlimit > PHP_OPENSSL_ITERLIMIT_MAX)) { zend_value_error("Time cost is outside of allowed time range"); return FAILURE; } - *opslimit = sopslimit; + *iterlimit = siterlimit; } if ((opt = zend_hash_str_find(options, "threads", strlen("threads"))) && (zval_get_long(opt) != 1)) { zend_long sthreads = zval_get_long(opt); - if ((sthreads < THREADS_MIN) || (sthreads > THREADS_MAX)) { + if ((sthreads < PHP_OPENSSL_THREADS_MIN) || (sthreads > PHP_OPENSSL_THREADS_MAX)) { zend_value_error("Invalid number of threads"); return FAILURE; } @@ -86,19 +87,22 @@ static inline int get_options(zend_array *options, uint32_t *memlimit, uint32_t static bool php_openssl_argon2_compute_hash( const char *algo, - uint32_t version, uint32_t memlimit, uint32_t opslimit, uint32_t threads, + uint32_t version, uint32_t memlimit, uint32_t iterlimit, uint32_t threads, const char *pass, size_t pass_len, const unsigned char *salt, size_t salt_len, unsigned char *hash, size_t hash_len) { OSSL_PARAM params[7], *p = params; + OSSL_LIB_CTX *ctx = NULL; EVP_KDF *kdf = NULL; EVP_KDF_CTX *kctx = NULL; bool ret = false; - if (threads > 1) { - if (OSSL_set_max_threads(NULL, threads) != 1) { + if ((ctx = OSSL_LIB_CTX_new()) == NULL) { + goto fail; + } + if (OSSL_set_max_threads(ctx, threads) != 1) { goto fail; } } @@ -108,22 +112,22 @@ static bool php_openssl_argon2_compute_hash( *p++ = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_LANES, &threads); *p++= OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ITER, - &opslimit); + &iterlimit); *p++ = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, &memlimit); *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, (void *)salt, salt_len); *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_PASSWORD, (void *)pass, pass_len); - *p++ = OSSL_PARAM_construct_end(); + *p++ = OSSL_PARAM_construct_end(); - if ((kdf = EVP_KDF_fetch(NULL, algo, NULL)) == NULL) { + if ((kdf = EVP_KDF_fetch(ctx, algo, NULL)) == NULL) { goto fail; } if ((kctx = EVP_KDF_CTX_new(kdf)) == NULL) { goto fail; } - if (EVP_KDF_derive(kctx, hash, hash_len, params) != 1) { + if (EVP_KDF_derive(kctx, hash, hash_len, params) != 1) { zend_value_error("Unexpected failure hashing password"); goto fail; } @@ -131,53 +135,54 @@ static bool php_openssl_argon2_compute_hash( ret = true; fail: - EVP_KDF_free(kdf); - EVP_KDF_CTX_free(kctx); + EVP_KDF_free(kdf); + EVP_KDF_CTX_free(kctx); if (threads > 1) { - OSSL_set_max_threads(NULL, 0); + OSSL_set_max_threads(ctx, 0); + OSSL_LIB_CTX_free(ctx); } return ret; } static zend_string *php_openssl_argon2_hash(const zend_string *password, zend_array *options, const char *algo) { - uint32_t opslimit, memlimit, threads, version = ARGON_VERSION; + uint32_t iterlimit, memlimit, threads, version = PHP_OPENSSL_ARGON_VERSION; zend_string *digest = NULL, *salt64 = NULL, *hash64 = NULL; - unsigned char hash[HASH_SIZE+1], salt[SALT_SIZE+1]; + unsigned char hash[PHP_OPENSSL_HASH_SIZE+1], salt[PHP_OPENSSL_SALT_SIZE+1]; if ((ZSTR_LEN(password) >= UINT32_MAX)) { zend_value_error("Password is too long"); return NULL; } - if (get_options(options, &memlimit, &opslimit, &threads) == FAILURE) { + if (get_options(options, &memlimit, &iterlimit, &threads) == FAILURE) { return NULL; } - if (FAILURE == php_random_bytes_throw(salt, SALT_SIZE)) { + if (RAND_bytes(salt, PHP_OPENSSL_SALT_SIZE) <= 0) { return NULL; } - if (!php_openssl_argon2_compute_hash(algo, version, memlimit, opslimit, threads, - ZSTR_VAL(password), ZSTR_LEN(password), salt, SALT_SIZE, hash, HASH_SIZE)) { + if (!php_openssl_argon2_compute_hash(algo, version, memlimit, iterlimit, threads, + ZSTR_VAL(password), ZSTR_LEN(password), salt, PHP_OPENSSL_SALT_SIZE, hash, PHP_OPENSSL_HASH_SIZE)) { return NULL; } - hash64 = php_base64_encode_ex(hash, HASH_SIZE, PHP_BASE64_NO_PADDING); + hash64 = php_base64_encode_ex(hash, PHP_OPENSSL_HASH_SIZE, PHP_BASE64_NO_PADDING); - salt64 = php_base64_encode_ex(salt, SALT_SIZE, PHP_BASE64_NO_PADDING); + salt64 = php_base64_encode_ex(salt, PHP_OPENSSL_SALT_SIZE, PHP_BASE64_NO_PADDING); - digest = zend_string_alloc(DIGEST_SIZE, 0); + digest = zend_string_alloc(PHP_OPENSSL_DIGEST_SIZE, 0); ZSTR_LEN(digest) = snprintf(ZSTR_VAL(digest), ZSTR_LEN(digest), "$%s$v=%d$m=%u,t=%u,p=%u$%s$%s", - algo, version, memlimit, opslimit, threads, ZSTR_VAL(salt64), ZSTR_VAL(hash64)); + algo, version, memlimit, iterlimit, threads, ZSTR_VAL(salt64), ZSTR_VAL(hash64)); zend_string_release(salt64); zend_string_release(hash64); - return digest; + return digest; } static int php_openssl_argon2_extract( - const zend_string *digest, uint32_t *version, uint32_t *memlimit, uint32_t *opslimit, + const zend_string *digest, uint32_t *version, uint32_t *memlimit, uint32_t *iterlimit, uint32_t *threads, zend_string **salt, zend_string **hash) { const char *p; @@ -195,7 +200,7 @@ static int php_openssl_argon2_extract( return FAILURE; } if (sscanf(p, "v=%" PRIu32 "$m=%" PRIu32 ",t=%" PRIu32 ",p=%" PRIu32, - version, memlimit, opslimit, threads) != 4) { + version, memlimit, iterlimit, threads) != 4) { return FAILURE; } if (salt && hash) { @@ -226,19 +231,19 @@ static int php_openssl_argon2_extract( static bool php_openssl_argon2_verify(const zend_string *password, const zend_string *digest, const char *algo) { - uint32_t version, opslimit, memlimit, threads; + uint32_t version, iterlimit, memlimit, threads; zend_string *salt, *hash, *new; bool ret = false; if ((ZSTR_LEN(password) >= UINT32_MAX) || (ZSTR_LEN(digest) >= UINT32_MAX)) { return false; } - if (FAILURE == php_openssl_argon2_extract(digest, &version, &memlimit, &opslimit, &threads, &salt, &hash)) { + if (FAILURE == php_openssl_argon2_extract(digest, &version, &memlimit, &iterlimit, &threads, &salt, &hash)) { return false; } new = zend_string_alloc(ZSTR_LEN(hash), 0); - if (php_openssl_argon2_compute_hash(algo, version, memlimit, opslimit, threads, + if (php_openssl_argon2_compute_hash(algo, version, memlimit, iterlimit, threads, ZSTR_VAL(password), ZSTR_LEN(password), (unsigned char *)ZSTR_VAL(salt), ZSTR_LEN(salt), (unsigned char *)ZSTR_VAL(new), ZSTR_LEN(new))) { ret = (php_safe_bcmp(hash, new) == 0); @@ -263,19 +268,19 @@ static bool php_openssl_argon2id_verify(const zend_string *password, const zend_ static bool php_openssl_argon2_needs_rehash(const zend_string *hash, zend_array *options) { - uint32_t version, opslimit, memlimit, threads; - uint32_t new_version = ARGON_VERSION, new_opslimit, new_memlimit, new_threads; + uint32_t version, iterlimit, memlimit, threads; + uint32_t new_version = PHP_OPENSSL_ARGON_VERSION, new_iterlimit, new_memlimit, new_threads; - if (FAILURE == get_options(options, &new_memlimit, &new_opslimit, &new_threads)) { + if (FAILURE == get_options(options, &new_memlimit, &new_iterlimit, &new_threads)) { return true; } - if (FAILURE == php_openssl_argon2_extract(hash, &version, &memlimit, &opslimit, &threads, NULL, NULL)) { + if (FAILURE == php_openssl_argon2_extract(hash, &version, &memlimit, &iterlimit, &threads, NULL, NULL)) { return true; } // Algo already checked in pasword_needs_rehash implementation return (version != new_version) || - (opslimit != new_opslimit) || + (iterlimit != new_iterlimit) || (memlimit != new_memlimit) || (threads != new_threads); } diff --git a/ext/openssl/openssl_pwhash.stub.php b/ext/openssl/openssl_pwhash.stub.php index 9f7d7b6992b00..0ca966417ffb0 100644 --- a/ext/openssl/openssl_pwhash.stub.php +++ b/ext/openssl/openssl_pwhash.stub.php @@ -18,7 +18,7 @@ const PASSWORD_ARGON2_DEFAULT_MEMORY_COST = UNKNOWN; /** * @var int - * @cvalue PHP_OPENSSL_PWHASH_OPSLIMIT + * @cvalue PHP_OPENSSL_PWHASH_ITERLIMIT */ const PASSWORD_ARGON2_DEFAULT_TIME_COST = UNKNOWN; /** diff --git a/ext/openssl/openssl_pwhash_arginfo.h b/ext/openssl/openssl_pwhash_arginfo.h index 5a785ee01cb34..7824d14ae2d98 100644 --- a/ext/openssl/openssl_pwhash_arginfo.h +++ b/ext/openssl/openssl_pwhash_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 6d501df8b3a9098e1656e5128d39603a38a07eca */ + * Stub hash: cd92e58d51e154fee46a53484d8a32045f3a22e1 */ static void register_openssl_pwhash_symbols(int module_number) { @@ -13,7 +13,7 @@ static void register_openssl_pwhash_symbols(int module_number) REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_MEMORY_COST", PHP_OPENSSL_PWHASH_MEMLIMIT, CONST_PERSISTENT); #endif #if PHP_OPENSSL_API_VERSION >= 0x30200 - REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_TIME_COST", PHP_OPENSSL_PWHASH_OPSLIMIT, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_TIME_COST", PHP_OPENSSL_PWHASH_ITERLIMIT, CONST_PERSISTENT); #endif #if PHP_OPENSSL_API_VERSION >= 0x30200 REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_THREADS", PHP_OPENSSL_PWHASH_THREADS, CONST_PERSISTENT); diff --git a/ext/openssl/php_openssl.h b/ext/openssl/php_openssl.h index 20b60a2f044fe..b9d8ff7d6c896 100644 --- a/ext/openssl/php_openssl.h +++ b/ext/openssl/php_openssl.h @@ -172,9 +172,9 @@ static inline php_openssl_certificate_object *php_openssl_certificate_from_obj(z #define PHP_OPENSSL_PWHASH_MEMLIMIT (64 << 10) #endif #if defined(PHP_PASSWORD_ARGON2_TIME_COST) -#define PHP_OPENSSL_PWHASH_OPSLIMIT PHP_PASSWORD_ARGON2_TIME_COST +#define PHP_OPENSSL_PWHASH_ITERLIMIT PHP_PASSWORD_ARGON2_TIME_COST #else -#define PHP_OPENSSL_PWHASH_OPSLIMIT 4 +#define PHP_OPENSSL_PWHASH_ITERLIMIT 4 #endif #if defined(PHP_PASSWORD_ARGON2_THREADS) #define PHP_OPENSSL_PWHASH_THREADS PHP_PASSWORD_ARGON2_THREADS From 668b3f0e48c07856275ba8aa45b20be14e67728b Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Fri, 19 Jul 2024 09:55:49 +0200 Subject: [PATCH 5/9] add --with-openssl-argon2 build option --- ext/openssl/config0.m4 | 17 +++++++++++++++++ ext/openssl/openssl.c | 6 +++--- ext/openssl/openssl.stub.php | 2 +- ext/openssl/openssl_arginfo.h | 18 +++++++++--------- ext/openssl/openssl_pwhash.c | 4 ++-- ext/openssl/openssl_pwhash.stub.php | 2 +- ext/openssl/openssl_pwhash_arginfo.h | 14 +++++++------- ext/openssl/php_openssl.h | 4 ++-- 8 files changed, 42 insertions(+), 25 deletions(-) diff --git a/ext/openssl/config0.m4 b/ext/openssl/config0.m4 index 6770337a09f46..60440e6801cff 100644 --- a/ext/openssl/config0.m4 +++ b/ext/openssl/config0.m4 @@ -17,6 +17,13 @@ PHP_ARG_WITH([openssl-legacy-provider], [no], [no]) +PHP_ARG_WITH([openssl-argon2], + [whether to enable argon2 password hashing (requires OpenSSL >= 3.2)], + [AS_HELP_STRING([--with-openssl-argon2], + [OPENSSL: Enable argon2 password hashing])], + [no], + [no]) + if test "$PHP_OPENSSL" != "no"; then PHP_NEW_EXTENSION([openssl], [openssl.c openssl_pwhash.c xp_ssl.c], [$ext_shared]) PHP_SUBST([OPENSSL_SHARED_LIBADD]) @@ -38,4 +45,14 @@ if test "$PHP_OPENSSL" != "no"; then [AC_DEFINE([LOAD_OPENSSL_LEGACY_PROVIDER], [1], [Define to 1 to load the OpenSSL legacy algorithm provider in addition to the default provider.])]) + + if test "$PHP_OPENSSL_ARGON2" != "no"; then + if test "$PHP_THREAD_SAFETY" != "no"; then + AC_MSG_ERROR([Not supported in ZTS mode for now]) + fi + PHP_CHECK_LIBRARY([crypto], [OSSL_set_max_threads], + [AC_DEFINE(HAVE_OPENSSL_ARGON2,1,[ Enable argon2 password hashing ])], + [AC_MSG_ERROR([argon2 hashing requires OpenSSL 3.2])], + [$OPENSSL_LIBS]) + fi fi diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 1891ad2b83606..05d5d185d9113 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -270,7 +270,7 @@ static void php_openssl_pkey_free_obj(zend_object *object) zend_object_std_dtor(&key_object->std); } -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) static const zend_module_dep openssl_deps[] = { ZEND_MOD_REQUIRED("standard") ZEND_MOD_END @@ -278,7 +278,7 @@ static const zend_module_dep openssl_deps[] = { #endif /* {{{ openssl_module_entry */ zend_module_entry openssl_module_entry = { -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) STANDARD_MODULE_HEADER_EX, NULL, openssl_deps, #else @@ -1341,7 +1341,7 @@ PHP_MINIT_FUNCTION(openssl) REGISTER_INI_ENTRIES(); -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) if (FAILURE == PHP_MINIT(openssl_pwhash)(INIT_FUNC_ARGS_PASSTHRU)) { return FAILURE; } diff --git a/ext/openssl/openssl.stub.php b/ext/openssl/openssl.stub.php index f45933e498242..9da0a64468857 100644 --- a/ext/openssl/openssl.stub.php +++ b/ext/openssl/openssl.stub.php @@ -679,7 +679,7 @@ function openssl_spki_export_challenge(string $spki): string|false {} */ function openssl_get_cert_locations(): array {} -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) function openssl_password_hash(string $algo, #[\SensitiveParameter] string $password, array $options = []): string {} function openssl_password_verify(string $algo, #[\SensitiveParameter] string $password, string $hash): bool {} #endif diff --git a/ext/openssl/openssl_arginfo.h b/ext/openssl/openssl_arginfo.h index da5031c31fc5d..67afda4b2fd87 100644 --- a/ext/openssl/openssl_arginfo.h +++ b/ext/openssl/openssl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: c99b8b1efdc32b8d4315c11dd1fe55212445bff0 */ + * Stub hash: 62716ca2f10c7df42529a9140d80bd7cbc2dc481 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_x509_export_to_file, 0, 2, _IS_BOOL, 0) ZEND_ARG_OBJ_TYPE_MASK(0, certificate, OpenSSLCertificate, MAY_BE_STRING, NULL) @@ -388,7 +388,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_get_cert_locations, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_password_hash, 0, 2, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, algo, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, password, IS_STRING, 0) @@ -396,7 +396,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_password_hash, 0, 2, IS_ ZEND_END_ARG_INFO() #endif -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_password_verify, 0, 3, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, algo, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, password, IS_STRING, 0) @@ -467,10 +467,10 @@ ZEND_FUNCTION(openssl_spki_verify); ZEND_FUNCTION(openssl_spki_export); ZEND_FUNCTION(openssl_spki_export_challenge); ZEND_FUNCTION(openssl_get_cert_locations); -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) ZEND_FUNCTION(openssl_password_hash); #endif -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) ZEND_FUNCTION(openssl_password_verify); #endif @@ -541,10 +541,10 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(openssl_spki_export, arginfo_openssl_spki_export) ZEND_FE(openssl_spki_export_challenge, arginfo_openssl_spki_export_challenge) ZEND_FE(openssl_get_cert_locations, arginfo_openssl_get_cert_locations) -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) ZEND_FE(openssl_password_hash, arginfo_openssl_password_hash) #endif -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) ZEND_FE(openssl_password_verify, arginfo_openssl_password_verify) #endif ZEND_FE_END @@ -787,11 +787,11 @@ static void register_openssl_symbols(int module_number) zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "openssl_pkey_derive", sizeof("openssl_pkey_derive") - 1), 1, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "openssl_spki_new", sizeof("openssl_spki_new") - 1), 0, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "openssl_password_hash", sizeof("openssl_password_hash") - 1), 1, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); #endif -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "openssl_password_verify", sizeof("openssl_password_verify") - 1), 1, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); #endif diff --git a/ext/openssl/openssl_pwhash.c b/ext/openssl/openssl_pwhash.c index b7c94252c8654..b10a93e08db7d 100644 --- a/ext/openssl/openssl_pwhash.c +++ b/ext/openssl/openssl_pwhash.c @@ -22,7 +22,7 @@ #include "ext/standard/php_password.h" #include "php_openssl.h" -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) #include "Zend/zend_attributes.h" #include "openssl_pwhash_arginfo.h" #include @@ -398,4 +398,4 @@ PHP_MINIT_FUNCTION(openssl_pwhash) return SUCCESS; } -#endif /* PHP_OPENSSL_API_VERSION >= 0x30200 */ +#endif /* HAVE_OPENSSL_ARGON2 */ diff --git a/ext/openssl/openssl_pwhash.stub.php b/ext/openssl/openssl_pwhash.stub.php index 0ca966417ffb0..c6e3a363c5220 100644 --- a/ext/openssl/openssl_pwhash.stub.php +++ b/ext/openssl/openssl_pwhash.stub.php @@ -2,7 +2,7 @@ /** @generate-class-entries */ -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) /** * @var string */ diff --git a/ext/openssl/openssl_pwhash_arginfo.h b/ext/openssl/openssl_pwhash_arginfo.h index 7824d14ae2d98..7696675a5a84f 100644 --- a/ext/openssl/openssl_pwhash_arginfo.h +++ b/ext/openssl/openssl_pwhash_arginfo.h @@ -1,24 +1,24 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: cd92e58d51e154fee46a53484d8a32045f3a22e1 */ + * Stub hash: b6056170a5c8f1a9582c5eef9261a0bd02f7a7e1 */ static void register_openssl_pwhash_symbols(int module_number) { -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) REGISTER_STRING_CONSTANT("PASSWORD_ARGON2I", "argon2i", CONST_PERSISTENT); #endif -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) REGISTER_STRING_CONSTANT("PASSWORD_ARGON2ID", "argon2id", CONST_PERSISTENT); #endif -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_MEMORY_COST", PHP_OPENSSL_PWHASH_MEMLIMIT, CONST_PERSISTENT); #endif -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_TIME_COST", PHP_OPENSSL_PWHASH_ITERLIMIT, CONST_PERSISTENT); #endif -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_THREADS", PHP_OPENSSL_PWHASH_THREADS, CONST_PERSISTENT); #endif -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) REGISTER_STRING_CONSTANT("PASSWORD_ARGON2_PROVIDER", "openssl", CONST_PERSISTENT); #endif } diff --git a/ext/openssl/php_openssl.h b/ext/openssl/php_openssl.h index b9d8ff7d6c896..134081f85507d 100644 --- a/ext/openssl/php_openssl.h +++ b/ext/openssl/php_openssl.h @@ -158,7 +158,7 @@ static inline php_openssl_certificate_object *php_openssl_certificate_from_obj(z #define Z_OPENSSL_CERTIFICATE_P(zv) php_openssl_certificate_from_obj(Z_OBJ_P(zv)) -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) /** * MEMLIMIT is normalized to KB even though sodium uses Bytes in order to @@ -189,7 +189,7 @@ PHP_MSHUTDOWN_FUNCTION(openssl); PHP_MINFO_FUNCTION(openssl); PHP_GINIT_FUNCTION(openssl); PHP_GSHUTDOWN_FUNCTION(openssl); -#if PHP_OPENSSL_API_VERSION >= 0x30200 +#if defined(HAVE_OPENSSL_ARGON2) PHP_MINIT_FUNCTION(openssl_pwhash); #endif From 3b67b4e750e8ff8a65fe929fd56d920e3f742187 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Mon, 19 Aug 2024 13:56:49 +0200 Subject: [PATCH 6/9] check OSSL_KDF_PARAM_ARGON2_LANES instead of OSSL_set_max_threads --- ext/openssl/config0.m4 | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ext/openssl/config0.m4 b/ext/openssl/config0.m4 index 60440e6801cff..ca6a638af1508 100644 --- a/ext/openssl/config0.m4 +++ b/ext/openssl/config0.m4 @@ -50,9 +50,12 @@ if test "$PHP_OPENSSL" != "no"; then if test "$PHP_THREAD_SAFETY" != "no"; then AC_MSG_ERROR([Not supported in ZTS mode for now]) fi - PHP_CHECK_LIBRARY([crypto], [OSSL_set_max_threads], - [AC_DEFINE(HAVE_OPENSSL_ARGON2,1,[ Enable argon2 password hashing ])], + CFLAGS_SAVE=$CFLAGS + CFLAGS="$CFLAGS $OPENSSL_CFLAGS" + AC_CHECK_DECL([OSSL_KDF_PARAM_ARGON2_LANES], + [AC_DEFINE([HAVE_OPENSSL_ARGON2],[1],[ Enable argon2 password hashing ])], [AC_MSG_ERROR([argon2 hashing requires OpenSSL 3.2])], - [$OPENSSL_LIBS]) + [[#include ]]) + CFLAGS=$CFLAGS_SAVE fi fi From ddcc40f7cadcfcd88b2902a244a39c44f08f90e8 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Mon, 19 Aug 2024 13:57:09 +0200 Subject: [PATCH 7/9] Cleanup and CS --- ext/openssl/openssl_pwhash.c | 15 +++----- ext/openssl/tests/openssl_password.phpt | 24 ++++++------- .../tests/openssl_password_compat.phpt | 34 +++++++++---------- .../tests/openssl_password_compat2.phpt | 34 +++++++++---------- 4 files changed, 51 insertions(+), 56 deletions(-) diff --git a/ext/openssl/openssl_pwhash.c b/ext/openssl/openssl_pwhash.c index b10a93e08db7d..1f4edb5ef44f2 100644 --- a/ext/openssl/openssl_pwhash.c +++ b/ext/openssl/openssl_pwhash.c @@ -44,7 +44,7 @@ #define PHP_OPENSSL_SALT_SIZE 16 #define PHP_OPENSSL_HASH_SIZE 32 -#define PHP_OPENSSL_DIGEST_SIZE 128 +#define PHP_OPENSSL_DIGEST_SIZE 128 static inline zend_result get_options(zend_array *options, uint32_t *memlimit, uint32_t *iterlimit, uint32_t *threads) { @@ -93,16 +93,12 @@ static bool php_openssl_argon2_compute_hash( unsigned char *hash, size_t hash_len) { OSSL_PARAM params[7], *p = params; - OSSL_LIB_CTX *ctx = NULL; EVP_KDF *kdf = NULL; EVP_KDF_CTX *kctx = NULL; bool ret = false; if (threads > 1) { - if ((ctx = OSSL_LIB_CTX_new()) == NULL) { - goto fail; - } - if (OSSL_set_max_threads(ctx, threads) != 1) { + if (OSSL_set_max_threads(NULL, threads) != 1) { goto fail; } } @@ -121,7 +117,7 @@ static bool php_openssl_argon2_compute_hash( (void *)pass, pass_len); *p++ = OSSL_PARAM_construct_end(); - if ((kdf = EVP_KDF_fetch(ctx, algo, NULL)) == NULL) { + if ((kdf = EVP_KDF_fetch(NULL, algo, NULL)) == NULL) { goto fail; } if ((kctx = EVP_KDF_CTX_new(kdf)) == NULL) { @@ -139,8 +135,7 @@ static bool php_openssl_argon2_compute_hash( EVP_KDF_CTX_free(kctx); if (threads > 1) { - OSSL_set_max_threads(ctx, 0); - OSSL_LIB_CTX_free(ctx); + OSSL_set_max_threads(NULL, 0); } return ret; } @@ -200,7 +195,7 @@ static int php_openssl_argon2_extract( return FAILURE; } if (sscanf(p, "v=%" PRIu32 "$m=%" PRIu32 ",t=%" PRIu32 ",p=%" PRIu32, - version, memlimit, iterlimit, threads) != 4) { + version, memlimit, iterlimit, threads) != 4) { return FAILURE; } if (salt && hash) { diff --git a/ext/openssl/tests/openssl_password.phpt b/ext/openssl/tests/openssl_password.phpt index 78818030388e1..0dbc1ae42a3fb 100644 --- a/ext/openssl/tests/openssl_password.phpt +++ b/ext/openssl/tests/openssl_password.phpt @@ -5,7 +5,7 @@ openssl --SKIPIF-- --FILE-- @@ -15,18 +15,18 @@ echo 'Argon2 provider: '; var_dump(PASSWORD_ARGON2_PROVIDER); foreach([1, 2] as $mem) { - foreach([1, 2] as $time) { - $opts = [ - 'memory_cost' => PASSWORD_ARGON2_DEFAULT_MEMORY_COST / $mem, - 'time_cost' => PASSWORD_ARGON2_DEFAULT_TIME_COST / $time, - 'threads' => PASSWORD_ARGON2_DEFAULT_THREADS, - ]; - foreach(['argon2i', 'argon2id'] as $algo) { - $pass = "secret$mem$time$algo"; - $hash = openssl_password_hash($algo, $pass, $opts); - var_dump(openssl_password_verify($algo, $pass, $hash)); + foreach([1, 2] as $time) { + $opts = [ + 'memory_cost' => PASSWORD_ARGON2_DEFAULT_MEMORY_COST / $mem, + 'time_cost' => PASSWORD_ARGON2_DEFAULT_TIME_COST / $time, + 'threads' => PASSWORD_ARGON2_DEFAULT_THREADS, + ]; + foreach(['argon2i', 'argon2id'] as $algo) { + $pass = "secret$mem$time$algo"; + $hash = openssl_password_hash($algo, $pass, $opts); + var_dump(openssl_password_verify($algo, $pass, $hash)); + } } - } } ?> --EXPECTF-- diff --git a/ext/openssl/tests/openssl_password_compat.phpt b/ext/openssl/tests/openssl_password_compat.phpt index 0de683616aee8..7fb1e5e3c225e 100644 --- a/ext/openssl/tests/openssl_password_compat.phpt +++ b/ext/openssl/tests/openssl_password_compat.phpt @@ -6,11 +6,11 @@ sodium --SKIPIF-- --FILE-- @@ -20,23 +20,23 @@ echo 'Argon2 provider: '; var_dump(PASSWORD_ARGON2_PROVIDER); foreach([1, 2] as $mem) { - foreach([1, 2] as $time) { - $opts = [ - 'memory_cost' => PASSWORD_ARGON2_DEFAULT_MEMORY_COST / $mem, - 'time_cost' => PASSWORD_ARGON2_DEFAULT_TIME_COST / $time, - 'threads' => PASSWORD_ARGON2_DEFAULT_THREADS, - ]; - $algo = 'argon2id'; - $pass = "secret$mem$time$algo"; + foreach([1, 2] as $time) { + $opts = [ + 'memory_cost' => PASSWORD_ARGON2_DEFAULT_MEMORY_COST / $mem, + 'time_cost' => PASSWORD_ARGON2_DEFAULT_TIME_COST / $time, + 'threads' => PASSWORD_ARGON2_DEFAULT_THREADS, + ]; + $algo = 'argon2id'; + $pass = "secret$mem$time$algo"; - /* hash with libsodium / verify with openssl */ - $hash = sodium_crypto_pwhash_str($pass, PASSWORD_ARGON2_DEFAULT_TIME_COST / $time, PASSWORD_ARGON2_DEFAULT_MEMORY_COST / $mem); - var_dump(openssl_password_verify($algo, $pass, $hash)); + /* hash with libsodium / verify with openssl */ + $hash = sodium_crypto_pwhash_str($pass, PASSWORD_ARGON2_DEFAULT_TIME_COST / $time, PASSWORD_ARGON2_DEFAULT_MEMORY_COST / $mem); + var_dump(openssl_password_verify($algo, $pass, $hash)); - /* hash with openssl / verify with libsodium */ - $hash = openssl_password_hash($algo, $pass, $opts); - var_dump(sodium_crypto_pwhash_str_verify($hash, $pass)); - } + /* hash with openssl / verify with libsodium */ + $hash = openssl_password_hash($algo, $pass, $opts); + var_dump(sodium_crypto_pwhash_str_verify($hash, $pass)); + } } ?> --EXPECTF-- diff --git a/ext/openssl/tests/openssl_password_compat2.phpt b/ext/openssl/tests/openssl_password_compat2.phpt index 42cf8682fd70c..d854392d0e4d8 100644 --- a/ext/openssl/tests/openssl_password_compat2.phpt +++ b/ext/openssl/tests/openssl_password_compat2.phpt @@ -6,11 +6,11 @@ sodium --SKIPIF-- --FILE-- @@ -20,23 +20,23 @@ echo 'Argon2 provider: '; var_dump(PASSWORD_ARGON2_PROVIDER); foreach([1, 2] as $mem) { - foreach([1, 2] as $time) { - $opts = [ - 'memory_cost' => PASSWORD_ARGON2_DEFAULT_MEMORY_COST / $mem, - 'time_cost' => PASSWORD_ARGON2_DEFAULT_TIME_COST / $time, - 'threads' => PASSWORD_ARGON2_DEFAULT_THREADS, - ]; - $algo = 'argon2id'; - $pass = "secret$mem$time$algo"; + foreach([1, 2] as $time) { + $opts = [ + 'memory_cost' => PASSWORD_ARGON2_DEFAULT_MEMORY_COST / $mem, + 'time_cost' => PASSWORD_ARGON2_DEFAULT_TIME_COST / $time, + 'threads' => PASSWORD_ARGON2_DEFAULT_THREADS, + ]; + $algo = 'argon2id'; + $pass = "secret$mem$time$algo"; - /* hash with libargon2 / verify with openssl */ - $hash = password_hash($pass, PASSWORD_ARGON2ID, $opts); - var_dump(openssl_password_verify($algo, $pass, $hash)); + /* hash with libargon2 / verify with openssl */ + $hash = password_hash($pass, PASSWORD_ARGON2ID, $opts); + var_dump(openssl_password_verify($algo, $pass, $hash)); - /* hash with openssl / verify with libargon2 */ - $hash = openssl_password_hash($algo, $pass, $opts); - var_dump(password_verify($pass, $hash)); - } + /* hash with openssl / verify with libargon2 */ + $hash = openssl_password_hash($algo, $pass, $opts); + var_dump(password_verify($pass, $hash)); + } } ?> --EXPECT-- From 74880bede1ee36b393674e806f95f835c7e90265 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Sun, 25 Aug 2024 08:48:14 +0200 Subject: [PATCH 8/9] save/restore old threads config + CS --- ext/openssl/config0.m4 | 9 +++------ ext/openssl/openssl_pwhash.c | 22 +++++++++------------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/ext/openssl/config0.m4 b/ext/openssl/config0.m4 index ca6a638af1508..7c8dac5bdca8e 100644 --- a/ext/openssl/config0.m4 +++ b/ext/openssl/config0.m4 @@ -50,12 +50,9 @@ if test "$PHP_OPENSSL" != "no"; then if test "$PHP_THREAD_SAFETY" != "no"; then AC_MSG_ERROR([Not supported in ZTS mode for now]) fi - CFLAGS_SAVE=$CFLAGS - CFLAGS="$CFLAGS $OPENSSL_CFLAGS" - AC_CHECK_DECL([OSSL_KDF_PARAM_ARGON2_LANES], - [AC_DEFINE([HAVE_OPENSSL_ARGON2],[1],[ Enable argon2 password hashing ])], + PHP_CHECK_LIBRARY([crypto], [OSSL_set_max_threads], + [AC_DEFINE([HAVE_OPENSSL_ARGON2], [1], [ Define to 1 to enable argon2 password hashing ])], [AC_MSG_ERROR([argon2 hashing requires OpenSSL 3.2])], - [[#include ]]) - CFLAGS=$CFLAGS_SAVE + [$OPENSSL_LIBS]) fi fi diff --git a/ext/openssl/openssl_pwhash.c b/ext/openssl/openssl_pwhash.c index 1f4edb5ef44f2..f1fc94f227c25 100644 --- a/ext/openssl/openssl_pwhash.c +++ b/ext/openssl/openssl_pwhash.c @@ -95,26 +95,22 @@ static bool php_openssl_argon2_compute_hash( OSSL_PARAM params[7], *p = params; EVP_KDF *kdf = NULL; EVP_KDF_CTX *kctx = NULL; + uint32_t oldthreads; bool ret = false; if (threads > 1) { + oldthreads = OSSL_get_max_threads(NULL); if (OSSL_set_max_threads(NULL, threads) != 1) { goto fail; } } p = params; - *p++ = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_THREADS, - &threads); - *p++ = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_LANES, - &threads); - *p++= OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ITER, - &iterlimit); - *p++ = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, - &memlimit); - *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, - (void *)salt, salt_len); - *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_PASSWORD, - (void *)pass, pass_len); + *p++ = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_THREADS, &threads); + *p++ = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_LANES, &threads); + *p++ = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ITER, &iterlimit); + *p++ = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, &memlimit); + *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, (void *)salt, salt_len); + *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_PASSWORD, (void *)pass, pass_len); *p++ = OSSL_PARAM_construct_end(); if ((kdf = EVP_KDF_fetch(NULL, algo, NULL)) == NULL) { @@ -135,7 +131,7 @@ static bool php_openssl_argon2_compute_hash( EVP_KDF_CTX_free(kctx); if (threads > 1) { - OSSL_set_max_threads(NULL, 0); + OSSL_set_max_threads(NULL, oldthreads); } return ret; } From 0dd9cc733911059ed13f60e316c7b96ce4fff433 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Mon, 2 Sep 2024 12:08:43 +0200 Subject: [PATCH 9/9] remove unneeded check --- ext/openssl/openssl_pwhash.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/ext/openssl/openssl_pwhash.c b/ext/openssl/openssl_pwhash.c index f1fc94f227c25..5fe5bb5cf1bba 100644 --- a/ext/openssl/openssl_pwhash.c +++ b/ext/openssl/openssl_pwhash.c @@ -98,11 +98,9 @@ static bool php_openssl_argon2_compute_hash( uint32_t oldthreads; bool ret = false; - if (threads > 1) { - oldthreads = OSSL_get_max_threads(NULL); - if (OSSL_set_max_threads(NULL, threads) != 1) { - goto fail; - } + oldthreads = OSSL_get_max_threads(NULL); + if (OSSL_set_max_threads(NULL, threads) != 1) { + goto fail; } p = params; *p++ = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_THREADS, &threads); @@ -129,10 +127,8 @@ static bool php_openssl_argon2_compute_hash( fail: EVP_KDF_free(kdf); EVP_KDF_CTX_free(kctx); + OSSL_set_max_threads(NULL, oldthreads); - if (threads > 1) { - OSSL_set_max_threads(NULL, oldthreads); - } return ret; }