1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 5 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2016 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
34 #if 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 size_t ret_len = 0;
86 unsigned char *buffer;
87 if ((int) str_len < 0) {
88 return FAILURE;
89 }
90 buffer = php_base64_encode((unsigned char*) str, (int) str_len, (int*) &ret_len);
91 if (ret_len < out_len) {
92 /* Too short of an encoded string generated */
93 efree(buffer);
94 return FAILURE;
95 }
96 for (pos = 0; pos < out_len; pos++) {
97 if (buffer[pos] == '+') {
98 ret[pos] = '.';
99 } else if (buffer[pos] == '=') {
100 efree(buffer);
101 return FAILURE;
102 } else {
103 ret[pos] = buffer[pos];
104 }
105 }
106 efree(buffer);
107 return SUCCESS;
108 }
109 /* }}} */
110
php_password_make_salt(size_t length,char * ret TSRMLS_DC)111 static int php_password_make_salt(size_t length, char *ret TSRMLS_DC) /* {{{ */
112 {
113 int buffer_valid = 0;
114 size_t i, raw_length;
115 char *buffer;
116 char *result;
117
118 if (length > (INT_MAX / 3)) {
119 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Length is too large to safely generate");
120 return FAILURE;
121 }
122
123 raw_length = length * 3 / 4 + 1;
124
125 buffer = (char *) safe_emalloc(raw_length, 1, 1);
126
127 #if PHP_WIN32
128 {
129 BYTE *iv_b = (BYTE *) buffer;
130 if (php_win32_get_random_bytes(iv_b, raw_length) == SUCCESS) {
131 buffer_valid = 1;
132 }
133 }
134 #else
135 {
136 int fd, n;
137 size_t read_bytes = 0;
138 fd = open("/dev/urandom", O_RDONLY);
139 if (fd >= 0) {
140 while (read_bytes < raw_length) {
141 n = read(fd, buffer + read_bytes, raw_length - read_bytes);
142 if (n < 0) {
143 break;
144 }
145 read_bytes += (size_t) n;
146 }
147 close(fd);
148 }
149 if (read_bytes >= raw_length) {
150 buffer_valid = 1;
151 }
152 }
153 #endif
154 if (!buffer_valid) {
155 for (i = 0; i < raw_length; i++) {
156 buffer[i] ^= (char) (255.0 * php_rand(TSRMLS_C) / RAND_MAX);
157 }
158 }
159
160 result = safe_emalloc(length, 1, 1);
161 if (php_password_salt_to64(buffer, raw_length, length, result) == FAILURE) {
162 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Generated salt too short");
163 efree(buffer);
164 efree(result);
165 return FAILURE;
166 }
167 memcpy(ret, result, (int) length);
168 efree(result);
169 efree(buffer);
170 ret[length] = 0;
171 return SUCCESS;
172 }
173 /* }}} */
174
PHP_FUNCTION(password_get_info)175 PHP_FUNCTION(password_get_info)
176 {
177 php_password_algo algo;
178 int hash_len;
179 char *hash, *algo_name;
180 zval *options;
181
182 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &hash, &hash_len) == FAILURE) {
183 return;
184 }
185
186 if (hash_len < 0) {
187 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Supplied password hash too long to safely identify");
188 RETURN_FALSE;
189 }
190
191 ALLOC_INIT_ZVAL(options);
192 array_init(options);
193
194 algo = php_password_determine_algo(hash, (size_t) hash_len);
195 algo_name = php_password_get_algo_name(algo);
196
197 switch (algo) {
198 case PHP_PASSWORD_BCRYPT:
199 {
200 long cost = PHP_PASSWORD_BCRYPT_COST;
201 sscanf(hash, "$2y$%ld$", &cost);
202 add_assoc_long(options, "cost", cost);
203 }
204 break;
205 case PHP_PASSWORD_UNKNOWN:
206 default:
207 break;
208 }
209
210 array_init(return_value);
211
212 add_assoc_long(return_value, "algo", algo);
213 add_assoc_string(return_value, "algoName", algo_name, 1);
214 add_assoc_zval(return_value, "options", options);
215 }
216
PHP_FUNCTION(password_needs_rehash)217 PHP_FUNCTION(password_needs_rehash)
218 {
219 long new_algo = 0;
220 php_password_algo algo;
221 int hash_len;
222 char *hash;
223 HashTable *options = 0;
224 zval **option_buffer;
225
226 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl|H", &hash, &hash_len, &new_algo, &options) == FAILURE) {
227 return;
228 }
229
230 if (hash_len < 0) {
231 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Supplied password hash too long to safely identify");
232 RETURN_FALSE;
233 }
234
235 algo = php_password_determine_algo(hash, (size_t) hash_len);
236
237 if (algo != new_algo) {
238 RETURN_TRUE;
239 }
240
241 switch (algo) {
242 case PHP_PASSWORD_BCRYPT:
243 {
244 long new_cost = PHP_PASSWORD_BCRYPT_COST, cost = 0;
245
246 if (options && zend_symtable_find(options, "cost", sizeof("cost"), (void **) &option_buffer) == SUCCESS) {
247 if (Z_TYPE_PP(option_buffer) != IS_LONG) {
248 zval cast_option_buffer;
249 MAKE_COPY_ZVAL(option_buffer, &cast_option_buffer);
250 convert_to_long(&cast_option_buffer);
251 new_cost = Z_LVAL(cast_option_buffer);
252 zval_dtor(&cast_option_buffer);
253 } else {
254 new_cost = Z_LVAL_PP(option_buffer);
255 }
256 }
257
258 sscanf(hash, "$2y$%ld$", &cost);
259 if (cost != new_cost) {
260 RETURN_TRUE;
261 }
262 }
263 break;
264 case PHP_PASSWORD_UNKNOWN:
265 default:
266 break;
267 }
268 RETURN_FALSE;
269 }
270
271 /* {{{ proto boolean password_make_salt(string password, string hash)
272 Verify a hash created using crypt() or password_hash() */
PHP_FUNCTION(password_verify)273 PHP_FUNCTION(password_verify)
274 {
275 int status = 0, i;
276 int password_len, hash_len;
277 char *ret, *password, *hash;
278
279 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &password, &password_len, &hash, &hash_len) == FAILURE) {
280 RETURN_FALSE;
281 }
282 if (php_crypt(password, password_len, hash, hash_len, &ret) == FAILURE) {
283 RETURN_FALSE;
284 }
285
286 if (strlen(ret) != hash_len || hash_len < 13) {
287 efree(ret);
288 RETURN_FALSE;
289 }
290
291 /* We're using this method instead of == in order to provide
292 * resistence towards timing attacks. This is a constant time
293 * equality check that will always check every byte of both
294 * values. */
295 for (i = 0; i < hash_len; i++) {
296 status |= (ret[i] ^ hash[i]);
297 }
298
299 efree(ret);
300
301 RETURN_BOOL(status == 0);
302
303 }
304 /* }}} */
305
306 /* {{{ proto string password_hash(string password, int algo, array options = array())
307 Hash a password */
PHP_FUNCTION(password_hash)308 PHP_FUNCTION(password_hash)
309 {
310 char hash_format[8], *hash, *salt, *password, *result;
311 long algo = 0;
312 int password_len = 0, hash_len;
313 size_t salt_len = 0, required_salt_len = 0, hash_format_len;
314 HashTable *options = 0;
315 zval **option_buffer;
316
317 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl|H", &password, &password_len, &algo, &options) == FAILURE) {
318 return;
319 }
320
321 switch (algo) {
322 case PHP_PASSWORD_BCRYPT:
323 {
324 long cost = PHP_PASSWORD_BCRYPT_COST;
325
326 if (options && zend_symtable_find(options, "cost", 5, (void **) &option_buffer) == SUCCESS) {
327 if (Z_TYPE_PP(option_buffer) != IS_LONG) {
328 zval cast_option_buffer;
329 MAKE_COPY_ZVAL(option_buffer, &cast_option_buffer);
330 convert_to_long(&cast_option_buffer);
331 cost = Z_LVAL(cast_option_buffer);
332 zval_dtor(&cast_option_buffer);
333 } else {
334 cost = Z_LVAL_PP(option_buffer);
335 }
336 }
337
338 if (cost < 4 || cost > 31) {
339 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid bcrypt cost parameter specified: %ld", cost);
340 RETURN_NULL();
341 }
342
343 required_salt_len = 22;
344 sprintf(hash_format, "$2y$%02ld$", cost);
345 hash_format_len = 7;
346 }
347 break;
348 case PHP_PASSWORD_UNKNOWN:
349 default:
350 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown password hashing algorithm: %ld", algo);
351 RETURN_NULL();
352 }
353
354 if (options && zend_symtable_find(options, "salt", 5, (void**) &option_buffer) == SUCCESS) {
355 char *buffer;
356 int buffer_len_int = 0;
357 size_t buffer_len;
358 zval cast_option_buffer = zval_used_for_init;
359 switch (Z_TYPE_PP(option_buffer)) {
360 case IS_STRING:
361 buffer = Z_STRVAL_PP(option_buffer);
362 buffer_len_int = Z_STRLEN_PP(option_buffer);
363 break;
364 case IS_LONG:
365 case IS_DOUBLE:
366 case IS_OBJECT:
367 MAKE_COPY_ZVAL(option_buffer, &cast_option_buffer);
368 convert_to_string(&cast_option_buffer);
369 if (Z_TYPE(cast_option_buffer) == IS_STRING) {
370 buffer = Z_STRVAL(cast_option_buffer);
371 buffer_len_int = Z_STRLEN(cast_option_buffer);
372 break;
373 }
374 case IS_BOOL:
375 case IS_NULL:
376 case IS_RESOURCE:
377 case IS_ARRAY:
378 default:
379 if (Z_TYPE(cast_option_buffer) != IS_NULL) {
380 zval_dtor(&cast_option_buffer);
381 }
382 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Non-string salt parameter supplied");
383 RETURN_NULL();
384 }
385 if (buffer_len_int < 0) {
386 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Supplied salt is too long");
387 if (Z_TYPE(cast_option_buffer) != IS_NULL) {
388 zval_dtor(&cast_option_buffer);
389 }
390 RETURN_NULL();
391 }
392 buffer_len = (size_t) buffer_len_int;
393 if (buffer_len < required_salt_len) {
394 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Provided salt is too short: %lu expecting %lu", (unsigned long) buffer_len, (unsigned long) required_salt_len);
395 if (Z_TYPE(cast_option_buffer) != IS_NULL) {
396 zval_dtor(&cast_option_buffer);
397 }
398 RETURN_NULL();
399 } else if (php_password_salt_is_alphabet(buffer, buffer_len) == FAILURE) {
400 salt = safe_emalloc(required_salt_len, 1, 1);
401 if (php_password_salt_to64(buffer, buffer_len, required_salt_len, salt) == FAILURE) {
402 efree(salt);
403 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Provided salt is too short: %lu", (unsigned long) buffer_len);
404 if (Z_TYPE(cast_option_buffer) != IS_NULL) {
405 zval_dtor(&cast_option_buffer);
406 }
407 RETURN_NULL();
408 }
409 salt_len = required_salt_len;
410 } else {
411 salt = safe_emalloc(required_salt_len, 1, 1);
412 memcpy(salt, buffer, (int) required_salt_len);
413 salt_len = required_salt_len;
414 }
415 if (Z_TYPE(cast_option_buffer) != IS_NULL) {
416 zval_dtor(&cast_option_buffer);
417 }
418 } else {
419 salt = safe_emalloc(required_salt_len, 1, 1);
420 if (php_password_make_salt(required_salt_len, salt TSRMLS_CC) == FAILURE) {
421 efree(salt);
422 RETURN_FALSE;
423 }
424 salt_len = required_salt_len;
425 }
426
427 salt[salt_len] = 0;
428
429 hash = safe_emalloc(salt_len + hash_format_len, 1, 1);
430 sprintf(hash, "%s%s", hash_format, salt);
431 hash[hash_format_len + salt_len] = 0;
432
433 efree(salt);
434
435 /* This cast is safe, since both values are defined here in code and cannot overflow */
436 hash_len = (int) (hash_format_len + salt_len);
437
438 if (php_crypt(password, password_len, hash, hash_len, &result) == FAILURE) {
439 efree(hash);
440 RETURN_FALSE;
441 }
442
443 efree(hash);
444
445 if (strlen(result) < 13) {
446 efree(result);
447 RETURN_FALSE;
448 }
449
450 RETURN_STRING(result, 0);
451 }
452 /* }}} */
453
454 #endif /* HAVE_CRYPT */
455 /*
456 * Local variables:
457 * tab-width: 4
458 * c-basic-offset: 4
459 * End:
460 * vim600: sw=4 ts=4 fdm=marker
461 * vim<600: sw=4 ts=4
462 */
463