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