added predis and eseye back in.

This commit is contained in:
2020-12-25 11:28:41 +00:00
parent 0ddd298350
commit 017f72b42e
670 changed files with 60992 additions and 10 deletions

View File

@@ -0,0 +1,4 @@
# Contributing
This repository is a sub repository of [the JWT Framework](https://github.com/web-token/jwt-framework) project and is READ ONLY.
Please do not submit any Pull Requests here. It will be automatically closed.

View File

@@ -0,0 +1 @@
patreon: FlorentMorselli

View File

@@ -0,0 +1,3 @@
Please do not submit any Pull Requests here. It will be automatically closed.
You should submit it here: https://github.com/web-token/jwt-framework/pulls

29
vendor/web-token/jwt-core/Algorithm.php vendored Normal file
View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Jose\Component\Core;
interface Algorithm
{
/**
* Returns the name of the algorithm.
*/
public function name(): string;
/**
* Returns the key types suitable for this algorithm (e.g. "oct", "RSA"...).
*
* @return string[]
*/
public function allowedKeyTypes(): array;
}

View File

@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Jose\Component\Core;
use function array_key_exists;
use InvalidArgumentException;
class AlgorithmManager
{
/**
* @var array
*/
private $algorithms = [];
/**
* @param Algorithm[] $algorithms
*/
public function __construct(array $algorithms)
{
foreach ($algorithms as $algorithm) {
$this->add($algorithm);
}
}
/**
* Returns true if the algorithm is supported.
*
* @param string $algorithm The algorithm
*/
public function has(string $algorithm): bool
{
return array_key_exists($algorithm, $this->algorithms);
}
/**
* Returns the list of names of supported algorithms.
*
* @return string[]
*/
public function list(): array
{
return array_keys($this->algorithms);
}
/**
* Returns the algorithm if supported, otherwise throw an exception.
*
* @param string $algorithm The algorithm
*
* @throws InvalidArgumentException if the algorithm is not supported
*/
public function get(string $algorithm): Algorithm
{
if (!$this->has($algorithm)) {
throw new InvalidArgumentException(sprintf('The algorithm "%s" is not supported.', $algorithm));
}
return $this->algorithms[$algorithm];
}
/**
* Adds an algorithm to the manager.
*/
public function add(Algorithm $algorithm): void
{
$name = $algorithm->name();
$this->algorithms[$name] = $algorithm;
}
}

View File

@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Jose\Component\Core;
use InvalidArgumentException;
use function is_string;
class AlgorithmManagerFactory
{
/**
* @var array
*/
private $algorithms = [];
/**
* Adds an algorithm.
*
* Each algorithm is identified by an alias hence it is allowed to have the same algorithm twice (or more).
* This can be helpful when an algorithm have several configuration options.
*/
public function add(string $alias, Algorithm $algorithm): void
{
$this->algorithms[$alias] = $algorithm;
}
/**
* Returns the list of aliases.
*
* @return string[]
*/
public function aliases(): array
{
return array_keys($this->algorithms);
}
/**
* Returns all algorithms supported by this factory.
* This is an associative array. Keys are the aliases of the algorithms.
*
* @return Algorithm[]
*/
public function all(): array
{
return $this->algorithms;
}
/**
* Create an algorithm manager using the given aliases.
*
* @param string[] $aliases
*
* @throws InvalidArgumentException if the alias is invalid or is not supported
*/
public function create(array $aliases): AlgorithmManager
{
$algorithms = [];
foreach ($aliases as $alias) {
if (!is_string($alias)) {
throw new InvalidArgumentException('Invalid alias');
}
if (!isset($this->algorithms[$alias])) {
throw new InvalidArgumentException(sprintf('The algorithm with the alias "%s" is not supported.', $alias));
}
$algorithms[] = $this->algorithms[$alias];
}
return new AlgorithmManager($algorithms);
}
}

143
vendor/web-token/jwt-core/JWK.php vendored Normal file
View File

@@ -0,0 +1,143 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Jose\Component\Core;
use function array_key_exists;
use Base64Url\Base64Url;
use function in_array;
use InvalidArgumentException;
use function is_array;
use JsonSerializable;
class JWK implements JsonSerializable
{
/**
* @var array
*/
private $values = [];
/**
* Creates a JWK object using the given values.
* The member "kty" is mandatory. Other members are NOT checked.
*
* @throws InvalidArgumentException if the key parameter "kty" is missing
*/
public function __construct(array $values)
{
if (!isset($values['kty'])) {
throw new InvalidArgumentException('The parameter "kty" is mandatory.');
}
$this->values = $values;
}
/**
* Creates a JWK object using the given Json string.
*
* @throws InvalidArgumentException if the data is not valid
*
* @return JWK
*/
public static function createFromJson(string $json): self
{
$data = json_decode($json, true);
if (!is_array($data)) {
throw new InvalidArgumentException('Invalid argument.');
}
return new self($data);
}
/**
* Returns the values to be serialized.
*/
public function jsonSerialize(): array
{
return $this->values;
}
/**
* Get the value with a specific key.
*
* @param string $key The key
*
* @throws InvalidArgumentException if the key does not exist
*
* @return null|mixed
*/
public function get(string $key)
{
if (!$this->has($key)) {
throw new InvalidArgumentException(sprintf('The value identified by "%s" does not exist.', $key));
}
return $this->values[$key];
}
/**
* Returns true if the JWK has the value identified by.
*
* @param string $key The key
*/
public function has(string $key): bool
{
return array_key_exists($key, $this->values);
}
/**
* Get all values stored in the JWK object.
*
* @return array Values of the JWK object
*/
public function all(): array
{
return $this->values;
}
/**
* Returns the thumbprint of the key.
*
* @see https://tools.ietf.org/html/rfc7638
*
* @throws InvalidArgumentException if the hashing function is not supported
*/
public function thumbprint(string $hash_algorithm): string
{
if (!in_array($hash_algorithm, hash_algos(), true)) {
throw new InvalidArgumentException(sprintf('The hash algorithm "%s" is not supported.', $hash_algorithm));
}
$values = array_intersect_key($this->values, array_flip(['kty', 'n', 'e', 'crv', 'x', 'y', 'k']));
ksort($values);
$input = json_encode($values, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
return Base64Url::encode(hash($hash_algorithm, $input, true));
}
/**
* Returns the associated public key.
* This method has no effect for:
* - public keys
* - shared keys
* - unknown keys.
*
* Known keys are "oct", "RSA", "EC" and "OKP".
*
* @return JWK
*/
public function toPublic(): self
{
$values = array_diff_key($this->values, array_flip(['p', 'd', 'q', 'dp', 'dq', 'qi']));
return new self($values);
}
}

338
vendor/web-token/jwt-core/JWKSet.php vendored Normal file
View File

@@ -0,0 +1,338 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Jose\Component\Core;
use function array_key_exists;
use ArrayIterator;
use function count;
use Countable;
use function in_array;
use InvalidArgumentException;
use function is_array;
use IteratorAggregate;
use JsonSerializable;
use Traversable;
class JWKSet implements Countable, IteratorAggregate, JsonSerializable
{
/**
* @var array
*/
private $keys = [];
/**
* @param JWK[] $keys
*
* @throws InvalidArgumentException if the list is invalid
*/
public function __construct(array $keys)
{
foreach ($keys as $k => $key) {
if (!$key instanceof JWK) {
throw new InvalidArgumentException('Invalid list. Should only contains JWK objects');
}
if ($key->has('kid')) {
unset($keys[$k]);
$this->keys[$key->get('kid')] = $key;
} else {
$this->keys[] = $key;
}
}
}
/**
* Creates a JWKSet object using the given values.
*
* @throws InvalidArgumentException if the keyset is not valid
*
* @return JWKSet
*/
public static function createFromKeyData(array $data): self
{
if (!isset($data['keys'])) {
throw new InvalidArgumentException('Invalid data.');
}
if (!is_array($data['keys'])) {
throw new InvalidArgumentException('Invalid data.');
}
$jwkset = new self([]);
foreach ($data['keys'] as $key) {
$jwk = new JWK($key);
if ($jwk->has('kid')) {
$jwkset->keys[$jwk->get('kid')] = $jwk;
} else {
$jwkset->keys[] = $jwk;
}
}
return $jwkset;
}
/**
* Creates a JWKSet object using the given Json string.
*
* @throws InvalidArgumentException if the data is not valid
*
* @return JWKSet
*/
public static function createFromJson(string $json): self
{
$data = json_decode($json, true);
if (!is_array($data)) {
throw new InvalidArgumentException('Invalid argument.');
}
return self::createFromKeyData($data);
}
/**
* Returns an array of keys stored in the key set.
*
* @return JWK[]
*/
public function all(): array
{
return $this->keys;
}
/**
* Add key to store in the key set.
* This method is immutable and will return a new object.
*
* @return JWKSet
*/
public function with(JWK $jwk): self
{
$clone = clone $this;
if ($jwk->has('kid')) {
$clone->keys[$jwk->get('kid')] = $jwk;
} else {
$clone->keys[] = $jwk;
}
return $clone;
}
/**
* Remove key from the key set.
* This method is immutable and will return a new object.
*
* @param int|string $key Key to remove from the key set
*
* @return JWKSet
*/
public function without($key): self
{
if (!$this->has($key)) {
return $this;
}
$clone = clone $this;
unset($clone->keys[$key]);
return $clone;
}
/**
* Returns true if the key set contains a key with the given index.
*
* @param int|string $index
*/
public function has($index): bool
{
return array_key_exists($index, $this->keys);
}
/**
* Returns the key with the given index. Throws an exception if the index is not present in the key store.
*
* @param int|string $index
*
* @throws InvalidArgumentException if the index is not defined
*/
public function get($index): JWK
{
if (!$this->has($index)) {
throw new InvalidArgumentException('Undefined index.');
}
return $this->keys[$index];
}
/**
* Returns the values to be serialized.
*/
public function jsonSerialize(): array
{
return ['keys' => array_values($this->keys)];
}
/**
* Returns the number of keys in the key set.
*
* @param int $mode
*/
public function count($mode = COUNT_NORMAL): int
{
return count($this->keys, $mode);
}
/**
* Try to find a key that fits on the selected requirements.
* Returns null if not found.
*
* @param string $type Must be 'sig' (signature) or 'enc' (encryption)
* @param null|Algorithm $algorithm Specifies the algorithm to be used
* @param array $restrictions More restrictions such as 'kid' or 'kty'
*
* @throws InvalidArgumentException if the key type is not valid (must be "sig" or "enc")
*/
public function selectKey(string $type, ?Algorithm $algorithm = null, array $restrictions = []): ?JWK
{
if (!in_array($type, ['enc', 'sig'], true)) {
throw new InvalidArgumentException('Allowed key types are "sig" or "enc".');
}
$result = [];
foreach ($this->keys as $key) {
$ind = 0;
$can_use = $this->canKeyBeUsedFor($type, $key);
if (false === $can_use) {
continue;
}
$ind += $can_use;
$alg = $this->canKeyBeUsedWithAlgorithm($algorithm, $key);
if (false === $alg) {
continue;
}
$ind += $alg;
if (false === $this->doesKeySatisfyRestrictions($restrictions, $key)) {
continue;
}
$result[] = ['key' => $key, 'ind' => $ind];
}
if (0 === count($result)) {
return null;
}
usort($result, [$this, 'sortKeys']);
return $result[0]['key'];
}
/**
* Internal method only. Should not be used.
*
* @internal
* @internal
*/
public static function sortKeys(array $a, array $b): int
{
if ($a['ind'] === $b['ind']) {
return 0;
}
return ($a['ind'] > $b['ind']) ? -1 : 1;
}
/**
* Internal method only. Should not be used.
*
* @internal
*/
public function getIterator(): Traversable
{
return new ArrayIterator($this->keys);
}
/**
* @throws InvalidArgumentException if the key does not fulfill with the "key_ops" constraint
*
* @return bool|int
*/
private function canKeyBeUsedFor(string $type, JWK $key)
{
if ($key->has('use')) {
return $type === $key->get('use') ? 1 : false;
}
if ($key->has('key_ops')) {
$key_ops = $key->get('key_ops');
if (!is_array($key_ops)) {
throw new InvalidArgumentException('Invalid key parameter "key_ops". Should be a list of key operations');
}
return $type === self::convertKeyOpsToKeyUse($key_ops) ? 1 : false;
}
return 0;
}
/**
* @return bool|int
*/
private function canKeyBeUsedWithAlgorithm(?Algorithm $algorithm, JWK $key)
{
if (null === $algorithm) {
return 0;
}
if (!in_array($key->get('kty'), $algorithm->allowedKeyTypes(), true)) {
return false;
}
if ($key->has('alg')) {
return $algorithm->name() === $key->get('alg') ? 2 : false;
}
return 1;
}
private function doesKeySatisfyRestrictions(array $restrictions, JWK $key): bool
{
foreach ($restrictions as $k => $v) {
if (!$key->has($k) || $v !== $key->get($k)) {
return false;
}
}
return true;
}
/**
* @throws InvalidArgumentException if the key operation is not supported
*/
private static function convertKeyOpsToKeyUse(array $key_ops): string
{
switch (true) {
case in_array('verify', $key_ops, true):
case in_array('sign', $key_ops, true):
return 'sig';
case in_array('encrypt', $key_ops, true):
case in_array('decrypt', $key_ops, true):
case in_array('wrapKey', $key_ops, true):
case in_array('unwrapKey', $key_ops, true):
case in_array('deriveKey', $key_ops, true):
case in_array('deriveBits', $key_ops, true):
return 'enc';
default:
throw new InvalidArgumentException(sprintf('Unsupported key operation value "%s"', $key_ops));
}
}
}

23
vendor/web-token/jwt-core/JWT.php vendored Normal file
View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Jose\Component\Core;
interface JWT
{
/**
* Returns the payload of the JWT.
* null is a valid payload (e.g. JWS with detached payload).
*/
public function getPayload(): ?string;
}

21
vendor/web-token/jwt-core/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-2019 Spomky-Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

15
vendor/web-token/jwt-core/README.md vendored Normal file
View File

@@ -0,0 +1,15 @@
PHP JWT Core Component
======================
This repository is a sub repository of [the JWT Framework](https://github.com/web-token/jwt-framework) project and is READ ONLY.
**Please do not submit any Pull Request here.**
You should go to [the main repository](https://github.com/web-token/jwt-framework) instead.
# Documentation
The official documentation is available as https://web-token.spomky-labs.com/
# Licence
This software is release under [MIT licence](LICENSE).

View File

@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Jose\Component\Core\Tests;
use InvalidArgumentException;
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Core\AlgorithmManagerFactory;
use PHPUnit\Framework\TestCase;
use TypeError;
/**
* @group unit
* @group JWAManager
*
* @internal
*/
class AlgorithmManagerFactoryTest extends TestCase
{
/**
* @var null|AlgorithmManagerFactory
*/
private $algorithmManagerFactory;
/**
* @test
* @covers \Jose\Component\Core\AlgorithmManagerFactory
*/
public function iCanListSupportedAliases(): void
{
static::assertEquals(['foo'], $this->getAlgorithmManagerFactory()->aliases());
static::assertEquals(['foo'], array_keys($this->getAlgorithmManagerFactory()->all()));
}
/**
* @test
* @covers \Jose\Component\Core\AlgorithmManager
*/
public function iCannotCreateAnAlgorithmManagerWithABadArgument(): void
{
$this->expectException(TypeError::class);
new AlgorithmManager(['foo']);
}
/**
* @test
* @covers \Jose\Component\Core\AlgorithmManager
*/
public function iCannotGetAnAlgorithmThatDoesNotExist(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The algorithm "HS384" is not supported.');
$manager = new AlgorithmManager([new FooAlgorithm()]);
static::assertEquals(['foo'], $manager->list());
static::assertTrue($manager->has('foo'));
static::assertFalse($manager->has('HS384'));
$manager->get('HS384');
}
private function getAlgorithmManagerFactory(): AlgorithmManagerFactory
{
if (null === $this->algorithmManagerFactory) {
$this->algorithmManagerFactory = new AlgorithmManagerFactory();
$this->algorithmManagerFactory->add('foo', new FooAlgorithm());
}
return $this->algorithmManagerFactory;
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Jose\Component\Core\Tests;
use Jose\Component\Core\Algorithm;
class FooAlgorithm implements Algorithm
{
public function name(): string
{
return 'foo';
}
public function allowedKeyTypes(): array
{
return ['FOO'];
}
}

View File

@@ -0,0 +1,286 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Jose\Component\Core\Tests;
use function count;
use InvalidArgumentException;
use Jose\Component\Core\JWK;
use Jose\Component\Core\JWKSet;
use PHPUnit\Framework\TestCase;
/**
* @group unit
* @group JWKSet
*
* @internal
*/
class JWKSetTest extends TestCase
{
/**
* @test
*/
public function iCanSelectAKeyInAKeySet(): void
{
$jwkset = $this->getPublicKeySet();
$jwk = $jwkset->selectKey('enc');
static::assertInstanceOf(JWK::class, $jwk);
}
/**
* @test
*/
public function iCannotSelectAKeyFromAKeySetWithUnsupportedUsageParameter(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Allowed key types are "sig" or "enc".');
$jwkset = $this->getPublicKeySet();
$jwkset->selectKey('foo');
}
/**
* @test
*/
public function iCannotCreateAKeySetWithBadArguments(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid data.');
JWKSet::createFromKeyData(['keys' => true]);
}
/**
* @test
*/
public function iCanGetAllKeysInAKeySet(): void
{
$jwkset = $this->getPublicKeySet();
static::assertEquals(3, count($jwkset->all()));
}
/**
* @test
*/
public function iCanAddKeysInAKeySet(): void
{
$jwkset = $this->getPublicKeySet();
$new_jwkset = $jwkset->with(new JWK(['kty' => 'none']));
static::assertEquals(4, count($new_jwkset->all()));
static::assertNotSame($jwkset, $new_jwkset);
}
/**
* @test
*/
public function iCanSelectAKeyWithAlgorithm(): void
{
$jwkset = $this->getPublicKeySet();
$jwk = $jwkset->selectKey('enc', new FooAlgorithm());
static::assertInstanceOf(JWK::class, $jwk);
static::assertEquals(
[
'kid' => '71ee230371d19630bc17fb90ccf20ae632ad8cf8',
'kty' => 'FOO',
'alg' => 'foo',
'use' => 'enc',
],
$jwk->all()
);
}
/**
* @test
*/
public function iCanSelectAKeyWithAlgorithmAndKeyId(): void
{
$jwkset = $this->getPublicKeySet();
$jwk = $jwkset->selectKey('sig', new FooAlgorithm(), ['kid' => '02491f945c951adf156f370788e8ccdabf8877a8']);
static::assertInstanceOf(JWK::class, $jwk);
static::assertEquals(
[
'kid' => '02491f945c951adf156f370788e8ccdabf8877a8',
'kty' => 'FOO',
'alg' => 'foo',
'use' => 'sig',
],
$jwk->all()
);
}
/**
* @test
*/
public function iCanSelectAKeyWithWithKeyId(): void
{
$jwkset = $this->getPublicKeySet();
$jwk = $jwkset->selectKey('sig', null, ['kid' => '02491f945c951adf156f370788e8ccdabf8877a8']);
static::assertInstanceOf(JWK::class, $jwk);
static::assertEquals(
[
'kid' => '02491f945c951adf156f370788e8ccdabf8877a8',
'kty' => 'FOO',
'alg' => 'foo',
'use' => 'sig',
],
$jwk->all()
);
}
/**
* @test
*/
public function theKeySetDoesNotContainsSuitableAKeyThatFitsOnTheRequirements(): void
{
$jwkset = $this->getPublicKeySet();
$jwk = $jwkset->selectKey('enc', null, ['kid' => '02491f945c951adf156f370788e8ccdabf8877a8']);
static::assertNull($jwk);
}
/**
* @test
*/
public function iCanCreateAKeySetUsingValues(): void
{
$values = ['keys' => [[
'kid' => '71ee230371d19630bc17fb90ccf20ae632ad8cf8',
'kty' => 'FOO',
'alg' => 'foo',
'use' => 'sig',
]]];
$jwkset = JWKSet::createFromKeyData($values);
static::assertEquals(1, count($jwkset));
static::assertTrue($jwkset->has('71ee230371d19630bc17fb90ccf20ae632ad8cf8'));
static::assertFalse($jwkset->has(0));
}
/**
* @test
*/
public function keySet(): void
{
$jwk1 = new JWK([
'kty' => 'EC',
'crv' => 'P-256',
'x' => 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
'y' => 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
'use' => 'sign',
'key_ops' => ['sign'],
'alg' => 'ES256',
'kid' => '0123456789',
]);
$jwk2 = new JWK([
'kty' => 'EC',
'crv' => 'P-256',
'x' => 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
'y' => 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
'd' => 'jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI',
'use' => 'sign',
'key_ops' => ['verify'],
'alg' => 'ES256',
'kid' => '9876543210',
]);
$jwkset = new JWKSet([$jwk1]);
$jwkset = $jwkset->with($jwk2);
static::assertEquals('{"keys":[{"kty":"EC","crv":"P-256","x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU","y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0","use":"sign","key_ops":["sign"],"alg":"ES256","kid":"0123456789"},{"kty":"EC","crv":"P-256","x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU","y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0","d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI","use":"sign","key_ops":["verify"],"alg":"ES256","kid":"9876543210"}]}', json_encode($jwkset));
static::assertEquals(2, count($jwkset));
static::assertEquals(2, $jwkset->count());
static::assertTrue($jwkset->has('0123456789'));
static::assertTrue($jwkset->has('9876543210'));
static::assertFalse($jwkset->has(0));
foreach ($jwkset as $key) {
static::assertEquals('EC', $key->get('kty'));
}
static::assertEquals('9876543210', $jwkset->get('9876543210')->get('kid'));
$jwkset = $jwkset->without('9876543210');
$jwkset = $jwkset->without('9876543210');
static::assertEquals(1, count($jwkset));
static::assertEquals(1, $jwkset->count());
$jwkset = $jwkset->without('0123456789');
static::assertEquals(0, $jwkset->count());
}
/**
* @test
*/
public function keySet2(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Undefined index.');
$jwk1 = new JWK([
'kty' => 'EC',
'crv' => 'P-256',
'x' => 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
'y' => 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
'use' => 'sign',
'key_ops' => ['sign'],
'alg' => 'ES256',
'kid' => '0123456789',
]);
$jwk2 = new JWK([
'kty' => 'EC',
'crv' => 'P-256',
'x' => 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
'y' => 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
'd' => 'jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI',
'use' => 'sign',
'key_ops' => ['verify'],
'alg' => 'ES256',
'kid' => '9876543210',
]);
$jwkset = new JWKSet([$jwk1, $jwk2]);
$jwkset->get(2);
}
private function getPublicKeySet(): JWKSet
{
$keys = ['keys' => [
[
'kid' => '71ee230371d19630bc17fb90ccf20ae632ad8cf8',
'kty' => 'FOO',
'alg' => 'foo',
'use' => 'enc',
],
[
'kid' => '02491f945c951adf156f370788e8ccdabf8877a8',
'kty' => 'FOO',
'alg' => 'foo',
'use' => 'sig',
],
[
'kty' => 'EC',
'crv' => 'P-256',
'x' => 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
'y' => 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
],
]];
return JWKSet::createFromKeyData($keys);
}
}

View File

@@ -0,0 +1,147 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Jose\Component\Core\Tests;
use InvalidArgumentException;
use Jose\Component\Core\JWK;
use PHPUnit\Framework\TestCase;
/**
* @group unit
* @group JWK
*
* @internal
*/
class JWKTest extends TestCase
{
/**
* @test
*/
public function aKeyContainsAllExpectedParameters(): void
{
$jwk = new JWK([
'kty' => 'EC',
'crv' => 'P-256',
'x' => 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
'y' => 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
'use' => 'sig',
'key_ops' => ['sign'],
'alg' => 'ES256',
'bar' => 'plic',
]);
static::assertEquals('EC', $jwk->get('kty'));
static::assertEquals('ES256', $jwk->get('alg'));
static::assertEquals('sig', $jwk->get('use'));
static::assertFalse($jwk->has('kid'));
static::assertEquals(['sign'], $jwk->get('key_ops'));
static::assertEquals('P-256', $jwk->get('crv'));
static::assertFalse($jwk->has('x5u'));
static::assertFalse($jwk->has('x5c'));
static::assertFalse($jwk->has('x5t'));
static::assertFalse($jwk->has('x5t#256'));
static::assertEquals('f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU', $jwk->get('x'));
static::assertEquals('x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0', $jwk->get('y'));
static::assertEquals('{"kty":"EC","crv":"P-256","x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU","y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0","use":"sig","key_ops":["sign"],"alg":"ES256","bar":"plic"}', json_encode($jwk));
static::assertEquals('oKIywvGUpTVTyxMQ3bwIIeQUudfr_CkLMjCE19ECD-U', $jwk->thumbprint('sha256'));
static::assertEquals('EMMMl6Rj75mqhcABihxxl_VCN9s', $jwk->thumbprint('sha1'));
static::assertEquals('dqwHnan4iJ1_eEll-o4Egw', $jwk->thumbprint('md5'));
}
/**
* @test
*/
public function iCannotGetTheThumbprintOfTheKeyWhenIUseAnUnsupportedHashingAlgorithm(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The hash algorithm "foo" is not supported.');
$jwk = new JWK([
'kty' => 'EC',
'crv' => 'P-256',
'x' => 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
'y' => 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
'use' => 'sig',
'key_ops' => ['sign'],
'alg' => 'ES256',
'bar' => 'plic',
]);
$jwk->thumbprint('foo');
}
/**
* @test
*/
public function iMustSetAtLeastTheKtyParameter(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The parameter "kty" is mandatory.');
new JWK([]);
}
/**
* @test
*/
public function iCannotGetAParameterThatDoesNotExist(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The value identified by "ABCD" does not exist.');
$jwk = new JWK([
'kty' => 'EC',
'crv' => 'P-256',
'x' => 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
'y' => 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
'use' => 'sign',
'key_ops' => ['sign'],
'alg' => 'ES256',
'bar' => 'plic',
]);
$jwk->get('ABCD');
}
/**
* @test
*/
public function iCanConvertAPrivateKeyIntoPublicKey(): void
{
$private = new JWK([
'kty' => 'EC',
'crv' => 'P-256',
'x' => 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
'y' => 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
'd' => 'jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI',
'use' => 'sign',
'key_ops' => ['verify'],
'alg' => 'ES256',
'kid' => '9876543210',
]);
$public = $private->toPublic();
static::assertEquals(json_encode([
'kty' => 'EC',
'crv' => 'P-256',
'x' => 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
'y' => 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
'use' => 'sign',
'key_ops' => ['verify'],
'alg' => 'ES256',
'kid' => '9876543210',
]), json_encode($public));
}
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Jose\Component\Core\Tests;
use Jose\Component\Core\Util\JsonConverter;
use PHPUnit\Framework\TestCase;
/**
* @group unit
* @group JsonConverter
*
* @internal
*/
class JsonConverterTest extends TestCase
{
/**
* @test
*/
public function iCanConvertAnObjectIntoAJsonString(): void
{
static::assertEquals('{"foo":"BAR"}', JsonConverter::encode(['foo' => 'BAR']));
static::assertEquals(['foo' => 'BAR'], JsonConverter::decode('{"foo":"BAR"}'));
}
}

View File

@@ -0,0 +1,223 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Jose\Component\Core\Util;
use Brick\Math\BigInteger as BrickBigInteger;
use function chr;
/**
* @internal
*/
class BigInteger
{
/**
* Holds the BigInteger's value.
*
* @var BrickBigInteger
*/
private $value;
private function __construct(BrickBigInteger $value)
{
$this->value = $value;
}
/**
* @return BigInteger
*/
public static function createFromBinaryString(string $value): self
{
$data = current(unpack('H*', $value));
return new self(BrickBigInteger::fromBase($data, 16));
}
/**
* @return BigInteger
*/
public static function createFromDecimal(int $value): self
{
return new self(BrickBigInteger::of($value));
}
/**
* @return BigInteger
*/
public static function createFromBigInteger(BrickBigInteger $value): self
{
return new self($value);
}
/**
* Converts a BigInteger to a binary string.
*/
public function toBytes(): string
{
if ($this->value->isEqualTo(BrickBigInteger::zero())) {
return '';
}
$temp = $this->value->toBase(16);
$temp = 0 !== (mb_strlen($temp, '8bit') & 1) ? '0'.$temp : $temp;
$temp = hex2bin($temp);
return ltrim($temp, chr(0));
}
/**
* Adds two BigIntegers.
*
* @param BigInteger $y
*
* @return BigInteger
*/
public function add(self $y): self
{
$value = $this->value->plus($y->value);
return new self($value);
}
/**
* Subtracts two BigIntegers.
*
* @param BigInteger $y
*
* @return BigInteger
*/
public function subtract(self $y): self
{
$value = $this->value->minus($y->value);
return new self($value);
}
/**
* Multiplies two BigIntegers.
*
* @param BigInteger $x
*
* @return BigInteger
*/
public function multiply(self $x): self
{
$value = $this->value->multipliedBy($x->value);
return new self($value);
}
/**
* Divides two BigIntegers.
*
* @param BigInteger $x
*
* @return BigInteger
*/
public function divide(self $x): self
{
$value = $this->value->dividedBy($x->value);
return new self($value);
}
/**
* Performs modular exponentiation.
*
* @param BigInteger $e
* @param BigInteger $n
*
* @return BigInteger
*/
public function modPow(self $e, self $n): self
{
$value = $this->value->modPow($e->value, $n->value);
return new self($value);
}
/**
* Performs modular exponentiation.
*
* @param BigInteger $d
*
* @return BigInteger
*/
public function mod(self $d): self
{
$value = $this->value->mod($d->value);
return new self($value);
}
public function modInverse(BigInteger $m): BigInteger
{
return new self($this->value->modInverse($m->value));
}
/**
* Compares two numbers.
*
* @param BigInteger $y
*/
public function compare(self $y): int
{
return $this->value->compareTo($y->value);
}
/**
* @param BigInteger $y
*/
public function equals(self $y): bool
{
return $this->value->isEqualTo($y->value);
}
/**
* @param BigInteger $y
*
* @return BigInteger
*/
public static function random(self $y): self
{
return new self(BrickBigInteger::randomRange(0, $y->value));
}
/**
* @param BigInteger $y
*
* @return BigInteger
*/
public function gcd(self $y): self
{
return new self($this->value->gcd($y->value));
}
/**
* @param BigInteger $y
*/
public function lowerThan(self $y): bool
{
return $this->value->isLessThan($y->value);
}
public function isEven(): bool
{
return $this->value->isEven();
}
public function get(): BrickBigInteger
{
return $this->value;
}
}

335
vendor/web-token/jwt-core/Util/ECKey.php vendored Normal file
View File

@@ -0,0 +1,335 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Jose\Component\Core\Util;
use Base64Url\Base64Url;
use function extension_loaded;
use InvalidArgumentException;
use Jose\Component\Core\JWK;
use RuntimeException;
/**
* @internal
*/
class ECKey
{
public static function convertToPEM(JWK $jwk): string
{
if ($jwk->has('d')) {
return self::convertPrivateKeyToPEM($jwk);
}
return self::convertPublicKeyToPEM($jwk);
}
/**
* @throws InvalidArgumentException if the curve is not supported
*/
public static function convertPublicKeyToPEM(JWK $jwk): string
{
switch ($jwk->get('crv')) {
case 'P-256':
$der = self::p256PublicKey();
break;
case 'secp256k1':
$der = self::p256KPublicKey();
break;
case 'P-384':
$der = self::p384PublicKey();
break;
case 'P-521':
$der = self::p521PublicKey();
break;
default:
throw new InvalidArgumentException('Unsupported curve.');
}
$der .= self::getKey($jwk);
$pem = '-----BEGIN PUBLIC KEY-----'.PHP_EOL;
$pem .= chunk_split(base64_encode($der), 64, PHP_EOL);
$pem .= '-----END PUBLIC KEY-----'.PHP_EOL;
return $pem;
}
/**
* @throws InvalidArgumentException if the curve is not supported
*/
public static function convertPrivateKeyToPEM(JWK $jwk): string
{
switch ($jwk->get('crv')) {
case 'P-256':
$der = self::p256PrivateKey($jwk);
break;
case 'secp256k1':
$der = self::p256KPrivateKey($jwk);
break;
case 'P-384':
$der = self::p384PrivateKey($jwk);
break;
case 'P-521':
$der = self::p521PrivateKey($jwk);
break;
default:
throw new InvalidArgumentException('Unsupported curve.');
}
$der .= self::getKey($jwk);
$pem = '-----BEGIN EC PRIVATE KEY-----'.PHP_EOL;
$pem .= chunk_split(base64_encode($der), 64, PHP_EOL);
$pem .= '-----END EC PRIVATE KEY-----'.PHP_EOL;
return $pem;
}
/**
* Creates a EC key with the given curve and additional values.
*
* @param string $curve The curve
* @param array $values values to configure the key
*/
public static function createECKey(string $curve, array $values = []): JWK
{
$jwk = self::createECKeyUsingOpenSSL($curve);
$values = array_merge($values, $jwk);
return new JWK($values);
}
/**
* @throws InvalidArgumentException if the curve is not supported
*/
private static function getNistCurveSize(string $curve): int
{
switch ($curve) {
case 'P-256':
case 'secp256k1':
return 256;
case 'P-384':
return 384;
case 'P-521':
return 521;
default:
throw new InvalidArgumentException(sprintf('The curve "%s" is not supported.', $curve));
}
}
/**
* @throws RuntimeException if the extension OpenSSL is not available
* @throws RuntimeException if the key cannot be created
*/
private static function createECKeyUsingOpenSSL(string $curve): array
{
if (!extension_loaded('openssl')) {
throw new RuntimeException('Please install the OpenSSL extension');
}
$key = openssl_pkey_new([
'curve_name' => self::getOpensslCurveName($curve),
'private_key_type' => OPENSSL_KEYTYPE_EC,
]);
if (false === $key) {
throw new RuntimeException('Unable to create the key');
}
$result = openssl_pkey_export($key, $out);
if (false === $result) {
throw new RuntimeException('Unable to create the key');
}
$res = openssl_pkey_get_private($out);
if (false === $res) {
throw new RuntimeException('Unable to create the key');
}
$details = openssl_pkey_get_details($res);
$nistCurveSize = self::getNistCurveSize($curve);
return [
'kty' => 'EC',
'crv' => $curve,
'd' => Base64Url::encode(str_pad($details['ec']['d'], (int) ceil($nistCurveSize / 8), "\0", STR_PAD_LEFT)),
'x' => Base64Url::encode(str_pad($details['ec']['x'], (int) ceil($nistCurveSize / 8), "\0", STR_PAD_LEFT)),
'y' => Base64Url::encode(str_pad($details['ec']['y'], (int) ceil($nistCurveSize / 8), "\0", STR_PAD_LEFT)),
];
}
/**
* @throws InvalidArgumentException if the curve is not supported
*/
private static function getOpensslCurveName(string $curve): string
{
switch ($curve) {
case 'P-256':
return 'prime256v1';
case 'secp256k1':
return 'secp256k1';
case 'P-384':
return 'secp384r1';
case 'P-521':
return 'secp521r1';
default:
throw new InvalidArgumentException(sprintf('The curve "%s" is not supported.', $curve));
}
}
private static function p256PublicKey(): string
{
return pack(
'H*',
'3059' // SEQUENCE, length 89
.'3013' // SEQUENCE, length 19
.'0607' // OID, length 7
.'2a8648ce3d0201' // 1.2.840.10045.2.1 = EC Public Key
.'0608' // OID, length 8
.'2a8648ce3d030107' // 1.2.840.10045.3.1.7 = P-256 Curve
.'0342' // BIT STRING, length 66
.'00' // prepend with NUL - pubkey will follow
);
}
private static function p256KPublicKey(): string
{
return pack(
'H*',
'3056' // SEQUENCE, length 86
.'3010' // SEQUENCE, length 16
.'0607' // OID, length 7
.'2a8648ce3d0201' // 1.2.840.10045.2.1 = EC Public Key
.'0605' // OID, length 8
.'2B8104000A' // 1.3.132.0.10 secp256k1
.'0342' // BIT STRING, length 66
.'00' // prepend with NUL - pubkey will follow
);
}
private static function p384PublicKey(): string
{
return pack(
'H*',
'3076' // SEQUENCE, length 118
.'3010' // SEQUENCE, length 16
.'0607' // OID, length 7
.'2a8648ce3d0201' // 1.2.840.10045.2.1 = EC Public Key
.'0605' // OID, length 5
.'2b81040022' // 1.3.132.0.34 = P-384 Curve
.'0362' // BIT STRING, length 98
.'00' // prepend with NUL - pubkey will follow
);
}
private static function p521PublicKey(): string
{
return pack(
'H*',
'30819b' // SEQUENCE, length 154
.'3010' // SEQUENCE, length 16
.'0607' // OID, length 7
.'2a8648ce3d0201' // 1.2.840.10045.2.1 = EC Public Key
.'0605' // OID, length 5
.'2b81040023' // 1.3.132.0.35 = P-521 Curve
.'038186' // BIT STRING, length 134
.'00' // prepend with NUL - pubkey will follow
);
}
private static function p256PrivateKey(JWK $jwk): string
{
$d = unpack('H*', str_pad(Base64Url::decode($jwk->get('d')), 32, "\0", STR_PAD_LEFT))[1];
return pack(
'H*',
'3077' // SEQUENCE, length 87+length($d)=32
.'020101' // INTEGER, 1
.'0420' // OCTET STRING, length($d) = 32
.$d
.'a00a' // TAGGED OBJECT #0, length 10
.'0608' // OID, length 8
.'2a8648ce3d030107' // 1.3.132.0.34 = P-256 Curve
.'a144' // TAGGED OBJECT #1, length 68
.'0342' // BIT STRING, length 66
.'00' // prepend with NUL - pubkey will follow
);
}
private static function p256KPrivateKey(JWK $jwk): string
{
$d = unpack('H*', str_pad(Base64Url::decode($jwk->get('d')), 32, "\0", STR_PAD_LEFT))[1];
return pack(
'H*',
'3074' // SEQUENCE, length 84+length($d)=32
.'020101' // INTEGER, 1
.'0420' // OCTET STRING, length($d) = 32
.$d
.'a007' // TAGGED OBJECT #0, length 7
.'0605' // OID, length 5
.'2b8104000a' // 1.3.132.0.10 secp256k1
.'a144' // TAGGED OBJECT #1, length 68
.'0342' // BIT STRING, length 66
.'00' // prepend with NUL - pubkey will follow
);
}
private static function p384PrivateKey(JWK $jwk): string
{
$d = unpack('H*', str_pad(Base64Url::decode($jwk->get('d')), 48, "\0", STR_PAD_LEFT))[1];
return pack(
'H*',
'3081a4' // SEQUENCE, length 116 + length($d)=48
.'020101' // INTEGER, 1
.'0430' // OCTET STRING, length($d) = 30
.$d
.'a007' // TAGGED OBJECT #0, length 7
.'0605' // OID, length 5
.'2b81040022' // 1.3.132.0.34 = P-384 Curve
.'a164' // TAGGED OBJECT #1, length 100
.'0362' // BIT STRING, length 98
.'00' // prepend with NUL - pubkey will follow
);
}
private static function p521PrivateKey(JWK $jwk): string
{
$d = unpack('H*', str_pad(Base64Url::decode($jwk->get('d')), 66, "\0", STR_PAD_LEFT))[1];
return pack(
'H*',
'3081dc' // SEQUENCE, length 154 + length($d)=66
.'020101' // INTEGER, 1
.'0442' // OCTET STRING, length(d) = 66
.$d
.'a007' // TAGGED OBJECT #0, length 7
.'0605' // OID, length 5
.'2b81040023' // 1.3.132.0.35 = P-521 Curve
.'a18189' // TAGGED OBJECT #1, length 137
.'038186' // BIT STRING, length 134
.'00' // prepend with NUL - pubkey will follow
);
}
private static function getKey(JWK $jwk): string
{
$nistCurveSize = self::getNistCurveSize($jwk->get('crv'));
$length = (int) ceil($nistCurveSize / 8);
return
"\04"
.str_pad(Base64Url::decode($jwk->get('x')), $length, "\0", STR_PAD_LEFT)
.str_pad(Base64Url::decode($jwk->get('y')), $length, "\0", STR_PAD_LEFT);
}
}

View File

@@ -0,0 +1,132 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Jose\Component\Core\Util;
use InvalidArgumentException;
use const STR_PAD_LEFT;
/**
* @internal
*/
final class ECSignature
{
private const ASN1_SEQUENCE = '30';
private const ASN1_INTEGER = '02';
private const ASN1_MAX_SINGLE_BYTE = 128;
private const ASN1_LENGTH_2BYTES = '81';
private const ASN1_BIG_INTEGER_LIMIT = '7f';
private const ASN1_NEGATIVE_INTEGER = '00';
private const BYTE_SIZE = 2;
/**
* @throws InvalidArgumentException if the length of the signature is invalid
*/
public static function toAsn1(string $signature, int $length): string
{
$signature = bin2hex($signature);
if (self::octetLength($signature) !== $length) {
throw new InvalidArgumentException('Invalid signature length.');
}
$pointR = self::preparePositiveInteger(mb_substr($signature, 0, $length, '8bit'));
$pointS = self::preparePositiveInteger(mb_substr($signature, $length, null, '8bit'));
$lengthR = self::octetLength($pointR);
$lengthS = self::octetLength($pointS);
$totalLength = $lengthR + $lengthS + self::BYTE_SIZE + self::BYTE_SIZE;
$lengthPrefix = $totalLength > self::ASN1_MAX_SINGLE_BYTE ? self::ASN1_LENGTH_2BYTES : '';
return hex2bin(
self::ASN1_SEQUENCE
.$lengthPrefix.dechex($totalLength)
.self::ASN1_INTEGER.dechex($lengthR).$pointR
.self::ASN1_INTEGER.dechex($lengthS).$pointS
);
}
/**
* @throws InvalidArgumentException if the signature is not an ASN.1 sequence
*/
public static function fromAsn1(string $signature, int $length): string
{
$message = bin2hex($signature);
$position = 0;
if (self::ASN1_SEQUENCE !== self::readAsn1Content($message, $position, self::BYTE_SIZE)) {
throw new InvalidArgumentException('Invalid data. Should start with a sequence.');
}
if (self::ASN1_LENGTH_2BYTES === self::readAsn1Content($message, $position, self::BYTE_SIZE)) {
$position += self::BYTE_SIZE;
}
$pointR = self::retrievePositiveInteger(self::readAsn1Integer($message, $position));
$pointS = self::retrievePositiveInteger(self::readAsn1Integer($message, $position));
return hex2bin(str_pad($pointR, $length, '0', STR_PAD_LEFT).str_pad($pointS, $length, '0', STR_PAD_LEFT));
}
private static function octetLength(string $data): int
{
return (int) (mb_strlen($data, '8bit') / self::BYTE_SIZE);
}
private static function preparePositiveInteger(string $data): string
{
if (mb_substr($data, 0, self::BYTE_SIZE, '8bit') > self::ASN1_BIG_INTEGER_LIMIT) {
return self::ASN1_NEGATIVE_INTEGER.$data;
}
while (0 === mb_strpos($data, self::ASN1_NEGATIVE_INTEGER, 0, '8bit')
&& mb_substr($data, 2, self::BYTE_SIZE, '8bit') <= self::ASN1_BIG_INTEGER_LIMIT) {
$data = mb_substr($data, 2, null, '8bit');
}
return $data;
}
private static function readAsn1Content(string $message, int &$position, int $length): string
{
$content = mb_substr($message, $position, $length, '8bit');
$position += $length;
return $content;
}
/**
* @throws InvalidArgumentException if the data is not an integer
*/
private static function readAsn1Integer(string $message, int &$position): string
{
if (self::ASN1_INTEGER !== self::readAsn1Content($message, $position, self::BYTE_SIZE)) {
throw new InvalidArgumentException('Invalid data. Should contain an integer.');
}
$length = (int) hexdec(self::readAsn1Content($message, $position, self::BYTE_SIZE));
return self::readAsn1Content($message, $position, $length * self::BYTE_SIZE);
}
private static function retrievePositiveInteger(string $data): string
{
while (0 === mb_strpos($data, self::ASN1_NEGATIVE_INTEGER, 0, '8bit')
&& mb_substr($data, 2, self::BYTE_SIZE, '8bit') > self::ASN1_BIG_INTEGER_LIMIT) {
$data = mb_substr($data, 2, null, '8bit');
}
return $data;
}
}

103
vendor/web-token/jwt-core/Util/Hash.php vendored Normal file
View File

@@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Jose\Component\Core\Util;
/**
* @internal
*/
class Hash
{
/**
* Hash Parameter.
*
* @var string
*/
private $hash;
/**
* DER encoding T.
*
* @var string
*/
private $t;
/**
* Hash Length.
*
* @var int
*/
private $length;
private function __construct(string $hash, int $length, string $t)
{
$this->hash = $hash;
$this->length = $length;
$this->t = $t;
}
/**
* @return Hash
*/
public static function sha1(): self
{
return new self('sha1', 20, "\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14");
}
/**
* @return Hash
*/
public static function sha256(): self
{
return new self('sha256', 32, "\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20");
}
/**
* @return Hash
*/
public static function sha384(): self
{
return new self('sha384', 48, "\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30");
}
/**
* @return Hash
*/
public static function sha512(): self
{
return new self('sha512', 64, "\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40");
}
public function getLength(): int
{
return $this->length;
}
/**
* Compute the HMAC.
*/
public function hash(string $text): string
{
return hash($this->hash, $text, true);
}
public function name(): string
{
return $this->hash;
}
public function t(): string
{
return $this->t;
}
}

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Jose\Component\Core\Util;
use RuntimeException;
use Throwable;
final class JsonConverter
{
/**
* @param mixed $payload
*
* @throws RuntimeException if the payload cannot be encoded
*/
public static function encode($payload): string
{
try {
return json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
} catch (Throwable $throwable) {
throw new RuntimeException('Invalid content.', $throwable->getCode(), $throwable);
}
}
/**
* @throws RuntimeException if the payload cannot be decoded
*
* @return mixed
*/
public static function decode(string $payload)
{
try {
return json_decode($payload, true, 512, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
} catch (Throwable $throwable) {
throw new RuntimeException('Invalid content.', $throwable->getCode(), $throwable);
}
}
}

View File

@@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Jose\Component\Core\Util;
use function in_array;
use InvalidArgumentException;
use function is_array;
use Jose\Component\Core\JWK;
/**
* @internal
*/
class KeyChecker
{
public static function checkKeyUsage(JWK $key, string $usage): void
{
if ($key->has('use')) {
self::checkUsage($key, $usage);
}
if ($key->has('key_ops')) {
self::checkOperation($key, $usage);
}
}
/**
* @throws InvalidArgumentException if the key is not suitable for the selected algorithm
*/
public static function checkKeyAlgorithm(JWK $key, string $algorithm): void
{
if (!$key->has('alg')) {
return;
}
if ($key->get('alg') !== $algorithm) {
throw new InvalidArgumentException(sprintf('Key is only allowed for algorithm "%s".', $key->get('alg')));
}
}
/**
* @throws InvalidArgumentException if the key is not suitable for the selected operation
*/
private static function checkOperation(JWK $key, string $usage): void
{
$ops = $key->get('key_ops');
if (!is_array($ops)) {
throw new InvalidArgumentException('Invalid key parameter "key_ops". Should be a list of key operations');
}
switch ($usage) {
case 'verification':
if (!in_array('verify', $ops, true)) {
throw new InvalidArgumentException('Key cannot be used to verify a signature');
}
break;
case 'signature':
if (!in_array('sign', $ops, true)) {
throw new InvalidArgumentException('Key cannot be used to sign');
}
break;
case 'encryption':
if (!in_array('encrypt', $ops, true) && !in_array('wrapKey', $ops, true) && !in_array('deriveKey', $ops, true)) {
throw new InvalidArgumentException('Key cannot be used to encrypt');
}
break;
case 'decryption':
if (!in_array('decrypt', $ops, true) && !in_array('unwrapKey', $ops, true) && !in_array('deriveBits', $ops, true)) {
throw new InvalidArgumentException('Key cannot be used to decrypt');
}
break;
default:
throw new InvalidArgumentException('Unsupported key usage.');
}
}
/**
* @throws InvalidArgumentException if the key is not suitable for the selected operation
*/
private static function checkUsage(JWK $key, string $usage): void
{
$use = $key->get('use');
switch ($usage) {
case 'verification':
case 'signature':
if ('sig' !== $use) {
throw new InvalidArgumentException('Key cannot be used to sign or verify a signature.');
}
break;
case 'encryption':
case 'decryption':
if ('enc' !== $use) {
throw new InvalidArgumentException('Key cannot be used to encrypt or decrypt.');
}
break;
default:
throw new InvalidArgumentException('Unsupported key usage.');
}
}
}

View File

@@ -0,0 +1,310 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Jose\Component\Core\Util;
use function array_key_exists;
use Base64Url\Base64Url;
use function count;
use FG\ASN1\Universal\BitString;
use FG\ASN1\Universal\Integer;
use FG\ASN1\Universal\NullObject;
use FG\ASN1\Universal\ObjectIdentifier;
use FG\ASN1\Universal\OctetString;
use FG\ASN1\Universal\Sequence;
use Jose\Component\Core\JWK;
use RuntimeException;
/**
* @internal
*/
class RSAKey
{
/**
* @var Sequence
*/
private $sequence;
/**
* @var bool
*/
private $private;
/**
* @var array
*/
private $values;
/**
* @var BigInteger
*/
private $modulus;
/**
* @var int
*/
private $modulus_length;
/**
* @var BigInteger
*/
private $public_exponent;
/**
* @var null|BigInteger
*/
private $private_exponent;
/**
* @var BigInteger[]
*/
private $primes = [];
/**
* @var BigInteger[]
*/
private $exponents = [];
/**
* @var null|BigInteger
*/
private $coefficient;
private function __construct(JWK $data)
{
$this->values = $data->all();
$this->populateBigIntegers();
$this->private = array_key_exists('d', $this->values);
}
/**
* @return RSAKey
*/
public static function createFromJWK(JWK $jwk): self
{
return new self($jwk);
}
public function getModulus(): BigInteger
{
return $this->modulus;
}
public function getModulusLength(): int
{
return $this->modulus_length;
}
public function getExponent(): BigInteger
{
$d = $this->getPrivateExponent();
if (null !== $d) {
return $d;
}
return $this->getPublicExponent();
}
public function getPublicExponent(): BigInteger
{
return $this->public_exponent;
}
public function getPrivateExponent(): ?BigInteger
{
return $this->private_exponent;
}
/**
* @return BigInteger[]
*/
public function getPrimes(): array
{
return $this->primes;
}
/**
* @return BigInteger[]
*/
public function getExponents(): array
{
return $this->exponents;
}
public function getCoefficient(): ?BigInteger
{
return $this->coefficient;
}
public function isPublic(): bool
{
return !array_key_exists('d', $this->values);
}
/**
* @param RSAKey $private
*
* @return RSAKey
*/
public static function toPublic(self $private): self
{
$data = $private->toArray();
$keys = ['p', 'd', 'q', 'dp', 'dq', 'qi'];
foreach ($keys as $key) {
if (array_key_exists($key, $data)) {
unset($data[$key]);
}
}
return new self(new JWK($data));
}
public function toArray(): array
{
return $this->values;
}
public function toPEM(): string
{
if (null === $this->sequence) {
$this->sequence = new Sequence();
if (array_key_exists('d', $this->values)) {
$this->initPrivateKey();
} else {
$this->initPublicKey();
}
}
$result = '-----BEGIN '.($this->private ? 'RSA PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
$result .= chunk_split(base64_encode($this->sequence->getBinary()), 64, PHP_EOL);
$result .= '-----END '.($this->private ? 'RSA PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL;
return $result;
}
/**
* Exponentiate with or without Chinese Remainder Theorem.
* Operation with primes 'p' and 'q' is appox. 2x faster.
*
* @param RSAKey $key
*
* @throws RuntimeException if the exponentiation cannot be achieved
*/
public static function exponentiate(self $key, BigInteger $c): BigInteger
{
if ($c->compare(BigInteger::createFromDecimal(0)) < 0 || $c->compare($key->getModulus()) > 0) {
throw new RuntimeException();
}
if ($key->isPublic() || null === $key->getCoefficient() || 0 === count($key->getPrimes()) || 0 === count($key->getExponents())) {
return $c->modPow($key->getExponent(), $key->getModulus());
}
$p = $key->getPrimes()[0];
$q = $key->getPrimes()[1];
$dP = $key->getExponents()[0];
$dQ = $key->getExponents()[1];
$qInv = $key->getCoefficient();
$m1 = $c->modPow($dP, $p);
$m2 = $c->modPow($dQ, $q);
$h = $qInv->multiply($m1->subtract($m2)->add($p))->mod($p);
return $m2->add($h->multiply($q));
}
private function populateBigIntegers(): void
{
$this->modulus = $this->convertBase64StringToBigInteger($this->values['n']);
$this->modulus_length = mb_strlen($this->getModulus()->toBytes(), '8bit');
$this->public_exponent = $this->convertBase64StringToBigInteger($this->values['e']);
if (!$this->isPublic()) {
$this->private_exponent = $this->convertBase64StringToBigInteger($this->values['d']);
if (array_key_exists('p', $this->values) && array_key_exists('q', $this->values)) {
$this->primes = [
$this->convertBase64StringToBigInteger($this->values['p']),
$this->convertBase64StringToBigInteger($this->values['q']),
];
if (array_key_exists('dp', $this->values) && array_key_exists('dq', $this->values) && array_key_exists('qi', $this->values)) {
$this->exponents = [
$this->convertBase64StringToBigInteger($this->values['dp']),
$this->convertBase64StringToBigInteger($this->values['dq']),
];
$this->coefficient = $this->convertBase64StringToBigInteger($this->values['qi']);
}
}
}
}
private function convertBase64StringToBigInteger(string $value): BigInteger
{
return BigInteger::createFromBinaryString(Base64Url::decode($value));
}
private function initPublicKey(): void
{
$oid_sequence = new Sequence();
$oid_sequence->addChild(new ObjectIdentifier('1.2.840.113549.1.1.1'));
$oid_sequence->addChild(new NullObject());
$this->sequence->addChild($oid_sequence);
$n = new Integer($this->fromBase64ToInteger($this->values['n']));
$e = new Integer($this->fromBase64ToInteger($this->values['e']));
$key_sequence = new Sequence();
$key_sequence->addChild($n);
$key_sequence->addChild($e);
$key_bit_string = new BitString(bin2hex($key_sequence->getBinary()));
$this->sequence->addChild($key_bit_string);
}
private function initPrivateKey(): void
{
$this->sequence->addChild(new Integer(0));
$oid_sequence = new Sequence();
$oid_sequence->addChild(new ObjectIdentifier('1.2.840.113549.1.1.1'));
$oid_sequence->addChild(new NullObject());
$this->sequence->addChild($oid_sequence);
$v = new Integer(0);
$n = new Integer($this->fromBase64ToInteger($this->values['n']));
$e = new Integer($this->fromBase64ToInteger($this->values['e']));
$d = new Integer($this->fromBase64ToInteger($this->values['d']));
$p = new Integer($this->fromBase64ToInteger($this->values['p']));
$q = new Integer($this->fromBase64ToInteger($this->values['q']));
$dp = array_key_exists('dp', $this->values) ? new Integer($this->fromBase64ToInteger($this->values['dp'])) : new Integer(0);
$dq = array_key_exists('dq', $this->values) ? new Integer($this->fromBase64ToInteger($this->values['dq'])) : new Integer(0);
$qi = array_key_exists('qi', $this->values) ? new Integer($this->fromBase64ToInteger($this->values['qi'])) : new Integer(0);
$key_sequence = new Sequence();
$key_sequence->addChild($v);
$key_sequence->addChild($n);
$key_sequence->addChild($e);
$key_sequence->addChild($d);
$key_sequence->addChild($p);
$key_sequence->addChild($q);
$key_sequence->addChild($dp);
$key_sequence->addChild($dq);
$key_sequence->addChild($qi);
$key_octet_string = new OctetString(bin2hex($key_sequence->getBinary()));
$this->sequence->addChild($key_octet_string);
}
/**
* @param string $value
*
* @return string
*/
private function fromBase64ToInteger($value)
{
$hex = current(unpack('H*', Base64Url::decode($value)));
return \Brick\Math\BigInteger::fromBase($hex, 16)->toBase(10);
}
}

49
vendor/web-token/jwt-core/composer.json vendored Normal file
View File

@@ -0,0 +1,49 @@
{
"name": "web-token/jwt-core",
"description": "Core component of the JWT Framework.",
"type": "library",
"license": "MIT",
"keywords": ["JWS", "JWT", "JWE", "JWA", "JWK", "JWKSet", "Jot", "Jose", "RFC7515", "RFC7516", "RFC7517", "RFC7518", "RFC7519", "RFC7520", "Bundle", "Symfony"],
"homepage": "https://github.com/web-token",
"authors": [
{
"name": "Florent Morselli",
"homepage": "https://github.com/Spomky"
},{
"name": "All contributors",
"homepage": "https://github.com/web-token/jwt-framework/contributors"
}
],
"autoload": {
"psr-4": {
"Jose\\Component\\Core\\": ""
}
},
"require": {
"php": ">=7.2",
"ext-json": "*",
"ext-mbstring": "*",
"brick/math": "^0.8.17|^0.9",
"fgrosse/phpasn1": "^2.0",
"spomky-labs/base64url": "^1.0|^2.0"
},
"require-dev": {
"phpunit/phpunit": "^8.0"
},
"conflict": {
"spomky-labs/jose": "*"
},
"extra": {
"branch-alias": {
"v1.0": "1.0.x-dev",
"v1.1": "1.1.x-dev",
"v1.2": "1.2.x-dev",
"v1.3": "1.3.x-dev",
"v2.0": "2.0.x-dev",
"v2.1": "2.1.x-dev"
}
},
"config": {
"sort-packages": true
}
}

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
backupGlobals="false"
backupStaticAttributes="false"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="true"
bootstrap="vendor/autoload.php"
colors="true">
<testsuites>
<testsuite name="Test Suite">
<directory>./Tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">./</directory>
<exclude>
<directory>./vendor</directory>
<directory>./Tests</directory>
<directory suffix="Test.php">./src</directory>
</exclude>
</whitelist>
</filter>
</phpunit>