Skip to content

Commit 3bb58d7

Browse files
author
Chris Raynor
committed
Merge branch 'master' of git://github.com/BambooHR/php-jwt into BambooHR-master
2 parents 718e9c7 + 026a6d9 commit 3bb58d7

File tree

2 files changed

+84
-13
lines changed

2 files changed

+84
-13
lines changed

Authentication/JWT.php

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,18 @@
2626
*/
2727
class JWT
2828
{
29+
static $methods = array(
30+
'HS256' => array('hash_hmac', 'SHA256'),
31+
'HS512' => array('hash_hmac', 'SHA512'),
32+
'HS384' => array('hash_hmac', 'SHA384'),
33+
'RS256' => array('openssl', 'SHA256'),
34+
);
35+
2936
/**
3037
* Decodes a JWT string into a PHP object.
3138
*
3239
* @param string $jwt The JWT
33-
* @param string|null $key The secret key
40+
* @param string|Array|null $key The secret key, or map of keys
3441
* @param bool $verify Don't skip verification process
3542
*
3643
* @return object The JWT's payload as a PHP object
@@ -58,7 +65,14 @@ public static function decode($jwt, $key = null, $verify = true)
5865
if (empty($header->alg)) {
5966
throw new DomainException('Empty algorithm');
6067
}
61-
if ($sig != JWT::sign("$headb64.$bodyb64", $key, $header->alg)) {
68+
if (is_array($key)) {
69+
if(isset($header->kid)) {
70+
$key = $key[$header->kid];
71+
} else {
72+
throw new DomainException('"kid" empty, unable to lookup correct key');
73+
}
74+
}
75+
if (!JWT::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) {
6276
throw new UnexpectedValueException('Signature verification failed');
6377
}
6478
// Check token expiry time if defined.
@@ -81,10 +95,12 @@ public static function decode($jwt, $key = null, $verify = true)
8195
* @uses jsonEncode
8296
* @uses urlsafeB64Encode
8397
*/
84-
public static function encode($payload, $key, $algo = 'HS256')
98+
public static function encode($payload, $key, $algo = 'HS256', $keyId = null)
8599
{
86100
$header = array('typ' => 'JWT', 'alg' => $algo);
87-
101+
if($keyId !== null) {
102+
$header['kid'] = $keyId;
103+
}
88104
$segments = array();
89105
$segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($header));
90106
$segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($payload));
@@ -100,24 +116,60 @@ public static function encode($payload, $key, $algo = 'HS256')
100116
* Sign a string with a given key and algorithm.
101117
*
102118
* @param string $msg The message to sign
103-
* @param string $key The secret key
119+
* @param string|resource $key The secret key
104120
* @param string $method The signing algorithm. Supported
105-
* algorithms are 'HS256', 'HS384' and 'HS512'
121+
* algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
106122
*
107123
* @return string An encrypted message
108124
* @throws DomainException Unsupported algorithm was specified
109125
*/
110126
public static function sign($msg, $key, $method = 'HS256')
111127
{
112-
$methods = array(
113-
'HS256' => 'sha256',
114-
'HS384' => 'sha384',
115-
'HS512' => 'sha512',
116-
);
117-
if (empty($methods[$method])) {
128+
if (empty(self::$methods[$method])) {
118129
throw new DomainException('Algorithm not supported');
119130
}
120-
return hash_hmac($methods[$method], $msg, $key, true);
131+
list($function, $algo) = self::$methods[$method];
132+
switch($function) {
133+
case 'hash_hmac':
134+
return hash_hmac($algo, $msg, $key, true);
135+
case 'openssl':
136+
$signature = '';
137+
$success = openssl_sign($msg, $signature, $key, $algo);
138+
if(!$success) {
139+
throw new DomainException("OpenSSL unable to sign data");
140+
} else {
141+
return $signature;
142+
}
143+
}
144+
}
145+
146+
/**
147+
* Verify a signature with the mesage, key and method. Not all methods
148+
* are symmetric, so we must have a separate verify and sign method.
149+
* @param string $msg the original message
150+
* @param string $signature
151+
* @param string|resource $key for HS*, a string key works. for RS*, must be a resource of an openssl public key
152+
* @param string $method
153+
* @return bool
154+
* @throws DomainException Invalid Algorithm or OpenSSL failure
155+
*/
156+
public static function verify($msg, $signature, $key, $method = 'HS256') {
157+
if (empty(self::$methods[$method])) {
158+
throw new DomainException('Algorithm not supported');
159+
}
160+
list($function, $algo) = self::$methods[$method];
161+
switch($function) {
162+
case 'openssl':
163+
$success = openssl_verify($msg, $signature, $key, $algo);
164+
if(!$success) {
165+
throw new DomainException("OpenSSL unable to verify data: " . openssl_error_string());
166+
} else {
167+
return $signature;
168+
}
169+
case 'hash_hmac':
170+
default:
171+
return $signature === hash_hmac($algo, $msg, $key, true);
172+
}
121173
}
122174

123175
/**

tests/JWTTest.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,25 @@ function testValidToken(){
4646
$decoded = JWT::decode($encoded, 'my_key');
4747
$this->assertEquals($decoded->message, 'abc');
4848
}
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+
4968

5069
}
5170

0 commit comments

Comments
 (0)