Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed
- Stop adding ?schema=openid to userinfo endpoint URL. #449
- Add support for EdDSA signed JWTs

## [1.0.1] - 2024-09-13

Expand Down
53 changes: 45 additions & 8 deletions src/OpenIDConnectClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ class OpenIDConnectClient

/**
* @var mixed holds well-known openid configuration parameters, like policy for MS Azure AD B2C User Flow
* @see https://docs.microsoft.com/en-us/azure/active-directory-b2c/user-flow-overview
* @see https://docs.microsoft.com/en-us/azure/active-directory-b2c/user-flow-overview
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a whitespace fix

*/
private $wellKnownConfigParameters = [];

Expand Down Expand Up @@ -686,7 +686,7 @@ public function getRedirectURL(): string
} else {
$protocol = 'http';
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a whitespace fix

if (isset($_SERVER['HTTP_X_FORWARDED_PORT'])) {
$port = (int)$_SERVER['HTTP_X_FORWARDED_PORT'];
} elseif (isset($_SERVER['SERVER_PORT'])) {
Expand All @@ -709,7 +709,7 @@ public function getRedirectURL(): string
}

$port = (443 === $port) || (80 === $port) ? '' : ':' . $port;

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a whitespace fix

$explodedRequestUri = isset($_SERVER['REQUEST_URI']) ? explode('?', $_SERVER['REQUEST_URI']) : [];
return sprintf('%s://%s%s/%s', $protocol, $host, $port, trim(reset($explodedRequestUri), '/'));
}
Expand Down Expand Up @@ -1087,6 +1087,18 @@ private function verifyRSAJWTSignature(string $hashType, stdClass $key, $payload
return $key->verify($payload, $signature);
}

private function verifyEdDSAJWTsignature($key, $payload, $signature) {
if (!(property_exists($key, 'x'))) {
throw new OpenIDConnectClientException('Malformed key object');
}

if (!function_exists("sodium_crypto_sign_verify_detached")) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be handled via composer as well. Please adjust composer.json and add
"ext-sodium": "*",

throw new OpenIDConnectClientException('sodium_crypto_sign_verify_detached support unavailable.');
}

return sodium_crypto_sign_verify_detached($signature, $payload, base64url_decode($key->x));
}

private function verifyHMACJWTSignature(string $hashType, string $key, string $payload, string $signature): bool
{
$expected = hash_hmac($hashType, $payload, $key, true);
Expand Down Expand Up @@ -1145,6 +1157,24 @@ public function verifyJWTSignature(string $jwt): bool
$jwk,
$payload, $signature, $signatureType);
break;
case 'EdDSA':
if (isset($header->jwk)) {
$jwk = $header->jwk;
$this->verifyJWKHeader($jwk);
} else {
$jwks = json_decode($this->fetchURL($this->getProviderConfigValue('jwks_uri')));
if ($jwks === NULL) {
throw new OpenIDConnectClientException('Error decoding JSON from jwks_uri');
}
$jwk = $this->getKeyForHeader($jwks->keys, $header);
}

$verified = $this->verifyEdDSAJWTsignature(
$jwk,
$payload,
$signature
);
break;
case 'HS256':
case 'HS512':
case 'HS384':
Expand Down Expand Up @@ -1192,12 +1222,19 @@ protected function validateIssuer(string $iss): bool
protected function verifyJWTClaims($claims, string $accessToken = null): bool
{
if(isset($claims->at_hash, $accessToken)) {
if(isset($this->getIdTokenHeader()->alg) && $this->getIdTokenHeader()->alg !== 'none') {
$bit = substr($this->getIdTokenHeader()->alg, 2, 3);
} else {
// TODO: Error case. throw exception???
$bit = '256';
switch($this->getIdTokenHeader()->alg ?? '') {
case 'EdDSA':
$bit = '512';
break;
case 'none':
case '':
// TODO: Error case. throw exception???
$bit = '256';
break;
default:
$bit = substr($this->getIdTokenHeader()->alg, 2, 3);
}

$len = ((int)$bit)/16;
$expected_at_hash = $this->urlEncode(substr(hash('sha'.$bit, $accessToken, true), 0, $len));
}
Expand Down
4 changes: 3 additions & 1 deletion tests/TokenVerificationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ public function testTokenVerification($alg, $jwt)
public function providesTokens(): array
{
return [
'PS256' => ['ps256', 'eyJhbGciOiJQUzI1NiIsImtpZCI6Imtvbm5lY3RkLXRva2Vucy1zaWduaW5nLWtleSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJrcG9wLWh0dHBzOi8va29wYW5vLmRlbW8vbWVldC8iLCJleHAiOjE1NjgzNzE0NjEsImp0aSI6IkpkR0tDbEdOTXl2VXJpcmlRRUlWUXZCVmttT2FfQkRjIiwiaWF0IjoxNTY4MzcxMjIxLCJpc3MiOiJodHRwczovL2tvcGFuby5kZW1vIiwic3ViIjoiUHpUVWp3NHBlXzctWE5rWlBILXJxVHE0MTQ1Z3lDdlRvQmk4V1E5bFBrcW5rbEc1aktvRU5LM21Qb0I1WGY1ZTM5dFRMR2RKWXBMNEJubXFnelpaX0FAa29ubmVjdCIsImtjLmlzQWNjZXNzVG9rZW4iOnRydWUsImtjLmF1dGhvcml6ZWRTY29wZXMiOlsicHJvZmlsZSIsImVtYWlsIiwia29wYW5vL2t3bSIsImtvcGFuby9nYyIsImtvcGFuby9rdnMiLCJvcGVuaWQiXSwia2MuYXV0aG9yaXplZENsYWltcyI6eyJpZF90b2tlbiI6eyJuYW1lIjpudWxsfX0sImtjLmlkZW50aXR5Ijp7ImtjLmkuZG4iOiJKb25hcyBCcmVra2UiLCJrYy5pLmlkIjoiQUFBQUFLd2hxVkJBMCs1SXN4bjdwMU13UkNVQkFBQUFCZ0FBQUJzQUFBQk5VVDA5QUFBQUFBPT0iLCJrYy5pLnVuIjoidXNlcjEiLCJrYy5pLnVzIjoiTVEifSwia2MucHJvdmlkZXIiOiJpZGVudGlmaWVyLWtjIn0.hGRuXvul2kOiALHexwYp5MBEJVwz1YV3ehyM3AOuwCoK2w5sJxdciqqY_TfXCKyO6nAEbYLK3J0CBOjfup_IG0aCZcwzjto8khYlc4ezXkGnFsbJBNQdDGkpHtWnioWx-OJ3cXvY9F8aOvjaq0gw11ZDAcqQl0g7LTbJ9-J_yx0pmy3NGai2JB30Fh1OgSDzYfxWnE0RRgZG-x68e65RXfSBaEGW85OUh4wihxO2zdTGAHJ3Iq_-QAG4yRbXZtLx3ZspG7LNmqG-YE3huy3Rd8u3xrJNhmUOfEnz3x07q7VW0cj9NedX98BAbj3iNvksQsE0oG0J_f_Tu8Ai8VbWB72sJuXZWxANDKdz0BBYLzXhsjXkNByRq9x3zqDVsX-cVHei_XudxEOVRBjhkvW2MmIjcAHNKCKsdar865-gFG9McP4PCcBlY28tC0Cvnzyi83LBfpGRXdl6MJunnUsKQ1C79iCoVI1doK1erFN959Q-TGJfJA3Tr5LNpuGawB5rpe1nDGWvmYhg3uYfNl8uTTyvNgvvejcflEb2DURuXdqABuSiP7RkDWYtzx6mq49G0tRxelBbvyjQ2id2QjmRRdQ6dHEZ2NCJ51b8OFoDJBtxN1CD62TTxa3FUqCdZAPAUR3hHn_69vYq82MR514s-Gb67A6j2PbMPFATQP2UdK8']
'PS256' => ['ps256', 'eyJhbGciOiJQUzI1NiIsImtpZCI6Imtvbm5lY3RkLXRva2Vucy1zaWduaW5nLWtleSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJrcG9wLWh0dHBzOi8va29wYW5vLmRlbW8vbWVldC8iLCJleHAiOjE1NjgzNzE0NjEsImp0aSI6IkpkR0tDbEdOTXl2VXJpcmlRRUlWUXZCVmttT2FfQkRjIiwiaWF0IjoxNTY4MzcxMjIxLCJpc3MiOiJodHRwczovL2tvcGFuby5kZW1vIiwic3ViIjoiUHpUVWp3NHBlXzctWE5rWlBILXJxVHE0MTQ1Z3lDdlRvQmk4V1E5bFBrcW5rbEc1aktvRU5LM21Qb0I1WGY1ZTM5dFRMR2RKWXBMNEJubXFnelpaX0FAa29ubmVjdCIsImtjLmlzQWNjZXNzVG9rZW4iOnRydWUsImtjLmF1dGhvcml6ZWRTY29wZXMiOlsicHJvZmlsZSIsImVtYWlsIiwia29wYW5vL2t3bSIsImtvcGFuby9nYyIsImtvcGFuby9rdnMiLCJvcGVuaWQiXSwia2MuYXV0aG9yaXplZENsYWltcyI6eyJpZF90b2tlbiI6eyJuYW1lIjpudWxsfX0sImtjLmlkZW50aXR5Ijp7ImtjLmkuZG4iOiJKb25hcyBCcmVra2UiLCJrYy5pLmlkIjoiQUFBQUFLd2hxVkJBMCs1SXN4bjdwMU13UkNVQkFBQUFCZ0FBQUJzQUFBQk5VVDA5QUFBQUFBPT0iLCJrYy5pLnVuIjoidXNlcjEiLCJrYy5pLnVzIjoiTVEifSwia2MucHJvdmlkZXIiOiJpZGVudGlmaWVyLWtjIn0.hGRuXvul2kOiALHexwYp5MBEJVwz1YV3ehyM3AOuwCoK2w5sJxdciqqY_TfXCKyO6nAEbYLK3J0CBOjfup_IG0aCZcwzjto8khYlc4ezXkGnFsbJBNQdDGkpHtWnioWx-OJ3cXvY9F8aOvjaq0gw11ZDAcqQl0g7LTbJ9-J_yx0pmy3NGai2JB30Fh1OgSDzYfxWnE0RRgZG-x68e65RXfSBaEGW85OUh4wihxO2zdTGAHJ3Iq_-QAG4yRbXZtLx3ZspG7LNmqG-YE3huy3Rd8u3xrJNhmUOfEnz3x07q7VW0cj9NedX98BAbj3iNvksQsE0oG0J_f_Tu8Ai8VbWB72sJuXZWxANDKdz0BBYLzXhsjXkNByRq9x3zqDVsX-cVHei_XudxEOVRBjhkvW2MmIjcAHNKCKsdar865-gFG9McP4PCcBlY28tC0Cvnzyi83LBfpGRXdl6MJunnUsKQ1C79iCoVI1doK1erFN959Q-TGJfJA3Tr5LNpuGawB5rpe1nDGWvmYhg3uYfNl8uTTyvNgvvejcflEb2DURuXdqABuSiP7RkDWYtzx6mq49G0tRxelBbvyjQ2id2QjmRRdQ6dHEZ2NCJ51b8OFoDJBtxN1CD62TTxa3FUqCdZAPAUR3hHn_69vYq82MR514s-Gb67A6j2PbMPFATQP2UdK8'],
'RS512' => ['rs512', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiIsImtpZCI6IjNiNGI3ZmNiYWM4MTAwZmU1Mjg5MTI3NzY0MTcwMDlhIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.rBovVXkUymQRHeTolWO07nJyw2NJgho8JbyjPAbZ2VAcQKvrjL8SNrkIkdjuI4FDfJQvu_NOlsKu0LhGNUJATxQhGvFqWOfF9nggFtl7ZTpAu3E6Xm1s-VGSy9LOQvmBiFvXDQJ7bd0xn0Ld1XO2lIVLHItoPr6Gw1m0_vdtUlMrX_dF4ZrJCBaQWXw114zgwH4WIZ8nvvDRdw3n1FLvPrYFZzRBI0Z8wXkBVEnw_kxlQWi7waOp-5NwZFYF5Tei1KUDQMSUcxAckNh01it8UdHoQf4HhgRF_GeDi9HJRVPUCO4N1wVtKRVqDMKRvQxZCn-_ohsUHA2u1-CUakbsd1EDkP8SaPFtvtW0QKB7K3KWQVSHUh7Kp6cct4scbDCGzXPwrgGyKF9V3d1g4fed6epkFlFnif0ZM9JvMSp7ult40HdC7D-9YCdJ39d5T2RGVOeQEKEk0UqxanG-dbp2RmjMYms70h75XatR7Bfbt1bsDB0dwnEwbwFLps1H_dVS'],
'EdDSA' => ['eddsa', 'eyJraWQiOiItMTkwOTU3MjI1NyIsImFsZyI6IkVkRFNBIn0.eyJqdGkiOiIyMjkxNmYzYy05MDkzLTQ4MTMtODM5Ny1mMTBlNmI3MDRiNjgiLCJkZWxlZ2F0aW9uSWQiOiJiNGFlNDdhNy02MjVhLTQ2MzAtOTcyNy00NTc2NGE3MTJjY2UiLCJleHAiOjE2NTUyNzkxMDksIm5iZiI6MTY1NTI3ODgwOSwic2NvcGUiOiJyZWFkIG9wZW5pZCIsImlzcyI6Imh0dHBzOi8vaWRzdnIuZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VybmFtZSIsImF1ZCI6ImFwaS5leGFtcGxlLmNvbSIsImlhdCI6MTY1NTI3ODgwOSwicHVycG9zZSI6ImFjY2Vzc190b2tlbiJ9.rjeE8D_e4RYzgvpu-nOwwx7PWMiZyDZwkwO6RiHR5t8g4JqqVokUKQt-oST1s45wubacfeDSFogOrIhe3UHDAg']
];
}
}
11 changes: 11 additions & 0 deletions tests/data/jwks-eddsa.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"keys": [
{
"kty": "OKP",
"kid": "-1909572257",
"alg": "EdDSA",
"crv": "Ed25519",
"x": "XWxGtApfcqmKI7p0OKnF5JSEWMVoLsytFXLEP7xZ_l8"
}
]
}
13 changes: 13 additions & 0 deletions tests/data/jwks-rs512.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"keys": [
{
"alg": "RS512",
"e": "AQAB",
"key_ops": ["verify"],
"kty": "RSA",
"n": "sOFO_fifz82fLvrrOo9S3lBU347DrM3DmzH47SNw1ILMkNwDMK38on-GNcl59If0DTEEr7WITPLJ5FS_Rf3XcX09ui2Ol2y7LhMLpL3AQMf7kFyWKhMtOiEkaUrUc_nozyAF4tsKRAf8anYrImaS1NHHmsLBJ1QAWYd4N6b5L09zHX6LkOrLqBY-xgI1W4hQUOKNwF2BoA6KC8eX89iDqgRKQFXLt2pV6h55BRjJ7EBEkoT7KvaACVoUNL4Y5Pg2CLwLm0bYPMugaO-fLPp9tok5mdTD2fQZRGChzIQjWl6BLT2uO639Ccwmtb3VkjHDv061UI8TIAK4_mWDqscOUdbRpa5Z-oPq7ZM7eps83YxJXKvdkZzmx6tJayrNYH7BEHP03p3TZ1pDszDOLD-kmCppja0SG9NeH4Zu6PqxaNtimOKEakmUx0s9MhnlYWUZnB0mdl4YXr_Ypd0nsOJLrpvvjlQacozDIgflTaXcpZJkiwiRWoBmHuQXJHTuqP3_",
"use": "sig",
"kid": "3b4b7fcbac8100fe528912776417009a"
}
]
}