Skip to content

Commit

Permalink
feat: add defaultAlg param (#426)
Browse files Browse the repository at this point in the history
  • Loading branch information
bshaffer committed May 13, 2022
1 parent 2308363 commit d28e6df
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 9 deletions.
10 changes: 8 additions & 2 deletions src/CachedKeySet.php
Expand Up @@ -68,21 +68,27 @@ class CachedKeySet implements ArrayAccess
* @var int
*/
private $maxCallsPerMinute = 10;
/**
* @var string|null
*/
private $defaultAlg;

public function __construct(
string $jwksUri,
ClientInterface $httpClient,
RequestFactoryInterface $httpFactory,
CacheItemPoolInterface $cache,
int $expiresAfter = null,
bool $rateLimit = false
bool $rateLimit = false,
string $defaultAlg = null
) {
$this->jwksUri = $jwksUri;
$this->httpClient = $httpClient;
$this->httpFactory = $httpFactory;
$this->cache = $cache;
$this->expiresAfter = $expiresAfter;
$this->rateLimit = $rateLimit;
$this->defaultAlg = $defaultAlg;
$this->setCacheKeys();
}

Expand Down Expand Up @@ -143,7 +149,7 @@ private function keyIdExists(string $keyId): bool
$request = $this->httpFactory->createRequest('get', $this->jwksUri);
$jwksResponse = $this->httpClient->sendRequest($request);
$jwks = json_decode((string) $jwksResponse->getBody(), true);
$this->keySet = $keySetToCache = JWK::parseKeySet($jwks);
$this->keySet = $keySetToCache = JWK::parseKeySet($jwks, $this->defaultAlg);

if (!isset($this->keySet[$keyId])) {
return false;
Expand Down
22 changes: 15 additions & 7 deletions src/JWK.php
Expand Up @@ -24,6 +24,8 @@ class JWK
* Parse a set of JWK keys
*
* @param array<mixed> $jwks The JSON Web Key Set as an associative array
* @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the
* JSON Web Key Set
*
* @return array<string, Key> An associative array of key IDs (kid) to Key objects
*
Expand All @@ -33,7 +35,7 @@ class JWK
*
* @uses parseKey
*/
public static function parseKeySet(array $jwks): array
public static function parseKeySet(array $jwks, string $defaultAlg = null): array
{
$keys = [];

Expand All @@ -47,7 +49,7 @@ public static function parseKeySet(array $jwks): array

foreach ($jwks['keys'] as $k => $v) {
$kid = isset($v['kid']) ? $v['kid'] : $k;
if ($key = self::parseKey($v)) {
if ($key = self::parseKey($v, $defaultAlg)) {
$keys[(string) $kid] = $key;
}
}
Expand All @@ -63,6 +65,8 @@ public static function parseKeySet(array $jwks): array
* Parse a JWK key
*
* @param array<mixed> $jwk An individual JWK
* @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the
* JSON Web Key Set
*
* @return Key The key object for the JWK
*
Expand All @@ -72,7 +76,7 @@ public static function parseKeySet(array $jwks): array
*
* @uses createPemFromModulusAndExponent
*/
public static function parseKey(array $jwk): ?Key
public static function parseKey(array $jwk, string $defaultAlg = null): ?Key
{
if (empty($jwk)) {
throw new InvalidArgumentException('JWK must not be empty');
Expand All @@ -83,10 +87,14 @@ public static function parseKey(array $jwk): ?Key
}

if (!isset($jwk['alg'])) {
// The "alg" parameter is optional in a KTY, but is required for parsing in
// this library. Add it manually to your JWK array if it doesn't already exist.
// @see https://datatracker.ietf.org/doc/html/rfc7517#section-4.4
throw new UnexpectedValueException('JWK must contain an "alg" parameter');
if (\is_null($defaultAlg)) {
// The "alg" parameter is optional in a KTY, but an algorithm is required
// for parsing in this library. Use the $defaultAlg parameter when parsing the
// key set in order to prevent this error.
// @see https://datatracker.ietf.org/doc/html/rfc7517#section-4.4
throw new UnexpectedValueException('JWK must contain an "alg" parameter');
}
$jwk['alg'] = $defaultAlg;
}

switch ($jwk['kty']) {
Expand Down
16 changes: 16 additions & 0 deletions tests/CachedKeySetTest.php
Expand Up @@ -18,6 +18,7 @@ class CachedKeySetTest extends TestCase
private $testJwksUriKey = 'jwkshttpsjwk.uri';
private $testJwks1 = '{"keys": [{"kid":"foo","kty":"RSA","alg":"foo","n":"","e":""}]}';
private $testJwks2 = '{"keys": [{"kid":"bar","kty":"RSA","alg":"bar","n":"","e":""}]}';
private $testJwks3 = '{"keys": [{"kid":"baz","kty":"RSA","n":"","e":""}]}';

private $googleRsaUri = 'https://www.googleapis.com/oauth2/v3/certs';
// private $googleEcUri = 'https://www.gstatic.com/iap/verify/public_key-jwk';
Expand Down Expand Up @@ -95,6 +96,21 @@ public function testWithExistingKeyId()
$this->assertEquals('foo', $cachedKeySet['foo']->getAlgorithm());
}

public function testWithDefaultAlg()
{
$cachedKeySet = new CachedKeySet(
$this->testJwksUri,
$this->getMockHttpClient($this->testJwks3),
$this->getMockHttpFactory(),
$this->getMockEmptyCache(),
null,
false,
'baz256'
);
$this->assertInstanceOf(Key::class, $cachedKeySet['baz']);
$this->assertEquals('baz256', $cachedKeySet['baz']->getAlgorithm());
}

public function testKeyIdIsCached()
{
$cacheItem = $this->prophesize(CacheItemInterface::class);
Expand Down
12 changes: 12 additions & 0 deletions tests/JWKTest.php
Expand Up @@ -58,6 +58,18 @@ public function testParsePrivateKeyWithoutAlg()
JWK::parseKeySet($jwkSet);
}

public function testParsePrivateKeyWithoutAlgWithDefaultAlgParameter()
{
$jwkSet = json_decode(
file_get_contents(__DIR__ . '/data/rsa-jwkset.json'),
true
);
unset($jwkSet['keys'][0]['alg']);

$jwks = JWK::parseKeySet($jwkSet, 'foo');
$this->assertEquals('foo', $jwks['jwk1']->getAlgorithm());
}

public function testParseKeyWithEmptyDValue()
{
$jwkSet = json_decode(
Expand Down

0 comments on commit d28e6df

Please sign in to comment.