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