xref: /PHP-7.1/ext/standard/password.c (revision ccd4716e)
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