http = $http; $this->cache = $cache; $this->jwt = $jwt ?: $this->getJwtService(); } /** * Verifies an id token and returns the authenticated apiLoginTicket. * Throws an exception if the id token is not valid. * The audience parameter can be used to control which id tokens are * accepted. By default, the id token must have been issued to this OAuth2 client. * * @param $audience * @return array the token payload, if successful */ public function verifyIdToken($idToken, $audience = null) { if (empty($idToken)) { throw new \LogicException('id_token cannot be null'); } // set phpseclib constants if applicable $this->setPhpsecConstants(); // Check signature $certs = $this->getFederatedSignOnCerts(); foreach ($certs as $cert) { $bigIntClass = $this->getBigIntClass(); $rsaClass = $this->getRsaClass(); $modulus = new $bigIntClass($this->jwt->urlsafeB64Decode($cert['n']), 256); $exponent = new $bigIntClass($this->jwt->urlsafeB64Decode($cert['e']), 256); $rsa = new $rsaClass(); $rsa->loadKey(array('n' => $modulus, 'e' => $exponent)); try { $payload = $this->jwt->decode($idToken, $rsa->getPublicKey(), array('RS256')); if (\property_exists($payload, 'aud')) { if ($audience && $payload->aud != $audience) { return \false; } } // support HTTP and HTTPS issuers // @see https://developers.google.com/identity/sign-in/web/backend-auth $issuers = array(self::OAUTH2_ISSUER, self::OAUTH2_ISSUER_HTTPS); if (!isset($payload->iss) || !\in_array($payload->iss, $issuers)) { return \false; } return (array) $payload; } catch (\Google\Site_Kit_Dependencies\ExpiredException $e) { return \false; } catch (\Google\Site_Kit_Dependencies\Firebase\JWT\ExpiredException $e) { return \false; } catch (\Google\Site_Kit_Dependencies\Firebase\JWT\SignatureInvalidException $e) { // continue } catch (\DomainException $e) { // continue } } return \false; } private function getCache() { return $this->cache; } /** * Retrieve and cache a certificates file. * * @param $url string location * @throws Google_Exception * @return array certificates */ private function retrieveCertsFromLocation($url) { // If we're retrieving a local file, just grab it. if (0 !== \strpos($url, 'http')) { if (!($file = \file_get_contents($url))) { throw new \Google\Site_Kit_Dependencies\Google_Exception("Failed to retrieve verification certificates: '" . $url . "'."); } return \json_decode($file, \true); } $response = $this->http->get($url); if ($response->getStatusCode() == 200) { return \json_decode((string) $response->getBody(), \true); } throw new \Google\Site_Kit_Dependencies\Google_Exception(\sprintf('Failed to retrieve verification certificates: "%s".', $response->getBody()->getContents()), $response->getStatusCode()); } // Gets federated sign-on certificates to use for verifying identity tokens. // Returns certs as array structure, where keys are key ids, and values // are PEM encoded certificates. private function getFederatedSignOnCerts() { $certs = null; if ($cache = $this->getCache()) { $cacheItem = $cache->getItem('federated_signon_certs_v3'); $certs = $cacheItem->get(); } if (!$certs) { $certs = $this->retrieveCertsFromLocation(self::FEDERATED_SIGNON_CERT_URL); if ($cache) { $cacheItem->expiresAt(new \DateTime('+1 hour')); $cacheItem->set($certs); $cache->save($cacheItem); } } if (!isset($certs['keys'])) { throw new \InvalidArgumentException('federated sign-on certs expects "keys" to be set'); } return $certs['keys']; } private function getJwtService() { $jwtClass = 'JWT'; if (\class_exists('Google\\Site_Kit_Dependencies\\Firebase\\JWT\\JWT')) { $jwtClass = 'Google\\Site_Kit_Dependencies\\Firebase\\JWT\\JWT'; } if (\property_exists($jwtClass, 'leeway') && $jwtClass::$leeway < 1) { // Ensures JWT leeway is at least 1 // @see https://github.com/google/google-api-php-client/issues/827 $jwtClass::$leeway = 1; } return new $jwtClass(); } private function getRsaClass() { if (\class_exists('Google\\Site_Kit_Dependencies\\phpseclib\\Crypt\\RSA')) { return 'Google\\Site_Kit_Dependencies\\phpseclib\\Crypt\\RSA'; } return 'Crypt_RSA'; } private function getBigIntClass() { if (\class_exists('Google\\Site_Kit_Dependencies\\phpseclib\\Math\\BigInteger')) { return 'Google\\Site_Kit_Dependencies\\phpseclib\\Math\\BigInteger'; } return 'Math_BigInteger'; } private function getOpenSslConstant() { if (\class_exists('Google\\Site_Kit_Dependencies\\phpseclib\\Crypt\\RSA')) { return 'phpseclib\\Crypt\\RSA::MODE_OPENSSL'; } if (\class_exists('Google\\Site_Kit_Dependencies\\Crypt_RSA')) { return 'CRYPT_RSA_MODE_OPENSSL'; } throw new \Exception('Cannot find RSA class'); } /** * phpseclib calls "phpinfo" by default, which requires special * whitelisting in the AppEngine VM environment. This function * sets constants to bypass the need for phpseclib to check phpinfo * * @see phpseclib/Math/BigInteger * @see https://github.com/GoogleCloudPlatform/getting-started-php/issues/85 */ private function setPhpsecConstants() { if (\filter_var(\getenv('GAE_VM'), \FILTER_VALIDATE_BOOLEAN)) { if (!\defined('Google\\Site_Kit_Dependencies\\MATH_BIGINTEGER_OPENSSL_ENABLED')) { \define('Google\\Site_Kit_Dependencies\\MATH_BIGINTEGER_OPENSSL_ENABLED', \true); } if (!\defined('Google\\Site_Kit_Dependencies\\CRYPT_RSA_MODE')) { \define('Google\\Site_Kit_Dependencies\\CRYPT_RSA_MODE', \constant($this->getOpenSslConstant())); } } } }