xref: /PHP-8.4/ext/sodium/sodium_pwhash.c (revision 20c49f85)
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 "ext/standard/php_password.h"
23 
24 #include <sodium.h>
25 
26 #include "php_libsodium.h"
27 #include "sodium_pwhash_arginfo.h"
28 
29 #if SODIUM_LIBRARY_VERSION_MAJOR > 9 || (SODIUM_LIBRARY_VERSION_MAJOR == 9 && SODIUM_LIBRARY_VERSION_MINOR >= 6)
30 
get_options(zend_array * options,size_t * memlimit,size_t * opslimit)31 static inline int get_options(zend_array *options, size_t *memlimit, size_t *opslimit) {
32 	zval *opt;
33 
34 	*opslimit = PHP_SODIUM_PWHASH_OPSLIMIT;
35 	*memlimit = PHP_SODIUM_PWHASH_MEMLIMIT << 10;
36 	if (!options) {
37 		return SUCCESS;
38 	}
39 	if ((opt = zend_hash_str_find(options, "memory_cost", strlen("memory_cost")))) {
40 		zend_long smemlimit = zval_get_long(opt);
41 
42 		if ((smemlimit < 0) || (smemlimit < crypto_pwhash_MEMLIMIT_MIN >> 10) || (smemlimit > (crypto_pwhash_MEMLIMIT_MAX >> 10))) {
43 			zend_value_error("Memory cost is outside of allowed memory range");
44 			return FAILURE;
45 		}
46 		*memlimit = smemlimit << 10;
47 	}
48 	if ((opt = zend_hash_str_find(options, "time_cost", strlen("time_cost")))) {
49 		*opslimit = zval_get_long(opt);
50 		if ((*opslimit < crypto_pwhash_OPSLIMIT_MIN) || (*opslimit > crypto_pwhash_OPSLIMIT_MAX)) {
51 			zend_value_error("Time cost is outside of allowed time range");
52 			return FAILURE;
53 		}
54 	}
55 	if ((opt = zend_hash_str_find(options, "threads", strlen("threads"))) && (zval_get_long(opt) != 1)) {
56 		zend_value_error("A thread value other than 1 is not supported by this implementation");
57 		return FAILURE;
58 	}
59 	return SUCCESS;
60 }
61 
php_sodium_argon2_hash(const zend_string * password,zend_array * options,int alg)62 static zend_string *php_sodium_argon2_hash(const zend_string *password, zend_array *options, int alg) {
63 	size_t opslimit, memlimit;
64 	zend_string *ret;
65 
66 	if ((ZSTR_LEN(password) >= 0xffffffff)) {
67 		zend_value_error("Password is too long");
68 		return NULL;
69 	}
70 
71 	if (get_options(options, &memlimit, &opslimit) == FAILURE) {
72 		return NULL;
73 	}
74 
75 	ret = zend_string_alloc(crypto_pwhash_STRBYTES - 1, 0);
76 	if (crypto_pwhash_str_alg(ZSTR_VAL(ret), ZSTR_VAL(password), ZSTR_LEN(password), opslimit, memlimit, alg)) {
77 		zend_value_error("Unexpected failure hashing password");
78 		zend_string_release(ret);
79 		return NULL;
80 	}
81 
82 	ZSTR_LEN(ret) = strlen(ZSTR_VAL(ret));
83 	ZSTR_VAL(ret)[ZSTR_LEN(ret)] = 0;
84 
85 	return ret;
86 }
87 
php_sodium_argon2_verify(const zend_string * password,const zend_string * hash)88 static bool php_sodium_argon2_verify(const zend_string *password, const zend_string *hash) {
89 	if ((ZSTR_LEN(password) >= 0xffffffff) || (ZSTR_LEN(hash) >= 0xffffffff)) {
90 		return 0;
91 	}
92 	return crypto_pwhash_str_verify(ZSTR_VAL(hash), ZSTR_VAL(password), ZSTR_LEN(password)) == 0;
93 }
94 
php_sodium_argon2_needs_rehash(const zend_string * hash,zend_array * options)95 static bool php_sodium_argon2_needs_rehash(const zend_string *hash, zend_array *options) {
96 	size_t opslimit, memlimit;
97 
98 	if (get_options(options, &memlimit, &opslimit) == FAILURE) {
99 		return 1;
100 	}
101 	return crypto_pwhash_str_needs_rehash(ZSTR_VAL(hash), opslimit, memlimit);
102 }
103 
php_sodium_argon2_get_info(zval * return_value,const zend_string * hash)104 static int php_sodium_argon2_get_info(zval *return_value, const zend_string *hash) {
105 	const char* p = NULL;
106 	zend_long v = 0, threads = 1;
107 	zend_long memory_cost = PHP_SODIUM_PWHASH_MEMLIMIT;
108 	zend_long time_cost = PHP_SODIUM_PWHASH_OPSLIMIT;
109 
110 	if (!hash || (ZSTR_LEN(hash) < sizeof("$argon2id$"))) {
111 		return FAILURE;
112 	}
113 
114 	p = ZSTR_VAL(hash);
115 	if (!memcmp(p, "$argon2i$", strlen("$argon2i$"))) {
116 		p += strlen("$argon2i$");
117 	} else if (!memcmp(p, "$argon2id$", strlen("$argon2id$"))) {
118 		p += strlen("$argon2id$");
119 	} else {
120 		return FAILURE;
121 	}
122 
123 	sscanf(p, "v=" ZEND_LONG_FMT "$m=" ZEND_LONG_FMT ",t=" ZEND_LONG_FMT ",p=" ZEND_LONG_FMT,
124 	       &v, &memory_cost, &time_cost, &threads);
125 
126 	add_assoc_long(return_value, "memory_cost", memory_cost);
127 	add_assoc_long(return_value, "time_cost", time_cost);
128 	add_assoc_long(return_value, "threads", threads);
129 
130 	return SUCCESS;
131 }
132 
133 /* argon2i specific methods */
134 
php_sodium_argon2i_hash(const zend_string * password,zend_array * options)135 static zend_string *php_sodium_argon2i_hash(const zend_string *password, zend_array *options) {
136 	return php_sodium_argon2_hash(password, options, crypto_pwhash_ALG_ARGON2I13);
137 }
138 
139 static const php_password_algo sodium_algo_argon2i = {
140 	"argon2i",
141 	php_sodium_argon2i_hash,
142 	php_sodium_argon2_verify,
143 	php_sodium_argon2_needs_rehash,
144 	php_sodium_argon2_get_info,
145 	NULL,
146 };
147 
148 /* argon2id specific methods */
149 
php_sodium_argon2id_hash(const zend_string * password,zend_array * options)150 static zend_string *php_sodium_argon2id_hash(const zend_string *password, zend_array *options) {
151 	return php_sodium_argon2_hash(password, options, crypto_pwhash_ALG_ARGON2ID13);
152 }
153 
154 static const php_password_algo sodium_algo_argon2id = {
155 	"argon2id",
156 	php_sodium_argon2id_hash,
157 	php_sodium_argon2_verify,
158 	php_sodium_argon2_needs_rehash,
159 	php_sodium_argon2_get_info,
160 	NULL,
161 };
162 
PHP_MINIT_FUNCTION(sodium_password_hash)163 PHP_MINIT_FUNCTION(sodium_password_hash) /* {{{ */ {
164 	zend_string *argon2i = ZSTR_INIT_LITERAL("argon2i", 1);
165 
166 	if (php_password_algo_find(argon2i)) {
167 		/* Nothing to do. Core has registered these algorithms for us. */
168 		zend_string_release(argon2i);
169 		return SUCCESS;
170 	}
171 	zend_string_release(argon2i);
172 
173 	register_sodium_pwhash_symbols(module_number);
174 
175 	if (FAILURE == php_password_algo_register("argon2i", &sodium_algo_argon2i)) {
176 		return FAILURE;
177 	}
178 	if (FAILURE == php_password_algo_register("argon2id", &sodium_algo_argon2id)) {
179 		return FAILURE;
180 	}
181 
182 	return SUCCESS;
183 }
184 /* }}} */
185 
186 
187 #endif /* HAVE_SODIUM_PASSWORD_HASH */
188