-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
rules bugs fixes, oauth, jwt, authentications and protections
- Loading branch information
Showing
22 changed files
with
1,028 additions
and
101 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
<?php | ||
/** | ||
* This file is part of the O2System PHP Framework package. | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
* | ||
* @author Steeve Andrian Salim | ||
* @copyright Copyright (c) Steeve Andrian Salim | ||
*/ | ||
|
||
// ------------------------------------------------------------------------ | ||
|
||
namespace O2System\Security\Authentication\Jwt; | ||
|
||
// ------------------------------------------------------------------------ | ||
use O2System\Spl\Exceptions\Logic\DomainException; | ||
|
||
/** | ||
* Class Signature | ||
* @package O2System\Security\Generators | ||
*/ | ||
class Signature | ||
{ | ||
public static $supportedAlgorithms = [ | ||
'HS256' => ['hash_hmac', 'sha256'], | ||
'HS512' => ['hash_hmac', 'sha512'], | ||
'HS384' => ['hash_hmac', 'sha384'], | ||
'RS256' => ['openssl', OPENSSL_ALGO_SHA256], | ||
'RS384' => ['openssl', OPENSSL_ALGO_SHA384], | ||
'RS512' => ['openssl', OPENSSL_ALGO_SHA512], | ||
]; | ||
|
||
public static function validAlgorithm($algorithm) | ||
{ | ||
$algorithm = strtoupper($algorithm); | ||
|
||
return (bool)array_key_exists($algorithm, static::$supportedAlgorithms); | ||
} | ||
|
||
public static function generate(array $segments, $key, $algorithm = 'HS256') | ||
{ | ||
if (count($segments) == 2) { | ||
$data = implode('.', $segments); | ||
|
||
if (static::validAlgorithm($algorithm)) { | ||
list($function, $algorithm) = static::$supportedAlgorithms[ $algorithm ]; | ||
|
||
switch ($function) { | ||
case 'hash_hmac': | ||
return hash_hmac($algorithm, $data, $key, true); | ||
case 'openssl': | ||
if (false === ($success = openssl_sign($data, $signature, $key, $algorithm))) { | ||
throw new DomainException("OpenSSL unable to sign data"); | ||
} | ||
} | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
public static function verify($token, $signature, $key, $algorithm = 'HS256') | ||
{ | ||
$segments = explode('.', $token); | ||
$segments = array_map('trim', $segments); | ||
|
||
if (count($segments) == 3) { | ||
array_pop($segments); | ||
$data = implode('.', $segments); | ||
|
||
if (static::validAlgorithm($algorithm)) { | ||
list($function, $algorithm) = static::$supportedAlgorithms[ $algorithm ]; | ||
|
||
switch ($function) { | ||
case 'hash_hmac': | ||
return hash_hmac($algorithm, $data, $key, true) === $signature; | ||
case 'openssl': | ||
switch ($algorithm) { | ||
case 'RS256': | ||
return (bool)openssl_verify($data, $signature, $key, OPENSSL_ALGO_SHA256); | ||
case 'RS384': | ||
return (bool)openssl_verify($data, $signature, $key, OPENSSL_ALGO_SHA384); | ||
case 'RS512': | ||
return (bool)openssl_verify($data, $signature, $key, OPENSSL_ALGO_SHA512); | ||
} | ||
} | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,242 @@ | ||
<?php | ||
/** | ||
* This file is part of the O2System PHP Framework package. | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
* | ||
* @author Steeve Andrian Salim | ||
* @copyright Copyright (c) Steeve Andrian Salim | ||
*/ | ||
|
||
// ------------------------------------------------------------------------ | ||
|
||
namespace O2System\Security\Authentication\Jwt; | ||
|
||
// ------------------------------------------------------------------------ | ||
|
||
use O2System\Security\Encoders\Base64; | ||
use O2System\Security\Encoders\Json; | ||
use O2System\Spl\Traits\Collectors\ErrorCollectorTrait; | ||
|
||
/** | ||
* Class Token | ||
* @package O2System\Security\Authentication\Jwt | ||
*/ | ||
class Token | ||
{ | ||
use ErrorCollectorTrait; | ||
|
||
protected $key = null; | ||
protected $keyId; | ||
|
||
/** | ||
* When checking nbf, iat or expiration times, | ||
* we want to provide some extra leeway time to | ||
* account for clock skew. | ||
*/ | ||
protected $leeway = 0; | ||
|
||
/** | ||
* Allow the current timestamp to be specified. | ||
* Useful for fixing a value within unit testing. | ||
* | ||
* Will default to PHP time() value if null. | ||
*/ | ||
protected $timestamp; | ||
protected $algorithm = 'HS256'; | ||
protected $headers = [ | ||
'typ' => 'JWT', | ||
]; | ||
|
||
// ------------------------------------------------------------------------ | ||
|
||
public function setKey($key) | ||
{ | ||
$this->key = $key; | ||
|
||
return $this; | ||
} | ||
|
||
// ------------------------------------------------------------------------ | ||
|
||
public function setKeyId($keyId) | ||
{ | ||
$this->keyId = $keyId; | ||
|
||
return $this; | ||
} | ||
|
||
// ------------------------------------------------------------------------ | ||
|
||
public function setAlgorithm($algorithm) | ||
{ | ||
$algorithm = strtoupper($algorithm); | ||
|
||
if (Signature::validAlgorithm($algorithm)) { | ||
$this->algorithm = $algorithm; | ||
} | ||
|
||
return $this; | ||
} | ||
|
||
// ------------------------------------------------------------------------ | ||
|
||
public function setLeeway($leeway) | ||
{ | ||
$this->leeway = intval($leeway); | ||
|
||
return $this; | ||
} | ||
|
||
// ------------------------------------------------------------------------ | ||
|
||
public function setTimestamp($timestamp) | ||
{ | ||
$this->timestamp = is_numeric($timestamp) ? $timestamp : strtotime($timestamp); | ||
|
||
return $this; | ||
} | ||
|
||
// ------------------------------------------------------------------------ | ||
|
||
public function addHeader($key, $value) | ||
{ | ||
$this->headers[ $key ] = $value; | ||
} | ||
|
||
// ------------------------------------------------------------------------ | ||
|
||
public function encode(array $payload, $key = null) | ||
{ | ||
$key = empty($key) ? $this->key : $key; | ||
if (is_null($key)) { | ||
if (class_exists('O2System\Framework', false)) { | ||
$key = config()->getItem('security')->encryptionKey; | ||
} | ||
} | ||
|
||
$this->addHeader('alg', $this->algorithm); | ||
|
||
if ( ! empty($this->keyId)) { | ||
$this->addHeader('kid', $this->keyId); | ||
} | ||
|
||
// Create Header Segment | ||
$segments[] = Base64::encode(Json::encode($this->headers)); | ||
|
||
// Create Payload Segment | ||
$segments[] = Base64::encode(Json::encode($payload)); | ||
|
||
// Create Signature Segment | ||
$segments[] = Base64::encode(Signature::generate($segments, $key, $this->algorithm)); | ||
|
||
return implode('.', $segments); | ||
} | ||
|
||
// ------------------------------------------------------------------------ | ||
|
||
public function decode($token, $key = null) | ||
{ | ||
$key = empty($key) ? $this->key : $key; | ||
if (is_null($key)) { | ||
if (class_exists('O2System\Framework', false)) { | ||
$key = config()->getItem('security')->encryptionKey; | ||
} | ||
} | ||
|
||
$timestamp = empty($this->timestamp) ? time() : $this->timestamp; | ||
|
||
$segments = explode('.', $token); | ||
$segments = array_map('trim', $segments); | ||
|
||
if (count($segments) == 3) { | ||
list($headers, $payload, $signature) = $segments; | ||
|
||
// Base64 decode headers | ||
if(false === ($headers = Base64::decode($headers))) { | ||
$this->errors[] = 'Invalid header base64 decoding'; | ||
return false; | ||
} | ||
|
||
// Json decode headers | ||
if (null === ($headers = Json::decode($headers))) { | ||
$this->errors[] = 'Invalid header json decoding'; | ||
return false; | ||
} | ||
|
||
// Validate algorithm header | ||
if (empty($headers->alg)) { | ||
$this->errors[] = 'Invalid algorithm'; | ||
|
||
return false; | ||
} elseif ( ! Signature::validAlgorithm($headers->alg)) { | ||
$this->errors[] = 'Unsupported algorithm'; | ||
|
||
return false; | ||
} | ||
|
||
// Validate algorithm key id | ||
if (is_array($key) or $key instanceof \ArrayAccess) { | ||
if (isset($headers->kid)) { | ||
if ( ! isset($key[ $headers->kid ])) { | ||
$this->errors[] = 'Invalid Key Id'; | ||
return false; | ||
} | ||
|
||
$key = $key[ $headers->kid ]; | ||
} else { | ||
$this->errors[] = 'Empty Key id'; | ||
return false; | ||
} | ||
} | ||
|
||
// Base64 decode payload | ||
if(false === ($payload = Base64::decode($payload))) { | ||
$this->errors[] = 'Invalid payload base64 decoding'; | ||
return false; | ||
} | ||
|
||
// Json decode payload | ||
if (null === ($payload = Json::decode($payload))) { | ||
$this->errors[] = 'Invalid payload json decoding'; | ||
return false; | ||
} | ||
|
||
// Base64 decode payload | ||
if(false === ($signature = Base64::decode($signature))) { | ||
$this->errors[] = 'Invalid signature base64 decoding'; | ||
return false; | ||
} | ||
|
||
if(Signature::verify($token, $signature, $key, $headers->alg) === false) { | ||
$this->errors[] = 'Invalid signature'; | ||
return false; | ||
} | ||
|
||
// Check if the nbf if it is defined. This is the time that the | ||
// token can actually be used. If it's not yet that time, abort. | ||
if (isset($payload->nbf) && $payload->nbf > ($timestamp + $this->leeway)) { | ||
$this->errors[] = 'Cannot handle token prior to ' . date(\DateTime::ISO8601, $payload->nbf); | ||
return false; | ||
} | ||
|
||
// Check that this token has been created before 'now'. This prevents | ||
// using tokens that have been created for later use (and haven't | ||
// correctly used the nbf claim). | ||
if (isset($payload->iat) && $payload->iat > ($timestamp + $this->leeway)) { | ||
$this->errors[] = 'Cannot handle token prior to ' . date(\DateTime::ISO8601, $payload->iat); | ||
return false; | ||
} | ||
// Check if this token has expired. | ||
if (isset($payload->exp) && ($timestamp - $this->leeway) >= $payload->exp) { | ||
$this->errors[] = 'Expired token'; | ||
return false; | ||
} | ||
|
||
return $payload; | ||
} | ||
|
||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.