From 037ee17766a3bb6a176a58c471d77ee2136cacd9 Mon Sep 17 00:00:00 2001 From: a-Sansara Date: Sat, 11 Mar 2017 15:04:42 +0100 Subject: [PATCH] Initial commit --- LICENSE | 21 ++ README.md | 99 ++++++++++ src/MetaTech/PwsAuth/Authenticator.php | 257 +++++++++++++++++++++++++ src/MetaTech/PwsAuth/Token.php | 111 +++++++++++ src/MetaTech/Util/Tool.php | 81 ++++++++ 5 files changed, 569 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 src/MetaTech/PwsAuth/Authenticator.php create mode 100644 src/MetaTech/PwsAuth/Token.php create mode 100644 src/MetaTech/Util/Tool.php diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..79ad341 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016-2017 meta-tech.academy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2ce2410 --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +# PwsAuth + +PwsAuth is an authentication protocol throught http header designed to web services + +## Request Headers + +request headers must be define as follow : + + Pws-Authorization : $type $token + Pws-Ident : $userkey + +the **$token** can be either a `loginToken` or a `sessionToken` + +the **$token** is divided in four part + +* a datetime formatted with the `Authenticator::DATE_FORMAT` format +* an obfuscate part 's token builded by date, common salt & the third token 's part +* a loginToken representing a user signed token for a specific login at given date + OR + a session token representing the session id +* noise data to be removed + +the complete token is valid only if obfuscate part can be rebuild +this simple mecanism ensure that **sessionId** is valid and can be safety load + +Authenticator 's configuration comes with a `hash.session.index` and `hash.noise.length` values +wich can be redefined to move the session token part into the complete token + + << hash.session.index >> << hash.noise.length >> + |-----------------------------------------------------------<<-^->>---------------------------------------------<<-^->>--------| + |- type -|-- date ---|------------ obfuscate token ---------<<-^->>-------------- session token ----------------<<-^->> noise -| + | | 1 | 2 | 3 | 4 | + PwsAuth2 242003031711e1a6104135f04c6c01e6cd5952ecafbb53c928603b0gb64tqo609qse6ovd7lhdvk4fnaqk7cdl26e4d4qh7jb41eu5f1zb5y79m8pgu3 + + +### ClientSide + +a request header can be generated via the `generateHeader($login, $key, $sessid=null)` method +the third parameter determine wich kind of token will be generated + +### ServerSide + +the Token can be retriew via the `getToken` method + +`loginToken` is validate by the `check(Token $token, $login)` method +`loginToken` must match a public url with method `POST` and a couple of login/password +on successfull login, the session id must be transmit to the client. + +`sessionToken` is valid only if the session can effectively be loaded, and the +user key match the given `Pws-Ident` value + +### Configuration + +configuration must be the same on server and client sides +hash definition is a convenient way to obfuscate your tokens + +```yaml +pwsauth : + + type : PwsAuth2 + + header : + auth : Pws-Authorization + ident : Pws-Ident + + salt : + common : jK5#p9Mh5.Zv} + # used for generating user specific salt + user.index : 10 + user.length : 12 + + hash : + sep : / + algo : sha256 + # effective token length size. out of bound data is simply noise + length : 52 + # session index (or obfuscate length) + session.index : 58 + # ending noise data length) + noise.length : 12 +``` + +### Authenticator instanciation + +```php +config = $config; + } + + /*! + * check if specified Token is a valid token + * + * @method isValid + * @public + * @param Pluie\Auth\Token $token + * @return bool + */ + public function isValid(Token $token) + { + return $token->getType() == $this->config['type'] && $this->checkObfuscatePart($token); + } + + /*! + * generate a unique signature at given time for specifyed user + * + * @method sign + * @public + * @param str $dtime given time in sqldatetime format + * @param str $login the user login + * @param str $key the user key + * @return str + */ + public function sign($dtime, $login, $key, $length=null) + { + $str = Tool::concat($this->config['hash']['sep'], [$dtime, $login, $this->getUserSalt($login), $key]); + return substr(hash($this->config['hash']['algo'], $str), is_null($length) ? - $this->config['hash']['length'] : - $length); + } + + /*! + * generate the salt for a specific user + * + * @method getUserSalt + * @public + * @param str $login the user login + * @return str + */ + public function getUserSalt($login) + { + return substr( + hash(self::DEFAULT_ALGO, $login . $this->config['salt']['common']), + $this->config['salt']['user.index'], + $this->config['salt']['user.length'] + ); + } + + /*! + * generate noise to obfuscate token + * + * @method obfuscate + * @orivate + * @param str $data + * @return str + */ + private function obfuscate($data, $date) + { + return substr( + hash(self::DEFAULT_ALGO, $date . $data . $this->config['salt']['common']), + - $this->config['hash']['session.index'] + ); + } + + /*! + * check valid noise obfuscation + * + * @method checkObfuscatePart + * @public + * @param Pluie\Auth\Token $token + * @return bool + */ + public function checkObfuscatePart(Token $token) + { + $tokenValue = $token->getValue(); + return substr($tokenValue, 0, $this->config['hash']['session.index']) == $this->obfuscate($this->deobfuscate($tokenValue), $token->getDate()); + } + + /*! + * deoffuscate token + * + * @method deobfuscate + * @orivate + * @param str $data + * @return str + */ + private function deobfuscate($data) + { + return substr($data, $this->config['hash']['session.index']); + } + + /*! + * @method getSessionId + * @orivate + * @param Pluie\Auth\Token $token + * @return str + */ + public function getSessionId(Token $token) + { + return $this->deobfuscate($token->getValue()); + } + + /*! + * check validity of Token + * + * @mehtod check + * @public + * @param Pluie\Auth\Token $token + * @param str $login + * @return bool + */ + public function check(Token $token, $login) + { + return !is_null($token) && $this->deobfuscate($token->getValue()) == $this->sign($token->getDate(), $login, $token->getIdent()); + } + + /*! + * @method generateNoise + * @public + * @param str $data + * @return str + */ + public function generateNoise($data) + { + return substr(hash(self::DEFAULT_ALGO, str_shuffle($data)), - $this->config['hash']['noise.length']); + } + + /*! + * @method generateToken + * @public + * @param str $login + * @param str $key + * @param str $sessid|null + * @return Pluie\Auth\Token + */ + public function generateToken($login, $key, $sessid=null) + { + $date = Tool::now(); + $sessid = is_null($sessid) ? $this->sign($date, $login, $key) : $sessid; + $dt = Tool::formatDate($date, Tool::TIMESTAMP_SQLDATETIME, self::DATE_FORMAT); + $tokenValue = $dt . $this->obfuscate($sessid, $date) . $sessid; + $noise = $this->generateNoise($tokenValue); + return new Token($this->config['type'], $key, $date, $tokenValue, $noise); + } + + /*! + * @method generateHeader + * @public + * @param str $login + * @param str $key + * @param str $sessid + * @return [] + */ + public function generateHeader($login, $key, $sessid=null) + { + $token = $this->generateToken($login, $key, $sessid); + return array( + $this->config['header']['auth'] .': ' . $token->getType() . ' ' . $token->getValue() . $token->getNoise(), + $this->config['header']['ident'].': ' . $token->getIdent() + ); + } + + /*! + * get token from specified $header or request headers. + * + * @method getToken + * @public + * @param [assoc] $headers + * @throw Pluie\Auth\AuthenticateException + * @return Pluie\Auth\Token + */ + public function getToken($headers = null) + { + $token = null; + try { + if (is_null($headers)) { + $headers = apache_request_headers(); + } + $tokenValue = $headers[$this->config['header']['auth']] ?: ''; + $ident = $headers[$this->config['header']['ident']] ?: ''; + if (preg_match('/(?P[a-z\d]+) (?P\d{'.self::DATE_LENGTH.'})(?P[a-z\d]+)/i', $tokenValue, $rs)) { + $date = Tool::formatDate($rs['date'], self::DATE_FORMAT, Tool::TIMESTAMP_SQLDATETIME); + $tokenValue = substr($rs['id'], 0, -$this->config['hash']['noise.length']); + $noise = substr($rs['id'], -$this->config['hash']['noise.length']); + $token = new Token($rs['type'], $ident, $date, $tokenValue, $noise); + } + } + catch(\Exception $e) { + throw new AuthenticateException("invalid authentication protocol : ".$e->getMessage()); + } + return $token; + } + + /*! + * read header generate by generateHeader + * + * @method readHeader + * @public + * @param [str] $arrHeaders + * @return [assoc] + */ + public function readHeader($arrHeaders) + { + $headers = []; + foreach($arrHeaders as $h) { + $rs = preg_split('/:/', $h); + if (count($rs)==2) { + $headers[$rs[0]] = trim($rs[1]); + } + } + return $headers; + } +} diff --git a/src/MetaTech/PwsAuth/Token.php b/src/MetaTech/PwsAuth/Token.php new file mode 100644 index 0000000..9fafb2b --- /dev/null +++ b/src/MetaTech/PwsAuth/Token.php @@ -0,0 +1,111 @@ +type = $type; + $this->ident = $ident; + $this->date = $date; + $this->value = $value; + $this->noise = $noise; + } + + /*! + * desc + * + * @method getType + * @public + * @return str + */ + public function getType() + { + return $this->type; + } + + /*! + * desc + * + * @method getIdent + * @public + * @return str + */ + public function getIdent() + { + return $this->ident; + } + + /*! + * desc + * + * @method getDate + * @public + * @return str + */ + public function getDate() + { + return $this->date; + } + + /*! + * desc + * + * @method getValue + * @public + * @return str + */ + public function getValue() + { + return $this->value; + } + + /*! + * desc + * + * @method getNoise + * @public + * @return str + */ + public function getNoise() + { + return $this->noise; + } + +} diff --git a/src/MetaTech/Util/Tool.php b/src/MetaTech/Util/Tool.php new file mode 100644 index 0000000..06c1496 --- /dev/null +++ b/src/MetaTech/Util/Tool.php @@ -0,0 +1,81 @@ +format($toFormat); + } + + /*! + * concatenate various items in $list separate with specifyed separator $sep + * + * @method concat + * @public + * @param str $sep the used separator to concatenate items in $list + * @param [str] $list the list of items to concatenate + * @return str + */ + public static function concat($sep, $list) + { + $value = array_shift($list); + foreach ($list as $item) { + $value .= $sep . $item; + } + return $value; + } + +}