Skip to content

Commit

Permalink
Merge branch 'master_MDL-76916' of https://github.com/mattporritt/moodle
Browse files Browse the repository at this point in the history
  • Loading branch information
sarjona committed Mar 17, 2023
2 parents a15d363 + 63d6e74 commit 5c0e75d
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 28 deletions.
5 changes: 0 additions & 5 deletions lib/php-jwt/readme_moodle.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,3 @@ Instructions
5. Unzip it in lib as php-jwt.
6. Update entry for this library in lib/thirdpartylibs.xml.

2023/01/26
----------
- src/JWT.php file has minor changes for PHP 8.2 compatibility. See MDL-76415 for more details.
Since version v6.3.1, the php-jwt already has the fix, so if someone executing the upgrading version and
it has the patch, please ignore this note.
47 changes: 38 additions & 9 deletions lib/php-jwt/src/CachedKeySet.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
namespace Firebase\JWT;

use ArrayAccess;
use InvalidArgumentException;
use LogicException;
use OutOfBoundsException;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use RuntimeException;
use UnexpectedValueException;

/**
* @implements ArrayAccess<string, Key>
Expand Down Expand Up @@ -41,7 +43,7 @@ class CachedKeySet implements ArrayAccess
*/
private $cacheItem;
/**
* @var array<string, Key>
* @var array<string, array<mixed>>
*/
private $keySet;
/**
Expand Down Expand Up @@ -101,7 +103,7 @@ public function offsetGet($keyId): Key
if (!$this->keyIdExists($keyId)) {
throw new OutOfBoundsException('Key ID not found');
}
return $this->keySet[$keyId];
return JWK::parseKey($this->keySet[$keyId], $this->defaultAlg);
}

/**
Expand Down Expand Up @@ -130,33 +132,60 @@ public function offsetUnset($offset): void
throw new LogicException('Method not implemented');
}

/**
* @return array<mixed>
*/
private function formatJwksForCache(string $jwks): array
{
$jwks = json_decode($jwks, true);

if (!isset($jwks['keys'])) {
throw new UnexpectedValueException('"keys" member must exist in the JWK Set');
}

if (empty($jwks['keys'])) {
throw new InvalidArgumentException('JWK Set did not contain any keys');
}

$keys = [];
foreach ($jwks['keys'] as $k => $v) {
$kid = isset($v['kid']) ? $v['kid'] : $k;
$keys[(string) $kid] = $v;
}

return $keys;
}

private function keyIdExists(string $keyId): bool
{
if (null === $this->keySet) {
$item = $this->getCacheItem();
// Try to load keys from cache
if ($item->isHit()) {
// item found! Return it
$jwks = $item->get();
$this->keySet = JWK::parseKeySet(json_decode($jwks, true), $this->defaultAlg);
// item found! retrieve it
$this->keySet = $item->get();
// If the cached item is a string, the JWKS response was cached (previous behavior).
// Parse this into expected format array<kid, jwk> instead.
if (\is_string($this->keySet)) {
$this->keySet = $this->formatJwksForCache($this->keySet);
}
}
}

if (!isset($this->keySet[$keyId])) {
if ($this->rateLimitExceeded()) {
return false;
}
$request = $this->httpFactory->createRequest('get', $this->jwksUri);
$request = $this->httpFactory->createRequest('GET', $this->jwksUri);
$jwksResponse = $this->httpClient->sendRequest($request);
$jwks = (string) $jwksResponse->getBody();
$this->keySet = JWK::parseKeySet(json_decode($jwks, true), $this->defaultAlg);
$this->keySet = $this->formatJwksForCache((string) $jwksResponse->getBody());

if (!isset($this->keySet[$keyId])) {
return false;
}

$item = $this->getCacheItem();
$item->set($jwks);
$item->set($this->keySet);
if ($this->expiresAfter) {
$item->expiresAfter($this->expiresAfter);
}
Expand Down
1 change: 1 addition & 0 deletions lib/php-jwt/src/JWK.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class JWK
private const ASN1_BIT_STRING = 0x03;
private const EC_CURVES = [
'P-256' => '1.2.840.10045.3.1.7', // Len: 64
'secp256k1' => '1.3.132.0.10', // Len: 64
// 'P-384' => '1.3.132.0.34', // Len: 96 (not yet supported)
// 'P-521' => '1.3.132.0.35', // Len: 132 (not supported)
];
Expand Down
37 changes: 24 additions & 13 deletions lib/php-jwt/src/JWT.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class JWT
public static $supported_algs = [
'ES384' => ['openssl', 'SHA384'],
'ES256' => ['openssl', 'SHA256'],
'ES256K' => ['openssl', 'SHA256'],
'HS256' => ['hash_hmac', 'SHA256'],
'HS384' => ['hash_hmac', 'SHA384'],
'HS512' => ['hash_hmac', 'SHA512'],
Expand All @@ -76,7 +77,7 @@ class JWT
*
* @return stdClass The JWT's payload as a PHP object
*
* @throws InvalidArgumentException Provided key/key-array was empty
* @throws InvalidArgumentException Provided key/key-array was empty or malformed
* @throws DomainException Provided JWT is malformed
* @throws UnexpectedValueException Provided JWT was invalid
* @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed
Expand Down Expand Up @@ -132,11 +133,11 @@ public static function decode(
// See issue #351
throw new UnexpectedValueException('Incorrect key for this algorithm');
}
if ($header->alg === 'ES256' || $header->alg === 'ES384') {
// OpenSSL expects an ASN.1 DER sequence for ES256/ES384 signatures
if (\in_array($header->alg, ['ES256', 'ES256K', 'ES384'], true)) {
// OpenSSL expects an ASN.1 DER sequence for ES256/ES256K/ES384 signatures
$sig = self::signatureToDER($sig);
}
if (!self::verify("{$headb64}.{$bodyb64}", $sig, $key->getKeyMaterial(), $header->alg)) { // TODO: Remove this modification in MDL-76415.
if (!self::verify("{$headb64}.{$bodyb64}", $sig, $key->getKeyMaterial(), $header->alg)) {
throw new SignatureInvalidException('Signature verification failed');
}

Expand Down Expand Up @@ -166,12 +167,12 @@ public static function decode(
}

/**
* Converts and signs a PHP object or array into a JWT string.
* Converts and signs a PHP array into a JWT string.
*
* @param array<mixed> $payload PHP array
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
* @param string $alg Supported algorithms are 'ES384','ES256', 'HS256', 'HS384',
* 'HS512', 'RS256', 'RS384', and 'RS512'
* @param string $alg Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256',
* 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
* @param string $keyId
* @param array<string, string> $head An array with header elements to attach
*
Expand Down Expand Up @@ -210,8 +211,8 @@ public static function encode(
*
* @param string $msg The message to sign
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
* @param string $alg Supported algorithms are 'ES384','ES256', 'HS256', 'HS384',
* 'HS512', 'RS256', 'RS384', and 'RS512'
* @param string $alg Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256',
* 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
*
* @return string An encrypted message
*
Expand All @@ -238,7 +239,7 @@ public static function sign(
if (!$success) {
throw new DomainException('OpenSSL unable to sign data');
}
if ($alg === 'ES256') {
if ($alg === 'ES256' || $alg === 'ES256K') {
$signature = self::signatureFromDER($signature, 256);
} elseif ($alg === 'ES384') {
$signature = self::signatureFromDER($signature, 384);
Expand All @@ -255,6 +256,9 @@ public static function sign(
// The last non-empty line is used as the key.
$lines = array_filter(explode("\n", $key));
$key = base64_decode((string) end($lines));
if (\strlen($key) === 0) {
throw new DomainException('Key cannot be empty string');
}
return sodium_crypto_sign_detached($msg, $key);
} catch (Exception $e) {
throw new DomainException($e->getMessage(), 0, $e);
Expand Down Expand Up @@ -312,6 +316,12 @@ private static function verify(
// The last non-empty line is used as the key.
$lines = array_filter(explode("\n", $keyMaterial));
$key = base64_decode((string) end($lines));
if (\strlen($key) === 0) {
throw new DomainException('Key cannot be empty string');
}
if (\strlen($signature) === 0) {
throw new DomainException('Signature cannot be empty string');
}
return sodium_crypto_sign_verify_detached($signature, $msg, $key);
} catch (Exception $e) {
throw new DomainException($e->getMessage(), 0, $e);
Expand Down Expand Up @@ -425,14 +435,15 @@ private static function getKey(
return $keyOrKeyArray;
}

if (empty($kid)) {
throw new UnexpectedValueException('"kid" empty, unable to lookup correct key');
}

if ($keyOrKeyArray instanceof CachedKeySet) {
// Skip "isset" check, as this will automatically refresh if not set
return $keyOrKeyArray[$kid];
}

if (empty($kid)) {
throw new UnexpectedValueException('"kid" empty, unable to lookup correct key');
}
if (!isset($keyOrKeyArray[$kid])) {
throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key');
}
Expand Down
2 changes: 1 addition & 1 deletion lib/thirdpartylibs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ All rights reserved.</copyright>
<location>php-jwt</location>
<name>A simple library to encode and decode JSON Web Tokens (JWT) in PHP, conforming to RFC 7519</name>
<description>A simple library to encode and decode JSON Web Tokens (JWT) in PHP, conforming to RFC 7519</description>
<version>6.3.0</version>
<version>6.4.0</version>
<license>BSD</license>
<licenseversion>3-Clause</licenseversion>
<repository>https://github.com/firebase/php-jwt</repository>
Expand Down

0 comments on commit 5c0e75d

Please sign in to comment.