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