diff options
Diffstat (limited to 'vendor/jumbojett/openid-connect-php/src/OpenIDConnectClient.php')
-rw-r--r-- | vendor/jumbojett/openid-connect-php/src/OpenIDConnectClient.php | 790 |
1 files changed, 470 insertions, 320 deletions
diff --git a/vendor/jumbojett/openid-connect-php/src/OpenIDConnectClient.php b/vendor/jumbojett/openid-connect-php/src/OpenIDConnectClient.php index 6aa80b1..bac366c 100644 --- a/vendor/jumbojett/openid-connect-php/src/OpenIDConnectClient.php +++ b/vendor/jumbojett/openid-connect-php/src/OpenIDConnectClient.php @@ -22,16 +22,18 @@ namespace Jumbojett; +/** + * + * JWT signature verification support by Jonathan Reed <[email protected]> + * Licensed under the same license as the rest of this file. + * + * phpseclib is required to validate the signatures of some tokens. + * It can be downloaded from: http://phpseclib.sourceforge.net/ + */ -use Error; -use Exception; -use phpseclib3\Crypt\PublicKeyLoader; -use phpseclib3\Crypt\RSA; -use phpseclib3\Math\BigInteger; -use stdClass; -use function bin2hex; -use function is_object; -use function random_bytes; +if (!class_exists('\phpseclib3\Crypt\RSA') && !class_exists('\phpseclib\Crypt\RSA') && !class_exists('Crypt_RSA')) { + user_error('Unable to find phpseclib Crypt/RSA.php. Ensure phpseclib is installed and in include_path before you include this file'); +} /** * A wrapper around base64_decode which decodes Base64URL-encoded data, @@ -39,7 +41,7 @@ use function random_bytes; * @param string $base64url * @return bool|string */ -function base64url_decode(string $base64url) { +function base64url_decode($base64url) { return base64_decode(b64url2b64($base64url)); } @@ -51,8 +53,7 @@ function base64url_decode(string $base64url) { * @param string $base64url * @return string */ -function b64url2b64(string $base64url): string -{ +function b64url2b64($base64url) { // "Shouldn't" be necessary, but why not $padding = strlen($base64url) % 4; if ($padding > 0) { @@ -65,8 +66,19 @@ function b64url2b64(string $base64url): string /** * OpenIDConnect Exception Class */ -class OpenIDConnectClientException extends Exception +class OpenIDConnectClientException extends \Exception { + +} + +/** + * Require the CURL and JSON PHP extensions to be installed + */ +if (!function_exists('curl_init')) { + throw new OpenIDConnectClientException('OpenIDConnect needs the CURL PHP extension.'); +} +if (!function_exists('json_decode')) { + throw new OpenIDConnectClientException('OpenIDConnect needs the JSON PHP extension.'); } /** @@ -133,7 +145,7 @@ class OpenIDConnectClient protected $idToken; /** - * @var object stores the token response + * @var string stores the token response */ private $tokenResponse; @@ -148,14 +160,14 @@ class OpenIDConnectClient private $responseCode; /** - * @var string|null Content type from the server + * @var array holds response types */ - private $responseContentType; + private $responseTypes = []; /** - * @var array holds response types + * @var array holds a cache of info returned from the user info endpoint */ - private $responseTypes = []; + private $userInfo = []; /** * @var array holds authentication parameters @@ -173,7 +185,7 @@ class OpenIDConnectClient private $wellKnown = false; /** - * @var mixed holds well-known openid configuration parameters, like policy for MS Azure AD B2C User Flow + * @var mixed holds well-known opendid 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 */ private $wellKnownConfigParameters = []; @@ -194,7 +206,7 @@ class OpenIDConnectClient private $additionalJwks = []; /** - * @var object holds verified jwt claims + * @var array holds verified jwt claims */ protected $verifiedClaims = []; @@ -255,12 +267,13 @@ class OpenIDConnectClient private $token_endpoint_auth_methods_supported = ['client_secret_basic']; /** - * @param string|null $provider_url optional - * @param string|null $client_id optional - * @param string|null $client_secret optional - * @param string|null $issuer + * @param $provider_url string optional + * + * @param $client_id string optional + * @param $client_secret string optional + * @param null $issuer */ - public function __construct(string $provider_url = null, string $client_id = null, string $client_secret = null, string $issuer = null) { + public function __construct($provider_url = null, $client_id = null, $client_secret = null, $issuer = null) { $this->setProviderURL($provider_url); if ($issuer === null) { $this->setIssuer($provider_url); @@ -297,8 +310,8 @@ class OpenIDConnectClient * @return bool * @throws OpenIDConnectClientException */ - public function authenticate(): bool - { + public function authenticate() { + // Do a preemptive check to see if the provider has thrown an error from a previous redirect if (isset($_REQUEST['error'])) { $desc = isset($_REQUEST['error_description']) ? ' Description: ' . $_REQUEST['error_description'] : ''; @@ -320,7 +333,7 @@ class OpenIDConnectClient } // Do an OpenID Connect session check - if (!isset($_REQUEST['state']) || ($_REQUEST['state'] !== $this->getState())) { + if ($_REQUEST['state'] !== $this->getState()) { throw new OpenIDConnectClientException('Unable to determine state'); } @@ -331,26 +344,28 @@ class OpenIDConnectClient throw new OpenIDConnectClientException('User did not authorize openid scope.'); } - $id_token = $token_json->id_token; - $idTokenHeaders = $this->decodeJWT($id_token); - if (isset($idTokenHeaders->enc)) { - // Handle JWE - $id_token = $this->handleJweResponse($id_token); - } - - $claims = $this->decodeJWT($id_token, 1); + $claims = $this->decodeJWT($token_json->id_token, 1); // Verify the signature - $this->verifySignatures($id_token); + if ($this->canVerifySignatures()) { + if (!$this->getProviderConfigValue('jwks_uri')) { + throw new OpenIDConnectClientException ('Unable to verify signature due to no jwks_uri being defined'); + } + if (!$this->verifyJWTsignature($token_json->id_token)) { + throw new OpenIDConnectClientException ('Unable to verify signature'); + } + } else { + user_error('Warning: JWT signature verification unavailable.'); + } // Save the id token - $this->idToken = $id_token; + $this->idToken = $token_json->id_token; // Save the access token $this->accessToken = $token_json->access_token; // If this is a valid claim - if ($this->verifyJWTClaims($claims, $token_json->access_token)) { + if ($this->verifyJWTclaims($claims, $token_json->access_token)) { // Clean up the session a little $this->unsetNonce(); @@ -377,10 +392,13 @@ class OpenIDConnectClient // if we have no code but an id_token use that $id_token = $_REQUEST['id_token']; - $accessToken = $_REQUEST['access_token'] ?? null; + $accessToken = null; + if (isset($_REQUEST['access_token'])) { + $accessToken = $_REQUEST['access_token']; + } // Do an OpenID Connect session check - if (!isset($_REQUEST['state']) || ($_REQUEST['state'] !== $this->getState())) { + if ($_REQUEST['state'] !== $this->getState()) { throw new OpenIDConnectClientException('Unable to determine state'); } @@ -390,13 +408,22 @@ class OpenIDConnectClient $claims = $this->decodeJWT($id_token, 1); // Verify the signature - $this->verifySignatures($id_token); + if ($this->canVerifySignatures()) { + if (!$this->getProviderConfigValue('jwks_uri')) { + throw new OpenIDConnectClientException ('Unable to verify signature due to no jwks_uri being defined'); + } + if (!$this->verifyJWTsignature($id_token)) { + throw new OpenIDConnectClientException ('Unable to verify signature'); + } + } else { + user_error('Warning: JWT signature verification unavailable.'); + } // Save the id token $this->idToken = $id_token; // If this is a valid claim - if ($this->verifyJWTClaims($claims, $accessToken)) { + if ($this->verifyJWTclaims($claims, $accessToken)) { // Clean up the session a little $this->unsetNonce(); @@ -433,9 +460,10 @@ class OpenIDConnectClient * * @throws OpenIDConnectClientException */ - public function signOut(string $idToken, $redirect) { - $sign_out_endpoint = $this->getProviderConfigValue('end_session_endpoint'); + public function signOut($idToken, $redirect) { + $signout_endpoint = $this->getProviderConfigValue('end_session_endpoint'); + $signout_params = null; if($redirect === null){ $signout_params = ['id_token_hint' => $idToken]; } @@ -445,8 +473,8 @@ class OpenIDConnectClient 'post_logout_redirect_uri' => $redirect]; } - $sign_out_endpoint .= (strpos($sign_out_endpoint, '?') === false ? '?' : '&') . http_build_query( $signout_params, '', '&', $this->encType); - $this->redirect($sign_out_endpoint); + $signout_endpoint .= (strpos($signout_endpoint, '?') === false ? '?' : '&') . http_build_query( $signout_params, '', '&', $this->encType); + $this->redirect($signout_endpoint); } @@ -461,7 +489,7 @@ class OpenIDConnectClient * @return bool * @throws OpenIDConnectClientException */ - public function verifyLogoutToken(): bool + public function verifyLogoutToken() { if (isset($_REQUEST['logout_token'])) { $logout_token = $_REQUEST['logout_token']; @@ -469,11 +497,16 @@ class OpenIDConnectClient $claims = $this->decodeJWT($logout_token, 1); // Verify the signature - if (!$this->getProviderConfigValue('jwks_uri')) { - throw new OpenIDConnectClientException('Back-channel logout: Unable to verify signature due to no jwks_uri being defined'); + if ($this->canVerifySignatures()) { + if (!$this->getProviderConfigValue('jwks_uri')) { + throw new OpenIDConnectClientException('Back-channel logout: Unable to verify signature due to no jwks_uri being defined'); + } + if (!$this->verifyJWTsignature($logout_token)) { + throw new OpenIDConnectClientException('Back-channel logout: Unable to verify JWT signature'); + } } - if (!$this->verifyJWTSignature($logout_token)) { - throw new OpenIDConnectClientException('Back-channel logout: Unable to verify JWT signature'); + else { + user_error('Warning: JWT signature verification unavailable'); } // Verify Logout Token Claims @@ -496,7 +529,7 @@ class OpenIDConnectClient * @return bool * @throws OpenIDConnectClientException */ - public function verifyLogoutTokenClaims($claims): bool + public function verifyLogoutTokenClaims($claims) { // Verify that the Logout Token doesn't contain a nonce Claim. if (isset($claims->nonce)) { @@ -537,9 +570,7 @@ class OpenIDConnectClient return false; } // Validate the aud - $auds = $claims->aud; - $auds = is_array( $auds ) ? $auds : [ $auds ]; - if (!in_array($this->clientID, $auds, true)) { + if ((!$claims->aud === $this->clientID) || (!in_array($this->clientID, $claims->aud, true))) { return false; } // Validate the iat. At this point we can return true if it is ok @@ -553,25 +584,28 @@ class OpenIDConnectClient /** * @param array $scope - example: openid, given_name, etc... */ - public function addScope(array $scope) { - $this->scopes = array_merge($this->scopes, $scope); + public function addScope($scope) { + $this->scopes = array_merge($this->scopes, (array)$scope); } /** * @param array $param - example: prompt=login */ - public function addAuthParam(array $param) { - $this->authParams = array_merge($this->authParams, $param); + public function addAuthParam($param) { + $this->authParams = array_merge($this->authParams, (array)$param); } /** * @param array $param - example: post_logout_redirect_uris=[http://example.com/successful-logout] */ - public function addRegistrationParam(array $param) { - $this->registrationParams = array_merge($this->registrationParams, $param); + public function addRegistrationParam($param) { + $this->registrationParams = array_merge($this->registrationParams, (array)$param); } - public function setTokenEndpointAuthMethodsSupported(array $token_endpoint_auth_methods_supported) + /** + * @param array $token_endpoint_auth_methods_supported + */ + public function setTokenEndpointAuthMethodsSupported($token_endpoint_auth_methods_supported) { $this->token_endpoint_auth_methods_supported = $token_endpoint_auth_methods_supported; } @@ -584,16 +618,16 @@ class OpenIDConnectClient } /** - * Gets anything that we need configuration wise including endpoints, and other values + * Get's anything that we need configuration wise including endpoints, and other values * * @param string $param - * @param string|string[]|bool|null $default optional - * @return string|string[]|bool - * + * @param string $default optional * @throws OpenIDConnectClientException + * @return string|array + * */ - protected function getProviderConfigValue(string $param, $default = null) - { + protected function getProviderConfigValue($param, $default = null) { + // If the configuration value is not available, attempt to fetch it from a well known config endpoint // This is also known as auto "discovery" if (!isset($this->providerConfig[$param])) { @@ -604,16 +638,15 @@ class OpenIDConnectClient } /** - * Gets anything that we need configuration wise including endpoints, and other values + * Get's anything that we need configuration wise including endpoints, and other values * * @param string $param - * @param string|string[]|bool|null $default optional - * @return string|string[]|bool - * + * @param string $default optional * @throws OpenIDConnectClientException + * @return string + * */ - protected function getWellKnownConfigValue(string $param, $default = null) - { + private function getWellKnownConfigValue($param, $default = null) { // If the configuration value is not available, attempt to fetch it from a well known config endpoint // This is also known as auto "discovery" @@ -622,10 +655,13 @@ class OpenIDConnectClient if (count($this->wellKnownConfigParameters) > 0){ $well_known_config_url .= '?' . http_build_query($this->wellKnownConfigParameters) ; } - $this->wellKnown = json_decode($this->fetchURL($well_known_config_url), false); + $this->wellKnown = json_decode($this->fetchURL($well_known_config_url)); } - $value = $this->wellKnown->{$param} ?? false; + $value = false; + if(isset($this->wellKnown->{$param})){ + $value = $this->wellKnown->{$param}; + } if ($value) { return $value; @@ -636,11 +672,14 @@ class OpenIDConnectClient return $default; } - throw new OpenIDConnectClientException("The provider $param could not be fetched. Make sure your provider has a well known configuration available."); + throw new OpenIDConnectClientException("The provider {$param} could not be fetched. Make sure your provider has a well known configuration available."); } /** - * Set optional parameters for .well-known/openid-configuration + * Set optionnal parameters for .well-known/openid-configuration + * + * @param string $param + * */ public function setWellKnownConfigParameters(array $params = []){ $this->wellKnownConfigParameters=$params; @@ -650,7 +689,7 @@ class OpenIDConnectClient /** * @param string $url Sets redirect URL for auth flow */ - public function setRedirectURL(string $url) { + public function setRedirectURL($url) { if (parse_url($url,PHP_URL_HOST) !== false) { $this->redirectURL = $url; } @@ -661,8 +700,8 @@ class OpenIDConnectClient * * @return string */ - public function getRedirectURL(): string - { + public function getRedirectURL() { + // If the redirect URL has been set then return it. if (property_exists($this, 'redirectURL') && $this->redirectURL) { return $this->redirectURL; @@ -694,9 +733,9 @@ class OpenIDConnectClient } if (isset($_SERVER['HTTP_X_FORWARDED_PORT'])) { - $port = (int)$_SERVER['HTTP_X_FORWARDED_PORT']; + $port = intval($_SERVER['HTTP_X_FORWARDED_PORT']); } elseif (isset($_SERVER['SERVER_PORT'])) { - $port = $_SERVER['SERVER_PORT']; + $port = intval($_SERVER['SERVER_PORT']); } elseif ($protocol === 'https') { $port = 443; } else { @@ -725,10 +764,11 @@ class OpenIDConnectClient * @return string * @throws OpenIDConnectClientException */ - protected function generateRandString(): string - { + protected function generateRandString() { + // Error and Exception need to be catched in this order, see https://github.com/paragonie/random_compat/blob/master/README.md + // random_compat polyfill library should be removed if support for PHP versions < 7 is dropped try { - return bin2hex(random_bytes(16)); + return \bin2hex(\random_bytes(16)); } catch (Error $e) { throw new OpenIDConnectClientException('Random token generation failed.'); } catch (Exception $e) { @@ -740,7 +780,7 @@ class OpenIDConnectClient * Start Here * @return void * @throws OpenIDConnectClientException - * @throws Exception + * @throws \Exception */ private function requestAuthorization() { @@ -817,7 +857,7 @@ class OpenIDConnectClient // Convert token params to string format $post_params = http_build_query($post_data, '', '&', $this->encType); - return json_decode($this->fetchURL($token_endpoint, $post_params, $headers), false); + return json_decode($this->fetchURL($token_endpoint, $post_params, $headers)); } /** @@ -828,7 +868,7 @@ class OpenIDConnectClient * @return mixed * @throws OpenIDConnectClientException */ - public function requestResourceOwnerToken(bool $bClientAuth = false) { + public function requestResourceOwnerToken($bClientAuth = FALSE) { $token_endpoint = $this->getProviderConfigValue('token_endpoint'); $headers = []; @@ -856,7 +896,7 @@ class OpenIDConnectClient // Convert token params to string format $post_params = http_build_query($post_data, '', '&', $this->encType); - return json_decode($this->fetchURL($token_endpoint, $post_params, $headers), false); + return json_decode($this->fetchURL($token_endpoint, $post_params, $headers)); } @@ -868,7 +908,7 @@ class OpenIDConnectClient * @return mixed * @throws OpenIDConnectClientException */ - protected function requestTokens(string $code, array $headers = []) { + protected function requestTokens($code, $headers = array()) { $token_endpoint = $this->getProviderConfigValue('token_endpoint'); $token_endpoint_auth_methods_supported = $this->getProviderConfigValue('token_endpoint_auth_methods_supported', ['client_secret_basic']); @@ -886,10 +926,11 @@ class OpenIDConnectClient # Consider Basic authentication if provider config is set this way if ($this->supportsAuthMethod('client_secret_basic', $token_endpoint_auth_methods_supported)) { $authorizationHeader = 'Authorization: Basic ' . base64_encode(urlencode($this->clientID) . ':' . urlencode($this->clientSecret)); - unset($token_params['client_secret'], $token_params['client_id']); + unset($token_params['client_secret']); + unset($token_params['client_id']); } - // When there is a private key jwt generator, and it is supported then use it as client authentication + // When there is a private key jwt generator and it is supported then use it as client authentication if ($this->privateKeyJwtGenerator !== null && $this->supportsAuthMethod('private_key_jwt', $token_endpoint_auth_methods_supported)) { $token_params['client_assertion_type'] = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'; $token_params['client_assertion'] = $this->privateKeyJwtGenerator->__invoke($token_endpoint); @@ -904,7 +945,7 @@ class OpenIDConnectClient else{ $client_assertion = $this->getJWTClientAssertion($this->getProviderConfigValue('token_endpoint')); } - + $token_params['client_assertion_type'] = $client_assertion_type; $token_params['client_assertion'] = $client_assertion; unset($token_params['client_secret']); @@ -931,7 +972,7 @@ class OpenIDConnectClient $headers[] = $authorizationHeader; } - $this->tokenResponse = json_decode($this->fetchURL($token_endpoint, $token_params, $headers), false); + $this->tokenResponse = json_decode($this->fetchURL($token_endpoint, $token_params, $headers)); return $this->tokenResponse; } @@ -946,7 +987,7 @@ class OpenIDConnectClient * @return mixed * @throws OpenIDConnectClientException */ - public function requestTokenExchange(string $subjectToken, string $subjectTokenType, string $audience = '') { + public function requestTokenExchange($subjectToken, $subjectTokenType, $audience = '') { $token_endpoint = $this->getProviderConfigValue('token_endpoint'); $token_endpoint_auth_methods_supported = $this->getProviderConfigValue('token_endpoint_auth_methods_supported', ['client_secret_basic']); $headers = []; @@ -968,13 +1009,14 @@ class OpenIDConnectClient # Consider Basic authentication if provider config is set this way if ($this->supportsAuthMethod('client_secret_basic', $token_endpoint_auth_methods_supported)) { $headers = ['Authorization: Basic ' . base64_encode(urlencode($this->clientID) . ':' . urlencode($this->clientSecret))]; - unset($post_data['client_secret'], $post_data['client_id']); + unset($post_data['client_secret']); + unset($post_data['client_id']); } // Convert token params to string format $post_params = http_build_query($post_data, null, '&', $this->encType); - return json_decode($this->fetchURL($token_endpoint, $post_params, $headers), false); + return json_decode($this->fetchURL($token_endpoint, $post_params, $headers)); } @@ -985,7 +1027,7 @@ class OpenIDConnectClient * @return mixed * @throws OpenIDConnectClientException */ - public function refreshToken(string $refresh_token) { + public function refreshToken($refresh_token) { $token_endpoint = $this->getProviderConfigValue('token_endpoint'); $token_endpoint_auth_methods_supported = $this->getProviderConfigValue('token_endpoint_auth_methods_supported', ['client_secret_basic']); @@ -1004,13 +1046,14 @@ class OpenIDConnectClient # Consider Basic authentication if provider config is set this way if ($this->supportsAuthMethod('client_secret_basic', $token_endpoint_auth_methods_supported)) { $headers = ['Authorization: Basic ' . base64_encode(urlencode($this->clientID) . ':' . urlencode($this->clientSecret))]; - unset($token_params['client_secret'], $token_params['client_id']); + unset($token_params['client_secret']); + unset($token_params['client_id']); } if ($this->supportsAuthMethod('client_secret_jwt', $token_endpoint_auth_methods_supported)) { $client_assertion_type = $this->getProviderConfigValue('client_assertion_type'); $client_assertion = $this->getJWTClientAssertion($this->getProviderConfigValue('token_endpoint')); - + $token_params["grant_type"] = "urn:ietf:params:oauth:grant-type:token-exchange"; $token_params["subject_token"] = $refresh_token; $token_params["audience"] = $this->clientID; @@ -1018,13 +1061,14 @@ class OpenIDConnectClient $token_params["requested_token_type"] = "urn:ietf:params:oauth:token-type:access_token"; $token_params['client_assertion_type']=$client_assertion_type; $token_params['client_assertion'] = $client_assertion; - - unset($token_params['client_secret'], $token_params['client_id']); + + unset($token_params['client_secret']); + unset($token_params['client_id']); } // Convert token params to string format $token_params = http_build_query($token_params, '', '&', $this->encType); - $json = json_decode($this->fetchURL($token_endpoint, $token_params, $headers), false); + $json = json_decode($this->fetchURL($token_endpoint, $token_params, $headers)); if (isset($json->access_token)) { $this->accessToken = $json->access_token; @@ -1038,16 +1082,21 @@ class OpenIDConnectClient } /** + * @param array $keys + * @param array $header * @throws OpenIDConnectClientException + * @return object */ - private function getKeyForHeader(array $keys, stdClass $header) { + private function getKeyForHeader($keys, $header) { foreach ($keys as $key) { if ($key->kty === 'RSA') { if (!isset($header->kid) || $key->kid === $header->kid) { return $key; } - } else if (isset($key->alg) && $key->alg === $header->alg && $key->kid === $header->kid) { - return $key; + } else { + if (isset($key->alg) && $key->alg === $header->alg && $key->kid === $header->kid) { + return $key; + } } } if ($this->additionalJwks) { @@ -1056,8 +1105,10 @@ class OpenIDConnectClient if (!isset($header->kid) || $key->kid === $header->kid) { return $key; } - } else if (isset($key->alg) && $key->alg === $header->alg && $key->kid === $header->kid) { - return $key; + } else { + if (isset($key->alg) && $key->alg === $header->alg && $key->kid === $header->kid) { + return $key; + } } } } @@ -1068,43 +1119,94 @@ class OpenIDConnectClient throw new OpenIDConnectClientException('Unable to find a key for RSA'); } + /** + * @param string $hashtype + * @param object $key + * @param $payload + * @param $signature + * @param $signatureType + * @return bool * @throws OpenIDConnectClientException */ - private function verifyRSAJWTSignature(string $hashType, stdClass $key, $payload, $signature, $signatureType): bool - { + private function verifyRSAJWTsignature($hashtype, $key, $payload, $signature, $signatureType) { + if (!class_exists('\phpseclib3\Crypt\RSA') && !class_exists('\phpseclib\Crypt\RSA') && !class_exists('Crypt_RSA')) { + throw new OpenIDConnectClientException('Crypt_RSA support unavailable.'); + } if (!(property_exists($key, 'n') && property_exists($key, 'e'))) { throw new OpenIDConnectClientException('Malformed key object'); } - $key = RSA::load([ - 'publicExponent' => new BigInteger(base64_decode(b64url2b64($key->e)), 256), - 'modulus' => new BigInteger(base64_decode(b64url2b64($key->n)), 256), - 'isPublicKey' => true, - ]) - ->withHash($hashType); - if ($signatureType === 'PSS') { - $key = $key->withMGFHash($hashType) - ->withPadding(RSA::SIGNATURE_PSS); + /* We already have base64url-encoded data, so re-encode it as + regular base64 and use the XML key format for simplicity. + */ + $public_key_xml = "<RSAKeyValue>\r\n". + ' <Modulus>' . b64url2b64($key->n) . "</Modulus>\r\n" . + ' <Exponent>' . b64url2b64($key->e) . "</Exponent>\r\n" . + '</RSAKeyValue>'; + if (class_exists('\phpseclib3\Crypt\RSA', false)) { + $key = \phpseclib3\Crypt\PublicKeyLoader::load($public_key_xml) + ->withHash($hashtype); + if ($signatureType === 'PSS') { + $key = $key->withMGFHash($hashtype) + ->withPadding(\phpseclib3\Crypt\RSA::SIGNATURE_PSS); + } else { + $key = $key->withPadding(\phpseclib3\Crypt\RSA::SIGNATURE_PKCS1); + } + return $key->verify($payload, $signature); + } elseif (class_exists('Crypt_RSA', false)) { + $rsa = new Crypt_RSA(); + $rsa->setHash($hashtype); + if ($signatureType === 'PSS') { + $rsa->setMGFHash($hashtype); + } + $rsa->loadKey($public_key_xml, Crypt_RSA::PUBLIC_FORMAT_XML); + $rsa->setSignatureMode($signatureType === 'PSS' ? Crypt_RSA::SIGNATURE_PSS : Crypt_RSA::SIGNATURE_PKCS1); + return $rsa->verify($payload, $signature); } else { - $key = $key->withPadding(RSA::SIGNATURE_PKCS1); + $rsa = new \phpseclib\Crypt\RSA(); + $rsa->setHash($hashtype); + if ($signatureType === 'PSS') { + $rsa->setMGFHash($hashtype); + } + $rsa->loadKey($public_key_xml, \phpseclib\Crypt\RSA::PUBLIC_FORMAT_XML); + $rsa->setSignatureMode($signatureType === 'PSS' ? \phpseclib\Crypt\RSA::SIGNATURE_PSS : \phpseclib\Crypt\RSA::SIGNATURE_PKCS1); + return $rsa->verify($payload, $signature); } - return $key->verify($payload, $signature); } - private function verifyHMACJWTSignature(string $hashType, string $key, string $payload, string $signature): bool + /** + * @param string $hashtype + * @param object $key + * @param $payload + * @param $signature + * @return bool + * @throws OpenIDConnectClientException + */ + private function verifyHMACJWTsignature($hashtype, $key, $payload, $signature) { - $expected = hash_hmac($hashType, $payload, $key, true); - return hash_equals($signature, $expected); + if (!function_exists('hash_hmac')) { + throw new OpenIDConnectClientException('hash_hmac support unavailable.'); + } + + $expected=hash_hmac($hashtype, $payload, $key, true); + + if (function_exists('hash_equals')) { + return hash_equals($signature, $expected); + } + + return self::hashEquals($signature, $expected); } /** * @param string $jwt encoded JWT - * @return bool * @throws OpenIDConnectClientException + * @return bool */ - public function verifyJWTSignature(string $jwt): bool - { + public function verifyJWTsignature($jwt) { + if (!\is_string($jwt)) { + throw new OpenIDConnectClientException('Error token is not a string'); + } $parts = explode('.', $jwt); if (!isset($parts[0])) { throw new OpenIDConnectClientException('Error missing part 0 in token'); @@ -1113,8 +1215,8 @@ class OpenIDConnectClient if (false === $signature || '' === $signature) { throw new OpenIDConnectClientException('Error decoding signature from token'); } - $header = json_decode(base64url_decode($parts[0]), false); - if (!is_object($header)) { + $header = json_decode(base64url_decode($parts[0])); + if (null === $header || !\is_object($header)) { throw new OpenIDConnectClientException('Error decoding JSON from token header'); } if (!isset($header->alg)) { @@ -1128,28 +1230,28 @@ class OpenIDConnectClient case 'PS512': case 'RS384': case 'RS512': - $hashType = 'sha' . substr($header->alg, 2); + $hashtype = 'sha' . substr($header->alg, 2); $signatureType = $header->alg === 'PS256' || $header->alg === 'PS512' ? 'PSS' : ''; if (isset($header->jwk)) { $jwk = $header->jwk; $this->verifyJWKHeader($jwk); } else { - $jwks = json_decode($this->fetchURL($this->getProviderConfigValue('jwks_uri')), false); + $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->verifyRSAJWTSignature($hashType, + $verified = $this->verifyRSAJWTsignature($hashtype, $jwk, $payload, $signature, $signatureType); break; case 'HS256': case 'HS512': case 'HS384': - $hashType = 'SHA' . substr($header->alg, 2); - $verified = $this->verifyHMACJWTSignature($hashType, $this->getClientSecret(), $payload, $signature); + $hashtype = 'SHA' . substr($header->alg, 2); + $verified = $this->verifyHMACJWTsignature($hashtype, $this->getClientSecret(), $payload, $signature); break; default: throw new OpenIDConnectClientException('No support for signature type: ' . $header->alg); @@ -1158,27 +1260,11 @@ class OpenIDConnectClient } /** - * @param string $jwt encoded JWT - * @return void - * @throws OpenIDConnectClientException - */ - public function verifySignatures(string $jwt) - { - if (!$this->getProviderConfigValue('jwks_uri')) { - throw new OpenIDConnectClientException ('Unable to verify signature due to no jwks_uri being defined'); - } - if (!$this->verifyJWTSignature($jwt)) { - throw new OpenIDConnectClientException ('Unable to verify signature'); - } - } - - /** * @param string $iss * @return bool * @throws OpenIDConnectClientException */ - protected function validateIssuer(string $iss): bool - { + protected function validateIssuer($iss) { if ($this->issuerValidator !== null) { return $this->issuerValidator->__invoke($iss); } @@ -1190,11 +1276,9 @@ class OpenIDConnectClient * @param object $claims * @param string|null $accessToken * @return bool - * @throws OpenIDConnectClientException */ - protected function verifyJWTClaims($claims, string $accessToken = null): bool - { - if(isset($claims->at_hash, $accessToken)) { + protected function verifyJWTclaims($claims, $accessToken = null) { + if(isset($claims->at_hash) && isset($accessToken)) { if(isset($this->getIdTokenHeader()->alg) && $this->getIdTokenHeader()->alg !== 'none') { $bit = substr($this->getIdTokenHeader()->alg, 2, 3); } else { @@ -1207,17 +1291,21 @@ class OpenIDConnectClient return (($this->validateIssuer($claims->iss)) && (($claims->aud === $this->clientID) || in_array($this->clientID, $claims->aud, true)) && (!isset($claims->nonce) || $claims->nonce === $this->getNonce()) - && ( !isset($claims->exp) || ((is_int($claims->exp)) && ($claims->exp >= time() - $this->leeway))) - && ( !isset($claims->nbf) || ((is_int($claims->nbf)) && ($claims->nbf <= time() + $this->leeway))) + && ( !isset($claims->exp) || ((gettype($claims->exp) === 'integer') && ($claims->exp >= time() - $this->leeway))) + && ( !isset($claims->nbf) || ((gettype($claims->nbf) === 'integer') && ($claims->nbf <= time() + $this->leeway))) && ( !isset($claims->at_hash) || !isset($accessToken) || $claims->at_hash === $expected_at_hash ) ); } - protected function urlEncode(string $str): string - { + /** + * @param string $str + * @return string + */ + protected function urlEncode($str) { $enc = base64_encode($str); $enc = rtrim($enc, '='); - return strtr($enc, '+/', '-_'); + $enc = strtr($enc, '+/', '-_'); + return $enc; } /** @@ -1225,10 +1313,10 @@ class OpenIDConnectClient * @param int $section the section we would like to decode * @return object */ - protected function decodeJWT(string $jwt, int $section = 0): stdClass { + protected function decodeJWT($jwt, $section = 0) { $parts = explode('.', $jwt); - return json_decode(base64url_decode($parts[$section]), false); + return json_decode(base64url_decode($parts[$section])); } /** @@ -1253,13 +1341,13 @@ class OpenIDConnectClient * locale string The End-User's locale, represented as a BCP47 [RFC5646] language tag. This is typically an ISO 639-1 Alpha-2 [ISO639‑1] language code in lowercase and an ISO 3166-1 Alpha-2 [ISO3166‑1] country code in uppercase, separated by a dash. For example, en-US or fr-CA. As a compatibility note, some implementations have used an underscore as the separator rather than a dash, for example, en_US; Implementations MAY choose to accept this locale syntax as well. * phone_number string The End-User's preferred telephone number. E.164 [E.164] is RECOMMENDED as the format of this Claim. For example, +1 (425) 555-1212 or +56 (2) 687 2400. * address JSON object The End-User's preferred address. The value of the address member is a JSON [RFC4627] structure containing some or all of the members defined in Section 2.4.2.1. - * updated_time string Time the End-User's information was last updated, represented as an RFC 3339 [RFC3339] datetime. For example, 2011-01-03T23:58:42+0000. + * updated_time string Time the End-User's information was last updated, represented as a RFC 3339 [RFC3339] datetime. For example, 2011-01-03T23:58:42+0000. * * @return mixed * * @throws OpenIDConnectClientException */ - public function requestUserInfo(string $attribute = null) { + public function requestUserInfo($attribute = null) { $user_info_endpoint = $this->getProviderConfigValue('userinfo_endpoint'); $schema = 'openid'; @@ -1268,50 +1356,21 @@ class OpenIDConnectClient //The accessToken has to be sent in the Authorization header. // Accept json to indicate response type - $headers = ["Authorization: Bearer $this->accessToken", + $headers = ["Authorization: Bearer {$this->accessToken}", 'Accept: application/json']; - $response = $this->fetchURL($user_info_endpoint,null,$headers); - if ($this->getResponseCode() !== 200) { + $user_json = json_decode($this->fetchURL($user_info_endpoint,null,$headers)); + if ($this->getResponseCode() <> 200) { throw new OpenIDConnectClientException('The communication to retrieve user data has failed with status code '.$this->getResponseCode()); } - - // When we receive application/jwt, the UserInfo Response is signed and/or encrypted. - if ($this->getResponseContentType() === 'application/jwt' ) { - // Check if the response is encrypted - $jwtHeaders = $this->decodeJWT($response); - if (isset($jwtHeaders->enc)) { - // Handle JWE - $jwt = $this->handleJweResponse($response); - } else { - // If the response is not encrypted then it must be signed - $jwt = $response; - } - - // Verify the signature - $this->verifySignatures($jwt); - - // Get claims from JWT - $claims = $this->decodeJWT($jwt, 1); - - // Verify the JWT claims - if (!$this->verifyJWTClaims($claims)) { - throw new OpenIDConnectClientException('Invalid JWT signature'); - } - - $user_json = $claims; - } else { - $user_json = json_decode($response, false); - } - - $userInfo = $user_json; + $this->userInfo = $user_json; if($attribute === null) { - return $userInfo; + return $this->userInfo; } - if (property_exists($userInfo, $attribute)) { - return $userInfo->$attribute; + if (property_exists($this->userInfo, $attribute)) { + return $this->userInfo->$attribute; } return null; @@ -1330,13 +1389,13 @@ class OpenIDConnectClient * aud string Audience * nonce string nonce * iat int Issued At - * auth_time int Authentication time + * auth_time int Authenatication time * oid string Object id * * @return mixed * */ - public function getVerifiedClaims(string $attribute = null) { + public function getVerifiedClaims($attribute = null) { if($attribute === null) { return $this->verifiedClaims; @@ -1352,11 +1411,11 @@ class OpenIDConnectClient /** * @param string $url * @param string | null $post_body string If this is set the post type will be POST - * @param array $headers Extra headers to be sent with the request. Format as 'NameHeader: ValueHeader' - * @return bool|string + * @param array $headers Extra headers to be send with the request. Format as 'NameHeader: ValueHeader' * @throws OpenIDConnectClientException + * @return mixed */ - protected function fetchURL(string $url, string $post_body = null, array $headers = []) { + protected function fetchURL($url, $post_body = null, $headers = []) { // OK cool - then let's create a new cURL resource handle $ch = curl_init(); @@ -1364,7 +1423,7 @@ class OpenIDConnectClient // Determine whether this is a GET or POST if ($post_body !== null) { // curl_setopt($ch, CURLOPT_POST, 1); - // Allows to keep the POST method even after redirect + // Alows to keep the POST method even after redirect curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($ch, CURLOPT_POSTFIELDS, $post_body); @@ -1372,16 +1431,14 @@ class OpenIDConnectClient $content_type = 'application/x-www-form-urlencoded'; // Determine if this is a JSON payload and add the appropriate content type - if (is_object(json_decode($post_body, false))) { + if (is_object(json_decode($post_body))) { $content_type = 'application/json'; } // Add POST-specific headers - $headers[] = "Content-Type: $content_type"; - } + $headers[] = "Content-Type: {$content_type}"; - // Set the User-Agent - curl_setopt($ch, CURLOPT_USERAGENT, $this->getUserAgent()); + } // If we set some headers include them if(count($headers) > 0) { @@ -1433,7 +1490,6 @@ class OpenIDConnectClient // HTTP Response code from server may be required from subclass $info = curl_getinfo($ch); $this->responseCode = $info['http_code']; - $this->responseContentType = $info['content_type']; if ($output === false) { throw new OpenIDConnectClientException('Curl error: (' . curl_errno($ch) . ') ' . curl_error($ch)); @@ -1446,18 +1502,20 @@ class OpenIDConnectClient } /** + * @param bool $appendSlash + * @return string * @throws OpenIDConnectClientException */ - public function getWellKnownIssuer(bool $appendSlash = false): string - { + public function getWellKnownIssuer($appendSlash = false) { + return $this->getWellKnownConfigValue('issuer') . ($appendSlash ? '/' : ''); } /** + * @return string * @throws OpenIDConnectClientException */ - public function getIssuer(): string - { + public function getIssuer() { if (!isset($this->providerConfig['issuer'])) { throw new OpenIDConnectClientException('The issuer has not been set'); @@ -1478,16 +1536,25 @@ class OpenIDConnectClient return $this->providerConfig['providerUrl']; } - public function redirect(string $url) { + /** + * @param string $url + */ + public function redirect($url) { header('Location: ' . $url); exit; } - public function setHttpProxy(string $httpProxy) { + /** + * @param string $httpProxy + */ + public function setHttpProxy($httpProxy) { $this->httpProxy = $httpProxy; } - public function setCertPath(string $certPath) { + /** + * @param string $certPath + */ + public function setCertPath($certPath) { $this->certPath = $certPath; } @@ -1498,33 +1565,48 @@ class OpenIDConnectClient return $this->certPath; } - public function setVerifyPeer(bool $verifyPeer) { + /** + * @param bool $verifyPeer + */ + public function setVerifyPeer($verifyPeer) { $this->verifyPeer = $verifyPeer; } - public function setVerifyHost(bool $verifyHost) { + /** + * @param bool $verifyHost + */ + public function setVerifyHost($verifyHost) { $this->verifyHost = $verifyHost; } + /** * Controls whether http header HTTP_UPGRADE_INSECURE_REQUESTS should be considered * defaults to true + * @param bool $httpUpgradeInsecureRequests */ - public function setHttpUpgradeInsecureRequests(bool $httpUpgradeInsecureRequests) { + public function setHttpUpgradeInsecureRequests($httpUpgradeInsecureRequests) { $this->httpUpgradeInsecureRequests = $httpUpgradeInsecureRequests; } - public function getVerifyHost(): bool - { + /** + * @return bool + */ + public function getVerifyHost() { return $this->verifyHost; } - public function getVerifyPeer(): bool - { + /** + * @return bool + */ + public function getVerifyPeer() { return $this->verifyPeer; } - public function getHttpUpgradeInsecureRequests(): bool + /** + * @return bool + */ + public function getHttpUpgradeInsecureRequests() { return $this->httpUpgradeInsecureRequests; } @@ -1533,8 +1615,10 @@ class OpenIDConnectClient * Use this for custom issuer validation * The given function should accept the issuer string from the JWT claim as the only argument * and return true if the issuer is valid, otherwise return false + * + * @param callable $issuerValidator */ - public function setIssuerValidator(callable $issuerValidator) { + public function setIssuerValidator($issuerValidator) { $this->issuerValidator = $issuerValidator; } @@ -1543,20 +1627,24 @@ class OpenIDConnectClient * The given function should accept the token_endpoint string as the only argument * and return a jwt signed with your private key according to: * https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication + * + * @param callable $privateKeyJwtGenerator */ - public function setPrivateKeyJwtGenerator(callable $privateKeyJwtGenerator) { + public function setPrivateKeyJwtGenerator($privateKeyJwtGenerator) { $this->privateKeyJwtGenerator = $privateKeyJwtGenerator; } - public function setAllowImplicitFlow(bool $allowImplicitFlow) { + /** + * @param bool $allowImplicitFlow + */ + public function setAllowImplicitFlow($allowImplicitFlow) { $this->allowImplicitFlow = $allowImplicitFlow; } /** * @return bool */ - public function getAllowImplicitFlow(): bool - { + public function getAllowImplicitFlow() { return $this->allowImplicitFlow; } @@ -1567,15 +1655,21 @@ class OpenIDConnectClient * @param array $array * simple key => value */ - public function providerConfigParam(array $array) { + public function providerConfigParam($array) { $this->providerConfig = array_merge($this->providerConfig, $array); } - public function setClientSecret(string $clientSecret) { + /** + * @param string $clientSecret + */ + public function setClientSecret($clientSecret) { $this->clientSecret = $clientSecret; } - public function setClientID(string $clientID) { + /** + * @param string $clientID + */ + public function setClientID($clientID) { $this->clientID = $clientID; } @@ -1596,7 +1690,7 @@ class OpenIDConnectClient $response = $this->fetchURL($registration_endpoint, json_encode($send_object)); - $json_response = json_decode($response, false); + $json_response = json_decode($response); // Throw some errors if we encounter them if ($json_response === false) { @@ -1614,8 +1708,10 @@ class OpenIDConnectClient if (isset($json_response->{'client_secret'})) { $this->setClientSecret($json_response->{'client_secret'}); } else { - throw new OpenIDConnectClientException('Error registering: Please contact the OpenID Connect provider and obtain a Client ID and Secret directly from them'); + throw new OpenIDConnectClientException('Error registering: + Please contact the OpenID Connect provider and obtain a Client ID and Secret directly from them'); } + } /** @@ -1628,9 +1724,8 @@ class OpenIDConnectClient * @param string|null $clientSecret * @return mixed * @throws OpenIDConnectClientException - * @throws Exception */ - public function introspectToken(string $token, string $token_type_hint = '', string $clientId = null, string $clientSecret = null) { + public function introspectToken($token, $token_type_hint = '', $clientId = null, $clientSecret = null) { $introspection_endpoint = $this->getProviderConfigValue('introspection_endpoint'); $token_endpoint_auth_methods_supported = $this->getProviderConfigValue('token_endpoint_auth_methods_supported', ['client_secret_basic']); @@ -1639,8 +1734,8 @@ class OpenIDConnectClient if ($token_type_hint) { $post_data['token_type_hint'] = $token_type_hint; } - $clientId = $clientId ?? $this->clientID; - $clientSecret = $clientSecret ?? $this->clientSecret; + $clientId = $clientId !== null ? $clientId : $this->clientID; + $clientSecret = $clientSecret !== null ? $clientSecret : $this->clientSecret; // Convert token params to string format $headers = ['Authorization: Basic ' . base64_encode(urlencode($clientId) . ':' . urlencode($clientSecret)), @@ -1649,7 +1744,7 @@ class OpenIDConnectClient if ($this->supportsAuthMethod('client_secret_jwt', $token_endpoint_auth_methods_supported)) { $client_assertion_type = $this->getProviderConfigValue('client_assertion_type'); $client_assertion = $this->getJWTClientAssertion($this->getProviderConfigValue('introspection_endpoint')); - + $post_data['client_assertion_type']=$client_assertion_type; $post_data['client_assertion'] = $client_assertion; $headers = ['Accept: application/json']; @@ -1657,7 +1752,7 @@ class OpenIDConnectClient $post_params = http_build_query($post_data, '', '&'); - return json_decode($this->fetchURL($introspection_endpoint, $post_params, $headers), false); + return json_decode($this->fetchURL($introspection_endpoint, $post_params, $headers)); } /** @@ -1671,7 +1766,7 @@ class OpenIDConnectClient * @return mixed * @throws OpenIDConnectClientException */ - public function revokeToken(string $token, string $token_type_hint = '', string $clientId = null, string $clientSecret = null) { + public function revokeToken($token, $token_type_hint = '', $clientId = null, $clientSecret = null) { $revocation_endpoint = $this->getProviderConfigValue('revocation_endpoint'); $post_data = ['token' => $token]; @@ -1679,23 +1774,28 @@ class OpenIDConnectClient if ($token_type_hint) { $post_data['token_type_hint'] = $token_type_hint; } - $clientId = $clientId ?? $this->clientID; - $clientSecret = $clientSecret ?? $this->clientSecret; + $clientId = $clientId !== null ? $clientId : $this->clientID; + $clientSecret = $clientSecret !== null ? $clientSecret : $this->clientSecret; // Convert token params to string format $post_params = http_build_query($post_data, '', '&'); $headers = ['Authorization: Basic ' . base64_encode(urlencode($clientId) . ':' . urlencode($clientSecret)), 'Accept: application/json']; - return json_decode($this->fetchURL($revocation_endpoint, $post_params, $headers), false); + return json_decode($this->fetchURL($revocation_endpoint, $post_params, $headers)); } - public function getClientName(): string - { + /** + * @return string + */ + public function getClientName() { return $this->clientName; } - public function setClientName(string $clientName) { + /** + * @param string $clientName + */ + public function setClientName($clientName) { $this->clientName = $clientName; } @@ -1714,26 +1814,42 @@ class OpenIDConnectClient } /** + * @return bool + */ + public function canVerifySignatures() { + return class_exists('\phpseclib3\Crypt\RSA') || class_exists('\phpseclib\Crypt\RSA') || class_exists('Crypt_RSA'); + } + + /** * Set the access token. * * May be required for subclasses of this Client. + * + * @param string $accessToken + * @return void */ - public function setAccessToken(string $accessToken) { + public function setAccessToken($accessToken) { $this->accessToken = $accessToken; } - public function getAccessToken(): string - { + /** + * @return string + */ + public function getAccessToken() { return $this->accessToken; } - public function getRefreshToken(): string - { + /** + * @return string + */ + public function getRefreshToken() { return $this->refreshToken; } - public function getIdToken(): string - { + /** + * @return string + */ + public function getIdToken() { return $this->idToken; } @@ -1766,7 +1882,7 @@ class OpenIDConnectClient } /** - * @return object + * @return string */ public function getTokenResponse() { return $this->tokenResponse; @@ -1774,9 +1890,11 @@ class OpenIDConnectClient /** * Stores nonce + * + * @param string $nonce + * @return string */ - protected function setNonce(string $nonce): string - { + protected function setNonce($nonce) { $this->setSessionKey('openid_connect_nonce', $nonce); return $nonce; } @@ -1801,9 +1919,11 @@ class OpenIDConnectClient /** * Stores $state + * + * @param string $state + * @return string */ - protected function setState(string $state): string - { + protected function setState($state) { $this->setSessionKey('openid_connect_state', $state); return $state; } @@ -1828,9 +1948,11 @@ class OpenIDConnectClient /** * Stores $codeVerifier + * + * @param string $codeVerifier + * @return string */ - protected function setCodeVerifier(string $codeVerifier): string - { + protected function setCodeVerifier($codeVerifier) { $this->setSessionKey('openid_connect_code_verifier', $codeVerifier); return $codeVerifier; } @@ -1858,36 +1980,60 @@ class OpenIDConnectClient * * @return int */ - public function getResponseCode(): int - { + public function getResponseCode() { return $this->responseCode; } /** - * Get the content type from last action/curl request. - * - * @return string|null - */ - public function getResponseContentType() - { - return $this->responseContentType; - } - - /** * Set timeout (seconds) * * @param int $timeout */ - public function setTimeout(int $timeout) { + public function setTimeout($timeout) { $this->timeOut = $timeout; } - public function getTimeout(): int - { + /** + * @return int + */ + public function getTimeout() { return $this->timeOut; } /** + * Safely calculate length of binary string + * @param string $str + * @return int + */ + private static function safeLength($str) { + if (function_exists('mb_strlen')) { + return mb_strlen($str, '8bit'); + } + return strlen($str); + } + + /** + * Where hash_equals is not available, this provides a timing-attack safe string comparison + * @param string $str1 + * @param string $str2 + * @return bool + */ + private static function hashEquals($str1, $str2) { + $len1=static::safeLength($str1); + $len2=static::safeLength($str2); + + //compare strings without any early abort... + $len = min($len1, $len2); + $status = 0; + for ($i = 0; $i < $len; $i++) { + $status |= (ord($str1[$i]) ^ ord($str2[$i])); + } + //if strings were different lengths, we fail + $status |= ($len1 ^ $len2); + return ($status === 0); + } + + /** * Use session to manage a nonce */ protected function startSession() { @@ -1902,7 +2048,7 @@ class OpenIDConnectClient session_write_close(); } - protected function getSessionKey(string $key) { + protected function getSessionKey($key) { $this->startSession(); if (array_key_exists($key, $_SESSION)) { @@ -1911,23 +2057,19 @@ class OpenIDConnectClient return false; } - protected function setSessionKey(string $key, $value) { + protected function setSessionKey($key, $value) { $this->startSession(); $_SESSION[$key] = $value; } - protected function unsetSessionKey(string $key) { + protected function unsetSessionKey($key) { $this->startSession(); unset($_SESSION[$key]); } - /** - * @throws Exception - */ - protected function getJWTClientAssertion($aud): string - { + protected function getJWTClientAssertion($aud) { $jti = hash('sha256',bin2hex(random_bytes(64))); $now = time(); @@ -1943,7 +2085,7 @@ class OpenIDConnectClient ]); // Encode Header to Base64Url String $base64UrlHeader = $this->urlEncode($header); - + // Encode Payload to Base64Url String $base64UrlPayload = $this->urlEncode($payload); @@ -1958,10 +2100,12 @@ class OpenIDConnectClient // Encode Signature to Base64Url String $base64UrlSignature = $this->urlEncode($signature); + + $jwt = $base64UrlHeader . "." . $base64UrlPayload . "." . $base64UrlSignature; - return $base64UrlHeader . "." . $base64UrlPayload . "." . $base64UrlSignature; + return $jwt; } - + public function setUrlEncoding($curEncoding) { switch ($curEncoding) { @@ -1976,20 +2120,27 @@ class OpenIDConnectClient default: break; } + } - public function getScopes(): array - { + /** + * @return array + */ + public function getScopes() { return $this->scopes; } - public function getResponseTypes(): array - { + /** + * @return array + */ + public function getResponseTypes() { return $this->responseTypes; } - public function getAuthParams(): array - { + /** + * @return array + */ + public function getAuthParams() { return $this->authParams; } @@ -2008,8 +2159,10 @@ class OpenIDConnectClient return $this->privateKeyJwtGenerator; } - public function getLeeway(): int - { + /** + * @return int + */ + public function getLeeway() { return $this->leeway; } @@ -2020,7 +2173,10 @@ class OpenIDConnectClient return $this->codeChallengeMethod; } - public function setCodeChallengeMethod(string $codeChallengeMethod) { + /** + * @param string $codeChallengeMethod + */ + public function setCodeChallengeMethod($codeChallengeMethod) { $this->codeChallengeMethod = $codeChallengeMethod; } @@ -2032,27 +2188,26 @@ class OpenIDConnectClient throw new OpenIDConnectClientException('Self signed JWK header is not valid'); } - /** - * @param string $jwe The JWE to decrypt - * @return string the JWT payload - * @throws OpenIDConnectClientException + /* + * @return string */ - protected function handleJweResponse(string $jwe): string - { - throw new OpenIDConnectClientException('JWE response is not supported, please extend the class and implement this method'); - } - - public function getSidFromBackChannel(): string - { + public function getSidFromBackChannel() { return $this->backChannelSid; } - public function getSubjectFromBackChannel(): string - { + /** + * @return string + */ + public function getSubjectFromBackChannel() { return $this->backChannelSubject; } - public function supportsAuthMethod(string $auth_method, array $token_endpoint_auth_methods_supported): bool + /** + * @param string $auth_method + * @param array $token_endpoint_auth_methods_supported + * @return bool + */ + public function supportsAuthMethod($auth_method, $token_endpoint_auth_methods_supported) { # client_secret_jwt has to explicitly be enabled if (!in_array($auth_method, $this->token_endpoint_auth_methods_supported, true)) { @@ -2061,9 +2216,4 @@ class OpenIDConnectClient return in_array($auth_method, $token_endpoint_auth_methods_supported, true); } - - protected function getUserAgent(): string - { - return "jumbojett/OpenID-Connect-PHP"; - } } |