PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 14.2.2
Jetpack – WP Security, Backup, Speed, & Growth v14.2.2
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
427 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 $obj = json_decode( $input, false, 512, JSON_BIGINT_AS_STRING );
303 $errno = json_last_error();
304
305 if ( $errno ) {
306 static::handle_json_error( $errno );
307 } elseif ( null === $obj && 'null' !== $input ) {
308 throw new DomainException( 'Null result with non-null input' );
309 }
310 return $obj;
311 }
312
313 /**
314 * Encode a PHP object into a JSON string.
315 *
316 * @param object|array $input A PHP object or array.
317 *
318 * @return string JSON representation of the PHP object or array.
319 *
320 * @throws DomainException Provided object could not be encoded to valid JSON.
321 */
322 public static function json_encode( $input ) {
323 $json = wp_json_encode( $input );
324 $errno = json_last_error();
325
326 if ( $errno ) {
327 static::handle_json_error( $errno );
328 } elseif ( 'null' === $json && null !== $input ) {
329 throw new DomainException( 'Null result with non-null input' );
330 }
331 return $json;
332 }
333
334 /**
335 * Decode a string with URL-safe Base64.
336 *
337 * @param string $input A Base64 encoded string.
338 *
339 * @return string A decoded string
340 */
341 public static function urlsafe_b64_decode( $input ) {
342 $remainder = strlen( $input ) % 4;
343 if ( $remainder ) {
344 $padlen = 4 - $remainder;
345 $input .= str_repeat( '=', $padlen );
346 }
347 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
348 return base64_decode( strtr( $input, '-_', '+/' ) );
349 }
350
351 /**
352 * Encode a string with URL-safe Base64.
353 *
354 * @param string $input The string you want encoded.
355 *
356 * @return string The base64 encode of what you passed in
357 */
358 public static function urlsafe_b64_encode( $input ) {
359 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
360 return str_replace( '=', '', strtr( base64_encode( $input ), '+/', '-_' ) );
361 }
362
363 /**
364 * Helper method to create a JSON error.
365 *
366 * @param int $errno An error number from json_last_error().
367 * @throws DomainException .
368 *
369 * @return never
370 */
371 private static function handle_json_error( $errno ) {
372 $messages = array(
373 JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
374 JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
375 JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
376 JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
377 JSON_ERROR_UTF8 => 'Malformed UTF-8 characters',
378 );
379 throw new DomainException(
380 isset( $messages[ $errno ] )
381 ? $messages[ $errno ]
382 : 'Unknown JSON error: ' . $errno
383 );
384 }
385
386 /**
387 * Get the number of bytes in cryptographic strings.
388 *
389 * @param string $str .
390 *
391 * @return int
392 */
393 private static function safe_strlen( $str ) {
394 if ( function_exists( 'mb_strlen' ) ) {
395 return mb_strlen( $str, '8bit' );
396 }
397 return strlen( $str );
398 }
399 }
400
401 // phpcs:disable
402 if ( ! class_exists( 'SignatureInvalidException' ) ) {
403 /**
404 * SignatureInvalidException
405 *
406 * @package Automattic\Jetpack\Extensions\Premium_Content
407 */
408 class SignatureInvalidException extends \UnexpectedValueException { }
409 }
410 if ( ! class_exists( 'ExpiredException' ) ) {
411 /**
412 * ExpiredException
413 *
414 * @package Automattic\Jetpack\Extensions\Premium_Content
415 */
416 class ExpiredException extends \UnexpectedValueException { }
417 }
418 if ( ! class_exists( 'BeforeValidException' ) ) {
419 /**
420 * BeforeValidException
421 *
422 * @package Automattic\Jetpack\Extensions\Premium_Content
423 */
424 class BeforeValidException extends \UnexpectedValueException { }
425 }
426 // phpcs:enable
427