PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.2-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.2-a.1
16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-jwt / src / class-jwt.php
jetpack / jetpack_vendor / automattic / jetpack-jwt / src Last commit date
class-jwt.php 2 months ago
class-jwt.php
444 lines
1 <?php
2 /**
3 * JSON Web Token implementation, based on this spec:
4 * https://tools.ietf.org/html/rfc7519
5 *
6 * @package automattic/jetpack-jwt
7 */
8
9 namespace Automattic\Jetpack;
10
11 use DomainException;
12 use InvalidArgumentException;
13 use UnexpectedValueException;
14
15 /**
16 * JSON Web Token implementation, based on this spec:
17 * https://tools.ietf.org/html/rfc7519
18 *
19 * PHP version 5
20 *
21 * @category Authentication
22 * @package Authentication_JWT
23 * @author Neuman Vong <neuman@twilio.com>
24 * @author Anant Narayanan <anant@php.net>
25 * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
26 * @link https://github.com/firebase/php-jwt
27 */
28 class JWT {
29
30 const PACKAGE_VERSION = '0.2.5';
31 /**
32 * When checking nbf, iat or expiration times,
33 * we want to provide some extra leeway time to
34 * account for clock skew.
35 *
36 * @var int $leeway The leeway value.
37 */
38 public static $leeway = 0;
39
40 /**
41 * Allow the current timestamp to be specified.
42 * Useful for fixing a value within unit testing.
43 *
44 * Will default to PHP time() value if null.
45 *
46 * @var string $timestamp The timestamp.
47 */
48 public static $timestamp = null;
49
50 /**
51 * Supported algorithms.
52 *
53 * @var array $supported_algs Supported algorithms.
54 */
55 public static $supported_algs = array(
56 'HS256' => array( 'hash_hmac', 'SHA256' ),
57 'HS512' => array( 'hash_hmac', 'SHA512' ),
58 'HS384' => array( 'hash_hmac', 'SHA384' ),
59 'RS256' => array( 'openssl', 'SHA256' ),
60 'RS384' => array( 'openssl', 'SHA384' ),
61 'RS512' => array( 'openssl', 'SHA512' ),
62 );
63
64 /**
65 * Decodes a JWT string into a PHP object.
66 *
67 * @param string $jwt The JWT.
68 * @param string|array $key The key, or map of keys.
69 * If the algorithm used is asymmetric, this is the public key.
70 * @param array $allowed_algs List of supported verification algorithms.
71 * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'.
72 * @param bool $as_array Whether to return the result as an associative array.
73 *
74 * @return object|array The JWT's payload as a PHP object or array.
75 *
76 * @throws UnexpectedValueException Provided JWT was invalid.
77 * @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed.
78 * @throws InvalidArgumentException Provided JWT is trying to be used before it's eligible as defined by 'nbf'.
79 * @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat'.
80 * @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim.
81 *
82 * @uses json_decode
83 * @uses urlsafe_b64_decode
84 */
85 public static function decode( $jwt, $key, array $allowed_algs = array(), $as_array = false ) {
86 $timestamp = static::$timestamp === null ? time() : static::$timestamp;
87
88 if ( empty( $key ) ) {
89 throw new InvalidArgumentException( 'Key may not be empty' );
90 }
91
92 $tks = explode( '.', $jwt );
93 if ( count( $tks ) !== 3 ) {
94 throw new UnexpectedValueException( 'Wrong number of segments' );
95 }
96
97 list( $headb64, $bodyb64, $cryptob64 ) = $tks;
98
99 $header = static::json_decode( static::urlsafe_b64_decode( $headb64 ) );
100 if ( null === $header ) {
101 throw new UnexpectedValueException( 'Invalid header encoding' );
102 }
103
104 $payload = static::json_decode( static::urlsafe_b64_decode( $bodyb64 ), $as_array );
105 if ( null === $payload ) {
106 throw new UnexpectedValueException( 'Invalid claims encoding' );
107 }
108
109 $sig = static::urlsafe_b64_decode( $cryptob64 );
110 if ( false === $sig ) {
111 throw new UnexpectedValueException( 'Invalid signature encoding' );
112 }
113
114 if ( empty( $header->alg ) ) {
115 throw new UnexpectedValueException( 'Empty algorithm' );
116 }
117
118 if ( empty( static::$supported_algs[ $header->alg ] ) ) {
119 throw new UnexpectedValueException( 'Algorithm not supported' );
120 }
121
122 if ( ! in_array( $header->alg, $allowed_algs, true ) ) {
123 throw new UnexpectedValueException( 'Algorithm not allowed' );
124 }
125
126 if ( is_array( $key ) || $key instanceof \ArrayAccess ) {
127 if ( isset( $header->kid ) ) {
128 if ( ! isset( $key[ $header->kid ] ) ) {
129 throw new UnexpectedValueException( '"kid" invalid, unable to lookup correct key' );
130 }
131 $key = $key[ $header->kid ];
132 } else {
133 throw new UnexpectedValueException( '"kid" empty, unable to lookup correct key' );
134 }
135 }
136
137 // Check the signature.
138 if ( ! static::verify( "$headb64.$bodyb64", $sig, $key, $header->alg ) ) {
139 throw new SignatureInvalidException( 'Signature verification failed' );
140 }
141
142 $nbf = ! $as_array && isset( $payload->nbf ) ? $payload->nbf : null;
143 if ( $as_array && isset( $payload['nbf'] ) ) {
144 $nbf = $payload['nbf'];
145 }
146 // Check if the nbf if it is defined. This is the time that the
147 // token can actually be used. If it's not yet that time, abort.
148 if ( $nbf > ( $timestamp + static::$leeway ) ) {
149 throw new BeforeValidException(
150 'Cannot handle token prior to ' . gmdate( 'Y-m-d\\TH:i:sO', $nbf )
151 );
152 }
153
154 $iat = ! $as_array && isset( $payload->iat ) ? $payload->iat : null;
155 if ( $as_array && isset( $payload['iat'] ) ) {
156 $iat = $payload['iat'];
157 }
158 // Check that this token has been created before 'now'. This prevents
159 // using tokens that have been created for later use (and haven't
160 // correctly used the nbf claim).
161 if ( $iat > ( $timestamp + static::$leeway ) ) {
162 throw new BeforeValidException(
163 'Cannot handle token prior to ' . gmdate( 'Y-m-d\\TH:i:sO', $iat )
164 );
165 }
166
167 $exp = ! $as_array && isset( $payload->exp ) ? $payload->exp : null;
168 if ( $as_array && isset( $payload['exp'] ) ) {
169 $exp = $payload['exp'];
170 }
171
172 // Check if this token has expired.
173 if ( $exp && ( $timestamp - static::$leeway ) >= $exp ) {
174 throw new ExpiredException( 'Expired token' );
175 }
176
177 return $payload;
178 }
179
180 /**
181 * Converts and signs a PHP object or array into a JWT string.
182 *
183 * @param object|array $payload PHP object or array.
184 * @param string $key The secret key.
185 * If the algorithm used is asymmetric, this is the private key.
186 * @param string $alg The signing algorithm.
187 * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'.
188 * @param mixed $key_id The key ID.
189 * @param array $head An array with header elements to attach.
190 *
191 * @return string A signed JWT
192 *
193 * @uses json_encode
194 * @uses urlsafe_b64_decode
195 */
196 public static function encode( $payload, $key, $alg = 'HS256', $key_id = null, $head = null ) {
197 $header = array(
198 'typ' => 'JWT',
199 'alg' => $alg,
200 );
201
202 if ( null !== $key_id ) {
203 $header['kid'] = $key_id;
204 }
205
206 if ( isset( $head ) && is_array( $head ) ) {
207 $header = array_merge( $head, $header );
208 }
209
210 $segments = array();
211 $segments[] = static::urlsafe_b64_encode( static::json_encode( $header ) );
212 $segments[] = static::urlsafe_b64_encode( static::json_encode( $payload ) );
213 $signing_input = implode( '.', $segments );
214
215 $signature = static::sign( $signing_input, $key, $alg );
216 $segments[] = static::urlsafe_b64_encode( $signature );
217
218 return implode( '.', $segments );
219 }
220
221 /**
222 * Sign a string with a given key and algorithm.
223 *
224 * @param string $msg The message to sign.
225 * @param string|resource $key The secret key.
226 * @param string $alg The signing algorithm.
227 * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'.
228 *
229 * @return string An encrypted message
230 *
231 * @throws DomainException Unsupported algorithm was specified.
232 */
233 public static function sign( $msg, $key, $alg = 'HS256' ) {
234 if ( empty( static::$supported_algs[ $alg ] ) ) {
235 throw new DomainException( 'Algorithm not supported' );
236 }
237 list($function, $algorithm) = static::$supported_algs[ $alg ];
238 switch ( $function ) {
239 case 'hash_hmac':
240 return hash_hmac( $algorithm, $msg, $key, true );
241 case 'openssl':
242 $signature = '';
243 $success = openssl_sign( $msg, $signature, $key, $algorithm );
244 if ( ! $success ) {
245 throw new DomainException( 'OpenSSL unable to sign data' );
246 } else {
247 return $signature;
248 }
249 }
250 }
251
252 /**
253 * Verify a signature with the message, key and method. Not all methods
254 * are symmetric, so we must have a separate verify and sign method.
255 *
256 * @param string $msg The original message (header and body).
257 * @param string $signature The original signature.
258 * @param string|resource $key For HS*, a string key works. for RS*, must be a resource of an openssl public key.
259 * @param string $alg The algorithm.
260 *
261 * @return bool
262 *
263 * @throws DomainException Invalid Algorithm or OpenSSL failure.
264 */
265 private static function verify( $msg, $signature, $key, $alg ) {
266 if ( empty( static::$supported_algs[ $alg ] ) ) {
267 throw new DomainException( 'Algorithm not supported' );
268 }
269
270 list($function, $algorithm) = static::$supported_algs[ $alg ];
271 switch ( $function ) {
272 case 'openssl':
273 $success = openssl_verify( $msg, $signature, $key, $algorithm );
274
275 if ( 1 === $success ) {
276 return true;
277 } elseif ( 0 === $success ) {
278 return false;
279 }
280
281 // returns 1 on success, 0 on failure, -1 on error.
282 throw new DomainException(
283 'OpenSSL error: ' . openssl_error_string()
284 );
285 case 'hash_hmac':
286 default:
287 $hash = hash_hmac( $algorithm, $msg, $key, true );
288
289 if ( function_exists( 'hash_equals' ) ) {
290 return hash_equals( $signature, $hash );
291 }
292
293 $len = min( static::safe_strlen( $signature ), static::safe_strlen( $hash ) );
294
295 $status = 0;
296
297 for ( $i = 0; $i < $len; $i++ ) {
298 $status |= ( ord( $signature[ $i ] ) ^ ord( $hash[ $i ] ) );
299 }
300
301 $status |= ( static::safe_strlen( $signature ) ^ static::safe_strlen( $hash ) );
302
303 return ( 0 === $status );
304 }
305 }
306
307 /**
308 * Decode a JSON string into a PHP object.
309 *
310 * @param string $input JSON string.
311 * @param bool $as_array Whether to return the result as an associative array.
312 *
313 * @return object|array Object or Array representation of JSON string
314 *
315 * @throws DomainException Provided string was invalid JSON.
316 */
317 public static function json_decode( $input, $as_array = false ) {
318 $obj = json_decode( $input, $as_array, 512, JSON_BIGINT_AS_STRING );
319 $errno = json_last_error();
320
321 if ( $errno ) {
322 static::handle_json_error( $errno );
323 } elseif ( null === $obj && 'null' !== $input ) {
324 throw new DomainException( 'Null result with non-null input' );
325 } elseif ( $obj === null ) {
326 throw new DomainException( 'Null result' );
327 }
328
329 return $obj;
330 }
331
332 /**
333 * Encode a PHP object into a JSON string.
334 *
335 * @param object|array $input A PHP object or array.
336 *
337 * @return string JSON representation of the PHP object or array.
338 *
339 * @throws DomainException Provided object could not be encoded to valid JSON.
340 */
341 public static function json_encode( $input ) {
342 $json = \wp_json_encode( $input, JSON_UNESCAPED_SLASHES );
343 $errno = json_last_error();
344
345 if ( $errno ) {
346 static::handle_json_error( $errno );
347 } elseif ( 'null' === $json && null !== $input ) {
348 throw new DomainException( 'Null result with non-null input' );
349 }
350 return $json;
351 }
352
353 /**
354 * Decode a string with URL-safe Base64.
355 *
356 * @param string $input A Base64 encoded string.
357 *
358 * @return string A decoded string
359 */
360 public static function urlsafe_b64_decode( $input ) {
361 $remainder = strlen( $input ) % 4;
362 if ( $remainder ) {
363 $padlen = 4 - $remainder;
364 $input .= str_repeat( '=', $padlen );
365 }
366 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
367 return base64_decode( strtr( $input, '-_', '+/' ) );
368 }
369
370 /**
371 * Encode a string with URL-safe Base64.
372 *
373 * @param string $input The string you want encoded.
374 *
375 * @return string The base64 encode of what you passed in
376 */
377 public static function urlsafe_b64_encode( $input ) {
378 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
379 return str_replace( '=', '', strtr( base64_encode( $input ), '+/', '-_' ) );
380 }
381
382 /**
383 * Helper method to create a JSON error.
384 *
385 * @param int $errno An error number from json_last_error().
386 * @throws DomainException .
387 *
388 * @return never
389 */
390 private static function handle_json_error( $errno ) {
391 $messages = array(
392 JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
393 JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
394 JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
395 JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
396 JSON_ERROR_UTF8 => 'Malformed UTF-8 characters',
397 );
398 throw new DomainException(
399 $messages[ $errno ] ?? 'Unknown JSON error: ' . $errno
400 );
401 }
402
403 /**
404 * Get the number of bytes in cryptographic strings.
405 *
406 * @param string $str .
407 *
408 * @return int
409 */
410 private static function safe_strlen( $str ) {
411 if ( function_exists( 'mb_strlen' ) ) {
412 return mb_strlen( $str, '8bit' );
413 }
414 return strlen( $str );
415 }
416 }
417
418 // phpcs:disable
419 if ( ! class_exists( 'SignatureInvalidException' ) ) {
420 /**
421 * SignatureInvalidException
422 *
423 * @package Automattic\Jetpack\Extensions\Premium_Content
424 */
425 class SignatureInvalidException extends \UnexpectedValueException { }
426 }
427 if ( ! class_exists( 'ExpiredException' ) ) {
428 /**
429 * ExpiredException
430 *
431 * @package Automattic\Jetpack\Extensions\Premium_Content
432 */
433 class ExpiredException extends \UnexpectedValueException { }
434 }
435 if ( ! class_exists( 'BeforeValidException' ) ) {
436 /**
437 * BeforeValidException
438 *
439 * @package Automattic\Jetpack\Extensions\Premium_Content
440 */
441 class BeforeValidException extends \UnexpectedValueException { }
442 }
443 // phpcs:enable
444