summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrederic Guillot <fred@kanboard.net>2015-01-18 21:55:08 -0500
committerFrederic Guillot <fred@kanboard.net>2015-01-18 21:55:08 -0500
commit2bdd6a6b35f391d8281e3217e1c31ed469381c3e (patch)
tree1124677e9892660d0ff802daf86cf0b973d98cd6
parent4b89b90df253e5120341ce88c4bd1ae3f16b563b (diff)
Make Kanboard compatible with PHP 5.3.3
-rw-r--r--Vagrantfile5
-rw-r--r--app/Libs/password.php227
-rw-r--r--app/check_setup.php4
-rw-r--r--composer.json4
-rw-r--r--composer.lock41
-rw-r--r--docs/debian-installation.markdown2
-rw-r--r--docs/faq.markdown7
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?
------------------------------------------------------