PluginProbe
Media Cloud Sync / 1.4.1
Media Cloud Sync v1.4.1
1.4.1 1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 All 35 releases
← All changes | includes/sdk/google/firebase/php-jwt/src/JWT.php +116 -46 1.1.11.4.1 View file →
@@ -1,7 +1,7 @@
1 1 <?php
2 2
3 -namespace Dudlewebs\WPMCS\Firebase\JWT;
3 +namespace Dudlewebs\WPMCS\GCP\Firebase\JWT;
4 4
5 5 use ArrayAccess;
6 6 use DateTime;
7 7 use DomainException;
@@ -28,8 +28,9 @@
28 28 {
29 29 private const ASN1_INTEGER = 0x2;
30 30 private const ASN1_SEQUENCE = 0x10;
31 31 private const ASN1_BIT_STRING = 0x3;
32 + private const RSA_KEY_MIN_LENGTH = 2048;
32 33 /**
33 34 * When checking nbf, iat or expiration times,
34 35 * we want to provide some extra leeway time to
35 36 * account for clock skew.
@@ -76,9 +77,9 @@
76 77 *
77 78 * @uses jsonDecode
78 79 * @uses urlsafeB64Decode
79 80 */
80 - public static function decode(string $jwt, $keyOrKeyArray, stdClass &$headers = null): stdClass
81 + public static function decode(string $jwt, #[\SensitiveParameter] $keyOrKeyArray, ?stdClass &$headers = null) : stdClass
81 82 {
82 83 // Validate JWT
83 84 $timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp;
84 85 if (empty($keyOrKeyArray)) {
@@ -89,9 +90,9 @@
89 90 throw new UnexpectedValueException('Wrong number of segments');
90 91 }
91 92 list($headb64, $bodyb64, $cryptob64) = $tks;
92 93 $headerRaw = static::urlsafeB64Decode($headb64);
93 - if (null === $header = static::jsonDecode($headerRaw)) {
94 + if (null === ($header = static::jsonDecode($headerRaw))) {
94 95 throw new UnexpectedValueException('Invalid header encoding');
95 96 }
96 97 if ($headers !== null) {
97 98 $headers = $header;
@@ -96,9 +97,9 @@
96 97 if ($headers !== null) {
97 98 $headers = $header;
98 99 }
99 100 $payloadRaw = static::urlsafeB64Decode($bodyb64);
100 - if (null === $payload = static::jsonDecode($payloadRaw)) {
101 + if (null === ($payload = static::jsonDecode($payloadRaw))) {
101 102 throw new UnexpectedValueException('Invalid claims encoding');
102 103 }
103 104 if (\is_array($payload)) {
104 105 // prevent PHP Fatal Error in edge-cases when payload is empty array
@@ -106,8 +107,17 @@
106 107 }
107 108 if (!$payload instanceof stdClass) {
108 109 throw new UnexpectedValueException('Payload must be a JSON object');
109 110 }
111 + if (isset($payload->iat) && !\is_numeric($payload->iat)) {
112 + throw new UnexpectedValueException('Payload iat must be a number');
113 + }
114 + if (isset($payload->nbf) && !\is_numeric($payload->nbf)) {
115 + throw new UnexpectedValueException('Payload nbf must be a number');
116 + }
117 + if (isset($payload->exp) && !\is_numeric($payload->exp)) {
118 + throw new UnexpectedValueException('Payload exp must be a number');
119 + }
110 120 $sig = static::urlsafeB64Decode($cryptob64);
111 121 if (empty($header->alg)) {
112 122 throw new UnexpectedValueException('Empty algorithm');
113 123 }
@@ -113,9 +123,9 @@
113 123 }
114 124 if (empty(static::$supported_algs[$header->alg])) {
115 125 throw new UnexpectedValueException('Algorithm not supported');
116 126 }
117 - $key = self::getKey($keyOrKeyArray, property_exists($header, 'kid') ? $header->kid : null);
127 + $key = self::getKey($keyOrKeyArray, \property_exists($header, 'kid') ? $header->kid : null);
118 128 // Check the algorithm
119 129 if (!self::constantTimeEquals($key->getAlgorithm(), $header->alg)) {
120 130 // See issue #351
121 131 throw new UnexpectedValueException('Incorrect key for this algorithm');
@@ -128,10 +138,10 @@
128 138 throw new SignatureInvalidException('Signature verification failed');
129 139 }
130 140 // Check the nbf if it is defined. This is the time that the
131 141 // 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));
142 + if (isset($payload->nbf) && \floor($payload->nbf) > $timestamp + static::$leeway) {
143 + $ex = new BeforeValidException('Cannot handle token with nbf prior to ' . \date(DateTime::ATOM, (int) \floor($payload->nbf)));
134 144 $ex->setPayload($payload);
135 145 throw $ex;
136 146 }
137 147 // Check that this token has been created before 'now'. This prevents
@@ -136,10 +146,10 @@
136 146 }
137 147 // Check that this token has been created before 'now'. This prevents
138 148 // using tokens that have been created for later use (and haven't
139 149 // 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));
150 + if (!isset($payload->nbf) && isset($payload->iat) && \floor($payload->iat) > $timestamp + static::$leeway) {
151 + $ex = new BeforeValidException('Cannot handle token with iat prior to ' . \date(DateTime::ATOM, (int) \floor($payload->iat)));
142 152 $ex->setPayload($payload);
143 153 throw $ex;
144 154 }
145 155 // Check if this token has expired.
@@ -145,8 +155,9 @@
145 155 // Check if this token has expired.
146 156 if (isset($payload->exp) && $timestamp - static::$leeway >= $payload->exp) {
147 157 $ex = new ExpiredException('Expired token');
148 158 $ex->setPayload($payload);
159 + $ex->setTimestamp($timestamp);
149 160 throw $ex;
150 161 }
151 162 return $payload;
152 163 }
@@ -153,13 +164,13 @@
153 164 /**
154 165 * Converts and signs a PHP array into a JWT string.
155 166 *
156 167 * @param array<mixed> $payload PHP array
157 - * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
168 + * @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
158 169 * @param string $alg Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256',
159 170 * 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
160 171 * @param string $keyId
161 - * @param array<string, string> $head An array with header elements to attach
172 + * @param array<string, string|string[]> $head An array with header elements to attach
162 173 *
163 174 * @return string A signed JWT
164 175 *
165 176 * @uses jsonEncode
@@ -164,12 +175,12 @@
164 175 *
165 176 * @uses jsonEncode
166 177 * @uses urlsafeB64Encode
167 178 */
168 - public static function encode(array $payload, $key, string $alg, string $keyId = null, array $head = null): string
179 + public static function encode(array $payload, #[\SensitiveParameter] $key, string $alg, ?string $keyId = null, ?array $head = null) : string
169 180 {
170 181 $header = ['typ' => 'JWT'];
171 - if (isset($head) && \is_array($head)) {
182 + if (isset($head)) {
172 183 $header = \array_merge($header, $head);
173 184 }
174 185 $header['alg'] = $alg;
175 186 if ($keyId !== null) {
@@ -186,9 +197,9 @@
186 197 /**
187 198 * Sign a string with a given key and algorithm.
188 199 *
189 200 * @param string $msg The message to sign
190 - * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
201 + * @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
191 202 * @param string $alg Supported algorithms are 'EdDSA', 'ES384', 'ES256', 'ES256K', 'HS256',
192 203 * 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
193 204 *
194 205 * @return string An encrypted message
@@ -194,9 +205,9 @@
194 205 * @return string An encrypted message
195 206 *
196 207 * @throws DomainException Unsupported algorithm or bad key was specified
197 208 */
198 - public static function sign(string $msg, $key, string $alg): string
209 + public static function sign(string $msg, #[\SensitiveParameter] $key, string $alg) : string
199 210 {
200 211 if (empty(static::$supported_algs[$alg])) {
201 212 throw new DomainException('Algorithm not supported');
202 213 }
@@ -205,13 +216,21 @@
205 216 case 'hash_hmac':
206 217 if (!\is_string($key)) {
207 218 throw new InvalidArgumentException('key must be a string when using hmac');
208 219 }
220 + self::validateHmacKeyLength($key, $algorithm);
209 221 return \hash_hmac($algorithm, $msg, $key, \true);
210 222 case 'openssl':
211 223 $signature = '';
224 + if (!($key = \openssl_pkey_get_private($key))) {
225 + throw new DomainException('OpenSSL unable to validate key');
226 + }
227 + if (\str_starts_with($alg, 'RS')) {
228 + self::validateRsaKeyLength($key);
229 + } elseif (\str_starts_with($alg, 'ES')) {
230 + self::validateEcKeyLength($key, $alg);
231 + }
212 232 $success = \openssl_sign($msg, $signature, $key, $algorithm);
213 - // @phpstan-ignore-line
214 233 if (!$success) {
215 234 throw new DomainException('OpenSSL unable to sign data');
216 235 }
217 236 if ($alg === 'ES256' || $alg === 'ES256K') {
@@ -220,9 +239,9 @@
220 239 $signature = self::signatureFromDER($signature, 384);
221 240 }
222 241 return $signature;
223 242 case 'sodium_crypto':
224 - if (!\function_exists('sodium_crypto_sign_detached') && !\function_exists('Dudlewebs\WPMCS\sodium_crypto_sign_detached')) {
243 + if (!\function_exists('sodium_crypto_sign_detached')) {
225 244 throw new DomainException('libsodium is not available');
226 245 }
227 246 if (!\is_string($key)) {
228 247 throw new InvalidArgumentException('key must be a string when using EdDSA');
@@ -228,14 +247,14 @@
228 247 throw new InvalidArgumentException('key must be a string when using EdDSA');
229 248 }
230 249 try {
231 250 // The last non-empty line is used as the key.
232 - $lines = array_filter(explode("\n", $key));
233 - $key = base64_decode((string) end($lines));
251 + $lines = \array_filter(\explode("\n", $key));
252 + $key = \base64_decode((string) \end($lines));
234 253 if (\strlen($key) === 0) {
235 254 throw new DomainException('Key cannot be empty string');
236 255 }
237 - return sodium_crypto_sign_detached($msg, $key);
256 + return \sodium_crypto_sign_detached($msg, $key);
238 257 } catch (Exception $e) {
239 258 throw new DomainException($e->getMessage(), 0, $e);
240 259 }
241 260 }
@@ -246,9 +265,9 @@
246 265 * are symmetric, so we must have a separate verify and sign method.
247 266 *
248 267 * @param string $msg The original message (header and body)
249 268 * @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
269 + * @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For Ed*, ES*, HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey
251 270 * @param string $alg The algorithm
252 271 *
253 272 * @return bool
254 273 *
@@ -253,9 +272,9 @@
253 272 * @return bool
254 273 *
255 274 * @throws DomainException Invalid Algorithm, bad key, or OpenSSL failure
256 275 */
257 - private static function verify(string $msg, string $signature, $keyMaterial, string $alg): bool
276 + private static function verify(string $msg, string $signature, #[\SensitiveParameter] $keyMaterial, string $alg) : bool
258 277 {
259 278 if (empty(static::$supported_algs[$alg])) {
260 279 throw new DomainException('Algorithm not supported');
261 280 }
@@ -261,10 +280,17 @@
261 280 }
262 281 list($function, $algorithm) = static::$supported_algs[$alg];
263 282 switch ($function) {
264 283 case 'openssl':
284 + if (!($key = \openssl_pkey_get_public($keyMaterial))) {
285 + throw new DomainException('OpenSSL unable to validate key');
286 + }
287 + if (\str_starts_with($alg, 'RS')) {
288 + self::validateRsaKeyLength($key);
289 + } elseif (\str_starts_with($alg, 'ES')) {
290 + self::validateEcKeyLength($key, $alg);
291 + }
265 292 $success = \openssl_verify($msg, $signature, $keyMaterial, $algorithm);
266 - // @phpstan-ignore-line
267 293 if ($success === 1) {
268 294 return \true;
269 295 }
270 296 if ($success === 0) {
@@ -272,9 +298,9 @@
272 298 }
273 299 // returns 1 on success, 0 on failure, -1 on error.
274 300 throw new DomainException('OpenSSL error: ' . \openssl_error_string());
275 301 case 'sodium_crypto':
276 - if (!\function_exists('sodium_crypto_sign_verify_detached') && !\function_exists('Dudlewebs\WPMCS\sodium_crypto_sign_verify_detached')) {
302 + if (!\function_exists('sodium_crypto_sign_verify_detached')) {
277 303 throw new DomainException('libsodium is not available');
278 304 }
279 305 if (!\is_string($keyMaterial)) {
280 306 throw new InvalidArgumentException('key must be a string when using EdDSA');
@@ -280,10 +306,10 @@
280 306 throw new InvalidArgumentException('key must be a string when using EdDSA');
281 307 }
282 308 try {
283 309 // The last non-empty line is used as the key.
284 - $lines = array_filter(explode("\n", $keyMaterial));
285 - $key = base64_decode((string) end($lines));
310 + $lines = \array_filter(\explode("\n", $keyMaterial));
311 + $key = \base64_decode((string) \end($lines));
286 312 if (\strlen($key) === 0) {
287 313 throw new DomainException('Key cannot be empty string');
288 314 }
289 315 if (\strlen($signature) === 0) {
@@ -288,9 +314,9 @@
288 314 }
289 315 if (\strlen($signature) === 0) {
290 316 throw new DomainException('Signature cannot be empty string');
291 317 }
292 - return sodium_crypto_sign_verify_detached($signature, $msg, $key);
318 + return \sodium_crypto_sign_verify_detached($signature, $msg, $key);
293 319 } catch (Exception $e) {
294 320 throw new DomainException($e->getMessage(), 0, $e);
295 321 }
296 322 case 'hash_hmac':
@@ -297,8 +323,9 @@
297 323 default:
298 324 if (!\is_string($keyMaterial)) {
299 325 throw new InvalidArgumentException('key must be a string when using hmac');
300 326 }
327 + self::validateHmacKeyLength($keyMaterial, $algorithm);
301 328 $hash = \hash_hmac($algorithm, $msg, $keyMaterial, \true);
302 329 return self::constantTimeEquals($hash, $signature);
303 330 }
304 331 }
@@ -329,16 +356,11 @@
329 356 * @return string JSON representation of the PHP array
330 357 *
331 358 * @throws DomainException Provided object could not be encoded to valid JSON
332 359 */
333 - public static function jsonEncode(array $input): string
360 + public static function jsonEncode(array $input) : string
334 361 {
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 - }
362 + $json = \json_encode($input, \JSON_UNESCAPED_SLASHES);
341 363 if ($errno = \json_last_error()) {
342 364 self::handleJsonError($errno);
343 365 } elseif ($json === 'null') {
344 366 throw new DomainException('Null result with non-null input');
@@ -356,9 +378,9 @@
356 378 * @return string A decoded string
357 379 *
358 380 * @throws InvalidArgumentException invalid base64 characters
359 381 */
360 - public static function urlsafeB64Decode(string $input): string
382 + public static function urlsafeB64Decode(string $input) : string
361 383 {
362 384 return \base64_decode(self::convertBase64UrlToBase64($input));
363 385 }
364 386 /**
@@ -370,9 +392,9 @@
370 392 * needed.
371 393 *
372 394 * @see https://www.rfc-editor.org/rfc/rfc4648
373 395 */
374 - public static function convertBase64UrlToBase64(string $input): string
396 + public static function convertBase64UrlToBase64(string $input) : string
375 397 {
376 398 $remainder = \strlen($input) % 4;
377 399 if ($remainder) {
378 400 $padlen = 4 - $remainder;
@@ -386,9 +408,9 @@
386 408 * @param string $input The string you want encoded
387 409 *
388 410 * @return string The base64 encode of what you passed in
389 411 */
390 - public static function urlsafeB64Encode(string $input): string
412 + public static function urlsafeB64Encode(string $input) : string
391 413 {
392 414 return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_'));
393 415 }
394 416 /**
@@ -400,9 +422,9 @@
400 422 * @throws UnexpectedValueException
401 423 *
402 424 * @return Key
403 425 */
404 - private static function getKey($keyOrKeyArray, ?string $kid): Key
426 + private static function getKey(#[\SensitiveParameter] $keyOrKeyArray, ?string $kid) : Key
405 427 {
406 428 if ($keyOrKeyArray instanceof Key) {
407 429 return $keyOrKeyArray;
408 430 }
@@ -422,9 +444,9 @@
422 444 * @param string $left The string of known length to compare against
423 445 * @param string $right The user-supplied string
424 446 * @return bool
425 447 */
426 - public static function constantTimeEquals(string $left, string $right): bool
448 + public static function constantTimeEquals(string $left, string $right) : bool
427 449 {
428 450 if (\function_exists('hash_equals')) {
429 451 return \hash_equals($left, $right);
430 452 }
@@ -444,9 +466,9 @@
444 466 * @throws DomainException
445 467 *
446 468 * @return void
447 469 */
448 - private static function handleJsonError(int $errno): void
470 + private static function handleJsonError(int $errno) : void
449 471 {
450 472 $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 473 throw new DomainException(isset($messages[$errno]) ? $messages[$errno] : 'Unknown JSON error: ' . $errno);
452 474 }
@@ -456,9 +478,9 @@
456 478 * @param string $str
457 479 *
458 480 * @return int
459 481 */
460 - private static function safeStrlen(string $str): int
482 + private static function safeStrlen(string $str) : int
461 483 {
462 484 if (\function_exists('mb_strlen')) {
463 485 return \mb_strlen($str, '8bit');
464 486 }
@@ -469,12 +491,12 @@
469 491 *
470 492 * @param string $sig The ECDSA signature to convert
471 493 * @return string The encoded DER object
472 494 */
473 - private static function signatureToDER(string $sig): string
495 + private static function signatureToDER(string $sig) : string
474 496 {
475 497 // Separate the signature into r-value and s-value
476 - $length = max(1, (int) (\strlen($sig) / 2));
498 + $length = \max(1, (int) (\strlen($sig) / 2));
477 499 list($r, $s) = \str_split($sig, $length);
478 500 // Trim leading zeros
479 501 $r = \ltrim($r, "\x00");
480 502 $s = \ltrim($s, "\x00");
@@ -495,9 +517,9 @@
495 517 * @param string $value the value to encode
496 518 *
497 519 * @return string the encoded object
498 520 */
499 - private static function encodeDER(int $type, string $value): string
521 + private static function encodeDER(int $type, string $value) : string
500 522 {
501 523 $tag_header = 0;
502 524 if ($type === self::ASN1_SEQUENCE) {
503 525 $tag_header |= 0x20;
@@ -515,9 +537,9 @@
515 537 * @param int $keySize the number of bits in the key
516 538 *
517 539 * @return string the signature
518 540 */
519 - private static function signatureFromDER(string $der, int $keySize): string
541 + private static function signatureFromDER(string $der, int $keySize) : string
520 542 {
521 543 // OpenSSL returns the ECDSA signatures as a binary ASN.1 DER SEQUENCE
522 544 list($offset, $_) = self::readDER($der);
523 545 list($offset, $r) = self::readDER($der, $offset);
@@ -539,9 +561,9 @@
539 561 * to decode
540 562 *
541 563 * @return array{int, string|null} the new offset and the decoded object
542 564 */
543 - private static function readDER(string $der, int $offset = 0): array
565 + private static function readDER(string $der, int $offset = 0) : array
544 566 {
545 567 $pos = $offset;
546 568 $size = \strlen($der);
547 569 $constructed = \ord($der[$pos]) >> 5 & 0x1;
@@ -567,6 +589,54 @@
567 589 } else {
568 590 $data = null;
569 591 }
570 592 return [$pos, $data];
593 + }
594 + /**
595 + * Validate HMAC key length
596 + *
597 + * @param string $key HMAC key material
598 + * @param string $algorithm The algorithm
599 + *
600 + * @throws DomainException Provided key is too short
601 + */
602 + private static function validateHmacKeyLength(string $key, string $algorithm) : void
603 + {
604 + $keyLength = \strlen($key) * 8;
605 + $minKeyLength = (int) \str_replace('SHA', '', $algorithm);
606 + if ($keyLength < $minKeyLength) {
607 + throw new DomainException('Provided key is too short');
608 + }
609 + }
610 + /**
611 + * Validate RSA key length
612 + *
613 + * @param OpenSSLAsymmetricKey $key RSA key material
614 + * @throws DomainException Provided key is too short
615 + */
616 + private static function validateRsaKeyLength(#[\SensitiveParameter] OpenSSLAsymmetricKey $key) : void
617 + {
618 + if (!($keyDetails = \openssl_pkey_get_details($key))) {
619 + throw new DomainException('Unable to validate key');
620 + }
621 + if ($keyDetails['bits'] < self::RSA_KEY_MIN_LENGTH) {
622 + throw new DomainException('Provided key is too short');
623 + }
624 + }
625 + /**
626 + * Validate RSA key length
627 + *
628 + * @param OpenSSLAsymmetricKey $key RSA key material
629 + * @param string $algorithm The algorithm
630 + * @throws DomainException Provided key is too short
631 + */
632 + private static function validateEcKeyLength(#[\SensitiveParameter] OpenSSLAsymmetricKey $key, string $algorithm) : void
633 + {
634 + if (!($keyDetails = \openssl_pkey_get_details($key))) {
635 + throw new DomainException('Unable to validate key');
636 + }
637 + $minKeyLength = (int) \str_replace('ES', '', $algorithm);
638 + if ($keyDetails['bits'] < $minKeyLength) {
639 + throw new DomainException('Provided key is too short');
640 + }
571 641 }
572 642 }