diff options
author | Frederic Guillot <fred@kanboard.net> | 2015-01-18 21:55:08 -0500 |
---|---|---|
committer | Frederic Guillot <fred@kanboard.net> | 2015-01-18 21:55:08 -0500 |
commit | 2bdd6a6b35f391d8281e3217e1c31ed469381c3e (patch) | |
tree | 1124677e9892660d0ff802daf86cf0b973d98cd6 | |
parent | 4b89b90df253e5120341ce88c4bd1ae3f16b563b (diff) |
Make Kanboard compatible with PHP 5.3.3
-rw-r--r-- | Vagrantfile | 5 | ||||
-rw-r--r-- | app/Libs/password.php | 227 | ||||
-rw-r--r-- | app/check_setup.php | 4 | ||||
-rw-r--r-- | composer.json | 4 | ||||
-rw-r--r-- | composer.lock | 41 | ||||
-rw-r--r-- | docs/debian-installation.markdown | 2 | ||||
-rw-r--r-- | docs/faq.markdown | 7 |
7 files changed, 242 insertions, 48 deletions
diff --git a/Vagrantfile b/Vagrantfile index 1132337c..35c6f419 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -65,6 +65,11 @@ Vagrant.configure("2") do |config| m.vm.synced_folder ".", "/var/www/html", owner: "apache", group: "apache" end + config.vm.define "centos65" do |m| + m.vm.box = "chef/centos-6.5" + m.vm.synced_folder ".", "/var/www/html", owner: "apache", group: "apache" + end + config.vm.define "freebsd10" do |m| m.vm.box = "chef/freebsd-10.0" m.vm.synced_folder ".", "/usr/local/www/apache24/data", type: "rsync", owner: "www", group: "www" diff --git a/app/Libs/password.php b/app/Libs/password.php new file mode 100644 index 00000000..c6e84cbd --- /dev/null +++ b/app/Libs/password.php @@ -0,0 +1,227 @@ +<?php +/** + * A Compatibility library with PHP 5.5's simplified password hashing API. + * + * @author Anthony Ferrara <ircmaxell@php.net> + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @copyright 2012 The Authors + */ + +if (!defined('PASSWORD_BCRYPT')) { + + define('PASSWORD_BCRYPT', 1); + define('PASSWORD_DEFAULT', PASSWORD_BCRYPT); + + if (version_compare(PHP_VERSION, '5.3.7', '<')) { + + define('PASSWORD_PREFIX', '$2a$'); + } + else { + + define('PASSWORD_PREFIX', '$2y$'); + } + + /** + * Hash the password using the specified algorithm + * + * @param string $password The password to hash + * @param int $algo The algorithm to use (Defined by PASSWORD_* constants) + * @param array $options The options for the algorithm to use + * + * @return string|false The hashed password, or false on error. + */ + function password_hash($password, $algo, array $options = array()) { + if (!function_exists('crypt')) { + trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING); + return null; + } + if (!is_string($password)) { + trigger_error("password_hash(): Password must be a string", E_USER_WARNING); + return null; + } + if (!is_int($algo)) { + trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING); + return null; + } + switch ($algo) { + case PASSWORD_BCRYPT: + // Note that this is a C constant, but not exposed to PHP, so we don't define it here. + $cost = 10; + if (isset($options['cost'])) { + $cost = $options['cost']; + if ($cost < 4 || $cost > 31) { + trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING); + return null; + } + } + $required_salt_len = 22; + $hash_format = sprintf("%s%02d$", PASSWORD_PREFIX, $cost); + break; + default: + trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING); + return null; + } + if (isset($options['salt'])) { + switch (gettype($options['salt'])) { + case 'NULL': + case 'boolean': + case 'integer': + case 'double': + case 'string': + $salt = (string) $options['salt']; + break; + case 'object': + if (method_exists($options['salt'], '__tostring')) { + $salt = (string) $options['salt']; + break; + } + case 'array': + case 'resource': + default: + trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING); + return null; + } + if (strlen($salt) < $required_salt_len) { + trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", strlen($salt), $required_salt_len), E_USER_WARNING); + return null; + } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) { + $salt = str_replace('+', '.', base64_encode($salt)); + } + } else { + $buffer = ''; + $raw_length = (int) ($required_salt_len * 3 / 4 + 1); + $buffer_valid = false; + if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) { + $buffer = mcrypt_create_iv($raw_length, MCRYPT_DEV_URANDOM); + if ($buffer) { + $buffer_valid = true; + } + } + if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) { + $buffer = openssl_random_pseudo_bytes($raw_length); + if ($buffer) { + $buffer_valid = true; + } + } + if (!$buffer_valid && is_readable('/dev/urandom')) { + $f = fopen('/dev/urandom', 'r'); + $read = strlen($buffer); + while ($read < $raw_length) { + $buffer .= fread($f, $raw_length - $read); + $read = strlen($buffer); + } + fclose($f); + if ($read >= $raw_length) { + $buffer_valid = true; + } + } + if (!$buffer_valid || strlen($buffer) < $raw_length) { + $bl = strlen($buffer); + for ($i = 0; $i < $raw_length; $i++) { + if ($i < $bl) { + $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255)); + } else { + $buffer .= chr(mt_rand(0, 255)); + } + } + } + $salt = str_replace('+', '.', base64_encode($buffer)); + + } + $salt = substr($salt, 0, $required_salt_len); + + $hash = $hash_format . $salt; + + $ret = crypt($password, $hash); + + if (!is_string($ret) || strlen($ret) <= 13) { + return false; + } + + return $ret; + } + + /** + * Get information about the password hash. Returns an array of the information + * that was used to generate the password hash. + * + * array( + * 'algo' => 1, + * 'algoName' => 'bcrypt', + * 'options' => array( + * 'cost' => 10, + * ), + * ) + * + * @param string $hash The password hash to extract info from + * + * @return array The array of information about the hash. + */ + function password_get_info($hash) { + $return = array( + 'algo' => 0, + 'algoName' => 'unknown', + 'options' => array(), + ); + if (substr($hash, 0, 4) == PASSWORD_PREFIX && strlen($hash) == 60) { + $return['algo'] = PASSWORD_BCRYPT; + $return['algoName'] = 'bcrypt'; + list($cost) = sscanf($hash, PASSWORD_PREFIX."%d$"); + $return['options']['cost'] = $cost; + } + return $return; + } + + /** + * Determine if the password hash needs to be rehashed according to the options provided + * + * If the answer is true, after validating the password using password_verify, rehash it. + * + * @param string $hash The hash to test + * @param int $algo The algorithm used for new password hashes + * @param array $options The options array passed to password_hash + * + * @return boolean True if the password needs to be rehashed. + */ + function password_needs_rehash($hash, $algo, array $options = array()) { + $info = password_get_info($hash); + if ($info['algo'] != $algo) { + return true; + } + switch ($algo) { + case PASSWORD_BCRYPT: + $cost = isset($options['cost']) ? $options['cost'] : 10; + if ($cost != $info['options']['cost']) { + return true; + } + break; + } + return false; + } + + /** + * Verify a password against a hash using a timing attack resistant approach + * + * @param string $password The password to verify + * @param string $hash The hash to verify against + * + * @return boolean If the password matches the hash + */ + function password_verify($password, $hash) { + if (!function_exists('crypt')) { + trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING); + return false; + } + $ret = crypt($password, $hash); + if (!is_string($ret) || strlen($ret) != strlen($hash) || strlen($ret) <= 13) { + return false; + } + + $status = 0; + for ($i = 0; $i < strlen($ret); $i++) { + $status |= (ord($ret[$i]) ^ ord($hash[$i])); + } + + return $status === 0; + } +} diff --git a/app/check_setup.php b/app/check_setup.php index f0059215..afb08f6a 100644 --- a/app/check_setup.php +++ b/app/check_setup.php @@ -1,8 +1,8 @@ <?php // PHP 5.3.3 minimum -if (version_compare(PHP_VERSION, '5.3.7', '<')) { - die('This software require PHP 5.3.7 minimum'); +if (version_compare(PHP_VERSION, '5.3.3', '<')) { + die('This software require PHP 5.3.3 minimum'); } // Checks for PHP < 5.4 diff --git a/composer.json b/composer.json index 30530ed8..8ae49653 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,6 @@ { "require": { "ext-mbstring": "*", - "ircmaxell/password-compat": "1.0.3", "fguillot/simple-validator": "0.0.1", "swiftmailer/swiftmailer": "@stable", "fguillot/json-rpc": "0.0.1", @@ -16,7 +15,8 @@ "autoload": { "psr-0": {"": "app/"}, "files": [ - "app/functions.php" + "app/functions.php", + "app/Libs/password.php" ] }, "require-dev": { diff --git a/composer.lock b/composer.lock index 766bfba6..2283ee39 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "612189500b5effe5179e83afa3d3256c", + "hash": "b20bc90f39f04bf9d6828329d56cdc75", "packages": [ { "name": "erusev/parsedown", @@ -194,45 +194,6 @@ "time": "2015-01-02 03:40:21" }, { - "name": "ircmaxell/password-compat", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/ircmaxell/password_compat.git", - "reference": "1fc1521b5e9794ea77e4eca30717be9635f1d4f4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ircmaxell/password_compat/zipball/1fc1521b5e9794ea77e4eca30717be9635f1d4f4", - "reference": "1fc1521b5e9794ea77e4eca30717be9635f1d4f4", - "shasum": "" - }, - "type": "library", - "autoload": { - "files": [ - "lib/password.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Anthony Ferrara", - "email": "ircmaxell@ircmaxell.com", - "homepage": "http://blog.ircmaxell.com" - } - ], - "description": "A compatibility library for the proposed simplified password hashing algorithm: https://wiki.php.net/rfc/password_hash", - "homepage": "https://github.com/ircmaxell/password_compat", - "keywords": [ - "hashing", - "password" - ], - "time": "2013-04-30 19:58:08" - }, - { "name": "lusitanian/oauth", "version": "v0.3.5", "source": { diff --git a/docs/debian-installation.markdown b/docs/debian-installation.markdown index 7cc9b5b7..d76cb613 100644 --- a/docs/debian-installation.markdown +++ b/docs/debian-installation.markdown @@ -24,8 +24,6 @@ rm kanboard-latest.zip Debian 6 (Squeeze) ------------------ -**Kanboard >= 1.0.10 require at least PHP 5.3.7 and Debian 6 provide PHP 5.3.3 by default** - Install Apache and PHP: ```bash diff --git a/docs/faq.markdown b/docs/faq.markdown index c8cc8406..8959a320 100644 --- a/docs/faq.markdown +++ b/docs/faq.markdown @@ -45,14 +45,17 @@ The project [eAccelerator seems dead and not updated since 2012](https://github. We recommend to switch to the last version of PHP because it's bundled with [OPcache](http://php.net/manual/en/intro.opcache.php). -Why the minimum requirement is PHP 5.3.7? +Why the minimum requirement is PHP 5.3.3? ----------------------------------------- Kanboard use the function `password_hash()` to crypt passwords but it's available only for PHP >= 5.5. However, there is a backport for [older versions of PHP](https://github.com/ircmaxell/password_compat#requirements). -This library require at least PHP 5.3.7 to work correctly. +This library require at least PHP 5.3.7 to work correctly. +Apparently, Centos and Debian backports security patches so PHP 5.3.3 should be ok. + +Kanboard v1.0.10 and v1.0.11 requires at least PHP 5.3.7 but this change has been reverted to be compatible with PHP 5.3.3 with Kanboard >= v1.0.12 How to test Kanboard with the PHP built-in web server? ------------------------------------------------------ |