@@ -29,6 +29,14 @@ class JWT
2929 */
3030 public static $ leeway = 0 ;
3131
32+ /**
33+ * Allow the current timestamp to be specified.
34+ * Useful for fixing a value within unit testing.
35+ *
36+ * Will default to PHP time() value if null.
37+ */
38+ public static $ timestamp = null ;
39+
3240 public static $ supported_algs = array (
3341 'HS256 ' => array ('hash_hmac ' , 'SHA256 ' ),
3442 'HS512 ' => array ('hash_hmac ' , 'SHA512 ' ),
@@ -39,15 +47,14 @@ class JWT
3947 /**
4048 * Decodes a JWT string into a PHP object.
4149 *
42- * @param string $jwt The JWT
43- * @param string|array|null $key The key, or map of keys.
44- * If the algorithm used is asymmetric, this is the public key
45- * @param array $allowed_algs List of supported verification algorithms
46- * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
50+ * @param string $jwt The JWT
51+ * @param string|array $key The key, or map of keys.
52+ * If the algorithm used is asymmetric, this is the public key
53+ * @param array $allowed_algs List of supported verification algorithms
54+ * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
4755 *
4856 * @return object The JWT's payload as a PHP object
4957 *
50- * @throws DomainException Algorithm was not provided
5158 * @throws UnexpectedValueException Provided JWT was invalid
5259 * @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed
5360 * @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf'
@@ -59,47 +66,52 @@ class JWT
5966 */
6067 public static function decode ($ jwt , $ key , $ allowed_algs = array ())
6168 {
69+ $ timestamp = is_null (static ::$ timestamp ) ? time () : static ::$ timestamp ;
70+
6271 if (empty ($ key )) {
6372 throw new InvalidArgumentException ('Key may not be empty ' );
6473 }
74+ if (!is_array ($ allowed_algs )) {
75+ throw new InvalidArgumentException ('Algorithm not allowed ' );
76+ }
6577 $ tks = explode ('. ' , $ jwt );
6678 if (count ($ tks ) != 3 ) {
6779 throw new UnexpectedValueException ('Wrong number of segments ' );
6880 }
6981 list ($ headb64 , $ bodyb64 , $ cryptob64 ) = $ tks ;
70- if (null === ($ header = JWT ::jsonDecode (JWT ::urlsafeB64Decode ($ headb64 )))) {
82+ if (null === ($ header = static ::jsonDecode (static ::urlsafeB64Decode ($ headb64 )))) {
7183 throw new UnexpectedValueException ('Invalid header encoding ' );
7284 }
73- if (null === $ payload = JWT ::jsonDecode (JWT ::urlsafeB64Decode ($ bodyb64 ))) {
85+ if (null === $ payload = static ::jsonDecode (static ::urlsafeB64Decode ($ bodyb64 ))) {
7486 throw new UnexpectedValueException ('Invalid claims encoding ' );
7587 }
76- $ sig = JWT ::urlsafeB64Decode ($ cryptob64 );
77-
88+ $ sig = static ::urlsafeB64Decode ($ cryptob64 );
89+
7890 if (empty ($ header ->alg )) {
79- throw new DomainException ('Empty algorithm ' );
91+ throw new UnexpectedValueException ('Empty algorithm ' );
8092 }
81- if (empty (self ::$ supported_algs [$ header ->alg ])) {
82- throw new DomainException ('Algorithm not supported ' );
93+ if (empty (static ::$ supported_algs [$ header ->alg ])) {
94+ throw new UnexpectedValueException ('Algorithm not supported ' );
8395 }
84- if (!is_array ( $ allowed_algs ) || ! in_array ($ header ->alg , $ allowed_algs )) {
85- throw new DomainException ('Algorithm not allowed ' );
96+ if (!in_array ($ header ->alg , $ allowed_algs )) {
97+ throw new UnexpectedValueException ('Algorithm not allowed ' );
8698 }
8799 if (is_array ($ key ) || $ key instanceof \ArrayAccess) {
88100 if (isset ($ header ->kid )) {
89101 $ key = $ key [$ header ->kid ];
90102 } else {
91- throw new DomainException ('"kid" empty, unable to lookup correct key ' );
103+ throw new UnexpectedValueException ('"kid" empty, unable to lookup correct key ' );
92104 }
93105 }
94106
95107 // Check the signature
96- if (!JWT ::verify ("$ headb64. $ bodyb64 " , $ sig , $ key , $ header ->alg )) {
108+ if (!static ::verify ("$ headb64. $ bodyb64 " , $ sig , $ key , $ header ->alg )) {
97109 throw new SignatureInvalidException ('Signature verification failed ' );
98110 }
99111
100112 // Check if the nbf if it is defined. This is the time that the
101113 // token can actually be used. If it's not yet that time, abort.
102- if (isset ($ payload ->nbf ) && $ payload ->nbf > (time () + self ::$ leeway )) {
114+ if (isset ($ payload ->nbf ) && $ payload ->nbf > ($ timestamp + static ::$ leeway )) {
103115 throw new BeforeValidException (
104116 'Cannot handle token prior to ' . date (DateTime::ISO8601 , $ payload ->nbf )
105117 );
@@ -108,14 +120,14 @@ public static function decode($jwt, $key, $allowed_algs = array())
108120 // Check that this token has been created before 'now'. This prevents
109121 // using tokens that have been created for later use (and haven't
110122 // correctly used the nbf claim).
111- if (isset ($ payload ->iat ) && $ payload ->iat > (time () + self ::$ leeway )) {
123+ if (isset ($ payload ->iat ) && $ payload ->iat > ($ timestamp + static ::$ leeway )) {
112124 throw new BeforeValidException (
113125 'Cannot handle token prior to ' . date (DateTime::ISO8601 , $ payload ->iat )
114126 );
115127 }
116128
117129 // Check if this token has expired.
118- if (isset ($ payload ->exp ) && (time () - self ::$ leeway ) >= $ payload ->exp ) {
130+ if (isset ($ payload ->exp ) && ($ timestamp - static ::$ leeway ) >= $ payload ->exp ) {
119131 throw new ExpiredException ('Expired token ' );
120132 }
121133
@@ -130,6 +142,7 @@ public static function decode($jwt, $key, $allowed_algs = array())
130142 * If the algorithm used is asymmetric, this is the private key
131143 * @param string $alg The signing algorithm.
132144 * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
145+ * @param mixed $keyId
133146 * @param array $head An array with header elements to attach
134147 *
135148 * @return string A signed JWT
@@ -147,12 +160,12 @@ public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $he
147160 $ header = array_merge ($ head , $ header );
148161 }
149162 $ segments = array ();
150- $ segments [] = JWT ::urlsafeB64Encode (JWT ::jsonEncode ($ header ));
151- $ segments [] = JWT ::urlsafeB64Encode (JWT ::jsonEncode ($ payload ));
163+ $ segments [] = static ::urlsafeB64Encode (static ::jsonEncode ($ header ));
164+ $ segments [] = static ::urlsafeB64Encode (static ::jsonEncode ($ payload ));
152165 $ signing_input = implode ('. ' , $ segments );
153166
154- $ signature = JWT ::sign ($ signing_input , $ key , $ alg );
155- $ segments [] = JWT ::urlsafeB64Encode ($ signature );
167+ $ signature = static ::sign ($ signing_input , $ key , $ alg );
168+ $ segments [] = static ::urlsafeB64Encode ($ signature );
156169
157170 return implode ('. ' , $ segments );
158171 }
@@ -171,10 +184,10 @@ public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $he
171184 */
172185 public static function sign ($ msg , $ key , $ alg = 'HS256 ' )
173186 {
174- if (empty (self ::$ supported_algs [$ alg ])) {
187+ if (empty (static ::$ supported_algs [$ alg ])) {
175188 throw new DomainException ('Algorithm not supported ' );
176189 }
177- list ($ function , $ algorithm ) = self ::$ supported_algs [$ alg ];
190+ list ($ function , $ algorithm ) = static ::$ supported_algs [$ alg ];
178191 switch ($ function ) {
179192 case 'hash_hmac ' :
180193 return hash_hmac ($ algorithm , $ msg , $ key , true );
@@ -204,11 +217,11 @@ public static function sign($msg, $key, $alg = 'HS256')
204217 */
205218 private static function verify ($ msg , $ signature , $ key , $ alg )
206219 {
207- if (empty (self ::$ supported_algs [$ alg ])) {
220+ if (empty (static ::$ supported_algs [$ alg ])) {
208221 throw new DomainException ('Algorithm not supported ' );
209222 }
210223
211- list ($ function , $ algorithm ) = self ::$ supported_algs [$ alg ];
224+ list ($ function , $ algorithm ) = static ::$ supported_algs [$ alg ];
212225 switch ($ function ) {
213226 case 'openssl ' :
214227 $ success = openssl_verify ($ msg , $ signature , $ key , $ algorithm );
@@ -223,13 +236,13 @@ private static function verify($msg, $signature, $key, $alg)
223236 if (function_exists ('hash_equals ' )) {
224237 return hash_equals ($ signature , $ hash );
225238 }
226- $ len = min (self ::safeStrlen ($ signature ), self ::safeStrlen ($ hash ));
239+ $ len = min (static ::safeStrlen ($ signature ), static ::safeStrlen ($ hash ));
227240
228241 $ status = 0 ;
229242 for ($ i = 0 ; $ i < $ len ; $ i ++) {
230243 $ status |= (ord ($ signature [$ i ]) ^ ord ($ hash [$ i ]));
231244 }
232- $ status |= (self ::safeStrlen ($ signature ) ^ self ::safeStrlen ($ hash ));
245+ $ status |= (static ::safeStrlen ($ signature ) ^ static ::safeStrlen ($ hash ));
233246
234247 return ($ status === 0 );
235248 }
@@ -263,7 +276,7 @@ public static function jsonDecode($input)
263276 }
264277
265278 if (function_exists ('json_last_error ' ) && $ errno = json_last_error ()) {
266- JWT ::handleJsonError ($ errno );
279+ static ::handleJsonError ($ errno );
267280 } elseif ($ obj === null && $ input !== 'null ' ) {
268281 throw new DomainException ('Null result with non-null input ' );
269282 }
@@ -283,7 +296,7 @@ public static function jsonEncode($input)
283296 {
284297 $ json = json_encode ($ input );
285298 if (function_exists ('json_last_error ' ) && $ errno = json_last_error ()) {
286- JWT ::handleJsonError ($ errno );
299+ static ::handleJsonError ($ errno );
287300 } elseif ($ json === 'null ' && $ input !== null ) {
288301 throw new DomainException ('Null result with non-null input ' );
289302 }
@@ -335,8 +348,8 @@ private static function handleJsonError($errno)
335348 );
336349 throw new DomainException (
337350 isset ($ messages [$ errno ])
338- ? $ messages [$ errno ]
339- : 'Unknown JSON error: ' . $ errno
351+ ? $ messages [$ errno ]
352+ : 'Unknown JSON error: ' . $ errno
340353 );
341354 }
342355
@@ -354,4 +367,4 @@ private static function safeStrlen($str)
354367 }
355368 return strlen ($ str );
356369 }
357- }
370+ }
0 commit comments