1/*
2 +----------------------------------------------------------------------+
3 | PHP Version 5 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2015 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
38PHP_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
49static 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
60static 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
69static 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
82static 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
111static 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
175PHP_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
217PHP_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)
272Verify a hash created using crypt() or password_hash() */
273PHP_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())
307Hash a password */
308PHP_FUNCTION(password_hash)
309{
310 char *hash_format, *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 hash_format = emalloc(8);
345 sprintf(hash_format, "$2y$%02ld$", cost);
346 hash_format_len = 7;
347 }
348 break;
349 case PHP_PASSWORD_UNKNOWN:
350 default:
351 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown password hashing algorithm: %ld", algo);
352 RETURN_NULL();
353 }
354
355 if (options && zend_symtable_find(options, "salt", 5, (void**) &option_buffer) == SUCCESS) {
356 char *buffer;
357 int buffer_len_int = 0;
358 size_t buffer_len;
359 switch (Z_TYPE_PP(option_buffer)) {
360 case IS_STRING:
361 buffer = estrndup(Z_STRVAL_PP(option_buffer), Z_STRLEN_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 zval cast_option_buffer;
368 MAKE_COPY_ZVAL(option_buffer, &cast_option_buffer);
369 convert_to_string(&cast_option_buffer);
370 if (Z_TYPE(cast_option_buffer) == IS_STRING) {
371 buffer = estrndup(Z_STRVAL(cast_option_buffer), Z_STRLEN(cast_option_buffer));
372 buffer_len_int = Z_STRLEN(cast_option_buffer);
373 zval_dtor(&cast_option_buffer);
374 break;
375 }
376 zval_dtor(&cast_option_buffer);
377 }
378 case IS_BOOL:
379 case IS_NULL:
380 case IS_RESOURCE:
381 case IS_ARRAY:
382 default:
383 efree(hash_format);
384 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Non-string salt parameter supplied");
385 RETURN_NULL();
386 }
387 if (buffer_len_int < 0) {
388 efree(hash_format);
389 efree(buffer);
390 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Supplied salt is too long");
391 }
392 buffer_len = (size_t) buffer_len_int;
393 if (buffer_len < required_salt_len) {
394 efree(hash_format);
395 efree(buffer);
396 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);
397 RETURN_NULL();
398 } else if (php_password_salt_is_alphabet(buffer, buffer_len) == FAILURE) {
399 salt = safe_emalloc(required_salt_len, 1, 1);
400 if (php_password_salt_to64(buffer, buffer_len, required_salt_len, salt) == FAILURE) {
401 efree(hash_format);
402 efree(buffer);
403 efree(salt);
404 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Provided salt is too short: %lu", (unsigned long) buffer_len);
405 RETURN_NULL();
406 }
407 salt_len = required_salt_len;
408 } else {
409 salt = safe_emalloc(required_salt_len, 1, 1);
410 memcpy(salt, buffer, (int) required_salt_len);
411 salt_len = required_salt_len;
412 }
413 efree(buffer);
414 } else {
415 salt = safe_emalloc(required_salt_len, 1, 1);
416 if (php_password_make_salt(required_salt_len, salt TSRMLS_CC) == FAILURE) {
417 efree(hash_format);
418 efree(salt);
419 RETURN_FALSE;
420 }
421 salt_len = required_salt_len;
422 }
423
424 salt[salt_len] = 0;
425
426 hash = safe_emalloc(salt_len + hash_format_len, 1, 1);
427 sprintf(hash, "%s%s", hash_format, salt);
428 hash[hash_format_len + salt_len] = 0;
429
430 efree(hash_format);
431 efree(salt);
432
433 /* This cast is safe, since both values are defined here in code and cannot overflow */
434 hash_len = (int) (hash_format_len + salt_len);
435
436 if (php_crypt(password, password_len, hash, hash_len, &result) == FAILURE) {
437 efree(hash);
438 RETURN_FALSE;
439 }
440
441 efree(hash);
442
443 if (strlen(result) < 13) {
444 efree(result);
445 RETURN_FALSE;
446 }
447
448 RETURN_STRING(result, 0);
449}
450/* }}} */
451
452#endif /* HAVE_CRYPT */
453/*
454 * Local variables:
455 * tab-width: 4
456 * c-basic-offset: 4
457 * End:
458 * vim600: sw=4 ts=4 fdm=marker
459 * vim<600: sw=4 ts=4
460 */
461