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