1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2018 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: Anthony Ferrara <ircmaxell@php.net> |
16 +----------------------------------------------------------------------+
17 */
18
19 /* $Id$ */
20
21 #include <stdlib.h>
22
23 #include "php.h"
24
25 #include "fcntl.h"
26 #include "php_password.h"
27 #include "php_rand.h"
28 #include "php_crypt.h"
29 #include "base64.h"
30 #include "zend_interfaces.h"
31 #include "info.h"
32 #include "php_random.h"
33
34 #ifdef PHP_WIN32
35 #include "win32/winutil.h"
36 #endif
37
PHP_MINIT_FUNCTION(password)38 PHP_MINIT_FUNCTION(password) /* {{{ */
39 {
40 REGISTER_LONG_CONSTANT("PASSWORD_DEFAULT", PHP_PASSWORD_DEFAULT, CONST_CS | CONST_PERSISTENT);
41 REGISTER_LONG_CONSTANT("PASSWORD_BCRYPT", PHP_PASSWORD_BCRYPT, CONST_CS | CONST_PERSISTENT);
42
43 REGISTER_LONG_CONSTANT("PASSWORD_BCRYPT_DEFAULT_COST", PHP_PASSWORD_BCRYPT_COST, CONST_CS | CONST_PERSISTENT);
44
45 return SUCCESS;
46 }
47 /* }}} */
48
php_password_get_algo_name(const php_password_algo algo)49 static char* php_password_get_algo_name(const php_password_algo algo)
50 {
51 switch (algo) {
52 case PHP_PASSWORD_BCRYPT:
53 return "bcrypt";
54 case PHP_PASSWORD_UNKNOWN:
55 default:
56 return "unknown";
57 }
58 }
59
php_password_determine_algo(const char * hash,const size_t len)60 static php_password_algo php_password_determine_algo(const char *hash, const size_t len)
61 {
62 if (len > 3 && hash[0] == '$' && hash[1] == '2' && hash[2] == 'y' && len == 60) {
63 return PHP_PASSWORD_BCRYPT;
64 }
65
66 return PHP_PASSWORD_UNKNOWN;
67 }
68
php_password_salt_is_alphabet(const char * str,const size_t len)69 static int php_password_salt_is_alphabet(const char *str, const size_t len) /* {{{ */
70 {
71 size_t i = 0;
72
73 for (i = 0; i < len; i++) {
74 if (!((str[i] >= 'A' && str[i] <= 'Z') || (str[i] >= 'a' && str[i] <= 'z') || (str[i] >= '0' && str[i] <= '9') || str[i] == '.' || str[i] == '/')) {
75 return FAILURE;
76 }
77 }
78 return SUCCESS;
79 }
80 /* }}} */
81
php_password_salt_to64(const char * str,const size_t str_len,const size_t out_len,char * ret)82 static int php_password_salt_to64(const char *str, const size_t str_len, const size_t out_len, char *ret) /* {{{ */
83 {
84 size_t pos = 0;
85 zend_string *buffer;
86 if ((int) str_len < 0) {
87 return FAILURE;
88 }
89 buffer = php_base64_encode((unsigned char*) str, str_len);
90 if (ZSTR_LEN(buffer) < out_len) {
91 /* Too short of an encoded string generated */
92 zend_string_release(buffer);
93 return FAILURE;
94 }
95 for (pos = 0; pos < out_len; pos++) {
96 if (ZSTR_VAL(buffer)[pos] == '+') {
97 ret[pos] = '.';
98 } else if (ZSTR_VAL(buffer)[pos] == '=') {
99 zend_string_free(buffer);
100 return FAILURE;
101 } else {
102 ret[pos] = ZSTR_VAL(buffer)[pos];
103 }
104 }
105 zend_string_free(buffer);
106 return SUCCESS;
107 }
108 /* }}} */
109
php_password_make_salt(size_t length,char * ret)110 static int php_password_make_salt(size_t length, char *ret) /* {{{ */
111 {
112 size_t raw_length;
113 char *buffer;
114 char *result;
115
116 if (length > (INT_MAX / 3)) {
117 php_error_docref(NULL, E_WARNING, "Length is too large to safely generate");
118 return FAILURE;
119 }
120
121 raw_length = length * 3 / 4 + 1;
122
123 buffer = (char *) safe_emalloc(raw_length, 1, 1);
124
125 if (FAILURE == php_random_bytes_silent(buffer, raw_length)) {
126 php_error_docref(NULL, E_WARNING, "Unable to generate salt");
127 efree(buffer);
128 return FAILURE;
129 }
130
131 result = safe_emalloc(length, 1, 1);
132 if (php_password_salt_to64(buffer, raw_length, length, result) == FAILURE) {
133 php_error_docref(NULL, E_WARNING, "Generated salt too short");
134 efree(buffer);
135 efree(result);
136 return FAILURE;
137 }
138 memcpy(ret, result, length);
139 efree(result);
140 efree(buffer);
141 ret[length] = 0;
142 return SUCCESS;
143 }
144 /* }}} */
145
PHP_FUNCTION(password_get_info)146 PHP_FUNCTION(password_get_info)
147 {
148 php_password_algo algo;
149 size_t hash_len;
150 char *hash, *algo_name;
151 zval options;
152
153 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &hash, &hash_len) == FAILURE) {
154 return;
155 }
156
157 array_init(&options);
158
159 algo = php_password_determine_algo(hash, (size_t) hash_len);
160 algo_name = php_password_get_algo_name(algo);
161
162 switch (algo) {
163 case PHP_PASSWORD_BCRYPT:
164 {
165 zend_long cost = PHP_PASSWORD_BCRYPT_COST;
166 sscanf(hash, "$2y$" ZEND_LONG_FMT "$", &cost);
167 add_assoc_long(&options, "cost", cost);
168 }
169 break;
170 case PHP_PASSWORD_UNKNOWN:
171 default:
172 break;
173 }
174
175 array_init(return_value);
176
177 add_assoc_long(return_value, "algo", algo);
178 add_assoc_string(return_value, "algoName", algo_name);
179 add_assoc_zval(return_value, "options", &options);
180 }
181
PHP_FUNCTION(password_needs_rehash)182 PHP_FUNCTION(password_needs_rehash)
183 {
184 zend_long new_algo = 0;
185 php_password_algo algo;
186 size_t hash_len;
187 char *hash;
188 HashTable *options = 0;
189 zval *option_buffer;
190
191 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sl|H", &hash, &hash_len, &new_algo, &options) == FAILURE) {
192 return;
193 }
194
195 algo = php_password_determine_algo(hash, (size_t) hash_len);
196
197 if ((zend_long)algo != new_algo) {
198 RETURN_TRUE;
199 }
200
201 switch (algo) {
202 case PHP_PASSWORD_BCRYPT:
203 {
204 zend_long new_cost = PHP_PASSWORD_BCRYPT_COST, cost = 0;
205
206 if (options && (option_buffer = zend_hash_str_find(options, "cost", sizeof("cost")-1)) != NULL) {
207 new_cost = zval_get_long(option_buffer);
208 }
209
210 sscanf(hash, "$2y$" ZEND_LONG_FMT "$", &cost);
211 if (cost != new_cost) {
212 RETURN_TRUE;
213 }
214 }
215 break;
216 case PHP_PASSWORD_UNKNOWN:
217 default:
218 break;
219 }
220 RETURN_FALSE;
221 }
222
223 /* {{{ proto boolean password_make_salt(string password, string hash)
224 Verify a hash created using crypt() or password_hash() */
PHP_FUNCTION(password_verify)225 PHP_FUNCTION(password_verify)
226 {
227 int status = 0;
228 size_t i, password_len, hash_len;
229 char *password, *hash;
230 zend_string *ret;
231
232 if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &password, &password_len, &hash, &hash_len) == FAILURE) {
233 RETURN_FALSE;
234 }
235 if ((ret = php_crypt(password, (int)password_len, hash, (int)hash_len, 1)) == NULL) {
236 RETURN_FALSE;
237 }
238
239 if (ZSTR_LEN(ret) != hash_len || hash_len < 13) {
240 zend_string_free(ret);
241 RETURN_FALSE;
242 }
243
244 /* We're using this method instead of == in order to provide
245 * resistance towards timing attacks. This is a constant time
246 * equality check that will always check every byte of both
247 * values. */
248 for (i = 0; i < hash_len; i++) {
249 status |= (ZSTR_VAL(ret)[i] ^ hash[i]);
250 }
251
252 zend_string_free(ret);
253
254 RETURN_BOOL(status == 0);
255
256 }
257 /* }}} */
258
259 /* {{{ proto string password_hash(string password, int algo, array options = array())
260 Hash a password */
PHP_FUNCTION(password_hash)261 PHP_FUNCTION(password_hash)
262 {
263 char hash_format[8], *hash, *salt, *password;
264 zend_long algo = 0;
265 size_t password_len = 0;
266 int hash_len;
267 size_t salt_len = 0, required_salt_len = 0, hash_format_len;
268 HashTable *options = 0;
269 zval *option_buffer;
270 zend_string *result;
271
272 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sl|H", &password, &password_len, &algo, &options) == FAILURE) {
273 return;
274 }
275
276 switch (algo) {
277 case PHP_PASSWORD_BCRYPT:
278 {
279 zend_long cost = PHP_PASSWORD_BCRYPT_COST;
280
281 if (options && (option_buffer = zend_hash_str_find(options, "cost", sizeof("cost")-1)) != NULL) {
282 cost = zval_get_long(option_buffer);
283 }
284
285 if (cost < 4 || cost > 31) {
286 php_error_docref(NULL, E_WARNING, "Invalid bcrypt cost parameter specified: " ZEND_LONG_FMT, cost);
287 RETURN_NULL();
288 }
289
290 required_salt_len = 22;
291 sprintf(hash_format, "$2y$%02ld$", (long) cost);
292 hash_format_len = 7;
293 }
294 break;
295 case PHP_PASSWORD_UNKNOWN:
296 default:
297 php_error_docref(NULL, E_WARNING, "Unknown password hashing algorithm: " ZEND_LONG_FMT, algo);
298 RETURN_NULL();
299 }
300
301 if (options && (option_buffer = zend_hash_str_find(options, "salt", sizeof("salt")-1)) != NULL) {
302 zend_string *buffer;
303
304 php_error_docref(NULL, E_DEPRECATED, "Use of the 'salt' option to password_hash is deprecated");
305
306 switch (Z_TYPE_P(option_buffer)) {
307 case IS_STRING:
308 buffer = zend_string_copy(Z_STR_P(option_buffer));
309 break;
310 case IS_LONG:
311 case IS_DOUBLE:
312 case IS_OBJECT:
313 buffer = zval_get_string(option_buffer);
314 break;
315 case IS_FALSE:
316 case IS_TRUE:
317 case IS_NULL:
318 case IS_RESOURCE:
319 case IS_ARRAY:
320 default:
321 php_error_docref(NULL, E_WARNING, "Non-string salt parameter supplied");
322 RETURN_NULL();
323 }
324
325 /* XXX all the crypt related APIs work with int for string length.
326 That should be revised for size_t and then we maybe don't require
327 the > INT_MAX check. */
328 if (ZSTR_LEN(buffer) > INT_MAX) {
329 php_error_docref(NULL, E_WARNING, "Supplied salt is too long");
330 RETURN_NULL();
331 } else if (ZSTR_LEN(buffer) < required_salt_len) {
332 php_error_docref(NULL, E_WARNING, "Provided salt is too short: %zd expecting %zd", ZSTR_LEN(buffer), required_salt_len);
333 zend_string_release(buffer);
334 RETURN_NULL();
335 } else if (php_password_salt_is_alphabet(ZSTR_VAL(buffer), ZSTR_LEN(buffer)) == FAILURE) {
336 salt = safe_emalloc(required_salt_len, 1, 1);
337 if (php_password_salt_to64(ZSTR_VAL(buffer), ZSTR_LEN(buffer), required_salt_len, salt) == FAILURE) {
338 efree(salt);
339 php_error_docref(NULL, E_WARNING, "Provided salt is too short: %zd", ZSTR_LEN(buffer));
340 zend_string_release(buffer);
341 RETURN_NULL();
342 }
343 salt_len = required_salt_len;
344 } else {
345 salt = safe_emalloc(required_salt_len, 1, 1);
346 memcpy(salt, ZSTR_VAL(buffer), required_salt_len);
347 salt_len = required_salt_len;
348 }
349 zend_string_release(buffer);
350 } else {
351 salt = safe_emalloc(required_salt_len, 1, 1);
352 if (php_password_make_salt(required_salt_len, salt) == FAILURE) {
353 efree(salt);
354 RETURN_FALSE;
355 }
356 salt_len = required_salt_len;
357 }
358
359 salt[salt_len] = 0;
360
361 hash = safe_emalloc(salt_len + hash_format_len, 1, 1);
362 sprintf(hash, "%s%s", hash_format, salt);
363 hash[hash_format_len + salt_len] = 0;
364
365 efree(salt);
366
367 /* This cast is safe, since both values are defined here in code and cannot overflow */
368 hash_len = (int) (hash_format_len + salt_len);
369
370 if ((result = php_crypt(password, (int)password_len, hash, hash_len, 1)) == NULL) {
371 efree(hash);
372 RETURN_FALSE;
373 }
374
375 efree(hash);
376
377 if (ZSTR_LEN(result) < 13) {
378 zend_string_free(result);
379 RETURN_FALSE;
380 }
381
382 RETURN_STR(result);
383 }
384 /* }}} */
385
386 /*
387 * Local variables:
388 * tab-width: 4
389 * c-basic-offset: 4
390 * End:
391 * vim600: sw=4 ts=4 fdm=marker
392 * vim<600: sw=4 ts=4
393 */
394