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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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() */ |
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 */ |
308 | PHP_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 | |