PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.16
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.16
1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 All 163 releases
woocommerce-pos / vendor_prefixed / firebase / php-jwt / src / JWT.php

JWT.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.16, at vendor_prefixed/firebase/php-jwt/src/JWT.php

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