PluginProbe
WP-Stateless – Google Cloud Storage / 3.0.3
WP-Stateless – Google Cloud Storage v3.0.3
4.4.3 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.3.0 2.3.1 2.3.2 3.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.2.0 3.2.1 3.2.2 All 62 releases
wp-stateless / vendor / firebase / php-jwt / src / JWT.php

JWT.php in WP-Stateless – Google Cloud Storage 3.0.3, at vendor/firebase/php-jwt/src/JWT.php

513 lines 18.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Firebase\JWT;
4
5 use \DomainException;
6 use \InvalidArgumentException;
7 use \UnexpectedValueException;
8 use \DateTime;
9
10 /**
11 * JSON Web Token implementation, based on this spec:
12 * https://tools.ietf.org/html/rfc7519
13 *
14 * PHP version 5
15 *
16 * @category Authentication
17 * @package Authentication_JWT
18 * @author Neuman Vong <neuman@twilio.com>
19 * @author Anant Narayanan <anant@php.net>
20 * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
21 * @link https://github.com/firebase/php-jwt
22 */
23 class JWT
24 {
25 const ASN1_INTEGER = 0x02;
26 const ASN1_SEQUENCE = 0x10;
27 const ASN1_BIT_STRING = 0x03;
28
29 /**
30 * When checking nbf, iat or expiration times,
31 * we want to provide some extra leeway time to
32 * account for clock skew.
33 */
34 public static $leeway = 0;
35
36 /**
37 * Allow the current timestamp to be specified.
38 * Useful for fixing a value within unit testing.
39 *
40 * Will default to PHP time() value if null.
41 */
42 public static $timestamp = null;
43
44 public static $supported_algs = array(
45 'ES256' => array('openssl', 'SHA256'),
46 'HS256' => array('hash_hmac', 'SHA256'),
47 'HS384' => array('hash_hmac', 'SHA384'),
48 'HS512' => array('hash_hmac', 'SHA512'),
49 'RS256' => array('openssl', 'SHA256'),
50 'RS384' => array('openssl', 'SHA384'),
51 'RS512' => array('openssl', 'SHA512'),
52 );
53
54 /**
55 * Decodes a JWT string into a PHP object.
56 *
57 * @param string $jwt The JWT
58 * @param string|array|resource $key The key, or map of keys.
59 * If the algorithm used is asymmetric, this is the public key
60 * @param array $allowed_algs List of supported verification algorithms
61 * Supported algorithms are 'ES256', 'HS256', 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
62 *
63 * @return object The JWT's payload as a PHP object
64 *
65 * @throws UnexpectedValueException Provided JWT was invalid
66 * @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed
67 * @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf'
68 * @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat'
69 * @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim
70 *
71 * @uses jsonDecode
72 * @uses urlsafeB64Decode
73 */
74 public static function decode($jwt, $key, array $allowed_algs = array())
75 {
76 $timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp;
77
78 if (empty($key)) {
79 throw new InvalidArgumentException('Key may not be empty');
80 }
81 $tks = \explode('.', $jwt);
82 if (\count($tks) != 3) {
83 throw new UnexpectedValueException('Wrong number of segments');
84 }
85 list($headb64, $bodyb64, $cryptob64) = $tks;
86 if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) {
87 throw new UnexpectedValueException('Invalid header encoding');
88 }
89 if (null === $payload = static::jsonDecode(static::urlsafeB64Decode($bodyb64))) {
90 throw new UnexpectedValueException('Invalid claims encoding');
91 }
92 if (false === ($sig = static::urlsafeB64Decode($cryptob64))) {
93 throw new UnexpectedValueException('Invalid signature encoding');
94 }
95 if (empty($header->alg)) {
96 throw new UnexpectedValueException('Empty algorithm');
97 }
98 if (empty(static::$supported_algs[$header->alg])) {
99 throw new UnexpectedValueException('Algorithm not supported');
100 }
101 if (!\in_array($header->alg, $allowed_algs)) {
102 throw new UnexpectedValueException('Algorithm not allowed');
103 }
104 if ($header->alg === 'ES256') {
105 // OpenSSL expects an ASN.1 DER sequence for ES256 signatures
106 $sig = self::signatureToDER($sig);
107 }
108
109 if (\is_array($key) || $key instanceof \ArrayAccess) {
110 if (isset($header->kid)) {
111 if (!isset($key[$header->kid])) {
112 throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key');
113 }
114 $key = $key[$header->kid];
115 } else {
116 throw new UnexpectedValueException('"kid" empty, unable to lookup correct key');
117 }
118 }
119
120 // Check the signature
121 if (!static::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) {
122 throw new SignatureInvalidException('Signature verification failed');
123 }
124
125 // Check the nbf if it is defined. This is the time that the
126 // token can actually be used. If it's not yet that time, abort.
127 if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) {
128 throw new BeforeValidException(
129 'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->nbf)
130 );
131 }
132
133 // Check that this token has been created before 'now'. This prevents
134 // using tokens that have been created for later use (and haven't
135 // correctly used the nbf claim).
136 if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) {
137 throw new BeforeValidException(
138 'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->iat)
139 );
140 }
141
142 // Check if this token has expired.
143 if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) {
144 throw new ExpiredException('Expired token');
145 }
146
147 return $payload;
148 }
149
150 /**
151 * Converts and signs a PHP object or array into a JWT string.
152 *
153 * @param object|array $payload PHP object or array
154 * @param string $key The secret key.
155 * If the algorithm used is asymmetric, this is the private key
156 * @param string $alg The signing algorithm.
157 * Supported algorithms are 'ES256', 'HS256', 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
158 * @param mixed $keyId
159 * @param array $head An array with header elements to attach
160 *
161 * @return string A signed JWT
162 *
163 * @uses jsonEncode
164 * @uses urlsafeB64Encode
165 */
166 public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null)
167 {
168 $header = array('typ' => 'JWT', 'alg' => $alg);
169 if ($keyId !== null) {
170 $header['kid'] = $keyId;
171 }
172 if (isset($head) && \is_array($head)) {
173 $header = \array_merge($head, $header);
174 }
175 $segments = array();
176 $segments[] = static::urlsafeB64Encode(static::jsonEncode($header));
177 $segments[] = static::urlsafeB64Encode(static::jsonEncode($payload));
178 $signing_input = \implode('.', $segments);
179
180 $signature = static::sign($signing_input, $key, $alg);
181 $segments[] = static::urlsafeB64Encode($signature);
182
183 return \implode('.', $segments);
184 }
185
186 /**
187 * Sign a string with a given key and algorithm.
188 *
189 * @param string $msg The message to sign
190 * @param string|resource $key The secret key
191 * @param string $alg The signing algorithm.
192 * Supported algorithms are 'ES256', 'HS256', 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
193 *
194 * @return string An encrypted message
195 *
196 * @throws DomainException Unsupported algorithm was specified
197 */
198 public static function sign($msg, $key, $alg = 'HS256')
199 {
200 if (empty(static::$supported_algs[$alg])) {
201 throw new DomainException('Algorithm not supported');
202 }
203 list($function, $algorithm) = static::$supported_algs[$alg];
204 switch ($function) {
205 case 'hash_hmac':
206 return \hash_hmac($algorithm, $msg, $key, true);
207 case 'openssl':
208 $signature = '';
209 $success = \openssl_sign($msg, $signature, $key, $algorithm);
210 if (!$success) {
211 throw new DomainException("OpenSSL unable to sign data");
212 } else {
213 if ($alg === 'ES256') {
214 $signature = self::signatureFromDER($signature, 256);
215 }
216 return $signature;
217 }
218 }
219 }
220
221 /**
222 * Verify a signature with the message, key and method. Not all methods
223 * are symmetric, so we must have a separate verify and sign method.
224 *
225 * @param string $msg The original message (header and body)
226 * @param string $signature The original signature
227 * @param string|resource $key For HS*, a string key works. for RS*, must be a resource of an openssl public key
228 * @param string $alg The algorithm
229 *
230 * @return bool
231 *
232 * @throws DomainException Invalid Algorithm or OpenSSL failure
233 */
234 private static function verify($msg, $signature, $key, $alg)
235 {
236 if (empty(static::$supported_algs[$alg])) {
237 throw new DomainException('Algorithm not supported');
238 }
239
240 list($function, $algorithm) = static::$supported_algs[$alg];
241 switch ($function) {
242 case 'openssl':
243 $success = \openssl_verify($msg, $signature, $key, $algorithm);
244 if ($success === 1) {
245 return true;
246 } elseif ($success === 0) {
247 return false;
248 }
249 // returns 1 on success, 0 on failure, -1 on error.
250 throw new DomainException(
251 'OpenSSL error: ' . \openssl_error_string()
252 );
253 case 'hash_hmac':
254 default:
255 $hash = \hash_hmac($algorithm, $msg, $key, true);
256 if (\function_exists('hash_equals')) {
257 return \hash_equals($signature, $hash);
258 }
259 $len = \min(static::safeStrlen($signature), static::safeStrlen($hash));
260
261 $status = 0;
262 for ($i = 0; $i < $len; $i++) {
263 $status |= (\ord($signature[$i]) ^ \ord($hash[$i]));
264 }
265 $status |= (static::safeStrlen($signature) ^ static::safeStrlen($hash));
266
267 return ($status === 0);
268 }
269 }
270
271 /**
272 * Decode a JSON string into a PHP object.
273 *
274 * @param string $input JSON string
275 *
276 * @return object Object representation of JSON string
277 *
278 * @throws DomainException Provided string was invalid JSON
279 */
280 public static function jsonDecode($input)
281 {
282 if (\version_compare(PHP_VERSION, '5.4.0', '>=') && !(\defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) {
283 /** In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you
284 * to specify that large ints (like Steam Transaction IDs) should be treated as
285 * strings, rather than the PHP default behaviour of converting them to floats.
286 */
287 $obj = \json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
288 } else {
289 /** Not all servers will support that, however, so for older versions we must
290 * manually detect large ints in the JSON string and quote them (thus converting
291 *them to strings) before decoding, hence the preg_replace() call.
292 */
293 $max_int_length = \strlen((string) PHP_INT_MAX) - 1;
294 $json_without_bigints = \preg_replace('/:\s*(-?\d{'.$max_int_length.',})/', ': "$1"', $input);
295 $obj = \json_decode($json_without_bigints);
296 }
297
298 if ($errno = \json_last_error()) {
299 static::handleJsonError($errno);
300 } elseif ($obj === null && $input !== 'null') {
301 throw new DomainException('Null result with non-null input');
302 }
303 return $obj;
304 }
305
306 /**
307 * Encode a PHP object into a JSON string.
308 *
309 * @param object|array $input A PHP object or array
310 *
311 * @return string JSON representation of the PHP object or array
312 *
313 * @throws DomainException Provided object could not be encoded to valid JSON
314 */
315 public static function jsonEncode($input)
316 {
317 $json = \json_encode($input);
318 if ($errno = \json_last_error()) {
319 static::handleJsonError($errno);
320 } elseif ($json === 'null' && $input !== null) {
321 throw new DomainException('Null result with non-null input');
322 }
323 return $json;
324 }
325
326 /**
327 * Decode a string with URL-safe Base64.
328 *
329 * @param string $input A Base64 encoded string
330 *
331 * @return string A decoded string
332 */
333 public static function urlsafeB64Decode($input)
334 {
335 $remainder = \strlen($input) % 4;
336 if ($remainder) {
337 $padlen = 4 - $remainder;
338 $input .= \str_repeat('=', $padlen);
339 }
340 return \base64_decode(\strtr($input, '-_', '+/'));
341 }
342
343 /**
344 * Encode a string with URL-safe Base64.
345 *
346 * @param string $input The string you want encoded
347 *
348 * @return string The base64 encode of what you passed in
349 */
350 public static function urlsafeB64Encode($input)
351 {
352 return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_'));
353 }
354
355 /**
356 * Helper method to create a JSON error.
357 *
358 * @param int $errno An error number from json_last_error()
359 *
360 * @return void
361 */
362 private static function handleJsonError($errno)
363 {
364 $messages = array(
365 JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
366 JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
367 JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
368 JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
369 JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3
370 );
371 throw new DomainException(
372 isset($messages[$errno])
373 ? $messages[$errno]
374 : 'Unknown JSON error: ' . $errno
375 );
376 }
377
378 /**
379 * Get the number of bytes in cryptographic strings.
380 *
381 * @param string $str
382 *
383 * @return int
384 */
385 private static function safeStrlen($str)
386 {
387 if (\function_exists('mb_strlen')) {
388 return \mb_strlen($str, '8bit');
389 }
390 return \strlen($str);
391 }
392
393 /**
394 * Convert an ECDSA signature to an ASN.1 DER sequence
395 *
396 * @param string $sig The ECDSA signature to convert
397 * @return string The encoded DER object
398 */
399 private static function signatureToDER($sig)
400 {
401 // Separate the signature into r-value and s-value
402 list($r, $s) = \str_split($sig, (int) (\strlen($sig) / 2));
403
404 // Trim leading zeros
405 $r = \ltrim($r, "\x00");
406 $s = \ltrim($s, "\x00");
407
408 // Convert r-value and s-value from unsigned big-endian integers to
409 // signed two's complement
410 if (\ord($r[0]) > 0x7f) {
411 $r = "\x00" . $r;
412 }
413 if (\ord($s[0]) > 0x7f) {
414 $s = "\x00" . $s;
415 }
416
417 return self::encodeDER(
418 self::ASN1_SEQUENCE,
419 self::encodeDER(self::ASN1_INTEGER, $r) .
420 self::encodeDER(self::ASN1_INTEGER, $s)
421 );
422 }
423
424 /**
425 * Encodes a value into a DER object.
426 *
427 * @param int $type DER tag
428 * @param string $value the value to encode
429 * @return string the encoded object
430 */
431 private static function encodeDER($type, $value)
432 {
433 $tag_header = 0;
434 if ($type === self::ASN1_SEQUENCE) {
435 $tag_header |= 0x20;
436 }
437
438 // Type
439 $der = \chr($tag_header | $type);
440
441 // Length
442 $der .= \chr(\strlen($value));
443
444 return $der . $value;
445 }
446
447 /**
448 * Encodes signature from a DER object.
449 *
450 * @param string $der binary signature in DER format
451 * @param int $keySize the number of bits in the key
452 * @return string the signature
453 */
454 private static function signatureFromDER($der, $keySize)
455 {
456 // OpenSSL returns the ECDSA signatures as a binary ASN.1 DER SEQUENCE
457 list($offset, $_) = self::readDER($der);
458 list($offset, $r) = self::readDER($der, $offset);
459 list($offset, $s) = self::readDER($der, $offset);
460
461 // Convert r-value and s-value from signed two's compliment to unsigned
462 // big-endian integers
463 $r = \ltrim($r, "\x00");
464 $s = \ltrim($s, "\x00");
465
466 // Pad out r and s so that they are $keySize bits long
467 $r = \str_pad($r, $keySize / 8, "\x00", STR_PAD_LEFT);
468 $s = \str_pad($s, $keySize / 8, "\x00", STR_PAD_LEFT);
469
470 return $r . $s;
471 }
472
473 /**
474 * Reads binary DER-encoded data and decodes into a single object
475 *
476 * @param string $der the binary data in DER format
477 * @param int $offset the offset of the data stream containing the object
478 * to decode
479 * @return array [$offset, $data] the new offset and the decoded object
480 */
481 private static function readDER($der, $offset = 0)
482 {
483 $pos = $offset;
484 $size = \strlen($der);
485 $constructed = (\ord($der[$pos]) >> 5) & 0x01;
486 $type = \ord($der[$pos++]) & 0x1f;
487
488 // Length
489 $len = \ord($der[$pos++]);
490 if ($len & 0x80) {
491 $n = $len & 0x1f;
492 $len = 0;
493 while ($n-- && $pos < $size) {
494 $len = ($len << 8) | \ord($der[$pos++]);
495 }
496 }
497
498 // Value
499 if ($type == self::ASN1_BIT_STRING) {
500 $pos++; // Skip the first contents octet (padding indicator)
501 $data = \substr($der, $pos, $len - 1);
502 $pos += $len - 1;
503 } elseif (!$constructed) {
504 $data = \substr($der, $pos, $len);
505 $pos += $len;
506 } else {
507 $data = null;
508 }
509
510 return array($pos, $data);
511 }
512 }
513