PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 13.1.5
Jetpack – WP Security, Backup, Speed, & Growth v13.1.5
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 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 / extensions / blocks / premium-content / _inc / subscription-service / class-jwt.php
jetpack / extensions / blocks / premium-content / _inc / subscription-service Last commit date
class-abstract-token-subscription-service.php 2 years ago class-jetpack-token-subscription-service.php 2 years ago class-jwt.php 2 years ago class-unconfigured-subscription-service.php 2 years ago class-wpcom-offline-subscription-service.php 3 years ago class-wpcom-online-subscription-service.php 2 years ago include.php 2 years ago interface-subscription-service.php 2 years ago
class-jwt.php
442 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\Extensions\Premium_Content
7 */
8
9 namespace Automattic\Jetpack\Extensions\Premium_Content;
10
11 use DateTime;
12 use DomainException;
13 use InvalidArgumentException;
14 use UnexpectedValueException;
15
16 /**
17 * JSON Web Token implementation, based on this spec:
18 * https://tools.ietf.org/html/rfc7519
19 *
20 * PHP version 5
21 *
22 * @category Authentication
23 * @package Authentication_JWT
24 * @author Neuman Vong <neuman@twilio.com>
25 * @author Anant Narayanan <anant@php.net>
26 * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
27 * @link https://github.com/firebase/php-jwt
28 */
29 class JWT {
30 /**
31 * When checking nbf, iat or expiration times,
32 * we want to provide some extra leeway time to
33 * account for clock skew.
34 *
35 * @var int $leeway The leeway value.
36 */
37 public static $leeway = 0;
38
39 /**
40 * Allow the current timestamp to be specified.
41 * Useful for fixing a value within unit testing.
42 *
43 * Will default to PHP time() value if null.
44 *
45 * @var string $timestamp The timestamp.
46 */
47 public static $timestamp = null;
48
49 /**
50 * Supported algorithms.
51 *
52 * @var array $supported_algs Supported algorithms.
53 */
54 public static $supported_algs = array(
55 'HS256' => array( 'hash_hmac', 'SHA256' ),
56 'HS512' => array( 'hash_hmac', 'SHA512' ),
57 'HS384' => array( 'hash_hmac', 'SHA384' ),
58 'RS256' => array( 'openssl', 'SHA256' ),
59 'RS384' => array( 'openssl', 'SHA384' ),
60 'RS512' => array( 'openssl', 'SHA512' ),
61 );
62
63 /**
64 * Decodes a JWT string into a PHP object.
65 *
66 * @param string $jwt The JWT.
67 * @param string|array $key The key, or map of keys.
68 * If the algorithm used is asymmetric, this is the public key.
69 * @param array $allowed_algs List of supported verification algorithms.
70 * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'.
71 *
72 * @return object The JWT's payload as a PHP object
73 *
74 * @throws UnexpectedValueException Provided JWT was invalid.
75 * @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed.
76 * @throws InvalidArgumentException Provided JWT is trying to be used before it's eligible as defined by 'nbf'.
77 * @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat'.
78 * @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim.
79 *
80 * @uses json_decode
81 * @uses urlsafe_b64_decode
82 */
83 public static function decode( $jwt, $key, array $allowed_algs = array() ) {
84 $timestamp = static::$timestamp === null ? time() : static::$timestamp;
85
86 if ( empty( $key ) ) {
87 throw new InvalidArgumentException( 'Key may not be empty' );
88 }
89
90 $tks = explode( '.', $jwt );
91 if ( count( $tks ) !== 3 ) {
92 throw new UnexpectedValueException( 'Wrong number of segments' );
93 }
94
95 list( $headb64, $bodyb64, $cryptob64 ) = $tks;
96
97 $header = static::json_decode( static::urlsafe_b64_decode( $headb64 ) );
98 if ( null === $header ) {
99 throw new UnexpectedValueException( 'Invalid header encoding' );
100 }
101
102 $payload = static::json_decode( static::urlsafe_b64_decode( $bodyb64 ) );
103 if ( null === $payload ) {
104 throw new UnexpectedValueException( 'Invalid claims encoding' );
105 }
106
107 $sig = static::urlsafe_b64_decode( $cryptob64 );
108 if ( false === $sig ) {
109 throw new UnexpectedValueException( 'Invalid signature encoding' );
110 }
111
112 if ( empty( $header->alg ) ) {
113 throw new UnexpectedValueException( 'Empty algorithm' );
114 }
115
116 if ( empty( static::$supported_algs[ $header->alg ] ) ) {
117 throw new UnexpectedValueException( 'Algorithm not supported' );
118 }
119
120 if ( ! in_array( $header->alg, $allowed_algs, true ) ) {
121 throw new UnexpectedValueException( 'Algorithm not allowed' );
122 }
123
124 if ( is_array( $key ) || $key instanceof \ArrayAccess ) {
125 if ( isset( $header->kid ) ) {
126 if ( ! isset( $key[ $header->kid ] ) ) {
127 throw new UnexpectedValueException( '"kid" invalid, unable to lookup correct key' );
128 }
129 $key = $key[ $header->kid ];
130 } else {
131 throw new UnexpectedValueException( '"kid" empty, unable to lookup correct key' );
132 }
133 }
134
135 // Check the signature.
136 if ( ! static::verify( "$headb64.$bodyb64", $sig, $key, $header->alg ) ) {
137 throw new SignatureInvalidException( 'Signature verification failed' );
138 }
139
140 // Check if the nbf if it is defined. This is the time that the
141 // token can actually be used. If it's not yet that time, abort.
142 if ( isset( $payload->nbf ) && $payload->nbf > ( $timestamp + static::$leeway ) ) {
143 throw new BeforeValidException(
144 'Cannot handle token prior to ' . gmdate( DateTime::ISO8601, $payload->nbf )
145 );
146 }
147
148 // Check that this token has been created before 'now'. This prevents
149 // using tokens that have been created for later use (and haven't
150 // correctly used the nbf claim).
151 if ( isset( $payload->iat ) && $payload->iat > ( $timestamp + static::$leeway ) ) {
152 throw new BeforeValidException(
153 'Cannot handle token prior to ' . gmdate( DateTime::ISO8601, $payload->iat )
154 );
155 }
156
157 // Check if this token has expired.
158 if ( isset( $payload->exp ) && ( $timestamp - static::$leeway ) >= $payload->exp ) {
159 throw new ExpiredException( 'Expired token' );
160 }
161
162 return $payload;
163 }
164
165 /**
166 * Converts and signs a PHP object or array into a JWT string.
167 *
168 * @param object|array $payload PHP object or array.
169 * @param string $key The secret key.
170 * If the algorithm used is asymmetric, this is the private key.
171 * @param string $alg The signing algorithm.
172 * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'.
173 * @param mixed $key_id The key ID.
174 * @param array $head An array with header elements to attach.
175 *
176 * @return string A signed JWT
177 *
178 * @uses json_encode
179 * @uses urlsafe_b64_decode
180 */
181 public static function encode( $payload, $key, $alg = 'HS256', $key_id = null, $head = null ) {
182 $header = array(
183 'typ' => 'JWT',
184 'alg' => $alg,
185 );
186
187 if ( null !== $key_id ) {
188 $header['kid'] = $key_id;
189 }
190
191 if ( isset( $head ) && is_array( $head ) ) {
192 $header = array_merge( $head, $header );
193 }
194
195 $segments = array();
196 $segments[] = static::urlsafe_b64_encode( static::json_encode( $header ) );
197 $segments[] = static::urlsafe_b64_encode( static::json_encode( $payload ) );
198 $signing_input = implode( '.', $segments );
199
200 $signature = static::sign( $signing_input, $key, $alg );
201 $segments[] = static::urlsafe_b64_encode( $signature );
202
203 return implode( '.', $segments );
204 }
205
206 /**
207 * Sign a string with a given key and algorithm.
208 *
209 * @param string $msg The message to sign.
210 * @param string|resource $key The secret key.
211 * @param string $alg The signing algorithm.
212 * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'.
213 *
214 * @return string An encrypted message
215 *
216 * @throws DomainException Unsupported algorithm was specified.
217 */
218 public static function sign( $msg, $key, $alg = 'HS256' ) {
219 if ( empty( static::$supported_algs[ $alg ] ) ) {
220 throw new DomainException( 'Algorithm not supported' );
221 }
222 list($function, $algorithm) = static::$supported_algs[ $alg ];
223 switch ( $function ) {
224 case 'hash_hmac':
225 return hash_hmac( $algorithm, $msg, $key, true );
226 case 'openssl':
227 $signature = '';
228 $success = openssl_sign( $msg, $signature, $key, $algorithm );
229 if ( ! $success ) {
230 throw new DomainException( 'OpenSSL unable to sign data' );
231 } else {
232 return $signature;
233 }
234 }
235 }
236
237 /**
238 * Verify a signature with the message, key and method. Not all methods
239 * are symmetric, so we must have a separate verify and sign method.
240 *
241 * @param string $msg The original message (header and body).
242 * @param string $signature The original signature.
243 * @param string|resource $key For HS*, a string key works. for RS*, must be a resource of an openssl public key.
244 * @param string $alg The algorithm.
245 *
246 * @return bool
247 *
248 * @throws DomainException Invalid Algorithm or OpenSSL failure.
249 */
250 private static function verify( $msg, $signature, $key, $alg ) {
251 if ( empty( static::$supported_algs[ $alg ] ) ) {
252 throw new DomainException( 'Algorithm not supported' );
253 }
254
255 list($function, $algorithm) = static::$supported_algs[ $alg ];
256 switch ( $function ) {
257 case 'openssl':
258 $success = openssl_verify( $msg, $signature, $key, $algorithm );
259
260 if ( 1 === $success ) {
261 return true;
262 } elseif ( 0 === $success ) {
263 return false;
264 }
265
266 // returns 1 on success, 0 on failure, -1 on error.
267 throw new DomainException(
268 'OpenSSL error: ' . openssl_error_string()
269 );
270 case 'hash_hmac':
271 default:
272 $hash = hash_hmac( $algorithm, $msg, $key, true );
273
274 if ( function_exists( 'hash_equals' ) ) {
275 return hash_equals( $signature, $hash );
276 }
277
278 $len = min( static::safe_strlen( $signature ), static::safe_strlen( $hash ) );
279
280 $status = 0;
281
282 for ( $i = 0; $i < $len; $i++ ) {
283 $status |= ( ord( $signature[ $i ] ) ^ ord( $hash[ $i ] ) );
284 }
285
286 $status |= ( static::safe_strlen( $signature ) ^ static::safe_strlen( $hash ) );
287
288 return ( 0 === $status );
289 }
290 }
291
292 /**
293 * Decode a JSON string into a PHP object.
294 *
295 * @param string $input JSON string.
296 *
297 * @return object Object representation of JSON string
298 *
299 * @throws DomainException Provided string was invalid JSON.
300 */
301 public static function json_decode( $input ) {
302 if ( version_compare( PHP_VERSION, '5.4.0', '>=' ) && ! ( defined( 'JSON_C_VERSION' ) && PHP_INT_SIZE > 4 ) ) {
303 /** In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you
304 * to specify that large ints (like Steam Transaction IDs) should be treated as
305 * strings, rather than the PHP default behaviour of converting them to floats.
306 */
307 $obj = json_decode( $input, false, 512, JSON_BIGINT_AS_STRING );
308 } else {
309 /** Not all servers will support that, however, so for older versions we must
310 * manually detect large ints in the JSON string and quote them (thus converting
311 *them to strings) before decoding, hence the preg_replace() call.
312 */
313 $max_int_length = strlen( (string) PHP_INT_MAX ) - 1;
314 $json_without_bigints = preg_replace( '/:\s*(-?\d{' . $max_int_length . ',})/', ': "$1"', $input );
315 $obj = json_decode( $json_without_bigints );
316 }
317
318 $errno = json_last_error();
319
320 if ( $errno ) {
321 static::handle_json_error( $errno );
322 } elseif ( null === $obj && 'null' !== $input ) {
323 throw new DomainException( 'Null result with non-null input' );
324 }
325 return $obj;
326 }
327
328 /**
329 * Encode a PHP object into a JSON string.
330 *
331 * @param object|array $input A PHP object or array.
332 *
333 * @return string JSON representation of the PHP object or array.
334 *
335 * @throws DomainException Provided object could not be encoded to valid JSON.
336 */
337 public static function json_encode( $input ) {
338 $json = wp_json_encode( $input );
339 $errno = json_last_error();
340
341 if ( $errno ) {
342 static::handle_json_error( $errno );
343 } elseif ( 'null' === $json && null !== $input ) {
344 throw new DomainException( 'Null result with non-null input' );
345 }
346 return $json;
347 }
348
349 /**
350 * Decode a string with URL-safe Base64.
351 *
352 * @param string $input A Base64 encoded string.
353 *
354 * @return string A decoded string
355 */
356 public static function urlsafe_b64_decode( $input ) {
357 $remainder = strlen( $input ) % 4;
358 if ( $remainder ) {
359 $padlen = 4 - $remainder;
360 $input .= str_repeat( '=', $padlen );
361 }
362 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
363 return base64_decode( strtr( $input, '-_', '+/' ) );
364 }
365
366 /**
367 * Encode a string with URL-safe Base64.
368 *
369 * @param string $input The string you want encoded.
370 *
371 * @return string The base64 encode of what you passed in
372 */
373 public static function urlsafe_b64_encode( $input ) {
374 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
375 return str_replace( '=', '', strtr( base64_encode( $input ), '+/', '-_' ) );
376 }
377
378 /**
379 * Helper method to create a JSON error.
380 *
381 * @param int $errno An error number from json_last_error().
382 * @throws DomainException .
383 *
384 * @return void
385 */
386 private static function handle_json_error( $errno ) {
387 $messages = array(
388 JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
389 JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
390 JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
391 JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
392 JSON_ERROR_UTF8 => 'Malformed UTF-8 characters',
393 );
394 throw new DomainException(
395 isset( $messages[ $errno ] )
396 ? $messages[ $errno ]
397 : 'Unknown JSON error: ' . $errno
398 );
399 }
400
401 /**
402 * Get the number of bytes in cryptographic strings.
403 *
404 * @param string $str .
405 *
406 * @return int
407 */
408 private static function safe_strlen( $str ) {
409 if ( function_exists( 'mb_strlen' ) ) {
410 return mb_strlen( $str, '8bit' );
411 }
412 return strlen( $str );
413 }
414 }
415
416 // phpcs:disable
417 if ( ! class_exists( 'SignatureInvalidException' ) ) {
418 /**
419 * SignatureInvalidException
420 *
421 * @package Automattic\Jetpack\Extensions\Premium_Content
422 */
423 class SignatureInvalidException extends \UnexpectedValueException { }
424 }
425 if ( ! class_exists( 'ExpiredException' ) ) {
426 /**
427 * ExpiredException
428 *
429 * @package Automattic\Jetpack\Extensions\Premium_Content
430 */
431 class ExpiredException extends \UnexpectedValueException { }
432 }
433 if ( ! class_exists( 'BeforeValidException' ) ) {
434 /**
435 * BeforeValidException
436 *
437 * @package Automattic\Jetpack\Extensions\Premium_Content
438 */
439 class BeforeValidException extends \UnexpectedValueException { }
440 }
441 // phpcs:enable
442