diff --git a/lib/phpSec/Auth/Authy.php b/lib/phpSec/Auth/Authy.php index 17fb436..4b84015 100644 --- a/lib/phpSec/Auth/Authy.php +++ b/lib/phpSec/Auth/Authy.php @@ -35,6 +35,16 @@ class Authy { */ public $lastError = null; + /** + * Authy ID + */ + public $authyId; + + /** + * \phpSec\Core object + */ + private $psl; + /** * Server URLs. */ @@ -43,6 +53,16 @@ class Authy { 'sandbox' => 'http://sandbox-api.authy.com', ); + /** + * Constructor. + * + * @param \phpSec\Core $psl + * phpSec core Pimple container. + */ + public function __construct(\phpSec\Core $psl) { + $this->psl = $psl; + } + /** * Add a new Authy user and get the Authy ID. * @@ -55,11 +75,14 @@ class Authy { * @param string $countrycode * User countrycode. Defaults to 1 (USA). * + * @param string $uid + * User ID + * * @return mixed * Returns the users Authy ID on success or false on errors. * @see \phpSec\Auth\Authy::$lastError. */ - public function userNew($email, $cellphone, $countrycode = 1) { + public function userNew($email, $cellphone, $countrycode = 1, $uid) { $data = array( 'user[email]' => $email, @@ -83,8 +106,15 @@ public function userNew($email, $cellphone, $countrycode = 1) { return false; } - if(isset($result->user->id)) { - return $result->user->id; + if (isset($result->user->id)) { + $this->authyId = $result->user->id; + + $store['authyId'] = $this->authyId; + $storeId = $this->getStoreId($uid); + + $this->psl['store']->write('authy', $storeId, $store); + + return $this->authyId; } $this->lastError = 'AUTHY_SERVER_SAYS_NO'; return false; @@ -103,7 +133,11 @@ public function userNew($email, $cellphone, $countrycode = 1) { * Return true if a valid Authy token is supplied, false on any errors. * @see \phpSec\Auth\Authy::$lastError. */ - public function verify($authyId, $token) { + public function verify($authyId = null, $token) { + if (!isset($authyId)) { + $authyId = $this->authyId; + } + $data = array( 'token' => $token, 'authy_id' => $authyId, @@ -249,4 +283,29 @@ private function apiCall($action, $data) { return json_decode($result); } + + /** + * Loads an authy entry from the database + * + * @param string $uid + * The user ID + * + * @return string + * Authy ID + */ + public function load($uid) + { + $store = $this->psl['store']; + $storeData = $store->read('authy', $this->getStoreId($uid)); + + if($storeData !== false) { + $this->authyId = $storeData['authyId']; + return $this->authyId; + } + return false; + } + + private function getStoreId($uid) { + return hash('sha512', $uid); + } } diff --git a/lib/phpSec/Auth/Gridcard.php b/lib/phpSec/Auth/Gridcard.php new file mode 100644 index 0000000..adf4bae --- /dev/null +++ b/lib/phpSec/Auth/Gridcard.php @@ -0,0 +1,273 @@ + + @copyright Copyright (c) Audun Larsen, 2011, 2012 + @link https://github.com/phpsec/phpSec + @license http://opensource.org/licenses/mit-license.php The MIT License + @package phpSec_Experimental + */ + +/** + * Providees pre shared password grid functionality. Experimental. + * @package phpSec_Experimental + */ +class Gridcard { + + public $numCols = 10; + public $numRows = 5; + public $gridChars = '0123456789'; + public $_charset = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + private $seedRandom; + private $nextCellsRandomSeed; + + /** + * Constructor. + * + * @param \phpSec\Core $psl + * phpSec core Pimple container. + */ + public function __construct(\phpSec\Core $psl) { + $this->psl = $psl; + } + + public function generate($expiry = null) { + $rand = $this->psl['crypt/rand']; + + $this->issued = time(); + + if (isset($expiry)) { + $this->expiry = $expiry; + } + else { + $this->expiry = strtotime("+ 1 year"); + } + + $this->seedRandom = $rand->str(64, $this->_charset); + + // Total number of different combinations is (numCols * numRows)^3 + $this->nextCellsRandomSeed = mt_rand(0, pow($this->numCols * $this->numRows, 3)); + } + + public function save($uid) + { + if (!isset($this->seedRandom) + || !isset($this->expiry) + || !isset($this->nextCellsRandomSeed) + ) { + throw new \phpSec\Exception\GeneralSecurityException('Variables not set correctly'); + } + $store['numCols'] = $this->numCols; + $store['numRows'] = $this->numRows; + $store['seedRandom'] = $this->seedRandom; + $store['chars'] = $this->gridChars; + $store['expiry'] = $this->expiry; + $store['nextCellsSeed'] = $this->nextCellsRandomSeed; + + $storeId = $this->getStoreId($uid); + + $this->psl['store']->write('gridcard', $storeId, $store); + } + + public function validate(Array $values, $uid) + { + $this->getGridValues(); + $cells = $this->getNextCells(); + + for ($i=0; $i < count($cells); $i++) { + preg_match('/\D+/', $cells[$i], $col_matches); + preg_match('/\d+/', $cells[$i], $row_matches); + $col = $col_matches[0]; + $row = $row_matches[0]; + + if (!$this->_validateCell($col, $row, $values[$i])) { + // Get new random seed for next cells + $this->_updateNextCellsRandomSeed($uid); + + return false; + } + } + // Get new random seed for next cells + $this->_updateNextCellsRandomSeed($uid); + return true; + } + + /** + * Validates the value from a specific cell + * @Param mixed $col Integer column number or string column name + * @Param Integer $row row number + * @Param String $val Value to check against. + * @Note This will show false negatives if $val is not a string for cell + * values that start with 0. + */ + private function _validateCell($col, $row, $val) + { + if (!is_numeric($col)) { + $col = $this->_letters_to_num($col); + } + $this->getGridValues(); + + return ((string)$this->_values[$row-1][$col-1] === (string)$val); + } + + public function load($uid) + { + $store = $this->psl['store']; + $storeData = $store->read('gridcard', $this->getStoreId($uid)); + + if($storeData !== false) { + // Delete the entry if it has expired + if ($storeData['expiry'] < time()) { + $store->delete('gridcard', $this->getStoreId($uid)); + return false; + } + + $this->numCols = $storeData['numCols']; + $this->numRows = $storeData['numRows']; + $this->seedRandom = $storeData['seedRandom']; + $this->gridChars = $storeData['chars']; + $this->expiry = $storeData['expiry']; + $this->nextCellsRandomSeed = $storeData['nextCellsSeed']; + + return true; + } + return false; + } + + /** + * Gets the next 3 cells to be used for validation. + * @Note Refreshing the page should *not* get different values. Only after + * validating should they change + */ + public function getNextCells() + { + $next_cells = array(); + // Seed the random number generator + mt_srand($this->nextCellsRandomSeed); + for ($i=0; $i < 3; $i++) { + // Loop to make sure next values are unique + while (!isset($next_cells[$i])) { + $value = $this->_num_to_letters(mt_rand(1, $this->numCols)); + $value .= mt_rand(1, $this->numRows); + if (array_search($value, $next_cells) === false) { + $next_cells[$i] = $value; + } + } + } + return $next_cells; + } + + public function getGridHTML() + { + $this->getGridValues(); + + $html = ' + + + + '; + + for ($c=1; $c<=$this->numCols; $c++) { + $html .= ''; + } + + $html .= ' + + + '; + + for ($r=0; $r<$this->numRows; $r++) { + $html .= ''; + for ($c=0; $c<=$this->numCols; $c++) { + if ($c === 0) { + $html .= ''; + } + else { + $html .= ''; + } + } + $html .= ''; + } + + $html .= ' + +
'.$this->_num_to_letters($c).'
'.($r+1).''.$this->_values[$r][$c-1].'
'; + + return $html; + } + + /** + * Gets all cell values for the grid + * @Return Array + */ + public function getGridValues() + { + $hash = $this->_getStringHash($this->numRows * $this->numCols * 2); + + $rows = str_split($hash, $this->numCols * 2); + + foreach ($rows as $index => $row) { + $cols = str_split($row, 2); + + $this->_values[$index] = $cols; + } + } + + /** + * @Note This needs to be called after every validation (failed or passed) + */ + private function _updateNextCellsRandomSeed($uid) + { + $this->nextCellsRandomSeed = mt_rand(0, pow($this->numCols * $this->numRows, 3)); + $this->save($uid); + } + + private function _getStringHash($length) + { + $string = ''; + + // Seed the random number generator with a number based on the hash so + // that it always returns a specific number + mt_srand(crc32($this->seedRandom)); + + $chars_array = str_split($this->gridChars); + for ($i = 0; $i < $length ; $i++) { + // Note, this is not actually random since we seeded the random + // number generator with a specific value + $string .= $this->gridChars[mt_rand(0,count($chars_array)-1)]; + } + + // Reset the random number generator in case it is used elsewhere + mt_srand(); + + return $string; + } + + private function _num_to_letters($num, $uppercase = true) + { + $letters = ''; + while ($num > 0) { + $code = ($num % 26 == 0) ? 26 : $num % 26; + $letters .= chr($code + 64); + $num = ($num - $code) / 26; + } + return ($uppercase) ? strtoupper(strrev($letters)) : strrev($letters); + } + + private function _letters_to_num($letters) + { + $num = 0; + $arr = array_reverse(str_split($letters)); + + for ($i = 0; $i < count($arr); $i++) { + $num += (ord(strtolower($arr[$i])) - 96) * (pow(26,$i)); + } + return $num; + } + + private function getStoreId($uid) { + return hash('sha512', $uid); + } +} diff --git a/lib/phpSec/Core.php b/lib/phpSec/Core.php index 19f76c6..f35273a 100644 --- a/lib/phpSec/Core.php +++ b/lib/phpSec/Core.php @@ -1,117 +1,127 @@ - - @copyright Copyright (c) Audun Larsen, 2012, 2013 - @link https://github.com/phpsec/phpSec - @license http://opensource.org/licenses/mit-license.php The MIT License - @package phpSec - */ - -/** - * phpSec core class. - * Includes Core phpSec methods, and act as an Pimple DI container. - * - * @author Audun Larsen - * @package phpSec - */ -class Core extends \Pimple { - - /** - * phpSec version consant. - */ - const VERSION = '0.6.3-dev'; - - /** - * Constructor. - * Set up the default objects for phpSec. - */ - public function __construct() { - + + @copyright Copyright (c) Audun Larsen, 2012, 2013 + @link https://github.com/phpsec/phpSec + @license http://opensource.org/licenses/mit-license.php The MIT License + @package phpSec + */ + +/** + * phpSec core class. + * Includes Core phpSec methods, and act as an Pimple DI container. + * + * @author Audun Larsen + * @package phpSec + */ +class Core extends \Pimple { + + /** + * phpSec version consant. + */ + const VERSION = '0.6.3-dev'; + + /** + * Constructor. + * Set up the default objects for phpSec. + */ + public function __construct() { + /* Store object. Must be defined by the developer. */ - $this['store'] = null; - - /* Cache object. Shared object that handles the cache. */ + $this['store'] = null; + + /* Cache object. Shared object that handles the cache. */ $this['cache'] = $this->share(function($psl) { return new Common\Cache($psl); - }); - - /* Session object. Shared object that handles sessions. */ + }); + + /* Session object. Shared object that handles sessions. */ $this['session'] = $this->share(function($psl) { return new Common\Session($psl); - }); - - /** - * Core phpSec objects. - */ - $this['auth/authy'] = function() { - return new Auth\Authy(); - }; - + }); + + /** + * Core phpSec objects. + */ + $this['auth/authy'] = function($psl) { + return new Auth\Authy($psl); + }; + + $this['auth/u2f'] = function($psl) { + $scheme = isset($_SERVER['HTTPS']) ? "https://" : "http://"; + $appId = $scheme . $_SERVER['HTTP_HOST']; + return new Auth\U2F($psl, $appId); + }; + $this['auth/mnemonic'] = function($psl) { return new Auth\Mnemonic($psl); - }; - + }; + $this['auth/google'] = function($psl) { return new Auth\Google($psl); - }; - + }; + + $this['auth/gridcard'] = function($psl) { + return new Auth\Gridcard($psl); + }; + $this['auth/otp'] = function($psl) { return new Auth\Otp($psl); - }; - + }; + $this['auth/yubikey'] = function($psl) { return new Auth\Yubikey($psl); - }; - + }; + $this['common/exec'] = function($psl) { return new Common\Exec(); - }; - + }; + $this['common/token'] = function($psl) { return new Common\Token($psl); - }; - + }; + $this['crypt/crypto'] = function($psl) { return new Crypt\Crypto($psl); - }; - + }; + $this['crypt/hash'] = function($psl) { return new Crypt\Hash($psl); - }; - + }; + $this['crypt/rand'] = function($psl) { return new Crypt\Rand(); - }; - + }; + $this['http/hsts'] = function($psl) { return new Http\Hsts(); - }; - + }; + $this['http/url'] = function($psl) { return new Http\Url($psl); - }; - + }; + $this['http/xfo'] = function($psl) { return new Http\Xfo(); - }; - + }; + $this['string/base32'] = function($psl) { return new String\Base32($psl); - }; - + }; + $this['string/compare'] = function($psl) { return new String\Compare(); - }; - + }; + $this['text/filter'] = function($psl) { return new Text\Filter($psl); - }; + }; + + + } - - } - /** * Check structure of an array. * This method checks the structure of an array (only the first layer of it) against @@ -187,8 +197,8 @@ public static function arrayCheck($array, $structure, $strict = true) { * Returns a unique identifier. */ public function genUid() { - $rand = $this['crypt/rand']; - + $rand = $this['crypt/rand']; + $hex = bin2hex($rand->Bytes(32)); $str = substr($hex,0,16) . '-' . substr($hex,16,8) . '-' . substr($hex,24,8) . '-' . substr($hex,32,8) . '-' . substr($hex,40,24); return $str; @@ -211,5 +221,5 @@ public function getUid() { } return $_SESSION['phpSec-uid']; } - -} + +}