Skip to content

Commit

Permalink
Add support for saving new public keys when fetched
Browse files Browse the repository at this point in the history
  • Loading branch information
spvickers committed Aug 29, 2024
1 parent 398ec88 commit 387a2f8
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 14 deletions.
16 changes: 15 additions & 1 deletion src/Jwt/ClientInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,27 @@ public static function getLastPayload(): array|object|null;
/**
* Verify the signature of the JWT.
*
* @deprecated Use verifySignature() instead
*
* @param string|null $publicKey Public key of issuer
* @param string|null $jku JSON Web Key URL of issuer (optional)
*
* @return bool True if the JWT has a valid signature
* @return bool True if the JWT has a valid signature
*/
public function verify(?string $publicKey, ?string $jku = null): bool;

/**
* Verify the signature of the JWT.
*
* If a new public key is fetched and used to successfully verify the signature, the value of the publicKey parameter is updated.
*
* @param string|null $publicKey Public key of issuer (passed by reference)
* @param string|null $jku JSON Web Key URL of issuer (optional)
*
* @return bool True if the JWT has a valid signature
*/
public function verifySignature(?string &$publicKey, ?string $jku = null): bool;

/**
* Sign the JWT.
*
Expand Down
38 changes: 32 additions & 6 deletions src/Jwt/FirebaseClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -234,12 +234,29 @@ public static function getLastPayload(): array|object|null
/**
* Verify the signature of the JWT.
*
* @deprecated Use verifySignature() instead
*
* @param string|null $publicKey Public key of issuer
* @param string|null $jku JSON Web Key URL of issuer (optional)
*
* @return bool True if the JWT has a valid signature
*/
public function verify(?string $publicKey, ?string $jku = null): bool
{
return $this->verifySignature($publicKey, $jku);
}

/**
* Verify the signature of the JWT.
*
* If a new public key is fetched and used to successfully verify the signature, the value of the publicKey parameter is updated.
*
* @param string|null $publicKey Public key of issuer (passed by reference)
* @param string|null $jku JSON Web Key URL of issuer (optional)
*
* @return bool True if the JWT has a valid signature
*/
public function verifySignature(?string &$publicKey, ?string $jku = null): bool
{
$ok = false;
$hasPublicKey = !empty($publicKey);
Expand All @@ -251,30 +268,36 @@ public function verify(?string $publicKey, ?string $jku = null): bool
$jwks = [
'keys' => [$json]
];
$publicKey = JWK::parseKeySet($jwks, $this->getHeader('alg'));
$key = JWK::parseKeySet($jwks, $this->getHeader('alg'));
} catch (\Exception $e) {

}
} else {
$publicKey = new Key($publicKey, $this->getHeader('alg'));
$key = new Key($publicKey, $this->getHeader('alg'));
}
}
} elseif (!empty($jku)) {
$publicKey = $this->fetchPublicKey($jku);
$key = $this->fetchPublicKey($jku);
}
JWT::$leeway = Jwt::$leeway;
$retry = false;
do {
try {
JWT::decode($this->jwtString, $publicKey);
JWT::decode($this->jwtString, $key);
$ok = true;
if (!$hasPublicKey || $retry) {
$keyDetails = openssl_pkey_get_details($key[$this->getHeader('kid')]->getKeyMaterial());
if ($keyDetails !== false) {
$publicKey = str_replace("\n", "\r\n", $keyDetails['key']);
}
}
} catch (\Exception $e) {
Util::logError($e->getMessage());
if ($retry) {
$retry = false;
} elseif ($hasPublicKey && !empty($jku)) {
try {
$publicKey = $this->fetchPublicKey($jku);
$key = $this->fetchPublicKey($jku);
$retry = true;
} catch (\Exception $e) {

Expand Down Expand Up @@ -418,7 +441,10 @@ private function fetchPublicKey(string $jku): array
$keys = Util::jsonDecode($http->response, true);
if (is_array($keys)) {
try {
$publicKey = JWK::parseKeySet($keys, $this->getHeader('alg'));
$keys = JWK::parseKeySet($keys, $this->getHeader('alg'));
if (array_key_exists($this->getHeader('kid'), $keys)) {
$publicKey[$this->getHeader('kid')] = $keys[$this->getHeader('kid')];
}
} catch (\Exception $e) {

}
Expand Down
20 changes: 20 additions & 0 deletions src/Jwt/WebTokenClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -265,12 +265,29 @@ public static function getLastPayload(): array|object|null
/**
* Verify the signature of the JWT.
*
* @deprecated Use verifySignature() instead
*
* @param string|null $publicKey Public key of issuer
* @param string|null $jku JSON Web Key URL of issuer (optional)
*
* @return bool True if the JWT has a valid signature
*/
public function verify(?string $publicKey, ?string $jku = null): bool
{
return $this->verifySignature($publicKey, $jku);
}

/**
* Verify the signature of the JWT.
*
* If a new public key is fetched and used to successfully verify the signature, the value of the publicKey parameter is updated.
*
* @param string|null $publicKey Public key of issuer (passed by reference)
* @param string|null $jku JSON Web Key URL of issuer (optional)
*
* @return bool True if the JWT has a valid signature
*/
public function verifySignature(?string &$publicKey, ?string $jku = null): bool
{
$ok = false;
$hasPublicKey = !empty($publicKey);
Expand Down Expand Up @@ -306,6 +323,9 @@ public function verify(?string $publicKey, ?string $jku = null): bool
}
$jwks = $this->fetchPublicKey($jwksUrl, $this->getHeader('kid'));
$ok = $jwsVerifier->verifyWithKeySet($this->jwt, $jwks, 0);
if ($ok) {
$publicKey = json_encode($jwks->get($this->getHeader('kid'))->all());
}
} else {
$json = Util::jsonDecode($publicKey, true); // Check if public key is in PEM or JWK format
if (is_null($json)) {
Expand Down
16 changes: 15 additions & 1 deletion src/System.php
Original file line number Diff line number Diff line change
Expand Up @@ -1193,7 +1193,21 @@ public function verifySignature(): bool
if (!$ok) {
$this->setReason('Invalid nonce');
} elseif (!empty($publicKey) || !empty($jku) || Jwt::$allowJkuHeader) {
$ok = $this->jwt->verify($publicKey, $jku);
$currentKey = $publicKey;
$ok = $this->jwt->verifySignature($publicKey, $jku);
if (!Util::$disableFetchedPublicKeysSave && ($currentKey !== $publicKey)) {
if ($this instanceof Tool) {
$this->platform->rsaKey = $publicKey;
$this->platform->save();
} else {
if (!empty(Tool::$defaultTool)) {
Tool::$defaultTool->rsaKey = $publicKey;
Tool::$defaultTool->save();
} else {
$this->rsaKey = $publicKey;
}
}
}
if (!$ok) {
$this->setReason('JWT signature check failed - perhaps an invalid public key or timestamp');
}
Expand Down
19 changes: 13 additions & 6 deletions src/Util.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,13 @@ final class Util
*/
public static bool $strictMode = false;

/**
* Whether the automatic saving of fetched valid public keys should be disabled.
*
* @var bool $disableFetchedPublicKeysSave
*/
public static bool $disableFetchedPublicKeysSave = false;

/**
* Delay (in seconds) before a manual button is displayed in case a browser is blocking a form submission.
*
Expand Down Expand Up @@ -873,9 +880,9 @@ public static function checkInteger(object $obj, string $fullname, bool $require
/**
* Get the named boolean element from object.
*
* @param object $obj Object containing the element
* @param string $fullname Name of element (may include a path)
* @param bool $required True if the element must be present
* @param object $obj Object containing the element
* @param string $fullname Name of element (may include a path)
* @param bool $required True if the element must be present
* @param string|null $default Value to return when a conversion is not possible (optional, default is an empty string)
*
* @return bool|null Value of element (or null if not found or valid)
Expand Down Expand Up @@ -908,9 +915,9 @@ public static function checkBoolean(object $obj, string $fullname, bool $require
/**
* Get the named number element from object.
*
* @param object $obj Object containing the element
* @param string $fullname Name of element (may include a path)
* @param bool $required True if the element must be present
* @param object $obj Object containing the element
* @param string $fullname Name of element (may include a path)
* @param bool $required True if the element must be present
*
* @return int Value of element (or 0 if not found or valid)
*/
Expand Down

0 comments on commit 387a2f8

Please sign in to comment.