xref: /PHP-7.4/ext/sodium/sodium_pwhash.c (revision 145ffd93)
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