1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2014 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 || (size_t) 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