expectException(UnexpectedValueException::class); $this->expectExceptionMessage('JWK must contain a "kty" parameter'); $badJwk = ['kid' => 'foo']; $keys = JWK::parseKeySet(['keys' => [$badJwk]]); } public function testInvalidAlgorithm() { $this->expectException(UnexpectedValueException::class); $this->expectExceptionMessage('No supported algorithms found in JWK Set'); $badJwk = ['kty' => 'BADTYPE', 'alg' => 'RSA256']; $keys = JWK::parseKeySet(['keys' => [$badJwk]]); } public function testParsePrivateKey() { $this->expectException(UnexpectedValueException::class); $this->expectExceptionMessage('RSA private keys are not supported'); $jwkSet = json_decode( file_get_contents(__DIR__ . '/data/rsa-jwkset.json'), true ); $jwkSet['keys'][0]['d'] = 'privatekeyvalue'; JWK::parseKeySet($jwkSet); } public function testParsePrivateKeyWithoutAlg() { $this->expectException(UnexpectedValueException::class); $this->expectExceptionMessage('JWK must contain an "alg" parameter'); $jwkSet = json_decode( file_get_contents(__DIR__ . '/data/rsa-jwkset.json'), true ); unset($jwkSet['keys'][0]['alg']); JWK::parseKeySet($jwkSet); } public function testParsePrivateKeyWithoutAlgWithDefaultAlgParameter() { $jwkSet = json_decode( file_get_contents(__DIR__ . '/data/rsa-jwkset.json'), true ); unset($jwkSet['keys'][0]['alg']); $jwks = JWK::parseKeySet($jwkSet, 'foo'); $this->assertSame('foo', $jwks['jwk1']->getAlgorithm()); } public function testParseKeyWithEmptyDValue() { $jwkSet = json_decode( file_get_contents(__DIR__ . '/data/rsa-jwkset.json'), true ); // empty or null values are ok $jwkSet['keys'][0]['d'] = null; $keys = JWK::parseKeySet($jwkSet); $this->assertTrue(\is_array($keys)); } public function testParseJwkKeySet() { $jwkSet = json_decode( file_get_contents(__DIR__ . '/data/rsa-jwkset.json'), true ); $keys = JWK::parseKeySet($jwkSet); $this->assertTrue(\is_array($keys)); $this->assertArrayHasKey('jwk1', $keys); self::$keys = $keys; } public function testParseJwkKey_empty() { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('JWK must not be empty'); JWK::parseKeySet(['keys' => [[]]]); } public function testParseJwkKeySet_empty() { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('JWK Set did not contain any keys'); JWK::parseKeySet(['keys' => []]); } /** * @depends testParseJwkKeySet */ public function testDecodeByJwkKeySetTokenExpired() { $privKey1 = file_get_contents(__DIR__ . '/data/rsa1-private.pem'); $payload = ['exp' => strtotime('-1 hour')]; $msg = JWT::encode($payload, $privKey1, 'RS256', 'jwk1'); $this->expectException(ExpiredException::class); JWT::decode($msg, self::$keys); } /** * @dataProvider provideDecodeByJwkKeySet */ public function testDecodeByJwkKeySet($pemFile, $jwkFile, $alg, $keyId) { $privKey1 = file_get_contents(__DIR__ . '/data/' . $pemFile); $payload = ['sub' => 'foo', 'exp' => strtotime('+10 seconds')]; $msg = JWT::encode($payload, $privKey1, $alg, $keyId); $jwkSet = json_decode( file_get_contents(__DIR__ . '/data/' . $jwkFile), true ); $keys = JWK::parseKeySet($jwkSet); $result = JWT::decode($msg, $keys); $this->assertSame('foo', $result->sub); } public function provideDecodeByJwkKeySet() { return [ ['rsa1-private.pem', 'rsa-jwkset.json', 'RS256', 'jwk1'], ['ecdsa256-private.pem', 'ec-jwkset.json', 'ES256', 'jwk1'], ['ecdsa384-private.pem', 'ec-jwkset.json', 'ES384', 'jwk4'], ['ed25519-1.sec', 'ed25519-jwkset.json', 'EdDSA', 'jwk1'], ]; } /** * @depends testParseJwkKeySet */ public function testDecodeByMultiJwkKeySet() { $privKey2 = file_get_contents(__DIR__ . '/data/rsa2-private.pem'); $payload = ['sub' => 'bar', 'exp' => strtotime('+10 seconds')]; $msg = JWT::encode($payload, $privKey2, 'RS256', 'jwk2'); $result = JWT::decode($msg, self::$keys); $this->assertSame('bar', $result->sub); } public function testDecodeByOctetJwkKeySet() { $jwkSet = json_decode( file_get_contents(__DIR__ . '/data/octet-jwkset.json'), true ); $keys = JWK::parseKeySet($jwkSet); $payload = ['sub' => 'foo', 'exp' => strtotime('+10 seconds')]; foreach ($keys as $keyId => $key) { $msg = JWT::encode($payload, $key->getKeyMaterial(), $key->getAlgorithm(), $keyId); $result = JWT::decode($msg, $keys); $this->assertSame('foo', $result->sub); } } public function testOctetJwkMissingK() { $this->expectException(UnexpectedValueException::class); $this->expectExceptionMessage('k not set'); $badJwk = ['kty' => 'oct', 'alg' => 'HS256']; $keys = JWK::parseKeySet(['keys' => [$badJwk]]); } public function testParseKey() { // Use a known module and exponent, and ensure it parses as expected $jwk = [ 'alg' => 'RS256', 'kty' => 'RSA', 'n' => 'hsYvCPtkUV7SIxwkOkJsJfhwV_CMdXU5i0UmY2QEs-Pa7v0-0y-s4EjEDtsQ8Yow6hc670JhkGBcMzhU4DtrqNGROXebyOse5FX0m0UvWo1qXqNTf28uBKB990mY42Icr8sGjtOw8ajyT9kufbmXi3eZKagKpG0TDGK90oBEfoGzCxoFT87F95liNth_GoyU5S8-G3OqIqLlQCwxkI5s-g2qvg_aooALfh1rhvx2wt4EJVMSrdnxtPQSPAtZBiw5SwCnVglc6OnalVNvAB2JArbqC9GAzzz9pApAk28SYg5a4hPiPyqwRv-4X1CXEK8bO5VesIeRX0oDf7UoM-pVAw', 'use' => 'sig', 'e' => 'AQAB', 'kid' => '838c06c62046c2d948affe137dd5310129f4d5d1' ]; $key = JWK::parseKey($jwk); $this->assertNotNull($key); $openSslKey = $key->getKeyMaterial(); $pubKey = openssl_pkey_get_public($openSslKey); $keyData = openssl_pkey_get_details($pubKey); $expectedPublicKey = <<assertEquals($expectedPublicKey, $keyData['key']); } }