Skip to content

Commit 17bfac6

Browse files
committed
Merge branch 'PHP-7.4'
* PHP-7.4: Provide argon2i(d) password hashing from sodium when needed
2 parents 27b6d36 + 0ba1db7 commit 17bfac6

File tree

10 files changed

+347
-166
lines changed

10 files changed

+347
-166
lines changed

ext/sodium/config.m4

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ if test "$PHP_SODIUM" != "no"; then
1111

1212
AC_DEFINE(HAVE_LIBSODIUMLIB, 1, [ ])
1313

14-
PHP_NEW_EXTENSION(sodium, libsodium.c, $ext_shared)
14+
PHP_NEW_EXTENSION(sodium, libsodium.c sodium_pwhash.c, $ext_shared)
1515
PHP_SUBST(SODIUM_SHARED_LIBADD)
1616
fi

ext/sodium/config.w32

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ ARG_WITH("sodium", "for libsodium support", "no");
44

55
if (PHP_SODIUM != "no") {
66
if (CHECK_LIB("libsodium.lib", "sodium", PHP_SODIUM) && CHECK_HEADER_ADD_INCLUDE("sodium.h", "CFLAGS_SODIUM")) {
7-
EXTENSION("sodium", "libsodium.c");
7+
EXTENSION("sodium", "libsodium.c sodium_pwhash.c");
88
AC_DEFINE('HAVE_LIBSODIUMLIB', 1 , 'Have the Sodium library');
99
AC_DEFINE("HAVE_CRYPTO_AEAD_AES256GCM", 1, "");
1010
PHP_INSTALL_HEADERS("ext/sodium/", "php_libsodium.h");

ext/sodium/libsodium.c

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,8 +358,18 @@ static const zend_function_entry sodium_functions[] = {
358358
PHP_FE_END
359359
};
360360

361+
/* Load after the "standard" module in order to give it
362+
* priority in registering argon2i/argon2id password hashers.
363+
*/
364+
static const zend_module_dep sodium_deps[] = {
365+
ZEND_MOD_REQUIRED("standard")
366+
ZEND_MOD_END
367+
};
368+
361369
zend_module_entry sodium_module_entry = {
362-
STANDARD_MODULE_HEADER,
370+
STANDARD_MODULE_HEADER_EX,
371+
NULL,
372+
sodium_deps,
363373
"sodium",
364374
sodium_functions,
365375
PHP_MINIT(sodium),
@@ -632,6 +642,13 @@ PHP_MINIT_FUNCTION(sodium)
632642
REGISTER_LONG_CONSTANT("SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING",
633643
sodium_base64_VARIANT_URLSAFE_NO_PADDING, CONST_CS | CONST_PERSISTENT);
634644
#endif
645+
646+
#if SODIUM_LIBRARY_VERSION_MAJOR > 9 || (SODIUM_LIBRARY_VERSION_MAJOR == 9 && SODIUM_LIBRARY_VERSION_MINOR >= 6)
647+
if (FAILURE == PHP_MINIT(sodium_password_hash)(INIT_FUNC_ARGS_PASSTHRU)) {
648+
return FAILURE;
649+
}
650+
#endif
651+
635652
return SUCCESS;
636653
}
637654

ext/sodium/php_libsodium.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ extern zend_module_entry sodium_module_entry;
2929
#endif
3030

3131
PHP_MINIT_FUNCTION(sodium);
32+
PHP_MINIT_FUNCTION(sodium_password_hash);
3233
PHP_MSHUTDOWN_FUNCTION(sodium);
3334
PHP_RINIT_FUNCTION(sodium);
3435
PHP_RSHUTDOWN_FUNCTION(sodium);

ext/sodium/sodium_pwhash.c

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| PHP Version 7 |
4+
+----------------------------------------------------------------------+
5+
| Copyright (c) The PHP Group |
6+
+----------------------------------------------------------------------+
7+
| This source file is subject to version 3.01 of the PHP license, |
8+
| that is bundled with this package in the file LICENSE, and is |
9+
| available through the world-wide-web at the following url: |
10+
| http://www.php.net/license/3_01.txt |
11+
| If you did not receive a copy of the PHP license and are unable to |
12+
| obtain it through the world-wide-web, please send a note to |
13+
| [email protected] so we can mail you a copy immediately. |
14+
+----------------------------------------------------------------------+
15+
| Authors: Sara Golemon <[email protected]> |
16+
+----------------------------------------------------------------------+
17+
*/
18+
19+
#ifdef HAVE_CONFIG_H
20+
# include "config.h"
21+
#endif
22+
23+
#include "php.h"
24+
#include "php_libsodium.h"
25+
#include "ext/standard/php_password.h"
26+
27+
#include <sodium.h>
28+
29+
#if SODIUM_LIBRARY_VERSION_MAJOR > 9 || (SODIUM_LIBRARY_VERSION_MAJOR == 9 && SODIUM_LIBRARY_VERSION_MINOR >= 6)
30+
31+
/**
32+
* OPSLIMIT and MEMLIMIT are taken from libsodium's MODERATE values.
33+
* MEMLIMIT is normalized to KB even though sodium uses Bytes in order to
34+
* present a consistent user-facing API.
35+
*
36+
* Threads are fixed at 1 by libsodium.
37+
*
38+
* When updating these values, synchronize ext/standard/php_password.h values.
39+
*/
40+
#define PHP_SODIUM_PWHASH_MEMLIMIT (256 << 10)
41+
#define PHP_SODIUM_PWHASH_OPSLIMIT 3
42+
#define PHP_SODIUM_PWHASH_THREADS 1
43+
44+
static zend_string *php_sodium_argon2_hash(const zend_string *password, zend_array *options, int alg) {
45+
size_t opslimit = PHP_SODIUM_PWHASH_OPSLIMIT;
46+
size_t memlimit = PHP_SODIUM_PWHASH_MEMLIMIT;
47+
zend_string *ret;
48+
49+
if ((ZSTR_LEN(password) >= 0xffffffff)) {
50+
php_error_docref(NULL, E_WARNING, "Password is too long");
51+
return NULL;
52+
}
53+
54+
if (options) {
55+
zval *opt;
56+
if ((opt = zend_hash_str_find(options, "memory_cost", strlen("memory_cost")))) {
57+
memlimit = zval_get_long(opt);
58+
if ((memlimit < crypto_pwhash_MEMLIMIT_MIN) || (memlimit > crypto_pwhash_MEMLIMIT_MAX)) {
59+
php_error_docref(NULL, E_WARNING, "Memory cost is outside of allowed memory range");
60+
return NULL;
61+
}
62+
}
63+
if ((opt = zend_hash_str_find(options, "time_cost", strlen("time_cost")))) {
64+
opslimit = zval_get_long(opt);
65+
if ((opslimit < crypto_pwhash_OPSLIMIT_MIN) || (opslimit > crypto_pwhash_OPSLIMIT_MAX)) {
66+
php_error_docref(NULL, E_WARNING, "Time cost is outside of allowed time range");
67+
return NULL;
68+
}
69+
}
70+
if ((opt = zend_hash_str_find(options, "threads", strlen("threads"))) && (zval_get_long(opt) != 1)) {
71+
php_error_docref(NULL, E_WARNING, "A thread value other than 1 is not supported by this implementation");
72+
return NULL;
73+
}
74+
}
75+
76+
ret = zend_string_alloc(crypto_pwhash_STRBYTES - 1, 0);
77+
if (crypto_pwhash_str_alg(ZSTR_VAL(ret), ZSTR_VAL(password), ZSTR_LEN(password), opslimit, memlimit << 10, alg)) {
78+
php_error_docref(NULL, E_WARNING, "Unexpected failure hashing password");
79+
zend_string_release(ret);
80+
return NULL;
81+
}
82+
83+
ZSTR_LEN(ret) = strlen(ZSTR_VAL(ret));
84+
ZSTR_VAL(ret)[ZSTR_LEN(ret)] = 0;
85+
86+
return ret;
87+
}
88+
89+
static zend_bool php_sodium_argon2_verify(const zend_string *password, const zend_string *hash) {
90+
if ((ZSTR_LEN(password) >= 0xffffffff) || (ZSTR_LEN(hash) >= 0xffffffff)) {
91+
return 0;
92+
}
93+
return crypto_pwhash_str_verify(ZSTR_VAL(hash), ZSTR_VAL(password), ZSTR_LEN(password)) == 0;
94+
}
95+
96+
static zend_bool php_sodium_argon2_needs_rehash(const zend_string *hash, zend_array *options) {
97+
size_t opslimit = PHP_SODIUM_PWHASH_OPSLIMIT;
98+
size_t memlimit = PHP_SODIUM_PWHASH_MEMLIMIT;
99+
100+
if (options) {
101+
zval *opt;
102+
if ((opt = zend_hash_str_find(options, "memory_cost", strlen("memory_cost")))) {
103+
memlimit = zval_get_long(opt);
104+
if ((memlimit < crypto_pwhash_MEMLIMIT_MIN) || (memlimit > crypto_pwhash_MEMLIMIT_MAX)) {
105+
php_error_docref(NULL, E_WARNING, "Memory cost is outside of allowed memory range");
106+
return 1;
107+
}
108+
}
109+
if ((opt = zend_hash_str_find(options, "time_cost", strlen("time_cost")))) {
110+
opslimit = zval_get_long(opt);
111+
if ((opslimit < crypto_pwhash_OPSLIMIT_MIN) || (opslimit > crypto_pwhash_OPSLIMIT_MAX)) {
112+
php_error_docref(NULL, E_WARNING, "Time cost is outside of allowed time range");
113+
return 1;
114+
}
115+
}
116+
if ((opt = zend_hash_str_find(options, "threads", strlen("threads"))) && (zval_get_long(opt) != 1)) {
117+
php_error_docref(NULL, E_WARNING, "A thread value other than 1 is not supported by this implementation");
118+
return 1;
119+
}
120+
}
121+
122+
return crypto_pwhash_str_needs_rehash(ZSTR_VAL(hash), opslimit, memlimit << 10);
123+
}
124+
125+
static int php_sodium_argon2_get_info(zval *return_value, const zend_string *hash) {
126+
const char* p = NULL;
127+
zend_long v = 0, threads = 1;
128+
zend_long memory_cost = PHP_SODIUM_PWHASH_MEMLIMIT;
129+
zend_long time_cost = PHP_SODIUM_PWHASH_OPSLIMIT;
130+
131+
if (!hash || (ZSTR_LEN(hash) < sizeof("$argon2id$"))) {
132+
return FAILURE;
133+
}
134+
135+
p = ZSTR_VAL(hash);
136+
if (!memcmp(p, "$argon2i$", strlen("$argon2i$"))) {
137+
p += strlen("$argon2i$");
138+
} else if (!memcmp(p, "$argon2id$", strlen("$argon2id$"))) {
139+
p += strlen("$argon2id$");
140+
} else {
141+
return FAILURE;
142+
}
143+
144+
sscanf(p, "v=" ZEND_LONG_FMT "$m=" ZEND_LONG_FMT ",t=" ZEND_LONG_FMT ",p=" ZEND_LONG_FMT,
145+
&v, &memory_cost, &time_cost, &threads);
146+
147+
add_assoc_long(return_value, "memory_cost", memory_cost);
148+
add_assoc_long(return_value, "time_cost", time_cost);
149+
add_assoc_long(return_value, "threads", threads);
150+
151+
return SUCCESS;
152+
}
153+
154+
/* argon2i specific methods */
155+
156+
static zend_string *php_sodium_argon2i_hash(const zend_string *password, zend_array *options) {
157+
return php_sodium_argon2_hash(password, options, crypto_pwhash_ALG_ARGON2I13);
158+
}
159+
160+
static const php_password_algo sodium_algo_argon2i = {
161+
"argon2i",
162+
php_sodium_argon2i_hash,
163+
php_sodium_argon2_verify,
164+
php_sodium_argon2_needs_rehash,
165+
php_sodium_argon2_get_info,
166+
NULL,
167+
};
168+
169+
/* argon2id specific methods */
170+
171+
static zend_string *php_sodium_argon2id_hash(const zend_string *password, zend_array *options) {
172+
return php_sodium_argon2_hash(password, options, crypto_pwhash_ALG_ARGON2ID13);
173+
}
174+
175+
static const php_password_algo sodium_algo_argon2id = {
176+
"argon2id",
177+
php_sodium_argon2id_hash,
178+
php_sodium_argon2_verify,
179+
php_sodium_argon2_needs_rehash,
180+
php_sodium_argon2_get_info,
181+
NULL,
182+
};
183+
184+
PHP_MINIT_FUNCTION(sodium_password_hash) /* {{{ */ {
185+
zend_string *argon2i = zend_string_init("argon2i", strlen("argon2i"), 1);
186+
187+
if (php_password_algo_find(argon2i)) {
188+
/* Nothing to do. Core has registered these algorithms for us. */
189+
zend_string_release(argon2i);
190+
return SUCCESS;
191+
}
192+
zend_string_release(argon2i);
193+
194+
if (FAILURE == php_password_algo_register("argon2i", &sodium_algo_argon2i)) {
195+
return FAILURE;
196+
}
197+
REGISTER_STRING_CONSTANT("PASSWORD_ARGON2I", "argon2i", CONST_CS | CONST_PERSISTENT);
198+
199+
if (FAILURE == php_password_algo_register("argon2id", &sodium_algo_argon2id)) {
200+
return FAILURE;
201+
}
202+
REGISTER_STRING_CONSTANT("PASSWORD_ARGON2ID", "argon2id", CONST_CS | CONST_PERSISTENT);
203+
204+
REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_MEMORY_COST", PHP_SODIUM_PWHASH_MEMLIMIT, CONST_CS | CONST_PERSISTENT);
205+
REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_TIME_COST", PHP_SODIUM_PWHASH_OPSLIMIT, CONST_CS | CONST_PERSISTENT);
206+
REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_THREADS", PHP_SODIUM_PWHASH_THREADS, CONST_CS | CONST_PERSISTENT);
207+
208+
REGISTER_STRING_CONSTANT("PASSWORD_ARGON2_PROVIDER", "sodium", CONST_CS | CONST_PERSISTENT);
209+
210+
return SUCCESS;
211+
}
212+
/* }}} */
213+
214+
215+
#endif /* HAVE_SODIUM_PASSWORD_HASH */

0 commit comments

Comments
 (0)