forked from luciferous/jwt
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This aims to provide backwards compatibility by guessing the algorithm for a key based on the key's contents, but it will likely fail in corner cases. If this is merged, users **SHOULD** be explicit about the algorithms they're using. i.e. Instead of $keyAsString, pass in (new JWTKey($keyAsString, 'ES384'))
- Loading branch information
1 parent
d2113d9
commit 31a7c16
Showing
3 changed files
with
254 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
<?php | ||
namespace Firebase\JWT\Keys; | ||
|
||
use Firebase\JWT\JWT; | ||
|
||
class JWTKey | ||
{ | ||
/** @var string $alg */ | ||
private $alg; | ||
|
||
/** @var string $keyMaterial */ | ||
private $keyMaterial; | ||
|
||
/** | ||
* @param string $keyMaterial | ||
* @param string|array|null $alg | ||
*/ | ||
public function __construct($keyMaterial, $alg = null) | ||
{ | ||
if (is_array($alg)) { | ||
$alg = self::guessAlgFromKeyMaterial($keyMaterial, $alg); | ||
} elseif (is_null($alg)) { | ||
$alg = self::guessAlgFromKeyMaterial($keyMaterial); | ||
} | ||
$this->keyMaterial = $keyMaterial; | ||
$this->alg = $alg; | ||
} | ||
|
||
/** | ||
* Is the header algorithm valid for this key? | ||
* | ||
* @param string $headerAlg | ||
* @return bool | ||
*/ | ||
public function isValidForAlg($headerAlg) | ||
{ | ||
return JWT::constantTimeEquals($this->alg, $headerAlg); | ||
} | ||
|
||
/** | ||
* @return string | ||
*/ | ||
public function getKeyMaterial() | ||
{ | ||
return $this->keyMaterial; | ||
} | ||
|
||
/** | ||
* This is a best-effort attempt to guess the algorithm for a given key | ||
* based on its contents. | ||
* | ||
* It will probably be wrong in a lot of corner cases. | ||
* | ||
* If it is, construct a JWTKey object and/or Keyring of JWTKey objects | ||
* with the correct algorithms. | ||
* | ||
* @param string $keyMaterial | ||
* @param array $candidates | ||
* @return string | ||
*/ | ||
public static function guessAlgFromKeyMaterial($keyMaterial, array $candidates = array()) | ||
{ | ||
$length = JWT::safeStrlen($keyMaterial); | ||
if ($length >= 720) { | ||
// RSA keys | ||
if (preg_match('#^-+BEGIN.+(PRIVATE|PUBLIC) KEY-+#', $keyMaterial)) { | ||
if (in_array('RS512', $candidates)) { | ||
return 'RS512'; | ||
} | ||
if (in_array('RS384', $candidates)) { | ||
return 'RS384'; | ||
} | ||
return 'RS256'; | ||
} | ||
} elseif ($length >= 220) { | ||
// ECDSA private keys | ||
if (preg_match('#^-+BEGIN EC PRIVATE KEY-+#', $keyMaterial)) { | ||
if (in_array('ES512', $candidates)) { | ||
return 'ES512'; | ||
} | ||
if (in_array('ES384', $candidates)) { | ||
return 'ES384'; | ||
} | ||
return 'ES256'; | ||
} | ||
} elseif ($length >= 170) { | ||
// ECDSA public keys | ||
if (preg_match('#^-+BEGIN EC PUBLICY-+#', $keyMaterial)) { | ||
if (in_array('ES512', $candidates)) { | ||
return 'ES512'; | ||
} | ||
if (in_array('ES384', $candidates)) { | ||
return 'ES384'; | ||
} | ||
return 'ES256'; | ||
} | ||
} elseif ($length >= 40 && $length <= 88) { | ||
// Likely base64-encoded EdDSA key | ||
if (in_array('EdDSA', $candidates)) { | ||
return 'EdDSA'; | ||
} | ||
} | ||
|
||
// Last resort: HMAC | ||
if (in_array('HS512', $candidates)) { | ||
return 'HS512'; | ||
} | ||
if (in_array('HS384', $candidates)) { | ||
return 'HS384'; | ||
} | ||
return 'HS256'; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
<?php | ||
namespace Firebase\JWT\Keys; | ||
|
||
use ArrayAccess; | ||
use RuntimeException; | ||
|
||
final class Keyring implements ArrayAccess | ||
{ | ||
/** @var array<string, JWTKey> $mapping */ | ||
private $mapping; | ||
|
||
/** | ||
* @param array<string, JWTKey> $mapping | ||
*/ | ||
public function __construct(array $mapping = array()) | ||
{ | ||
$this->mapping = $mapping; | ||
} | ||
|
||
/** | ||
* @param string $keyId | ||
* @param JWTKey $key | ||
* @return $this | ||
*/ | ||
public function mapKeyId($keyId, JWTKey $key) | ||
{ | ||
$this->mapping[$keyId] = $key; | ||
return $this; | ||
} | ||
|
||
/** | ||
* @param mixed $offset | ||
* @return bool | ||
*/ | ||
public function offsetExists($offset) | ||
{ | ||
if (!is_string($offset)) { | ||
throw new RuntimeException('Type error: argument 1 must be a string'); | ||
} | ||
return array_key_exists($offset, $this->mapping); | ||
} | ||
|
||
/** | ||
* @param mixed $offset | ||
* @return JWTKey | ||
*/ | ||
public function offsetGet($offset) | ||
{ | ||
$value = $this->mapping[$offset]; | ||
if (!($value instanceof JWTKey)) { | ||
throw new RuntimeException('Type error: return value not an instance of JWTKey'); | ||
} | ||
return $value; | ||
} | ||
|
||
/** | ||
* @param string $offset | ||
* @param JWTKey $value | ||
*/ | ||
public function offsetSet($offset, $value) | ||
{ | ||
if (!is_string($offset)) { | ||
throw new RuntimeException('Type error: argument 1 must be a string'); | ||
} | ||
if (!($value instanceof JWTKey)) { | ||
throw new RuntimeException('Type error: argument 2 must be an instance of JWT'); | ||
} | ||
$this->mapKeyId($offset, $value); | ||
} | ||
|
||
/** | ||
* @param string $offset | ||
*/ | ||
public function offsetUnset($offset) | ||
{ | ||
if (!is_string($offset)) { | ||
throw new RuntimeException('Type error: argument 1 must be a string'); | ||
} | ||
unset($this->mapping[$offset]); | ||
} | ||
} |