From 452ca3da7c848a4a4781991edd080c9175d121d5 Mon Sep 17 00:00:00 2001 From: Dmitry Patsura Date: Thu, 18 Jul 2019 21:58:33 +0300 Subject: [PATCH] Feature: Introduce RSA::fromRSAPublicKeyFile, rework JWKSet --- README.md | 1 + src/JWK.php | 14 ++++++++++++++ src/JWKSet.php | 20 +++++++++++--------- src/JWT.php | 6 +++--- tests/JWKTest.php | 6 +++--- tests/JWTTest.php | 13 ++++++++++--- 6 files changed, 42 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index b4269df..7db5898 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Implementation: - JWT (JSON Web Token) [RFC 7519](https://tools.ietf.org/html/rfc7519) - JWK (JSON Web Keys) [RFC 7517](https://tools.ietf.org/html/rfc7517) +- JWKs (JSON Web Key Set) [RFC 7517](https://tools.ietf.org/html/rfc7517#section-8.4) ## Encode diff --git a/src/JWK.php b/src/JWK.php index ba8ae54..fc3ffd0 100644 --- a/src/JWK.php +++ b/src/JWK.php @@ -157,6 +157,20 @@ protected function parseRSAKey(array $parameters) } } + /** + * @param string $file + * @return JWK + */ + public static function fromRSAPublicKeyFile(string $file): JWK + { + $contentOrFalse = file_get_contents($file); + if ($contentOrFalse === false) { + throw new RuntimeException('Unable to read public key'); + } + + return self::fromRSAPublicKey($contentOrFalse); + } + /** * @param string $content * @return JWK diff --git a/src/JWKSet.php b/src/JWKSet.php index ea5ff9e..5957281 100644 --- a/src/JWKSet.php +++ b/src/JWKSet.php @@ -17,24 +17,26 @@ class JWKSet protected $keys; /** - * @param array $keys + * @param array $parameters */ - public function __construct(array $keys) + public function __construct(array $parameters) { - $this->keys = array_map(static function ($key) { - return new JWK($key); - }, $keys); + if (isset($parameters['keys'])) { + $this->keys = $parameters['keys']; + } else { + throw new \InvalidArgumentException('JWKs must define keys'); + } } /** * @param string $kid * @return JWK */ - public function findKeyByKind(string $kid) + public function findKeyByKid(string $kid) { - foreach ($this->keys as $key => $jwk) { - if ($key === $kid) { - return $jwk; + foreach ($this->keys as $jwk) { + if (isset($jwk['kid']) && $jwk['kid'] === $kid) { + return new JWK($jwk); } } diff --git a/src/JWT.php b/src/JWT.php index 88140d0..4933d8e 100644 --- a/src/JWT.php +++ b/src/JWT.php @@ -103,14 +103,14 @@ public function __construct(array $payload, array $headers = [], $signature = nu /** * @param string $token - * @param string|JWKSet $publicKeyOrSecret + * @param string|JWKSet|mixed $publicKeyOrSecret * @param DecodeOptions $options * @return JWT * @throws InvalidJWT */ public static function decode(string $token, $publicKeyOrSecret, DecodeOptions $options) { - if (!is_string($publicKeyOrSecret) && !$publicKeyOrSecret instanceof JWKSet) { + if (!is_string($publicKeyOrSecret) && !($publicKeyOrSecret instanceof JWKSet)) { throw new \InvalidArgumentException('$privateKeyOrSecret must be string/JWK/JWKSet'); } @@ -263,7 +263,7 @@ protected function verifySignature($data, $publicKeyOrSecret, DecodeOptions $opt } if ($publicKeyOrSecret instanceof JWKSet) { - $jwk = $publicKeyOrSecret->findKeyByKind($this->headers['kid']); + $jwk = $publicKeyOrSecret->findKeyByKid($this->headers['kid']); $secretOrKey = $jwk->getPublicKey(); } else { $secretOrKey = $publicKeyOrSecret; diff --git a/tests/JWKTest.php b/tests/JWKTest.php index 88b4d2d..20462bb 100644 --- a/tests/JWKTest.php +++ b/tests/JWKTest.php @@ -12,7 +12,7 @@ class JWKTest extends AbstractTestCase { public function testFromRSA256PublicKey() { - $jwk = JWK::fromRSAPublicKey(file_get_contents(__DIR__ . '/assets/rs256.key.pub')); + $jwk = JWK::fromRSAPublicKeyFile(__DIR__ . '/assets/rs256.key.pub'); parent::assertSame( [ @@ -27,7 +27,7 @@ public function testFromRSA256PublicKey() public function testFromRSA384PublicKey() { - $jwk = JWK::fromRSAPublicKey(file_get_contents(__DIR__ . '/assets/rs384.key.pub')); + $jwk = JWK::fromRSAPublicKeyFile(__DIR__ . '/assets/rs384.key.pub'); parent::assertSame( [ @@ -42,7 +42,7 @@ public function testFromRSA384PublicKey() public function testFromRSA512PublicKey() { - $jwk = JWK::fromRSAPublicKey(file_get_contents(__DIR__ . '/assets/rs512.key.pub')); + $jwk = JWK::fromRSAPublicKeyFile(__DIR__ . '/assets/rs512.key.pub'); parent::assertSame( [ diff --git a/tests/JWTTest.php b/tests/JWTTest.php index f1add73..db67c04 100644 --- a/tests/JWTTest.php +++ b/tests/JWTTest.php @@ -165,7 +165,7 @@ public function testValidateHeaderSuccess() self::callProtectedMethod( $token, 'validateHeader', - new JWKSet([]), + new JWKSet(['keys' => []]), new DecodeOptions(['RS256']) ); @@ -208,7 +208,7 @@ public function testValidateHeaderNoKid() self::callProtectedMethod( $token, 'validateHeader', - new JWKSet([]), + new JWKSet(['keys' => []]), new DecodeOptions(['RS256']) ); } @@ -284,7 +284,14 @@ public function getEncodeToDecodeDataProvider() [ file_get_contents(__DIR__ . '/assets/rs512.key'), new JWKSet([ - $kid => JWK::fromRSAPublicKey(file_get_contents(__DIR__ . '/assets/rs512.key.pub'))->toArray() + 'keys' => [ + array_merge( + JWK::fromRSAPublicKeyFile(__DIR__ . '/assets/rs512.key.pub')->toArray(), + [ + 'kid' => $kid + ] + ) + ] ]), 'RS512', ['kid' => $kid],