Skip to content

Commit d28e6df

Browse files
authored
feat: add defaultAlg param (#426)
1 parent 2308363 commit d28e6df

File tree

4 files changed

+51
-9
lines changed

4 files changed

+51
-9
lines changed

src/CachedKeySet.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,21 +68,27 @@ class CachedKeySet implements ArrayAccess
6868
* @var int
6969
*/
7070
private $maxCallsPerMinute = 10;
71+
/**
72+
* @var string|null
73+
*/
74+
private $defaultAlg;
7175

7276
public function __construct(
7377
string $jwksUri,
7478
ClientInterface $httpClient,
7579
RequestFactoryInterface $httpFactory,
7680
CacheItemPoolInterface $cache,
7781
int $expiresAfter = null,
78-
bool $rateLimit = false
82+
bool $rateLimit = false,
83+
string $defaultAlg = null
7984
) {
8085
$this->jwksUri = $jwksUri;
8186
$this->httpClient = $httpClient;
8287
$this->httpFactory = $httpFactory;
8388
$this->cache = $cache;
8489
$this->expiresAfter = $expiresAfter;
8590
$this->rateLimit = $rateLimit;
91+
$this->defaultAlg = $defaultAlg;
8692
$this->setCacheKeys();
8793
}
8894

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

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

src/JWK.php

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class JWK
2424
* Parse a set of JWK keys
2525
*
2626
* @param array<mixed> $jwks The JSON Web Key Set as an associative array
27+
* @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the
28+
* JSON Web Key Set
2729
*
2830
* @return array<string, Key> An associative array of key IDs (kid) to Key objects
2931
*
@@ -33,7 +35,7 @@ class JWK
3335
*
3436
* @uses parseKey
3537
*/
36-
public static function parseKeySet(array $jwks): array
38+
public static function parseKeySet(array $jwks, string $defaultAlg = null): array
3739
{
3840
$keys = [];
3941

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

4850
foreach ($jwks['keys'] as $k => $v) {
4951
$kid = isset($v['kid']) ? $v['kid'] : $k;
50-
if ($key = self::parseKey($v)) {
52+
if ($key = self::parseKey($v, $defaultAlg)) {
5153
$keys[(string) $kid] = $key;
5254
}
5355
}
@@ -63,6 +65,8 @@ public static function parseKeySet(array $jwks): array
6365
* Parse a JWK key
6466
*
6567
* @param array<mixed> $jwk An individual JWK
68+
* @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the
69+
* JSON Web Key Set
6670
*
6771
* @return Key The key object for the JWK
6872
*
@@ -72,7 +76,7 @@ public static function parseKeySet(array $jwks): array
7276
*
7377
* @uses createPemFromModulusAndExponent
7478
*/
75-
public static function parseKey(array $jwk): ?Key
79+
public static function parseKey(array $jwk, string $defaultAlg = null): ?Key
7680
{
7781
if (empty($jwk)) {
7882
throw new InvalidArgumentException('JWK must not be empty');
@@ -83,10 +87,14 @@ public static function parseKey(array $jwk): ?Key
8387
}
8488

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

92100
switch ($jwk['kty']) {

tests/CachedKeySetTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class CachedKeySetTest extends TestCase
1818
private $testJwksUriKey = 'jwkshttpsjwk.uri';
1919
private $testJwks1 = '{"keys": [{"kid":"foo","kty":"RSA","alg":"foo","n":"","e":""}]}';
2020
private $testJwks2 = '{"keys": [{"kid":"bar","kty":"RSA","alg":"bar","n":"","e":""}]}';
21+
private $testJwks3 = '{"keys": [{"kid":"baz","kty":"RSA","n":"","e":""}]}';
2122

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

99+
public function testWithDefaultAlg()
100+
{
101+
$cachedKeySet = new CachedKeySet(
102+
$this->testJwksUri,
103+
$this->getMockHttpClient($this->testJwks3),
104+
$this->getMockHttpFactory(),
105+
$this->getMockEmptyCache(),
106+
null,
107+
false,
108+
'baz256'
109+
);
110+
$this->assertInstanceOf(Key::class, $cachedKeySet['baz']);
111+
$this->assertEquals('baz256', $cachedKeySet['baz']->getAlgorithm());
112+
}
113+
98114
public function testKeyIdIsCached()
99115
{
100116
$cacheItem = $this->prophesize(CacheItemInterface::class);

tests/JWKTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,18 @@ public function testParsePrivateKeyWithoutAlg()
5858
JWK::parseKeySet($jwkSet);
5959
}
6060

61+
public function testParsePrivateKeyWithoutAlgWithDefaultAlgParameter()
62+
{
63+
$jwkSet = json_decode(
64+
file_get_contents(__DIR__ . '/data/rsa-jwkset.json'),
65+
true
66+
);
67+
unset($jwkSet['keys'][0]['alg']);
68+
69+
$jwks = JWK::parseKeySet($jwkSet, 'foo');
70+
$this->assertEquals('foo', $jwks['jwk1']->getAlgorithm());
71+
}
72+
6173
public function testParseKeyWithEmptyDValue()
6274
{
6375
$jwkSet = json_decode(

0 commit comments

Comments
 (0)