Daniel Triendl 11 years ago
parent
commit
3eecd85257
5 changed files with 216 additions and 18 deletions
  1. 1 0
      index.php
  2. 1 1
      user.php
  3. 191 0
      weave_hash.php
  4. 13 16
      weave_storage.php
  5. 10 1
      weave_utils.php

+ 1 - 0
index.php

@@ -54,6 +54,7 @@
 	require_once 'weave_storage.php';
 	require_once 'weave_basic_object.php';
 	require_once 'weave_utils.php';
+	require_once 'weave_hash.php';
 
     require_once "WBOJsonOutput.php";
 	//header("Content-type: application/json");

+ 1 - 1
user.php

@@ -244,7 +244,7 @@
                    log_error("user.php: POST password ");
                   //to do
                   // change pw in db
-                  if($db->change_password($username, $new_pwd))
+                  if($db->change_password($new_pwd))
                     exit("success"); 
                   else
                     report_problem(WEAVE_ERROR_INVALID_PROTOCOL, 503); //server db messed up somehow

+ 191 - 0
weave_hash.php

@@ -0,0 +1,191 @@
+<?php
+
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is http://stackoverflow.com/a/6337021/833893
+#
+# Contributor(s):
+#   Daniel Triendl <daniel@pew.cc>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+interface WeaveHash {
+	public function hash($input);
+	public function verify($input, $existingHash);
+	public function needsUpdate($existingHash);
+}
+
+class WeaveHashMD5 implements WeaveHash {
+	public function hash($input) {
+		return md5($input);
+	}
+
+	public function verify($input, $existingHash) {
+		return $this->hash($input) == $existingHash;
+	}
+
+	public function needsUpdate($existingHash) {
+		return substr($existingHash, 0, 4) == "$2a$";
+	}
+}
+
+class WeaveHashBCrypt implements  WeaveHash {
+	private $_rounds;
+
+	public function  __construct($rounds = 12) {
+		if(CRYPT_BLOWFISH != 1) {
+			throw new Exception("bcrypt not available");
+		}
+
+		$this->_rounds = $rounds;
+	}
+
+	public function hash($input) {
+		$hash = crypt($input, $this->getSalt());
+
+		if (strlen($hash) <= 13) {
+			throw new Exception("error while generating hash");
+		}
+
+		return $hash;
+	}
+
+	public function verify($input, $existingHash) {
+		if ($this->isMD5($existingHash)) {
+			$md5 = new WeaveHashMD5();
+			return $md5->verify($input, $existingHash);
+		}
+		
+		$hash = crypt($input, $existingHash);
+
+		return $hash === $existingHash;
+	}
+
+	public function needsUpdate($existingHash) {
+		$identifier = $this->getIdentifier();
+		return substr($existingHash, 0, strlen($identifier)) != $identifier;
+	}
+	
+	private function isMD5($existingHash) {
+		return substr($existingHash, 0, 4) != "$2a$";
+	}
+
+	private function getSalt() {
+		$salt = $this->getIdentifier();
+
+		$bytes = $this->getRandomBytes(16);
+
+		$salt .= $this->encodeBytes($bytes);
+
+		return $salt;
+	}
+	
+	private function getIdentifier() {
+		return sprintf("$2a$%02d$", $this->_rounds);
+	}
+
+	private $randomState;
+	private function getRandomBytes($count) {
+		$bytes = '';
+
+		if(function_exists('openssl_random_pseudo_bytes') &&
+				(strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) { // OpenSSL slow on Win
+			$bytes = openssl_random_pseudo_bytes($count);
+		}
+
+		if($bytes === '' && is_readable('/dev/urandom') &&
+				($hRand = @fopen('/dev/urandom', 'rb')) !== FALSE) {
+			$bytes = fread($hRand, $count);
+			fclose($hRand);
+		}
+
+		if(strlen($bytes) < $count) {
+			$bytes = '';
+
+			if($this->randomState === null) {
+				$this->randomState = microtime();
+				if(function_exists('getmypid')) {
+					$this->randomState .= getmypid();
+				}
+			}
+
+			for($i = 0; $i < $count; $i += 16) {
+				$this->randomState = md5(microtime() . $this->randomState);
+
+				if (PHP_VERSION >= '5') {
+					$bytes .= md5($this->randomState, true);
+				} else {
+					$bytes .= pack('H*', md5($this->randomState));
+				}
+			}
+
+			$bytes = substr($bytes, 0, $count);
+		}
+
+		return $bytes;
+	}
+
+	private function encodeBytes($input) {
+		// The following is code from the PHP Password Hashing Framework
+		$itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+
+		$output = '';
+		$i = 0;
+		do {
+			$c1 = ord($input[$i++]);
+			$output .= $itoa64[$c1 >> 2];
+			$c1 = ($c1 & 0x03) << 4;
+			if ($i >= 16) {
+				$output .= $itoa64[$c1];
+				break;
+			}
+
+			$c2 = ord($input[$i++]);
+			$c1 |= $c2 >> 4;
+			$output .= $itoa64[$c1];
+			$c1 = ($c2 & 0x0f) << 2;
+
+			$c2 = ord($input[$i++]);
+			$c1 |= $c2 >> 6;
+			$output .= $itoa64[$c1];
+			$output .= $itoa64[$c2 & 0x3f];
+		} while (1);
+
+		return $output;
+	}
+}
+
+class WeaveHashFactory {
+	public static function factory() {
+		if (BCRYPT) {
+			return new WeaveHashBCrypt(BCRYPT_ROUNDS);
+		} else {
+			return new WeaveHashMD5();
+		}
+	}
+}
+
+?>

+ 13 - 16
weave_storage.php

@@ -22,6 +22,7 @@
 #
 # Contributor(s):
 #	Toby Elliott (telliott@mozilla.com)
+#   Daniel Triendl <daniel@pew.cc>
 #
 # Alternatively, the contents of this file may be used under the terms of
 # either the GNU General Public License Version 2 or later (the "GPL"), or
@@ -725,7 +726,8 @@ class WeaveStorage
             $create_statement = "insert into users values (:username, :md5)";
 
             $sth = $this->_dbh->prepare($create_statement);
-            $password = md5($password);
+            $hash = WeaveHashFactory::factory();
+            $password = $hash->hash($password);
             $sth->bindParam(':username', $username);
             $sth->bindParam(':md5', $password);
             $sth->execute();
@@ -739,16 +741,15 @@ class WeaveStorage
         return 1;
     }
 
-    function change_password($username, $password)
+    function change_password($hash)
     {
         try
         {
             $update_statement = "update users set md5 = :md5 where username = :username";
 
             $sth = $this->_dbh->prepare($update_statement);
-            $password = md5($password);
-            $sth->bindParam(':username', $username);
-            $sth->bindParam(':md5', $password);
+            $sth->bindParam(':username', $this->_username);
+            $sth->bindParam(':md5', $hash);
             $sth->execute();
         }
         catch( PDOException $exception )
@@ -784,31 +785,27 @@ class WeaveStorage
     }
 
 
-    function authenticate_user($password)
+    function get_password_hash()
     {
         log_error("auth-user: " . $this->_username);
         try
         {
-            $select_stmt = 'select username from users where username = :username and md5 = :md5';
+            $select_stmt = 'select md5 from users where username = :username';
             $sth = $this->_dbh->prepare($select_stmt);
             $username = $this->_username;
-            $password = md5($password);
             $sth->bindParam(':username', $username);
-            $sth->bindParam(':md5', $password);
             $sth->execute();
         }
         catch( PDOException $exception )
         {
-            error_log("authenticate_user: " . $exception->getMessage());
+            error_log("get_password_hash: " . $exception->getMessage());
             throw new Exception("Database unavailable", 503);
         }
 
-        if (!$result = $sth->fetch(PDO::FETCH_ASSOC))
-        {
-            return null;
-        }
-
-        return 1;
+        $result = $sth->fetchColumn();
+        if ($result === FALSE) $result = "";
+        
+        return $result; 
     }
 
 }

+ 10 - 1
weave_utils.php

@@ -19,6 +19,7 @@
 # the Initial Developer. All Rights Reserved.
 #
 # Contributor(s):
+#   Daniel Triendl <daniel@pew.cc>
 #
 # Alternatively, the contents of this file may be used under the terms of
 # either the GNU General Public License Version 2 or later (the "GPL"), or
@@ -212,13 +213,21 @@
 
 		try 
 		{
-			if ( ! $db->authenticate_user(fix_utf8_encoding($auth_pw)) )
+			$existingHash = $db->get_password_hash();
+			$hash = WeaveHashFactory::factory();
+			
+			if ( ! $hash->verify(fix_utf8_encoding($auth_pw), $existingHash) )
             {
                 log_error("Auth failed 2 {");
                 log_error(" User pw: ". $auth_user ."|".$auth_pw ."|md5:". md5($auth_pw) ."|fix:". fix_utf8_encoding($auth_pw) ."|fix md5 ". md5(fix_utf8_encoding($auth_pw)));
                 log_error(" Url_user: ".$url_user);
+                log_error(" Existing hash: ".$existingHash);
                 log_error("}");
 				report_problem('Authentication failed', '401');
+            } else {
+            	if ( $hash->needsUpdate($existingHash) ) {
+            		$db->change_password($hash->hash(fix_utf8_encoding($auth_pw)));
+            	}
             }
 		}
 		catch(Exception $e)