diff options
author | Chris Raynor <chris@firebase.com> | 2014-06-25 14:20:33 -0700 |
---|---|---|
committer | Chris Raynor <chris@firebase.com> | 2014-06-25 14:20:33 -0700 |
commit | fa7e362eeeec5ee808124626c3f1a612aadbce52 (patch) | |
tree | a1c7d6cf401cd7d9d9e2dcce28a831f51c5223f0 | |
parent | 47fc8a335817834c6ac5b0030097e81468a4a06c (diff) | |
parent | c5e07e0c0e8f7c2b0f33f23482d6672eb7798ed2 (diff) | |
download | php-jwt-fa7e362eeeec5ee808124626c3f1a612aadbce52.zip php-jwt-fa7e362eeeec5ee808124626c3f1a612aadbce52.tar.gz php-jwt-fa7e362eeeec5ee808124626c3f1a612aadbce52.tar.bz2 |
Merge pull request #11 from firebase/BambooHR-master
Bamboo hr master
-rw-r--r-- | Authentication/JWT.php | 97 | ||||
-rw-r--r-- | tests/JWTTest.php | 32 |
2 files changed, 94 insertions, 35 deletions
diff --git a/Authentication/JWT.php b/Authentication/JWT.php index 65be7a1..38a4f7e 100644 --- a/Authentication/JWT.php +++ b/Authentication/JWT.php @@ -13,25 +13,21 @@ * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD * @link https://github.com/firebase/php-jwt */ -/** - * JSON Web Token implementation, based on this spec: - * http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06 - * - * @category Authentication - * @package Authentication_JWT - * @author Neuman Vong <neuman@twilio.com> - * @author Anant Narayanan <anant@php.net> - * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD - * @link https://github.com/firebase/php-jwt - */ class JWT { + static $methods = array( + 'HS256' => array('hash_hmac', 'SHA256'), + 'HS512' => array('hash_hmac', 'SHA512'), + 'HS384' => array('hash_hmac', 'SHA384'), + 'RS256' => array('openssl', 'SHA256'), + ); + /** * Decodes a JWT string into a PHP object. * - * @param string $jwt The JWT - * @param string|null $key The secret key - * @param bool $verify Don't skip verification process + * @param string $jwt The JWT + * @param string|Array|null $key The secret key, or map of keys + * @param bool $verify Don't skip verification process * * @return object The JWT's payload as a PHP object * @throws UnexpectedValueException Provided JWT was invalid @@ -58,7 +54,14 @@ class JWT if (empty($header->alg)) { throw new DomainException('Empty algorithm'); } - if ($sig != JWT::sign("$headb64.$bodyb64", $key, $header->alg)) { + if (is_array($key)) { + if(isset($header->kid)) { + $key = $key[$header->kid]; + } else { + throw new DomainException('"kid" empty, unable to lookup correct key'); + } + } + if (!JWT::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) { throw new UnexpectedValueException('Signature verification failed'); } // Check token expiry time if defined. @@ -81,10 +84,12 @@ class JWT * @uses jsonEncode * @uses urlsafeB64Encode */ - public static function encode($payload, $key, $algo = 'HS256') + public static function encode($payload, $key, $algo = 'HS256', $keyId = null) { $header = array('typ' => 'JWT', 'alg' => $algo); - + if($keyId !== null) { + $header['kid'] = $keyId; + } $segments = array(); $segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($header)); $segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($payload)); @@ -99,25 +104,61 @@ class JWT /** * Sign a string with a given key and algorithm. * - * @param string $msg The message to sign - * @param string $key The secret key - * @param string $method The signing algorithm. Supported - * algorithms are 'HS256', 'HS384' and 'HS512' + * @param string $msg The message to sign + * @param string|resource $key The secret key + * @param string $method The signing algorithm. Supported algorithms + * are 'HS256', 'HS384', 'HS512' and 'RS256' * * @return string An encrypted message * @throws DomainException Unsupported algorithm was specified */ public static function sign($msg, $key, $method = 'HS256') { - $methods = array( - 'HS256' => 'sha256', - 'HS384' => 'sha384', - 'HS512' => 'sha512', - ); - if (empty($methods[$method])) { + if (empty(self::$methods[$method])) { + throw new DomainException('Algorithm not supported'); + } + list($function, $algo) = self::$methods[$method]; + switch($function) { + case 'hash_hmac': + return hash_hmac($algo, $msg, $key, true); + case 'openssl': + $signature = ''; + $success = openssl_sign($msg, $signature, $key, $algo); + if(!$success) { + throw new DomainException("OpenSSL unable to sign data"); + } else { + return $signature; + } + } + } + + /** + * Verify a signature with the mesage, key and method. Not all methods + * are symmetric, so we must have a separate verify and sign method. + * @param string $msg the original message + * @param string $signature + * @param string|resource $key for HS*, a string key works. for RS*, must be a resource of an openssl public key + * @param string $method + * @return bool + * @throws DomainException Invalid Algorithm or OpenSSL failure + */ + public static function verify($msg, $signature, $key, $method = 'HS256') { + if (empty(self::$methods[$method])) { throw new DomainException('Algorithm not supported'); } - return hash_hmac($methods[$method], $msg, $key, true); + list($function, $algo) = self::$methods[$method]; + switch($function) { + case 'openssl': + $success = openssl_verify($msg, $signature, $key, $algo); + if(!$success) { + throw new DomainException("OpenSSL unable to verify data: " . openssl_error_string()); + } else { + return $signature; + } + case 'hash_hmac': + default: + return $signature === hash_hmac($algo, $msg, $key, true); + } } /** diff --git a/tests/JWTTest.php b/tests/JWTTest.php index ae65f02..ee131d4 100644 --- a/tests/JWTTest.php +++ b/tests/JWTTest.php @@ -29,24 +29,42 @@ class JWTTest extends PHPUnit_Framework_TestCase { JWT::jsonDecode('this is not valid JSON string'); } - function testExpiredToken(){ + function testExpiredToken() { $this->setExpectedException('UnexpectedValueException'); $payload = array( - "message"=> "abc", - "exp"=> time()-20); // time in the past + "message" => "abc", + "exp" => time() - 20); // time in the past $encoded = JWT::encode($payload, 'my_key'); JWT::decode($encoded); } - function testValidToken(){ + function testValidToken() { $payload = array( - "message"=> "abc", - "exp"=> time()+20); // time in the future + "message" => "abc", + "exp" => time() + 20); // time in the future $encoded = JWT::encode($payload, 'my_key'); $decoded = JWT::decode($encoded, 'my_key'); $this->assertEquals($decoded->message, 'abc'); } - + + function testRSEncodeDecode() { + $privKey = openssl_pkey_new(array('digest_alg' => 'sha256', + 'private_key_bits' => 1024, + 'private_key_type' => OPENSSL_KEYTYPE_RSA)); + $msg = JWT::encode('abc', $privKey, 'RS256'); + $pubKey = openssl_pkey_get_details($privKey); + $pubKey = $pubKey['key']; + $decoded = JWT::decode($msg, $pubKey, true); + $this->assertEquals($decoded, 'abc'); + } + + function testKIDChooser() { + $keys = array('1' => 'my_key', '2' => 'my_key2'); + $msg = JWT::encode('abc', $keys['1'], 'HS256', '1'); + $decoded = JWT::decode($msg, $keys, true); + $this->assertEquals($decoded, 'abc'); + } + } ?> |