Skip to content

Commit fa7e362

Browse files
author
Chris Raynor
committed
Merge pull request #11 from firebase/BambooHR-master
Bamboo hr master
2 parents 47fc8a3 + c5e07e0 commit fa7e362

File tree

2 files changed

+94
-35
lines changed

2 files changed

+94
-35
lines changed

Authentication/JWT.php

Lines changed: 69 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,21 @@
1313
* @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
1414
* @link https://github.com/firebase/php-jwt
1515
*/
16-
/**
17-
* JSON Web Token implementation, based on this spec:
18-
* http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06
19-
*
20-
* @category Authentication
21-
* @package Authentication_JWT
22-
* @author Neuman Vong <[email protected]>
23-
* @author Anant Narayanan <[email protected]>
24-
* @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
25-
* @link https://github.com/firebase/php-jwt
26-
*/
2716
class JWT
2817
{
18+
static $methods = array(
19+
'HS256' => array('hash_hmac', 'SHA256'),
20+
'HS512' => array('hash_hmac', 'SHA512'),
21+
'HS384' => array('hash_hmac', 'SHA384'),
22+
'RS256' => array('openssl', 'SHA256'),
23+
);
24+
2925
/**
3026
* Decodes a JWT string into a PHP object.
3127
*
32-
* @param string $jwt The JWT
33-
* @param string|null $key The secret key
34-
* @param bool $verify Don't skip verification process
28+
* @param string $jwt The JWT
29+
* @param string|Array|null $key The secret key, or map of keys
30+
* @param bool $verify Don't skip verification process
3531
*
3632
* @return object The JWT's payload as a PHP object
3733
* @throws UnexpectedValueException Provided JWT was invalid
@@ -58,7 +54,14 @@ public static function decode($jwt, $key = null, $verify = true)
5854
if (empty($header->alg)) {
5955
throw new DomainException('Empty algorithm');
6056
}
61-
if ($sig != JWT::sign("$headb64.$bodyb64", $key, $header->alg)) {
57+
if (is_array($key)) {
58+
if(isset($header->kid)) {
59+
$key = $key[$header->kid];
60+
} else {
61+
throw new DomainException('"kid" empty, unable to lookup correct key');
62+
}
63+
}
64+
if (!JWT::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) {
6265
throw new UnexpectedValueException('Signature verification failed');
6366
}
6467
// Check token expiry time if defined.
@@ -81,10 +84,12 @@ public static function decode($jwt, $key = null, $verify = true)
8184
* @uses jsonEncode
8285
* @uses urlsafeB64Encode
8386
*/
84-
public static function encode($payload, $key, $algo = 'HS256')
87+
public static function encode($payload, $key, $algo = 'HS256', $keyId = null)
8588
{
8689
$header = array('typ' => 'JWT', 'alg' => $algo);
87-
90+
if($keyId !== null) {
91+
$header['kid'] = $keyId;
92+
}
8893
$segments = array();
8994
$segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($header));
9095
$segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($payload));
@@ -99,25 +104,61 @@ public static function encode($payload, $key, $algo = 'HS256')
99104
/**
100105
* Sign a string with a given key and algorithm.
101106
*
102-
* @param string $msg The message to sign
103-
* @param string $key The secret key
104-
* @param string $method The signing algorithm. Supported
105-
* algorithms are 'HS256', 'HS384' and 'HS512'
107+
* @param string $msg The message to sign
108+
* @param string|resource $key The secret key
109+
* @param string $method The signing algorithm. Supported algorithms
110+
* are 'HS256', 'HS384', 'HS512' and 'RS256'
106111
*
107112
* @return string An encrypted message
108113
* @throws DomainException Unsupported algorithm was specified
109114
*/
110115
public static function sign($msg, $key, $method = 'HS256')
111116
{
112-
$methods = array(
113-
'HS256' => 'sha256',
114-
'HS384' => 'sha384',
115-
'HS512' => 'sha512',
116-
);
117-
if (empty($methods[$method])) {
117+
if (empty(self::$methods[$method])) {
118+
throw new DomainException('Algorithm not supported');
119+
}
120+
list($function, $algo) = self::$methods[$method];
121+
switch($function) {
122+
case 'hash_hmac':
123+
return hash_hmac($algo, $msg, $key, true);
124+
case 'openssl':
125+
$signature = '';
126+
$success = openssl_sign($msg, $signature, $key, $algo);
127+
if(!$success) {
128+
throw new DomainException("OpenSSL unable to sign data");
129+
} else {
130+
return $signature;
131+
}
132+
}
133+
}
134+
135+
/**
136+
* Verify a signature with the mesage, key and method. Not all methods
137+
* are symmetric, so we must have a separate verify and sign method.
138+
* @param string $msg the original message
139+
* @param string $signature
140+
* @param string|resource $key for HS*, a string key works. for RS*, must be a resource of an openssl public key
141+
* @param string $method
142+
* @return bool
143+
* @throws DomainException Invalid Algorithm or OpenSSL failure
144+
*/
145+
public static function verify($msg, $signature, $key, $method = 'HS256') {
146+
if (empty(self::$methods[$method])) {
118147
throw new DomainException('Algorithm not supported');
119148
}
120-
return hash_hmac($methods[$method], $msg, $key, true);
149+
list($function, $algo) = self::$methods[$method];
150+
switch($function) {
151+
case 'openssl':
152+
$success = openssl_verify($msg, $signature, $key, $algo);
153+
if(!$success) {
154+
throw new DomainException("OpenSSL unable to verify data: " . openssl_error_string());
155+
} else {
156+
return $signature;
157+
}
158+
case 'hash_hmac':
159+
default:
160+
return $signature === hash_hmac($algo, $msg, $key, true);
161+
}
121162
}
122163

123164
/**

tests/JWTTest.php

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,42 @@ function testMalformedJsonThrowsException() {
2929
JWT::jsonDecode('this is not valid JSON string');
3030
}
3131

32-
function testExpiredToken(){
32+
function testExpiredToken() {
3333
$this->setExpectedException('UnexpectedValueException');
3434
$payload = array(
35-
"message"=> "abc",
36-
"exp"=> time()-20); // time in the past
35+
"message" => "abc",
36+
"exp" => time() - 20); // time in the past
3737
$encoded = JWT::encode($payload, 'my_key');
3838
JWT::decode($encoded);
3939
}
4040

41-
function testValidToken(){
41+
function testValidToken() {
4242
$payload = array(
43-
"message"=> "abc",
44-
"exp"=> time()+20); // time in the future
43+
"message" => "abc",
44+
"exp" => time() + 20); // time in the future
4545
$encoded = JWT::encode($payload, 'my_key');
4646
$decoded = JWT::decode($encoded, 'my_key');
4747
$this->assertEquals($decoded->message, 'abc');
4848
}
49-
49+
50+
function testRSEncodeDecode() {
51+
$privKey = openssl_pkey_new(array('digest_alg' => 'sha256',
52+
'private_key_bits' => 1024,
53+
'private_key_type' => OPENSSL_KEYTYPE_RSA));
54+
$msg = JWT::encode('abc', $privKey, 'RS256');
55+
$pubKey = openssl_pkey_get_details($privKey);
56+
$pubKey = $pubKey['key'];
57+
$decoded = JWT::decode($msg, $pubKey, true);
58+
$this->assertEquals($decoded, 'abc');
59+
}
60+
61+
function testKIDChooser() {
62+
$keys = array('1' => 'my_key', '2' => 'my_key2');
63+
$msg = JWT::encode('abc', $keys['1'], 'HS256', '1');
64+
$decoded = JWT::decode($msg, $keys, true);
65+
$this->assertEquals($decoded, 'abc');
66+
}
67+
5068
}
5169

5270
?>

0 commit comments

Comments
 (0)