Skip to content

Commit 8876ef8

Browse files
committed
fix(DiscoveryService): Handle missing kid
Signed-off-by: Git'Fellow <12234510+solracsf@users.noreply.github.com>
1 parent 956a873 commit 8876ef8

1 file changed

Lines changed: 51 additions & 17 deletions

File tree

lib/Service/DiscoveryService.php

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -121,35 +121,69 @@ public function buildAuthorizationUrl(string $authorizationEndpoint, array $extr
121121
/**
122122
* Inspired by https://github.com/snake/moodle/compare/880462a1685...MDL-77077-master
123123
*
124-
* @param array $jwks
125-
* @param string $jwt
126-
* @return array
127-
* @throws \Exception
124+
* @param array $jwks The JSON Web Key Set
125+
* @param string $jwt The JWT token
126+
* @return array The modified JWKS
127+
* @throws \RuntimeException if no matching key is found or algorithm is unsupported
128128
*/
129129
private function fixJwksAlg(array $jwks, string $jwt): array {
130-
$jwtParts = explode('.', $jwt);
131-
$jwtHeader = json_decode(JWT::urlsafeB64Decode($jwtParts[0]), true);
132-
if (!isset($jwtHeader['kid'])) {
133-
throw new \Exception('Error: kid must be provided in JWT header.');
130+
$jwtParts = explode('.', $jwt, 3);
131+
$header = json_decode(JWT::urlsafeB64Decode($jwtParts[0]), true);
132+
$kid = $header['kid'] ?? null;
133+
$alg = $header['alg'] ?? null;
134+
135+
$expectedKty = self::SUPPORTED_JWK_ALGS[$alg] ?? null;
136+
if ($expectedKty === null) {
137+
throw new \RuntimeException('Unsupported JWT alg: ' . ($alg ?? 'unknown'));
138+
}
139+
140+
$keys = $jwks['keys'] ?? null;
141+
if (!is_array($keys)) {
142+
throw new \RuntimeException('Invalid JWKS: missing "keys" array');
134143
}
135144

136-
foreach ($jwks['keys'] as $index => $key) {
137-
// Only fix the key being referred to in the JWT.
138-
if ($jwtHeader['kid'] != $key['kid']) {
145+
$matchingIndex = null;
146+
147+
foreach ($keys as $index => $key) {
148+
$keyKty = $key['kty'] ?? null;
149+
$keyUse = $key['use'] ?? null;
150+
151+
// Skip keys with incompatible type
152+
if ($keyKty !== $expectedKty) {
139153
continue;
140154
}
141155

142-
// Only fix the key if the alg is missing.
143-
if (!empty($key['alg'])) {
156+
// Skip keys not intended for signature
157+
if ($keyUse !== null && $keyUse !== 'sig') {
144158
continue;
145159
}
146160

147-
// The header alg must match the key type (family) specified in the JWK's kty.
148-
if (!isset(self::SUPPORTED_JWK_ALGS[$jwtHeader['alg']]) || self::SUPPORTED_JWK_ALGS[$jwtHeader['alg']] !== $key['kty']) {
149-
throw new \Exception('Error: Alg specified in the JWT header is incompatible with the JWK key type');
161+
// If JWT has a kid, match strictly
162+
if ($kid !== null) {
163+
if (($key['kid'] ?? null) !== $kid) {
164+
continue;
165+
}
166+
$matchingIndex = $index;
167+
break;
150168
}
151169

152-
$jwks['keys'][$index]['alg'] = $jwtHeader['alg'];
170+
// If no kid, select the first compatible key
171+
if ($matchingIndex === null) {
172+
$matchingIndex = $index;
173+
}
174+
}
175+
176+
if ($matchingIndex === null) {
177+
throw new \RuntimeException(sprintf(
178+
'No matching key found in JWKS (alg=%s, kid=%s)',
179+
$alg ?? 'unknown',
180+
$kid ?? 'none'
181+
));
182+
}
183+
184+
// Set 'alg' field if missing
185+
if (empty($jwks['keys'][$matchingIndex]['alg'])) {
186+
$jwks['keys'][$matchingIndex]['alg'] = $alg;
153187
}
154188

155189
return $jwks;

0 commit comments

Comments
 (0)