updated plugin WP-WebAuthn
version 1.3.1
This commit is contained in:
4
wp-content/plugins/wp-webauthn/wp-webauthn-vendor/web-token/jwt-core/.github/CONTRIBUTING.md
vendored
Normal file
4
wp-content/plugins/wp-webauthn/wp-webauthn-vendor/web-token/jwt-core/.github/CONTRIBUTING.md
vendored
Normal 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.
|
1
wp-content/plugins/wp-webauthn/wp-webauthn-vendor/web-token/jwt-core/.github/FUNDING.yml
vendored
Normal file
1
wp-content/plugins/wp-webauthn/wp-webauthn-vendor/web-token/jwt-core/.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
||||
patreon: FlorentMorselli
|
@ -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
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
<?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);
|
||||
if (false === $input) {
|
||||
throw new InvalidArgumentException('Unable to compute the key thumbprint');
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,340 @@
|
||||
<?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"', implode(', ', $key_ops)));
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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.
|
@ -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).
|
@ -0,0 +1,231 @@
|
||||
<?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;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @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
|
||||
{
|
||||
$res = unpack('H*', $value);
|
||||
if (false === $res) {
|
||||
throw new InvalidArgumentException('Unable to convert the value');
|
||||
}
|
||||
$data = current($res);
|
||||
|
||||
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);
|
||||
if (false === $temp) {
|
||||
throw new InvalidArgumentException('Unable to convert the value into bytes');
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,366 @@
|
||||
<?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 function is_array;
|
||||
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);
|
||||
if (false === $details) {
|
||||
throw new InvalidArgumentException('Unable to get the key details');
|
||||
}
|
||||
$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));
|
||||
if (!is_array($d) || !isset($d[1])) {
|
||||
throw new InvalidArgumentException('Unable to get the private key');
|
||||
}
|
||||
|
||||
return pack(
|
||||
'H*',
|
||||
'3077' // SEQUENCE, length 87+length($d)=32
|
||||
.'020101' // INTEGER, 1
|
||||
.'0420' // OCTET STRING, length($d) = 32
|
||||
.$d[1]
|
||||
.'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));
|
||||
if (!is_array($d) || !isset($d[1])) {
|
||||
throw new InvalidArgumentException('Unable to get the private key');
|
||||
}
|
||||
|
||||
return pack(
|
||||
'H*',
|
||||
'3074' // SEQUENCE, length 84+length($d)=32
|
||||
.'020101' // INTEGER, 1
|
||||
.'0420' // OCTET STRING, length($d) = 32
|
||||
.$d[1]
|
||||
.'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));
|
||||
if (!is_array($d) || !isset($d[1])) {
|
||||
throw new InvalidArgumentException('Unable to get the private key');
|
||||
}
|
||||
|
||||
return pack(
|
||||
'H*',
|
||||
'3081a4' // SEQUENCE, length 116 + length($d)=48
|
||||
.'020101' // INTEGER, 1
|
||||
.'0430' // OCTET STRING, length($d) = 30
|
||||
.$d[1]
|
||||
.'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));
|
||||
if (!is_array($d) || !isset($d[1])) {
|
||||
throw new InvalidArgumentException('Unable to get the private key');
|
||||
}
|
||||
|
||||
return pack(
|
||||
'H*',
|
||||
'3081dc' // SEQUENCE, length 154 + length($d)=66
|
||||
.'020101' // INTEGER, 1
|
||||
.'0442' // OCTET STRING, length(d) = 66
|
||||
.$d[1]
|
||||
.'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);
|
||||
}
|
||||
}
|
@ -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\Util;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use function is_string;
|
||||
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 : '';
|
||||
|
||||
$bin = hex2bin(
|
||||
self::ASN1_SEQUENCE
|
||||
.$lengthPrefix.dechex($totalLength)
|
||||
.self::ASN1_INTEGER.dechex($lengthR).$pointR
|
||||
.self::ASN1_INTEGER.dechex($lengthS).$pointS
|
||||
);
|
||||
if (!is_string($bin)) {
|
||||
throw new InvalidArgumentException('Unable to parse the data');
|
||||
}
|
||||
|
||||
return $bin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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));
|
||||
|
||||
$bin = hex2bin(str_pad($pointR, $length, '0', STR_PAD_LEFT).str_pad($pointS, $length, '0', STR_PAD_LEFT));
|
||||
if (!is_string($bin)) {
|
||||
throw new InvalidArgumentException('Unable to parse the data');
|
||||
}
|
||||
|
||||
return $bin;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
<?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 function is_string;
|
||||
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 {
|
||||
$data = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
if (!is_string($data)) {
|
||||
throw new InvalidArgumentException('Unable to encode the data');
|
||||
}
|
||||
|
||||
return $data;
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
<?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.');
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,315 @@
|
||||
<?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 InvalidArgumentException;
|
||||
use function is_array;
|
||||
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)
|
||||
{
|
||||
$unpacked = unpack('H*', Base64Url::decode($value));
|
||||
if (!is_array($unpacked) || 0 === count($unpacked)) {
|
||||
throw new InvalidArgumentException('Unable to get the private key');
|
||||
}
|
||||
|
||||
return \Brick\Math\BigInteger::fromBase(current($unpacked), 16)->toBase(10);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
{
|
||||
"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"
|
||||
},
|
||||
"conflict": {
|
||||
"spomky-labs/jose": "*"
|
||||
}
|
||||
}
|
@ -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.
|
1
wp-content/plugins/wp-webauthn/wp-webauthn-vendor/web-token/jwt-key-mgmt/.github/FUNDING.yml
vendored
Normal file
1
wp-content/plugins/wp-webauthn/wp-webauthn-vendor/web-token/jwt-key-mgmt/.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
||||
patreon: FlorentMorselli
|
@ -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
|
@ -0,0 +1,26 @@
|
||||
<?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\KeyManagement\Analyzer;
|
||||
|
||||
use Jose\Component\Core\JWK;
|
||||
|
||||
final class AlgorithmAnalyzer implements KeyAnalyzer
|
||||
{
|
||||
public function analyze(JWK $jwk, MessageBag $bag): void
|
||||
{
|
||||
if (!$jwk->has('alg')) {
|
||||
$bag->add(Message::medium('The parameter "alg" should be added.'));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
<?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\KeyManagement\Analyzer;
|
||||
|
||||
use Base64Url\Base64Url;
|
||||
use Brick\Math\BigInteger;
|
||||
use Jose\Component\Core\JWK;
|
||||
use Jose\Component\Core\Util\Ecc\NistCurve;
|
||||
use RuntimeException;
|
||||
|
||||
final class ES256KeyAnalyzer implements KeyAnalyzer
|
||||
{
|
||||
/**
|
||||
* @throws RuntimeException if the component "web-token/jwt-util-ecc" is missing
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
if (!class_exists(NistCurve::class)) {
|
||||
throw new RuntimeException('Please install web-token/jwt-util-ecc to use this key analyzer');
|
||||
}
|
||||
}
|
||||
|
||||
public function analyze(JWK $jwk, MessageBag $bag): void
|
||||
{
|
||||
if ('EC' !== $jwk->get('kty')) {
|
||||
return;
|
||||
}
|
||||
if (!$jwk->has('crv')) {
|
||||
$bag->add(Message::high('Invalid key. The components "crv" is missing.'));
|
||||
|
||||
return;
|
||||
}
|
||||
if ('P-256' !== $jwk->get('crv')) {
|
||||
return;
|
||||
}
|
||||
$x = Base64Url::decode($jwk->get('x'));
|
||||
$xLength = 8 * mb_strlen($x, '8bit');
|
||||
$y = Base64Url::decode($jwk->get('y'));
|
||||
$yLength = 8 * mb_strlen($y, '8bit');
|
||||
if ($yLength !== $xLength || 256 !== $yLength) {
|
||||
$bag->add(Message::high('Invalid key. The components "x" and "y" size shall be 256 bits.'));
|
||||
}
|
||||
$xBI = BigInteger::fromBase(bin2hex($x), 16);
|
||||
$yBI = BigInteger::fromBase(bin2hex($y), 16);
|
||||
$curve = NistCurve::curve256();
|
||||
if (!$curve->contains($xBI, $yBI)) {
|
||||
$bag->add(Message::high('Invalid key. The point is not on the curve.'));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
<?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\KeyManagement\Analyzer;
|
||||
|
||||
use Base64Url\Base64Url;
|
||||
use Brick\Math\BigInteger;
|
||||
use Jose\Component\Core\JWK;
|
||||
use Jose\Component\Core\Util\Ecc\NistCurve;
|
||||
use RuntimeException;
|
||||
|
||||
final class ES384KeyAnalyzer implements KeyAnalyzer
|
||||
{
|
||||
/**
|
||||
* @throws RuntimeException if the component "web-token/jwt-util-ecc" is missing
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
if (!class_exists(NistCurve::class)) {
|
||||
throw new RuntimeException('Please install web-token/jwt-util-ecc to use this key analyzer');
|
||||
}
|
||||
}
|
||||
|
||||
public function analyze(JWK $jwk, MessageBag $bag): void
|
||||
{
|
||||
if ('EC' !== $jwk->get('kty')) {
|
||||
return;
|
||||
}
|
||||
if (!$jwk->has('crv')) {
|
||||
$bag->add(Message::high('Invalid key. The components "crv" is missing.'));
|
||||
|
||||
return;
|
||||
}
|
||||
if ('P-384' !== $jwk->get('crv')) {
|
||||
return;
|
||||
}
|
||||
$x = Base64Url::decode($jwk->get('x'));
|
||||
$xLength = 8 * mb_strlen($x, '8bit');
|
||||
$y = Base64Url::decode($jwk->get('y'));
|
||||
$yLength = 8 * mb_strlen($y, '8bit');
|
||||
if ($yLength !== $xLength || 384 !== $yLength) {
|
||||
$bag->add(Message::high('Invalid key. The components "x" and "y" size shall be 384 bits.'));
|
||||
}
|
||||
$xBI = BigInteger::fromBase(bin2hex($x), 16);
|
||||
$yBI = BigInteger::fromBase(bin2hex($y), 16);
|
||||
$curve = NistCurve::curve384();
|
||||
if (!$curve->contains($xBI, $yBI)) {
|
||||
$bag->add(Message::high('Invalid key. The point is not on the curve.'));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
<?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\KeyManagement\Analyzer;
|
||||
|
||||
use Base64Url\Base64Url;
|
||||
use Brick\Math\BigInteger;
|
||||
use Jose\Component\Core\JWK;
|
||||
use Jose\Component\Core\Util\Ecc\NistCurve;
|
||||
use RuntimeException;
|
||||
|
||||
final class ES512KeyAnalyzer implements KeyAnalyzer
|
||||
{
|
||||
/**
|
||||
* @throws RuntimeException if the component "web-token/jwt-util-ecc" is missing
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
if (!class_exists(NistCurve::class)) {
|
||||
throw new RuntimeException('Please install web-token/jwt-util-ecc to use this key analyzer');
|
||||
}
|
||||
}
|
||||
|
||||
public function analyze(JWK $jwk, MessageBag $bag): void
|
||||
{
|
||||
if ('EC' !== $jwk->get('kty')) {
|
||||
return;
|
||||
}
|
||||
if (!$jwk->has('crv')) {
|
||||
$bag->add(Message::high('Invalid key. The components "crv" is missing.'));
|
||||
|
||||
return;
|
||||
}
|
||||
if ('P-521' !== $jwk->get('crv')) {
|
||||
return;
|
||||
}
|
||||
$x = Base64Url::decode($jwk->get('x'));
|
||||
$xLength = 8 * mb_strlen($x, '8bit');
|
||||
$y = Base64Url::decode($jwk->get('y'));
|
||||
$yLength = 8 * mb_strlen($y, '8bit');
|
||||
if ($yLength !== $xLength || 528 !== $yLength) {
|
||||
$bag->add(Message::high('Invalid key. The components "x" and "y" size shall be 528 bits.'));
|
||||
}
|
||||
$xBI = BigInteger::fromBase(bin2hex($x), 16);
|
||||
$yBI = BigInteger::fromBase(bin2hex($y), 16);
|
||||
$curve = NistCurve::curve521();
|
||||
if (!$curve->contains($xBI, $yBI)) {
|
||||
$bag->add(Message::high('Invalid key. The point is not on the curve.'));
|
||||
}
|
||||
}
|
||||
}
|
@ -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\KeyManagement\Analyzer;
|
||||
|
||||
use Base64Url\Base64Url;
|
||||
use Jose\Component\Core\JWK;
|
||||
|
||||
final class HS256KeyAnalyzer implements KeyAnalyzer
|
||||
{
|
||||
public function analyze(JWK $jwk, MessageBag $bag): void
|
||||
{
|
||||
if ('oct' !== $jwk->get('kty')) {
|
||||
return;
|
||||
}
|
||||
if (!$jwk->has('alg') || 'HS256' !== $jwk->get('alg')) {
|
||||
return;
|
||||
}
|
||||
$k = Base64Url::decode($jwk->get('k'));
|
||||
$kLength = 8 * mb_strlen($k, '8bit');
|
||||
if ($kLength < 256) {
|
||||
$bag->add(Message::high('HS256 algorithm requires at least 256 bits key length.'));
|
||||
}
|
||||
}
|
||||
}
|
@ -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\KeyManagement\Analyzer;
|
||||
|
||||
use Base64Url\Base64Url;
|
||||
use Jose\Component\Core\JWK;
|
||||
|
||||
final class HS384KeyAnalyzer implements KeyAnalyzer
|
||||
{
|
||||
public function analyze(JWK $jwk, MessageBag $bag): void
|
||||
{
|
||||
if ('oct' !== $jwk->get('kty')) {
|
||||
return;
|
||||
}
|
||||
if (!$jwk->has('alg') || 'HS384' !== $jwk->get('alg')) {
|
||||
return;
|
||||
}
|
||||
$k = Base64Url::decode($jwk->get('k'));
|
||||
$kLength = 8 * mb_strlen($k, '8bit');
|
||||
if ($kLength < 384) {
|
||||
$bag->add(Message::high('HS384 algorithm requires at least 384 bits key length.'));
|
||||
}
|
||||
}
|
||||
}
|
@ -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\KeyManagement\Analyzer;
|
||||
|
||||
use Base64Url\Base64Url;
|
||||
use Jose\Component\Core\JWK;
|
||||
|
||||
final class HS512KeyAnalyzer implements KeyAnalyzer
|
||||
{
|
||||
public function analyze(JWK $jwk, MessageBag $bag): void
|
||||
{
|
||||
if ('oct' !== $jwk->get('kty')) {
|
||||
return;
|
||||
}
|
||||
if (!$jwk->has('alg') || 'HS512' !== $jwk->get('alg')) {
|
||||
return;
|
||||
}
|
||||
$k = Base64Url::decode($jwk->get('k'));
|
||||
$kLength = 8 * mb_strlen($k, '8bit');
|
||||
if ($kLength < 512) {
|
||||
$bag->add(Message::high('HS512 algorithm requires at least 512 bits key length.'));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<?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\KeyManagement\Analyzer;
|
||||
|
||||
use Jose\Component\Core\JWK;
|
||||
|
||||
interface KeyAnalyzer
|
||||
{
|
||||
/**
|
||||
* This method will analyse the key and add messages to the message bag if needed.
|
||||
*/
|
||||
public function analyze(JWK $jwk, MessageBag $bag): void;
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
<?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\KeyManagement\Analyzer;
|
||||
|
||||
use Jose\Component\Core\JWK;
|
||||
|
||||
class KeyAnalyzerManager
|
||||
{
|
||||
/**
|
||||
* @var KeyAnalyzer[]
|
||||
*/
|
||||
private $analyzers = [];
|
||||
|
||||
/**
|
||||
* Adds a Key Analyzer to the manager.
|
||||
*/
|
||||
public function add(KeyAnalyzer $analyzer): void
|
||||
{
|
||||
$this->analyzers[] = $analyzer;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will analyze the JWK object using all analyzers.
|
||||
* It returns a message bag that may contains messages.
|
||||
*/
|
||||
public function analyze(JWK $jwk): MessageBag
|
||||
{
|
||||
$bag = new MessageBag();
|
||||
foreach ($this->analyzers as $analyzer) {
|
||||
$analyzer->analyze($jwk, $bag);
|
||||
}
|
||||
|
||||
return $bag;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?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\KeyManagement\Analyzer;
|
||||
|
||||
use Jose\Component\Core\JWK;
|
||||
|
||||
final class KeyIdentifierAnalyzer implements KeyAnalyzer
|
||||
{
|
||||
public function analyze(JWK $jwk, MessageBag $bag): void
|
||||
{
|
||||
if (!$jwk->has('kid')) {
|
||||
$bag->add(Message::medium('The parameter "kid" should be added.'));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<?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\KeyManagement\Analyzer;
|
||||
|
||||
use Jose\Component\Core\JWKSet;
|
||||
|
||||
interface KeysetAnalyzer
|
||||
{
|
||||
/**
|
||||
* This method will analyse the key set and add messages to the message bag if needed.
|
||||
*/
|
||||
public function analyze(JWKSet $JWKSet, MessageBag $bag): void;
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
<?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\KeyManagement\Analyzer;
|
||||
|
||||
use Jose\Component\Core\JWKSet;
|
||||
|
||||
class KeysetAnalyzerManager
|
||||
{
|
||||
/**
|
||||
* @var KeysetAnalyzer[]
|
||||
*/
|
||||
private $analyzers = [];
|
||||
|
||||
/**
|
||||
* Adds a Keyset Analyzer to the manager.
|
||||
*/
|
||||
public function add(KeysetAnalyzer $analyzer): void
|
||||
{
|
||||
$this->analyzers[] = $analyzer;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will analyze the JWKSet object using all analyzers.
|
||||
* It returns a message bag that may contains messages.
|
||||
*/
|
||||
public function analyze(JWKSet $jwkset): MessageBag
|
||||
{
|
||||
$bag = new MessageBag();
|
||||
foreach ($this->analyzers as $analyzer) {
|
||||
$analyzer->analyze($jwkset, $bag);
|
||||
}
|
||||
|
||||
return $bag;
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
<?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\KeyManagement\Analyzer;
|
||||
|
||||
use JsonSerializable;
|
||||
|
||||
class Message implements JsonSerializable
|
||||
{
|
||||
public const SEVERITY_LOW = 'low';
|
||||
|
||||
public const SEVERITY_MEDIUM = 'medium';
|
||||
|
||||
public const SEVERITY_HIGH = 'high';
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $message;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $severity;
|
||||
|
||||
/**
|
||||
* Message constructor.
|
||||
*/
|
||||
private function __construct(string $message, string $severity)
|
||||
{
|
||||
$this->message = $message;
|
||||
$this->severity = $severity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a message with severity=low.
|
||||
*
|
||||
* @return Message
|
||||
*/
|
||||
public static function low(string $message): self
|
||||
{
|
||||
return new self($message, self::SEVERITY_LOW);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a message with severity=medium.
|
||||
*
|
||||
* @return Message
|
||||
*/
|
||||
public static function medium(string $message): self
|
||||
{
|
||||
return new self($message, self::SEVERITY_MEDIUM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a message with severity=high.
|
||||
*
|
||||
* @return Message
|
||||
*/
|
||||
public static function high(string $message): self
|
||||
{
|
||||
return new self($message, self::SEVERITY_HIGH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message.
|
||||
*/
|
||||
public function getMessage(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the severity of the message.
|
||||
*/
|
||||
public function getSeverity(): string
|
||||
{
|
||||
return $this->severity;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'message' => $this->message,
|
||||
'severity' => $this->severity,
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
<?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\KeyManagement\Analyzer;
|
||||
|
||||
use ArrayIterator;
|
||||
use function count;
|
||||
use Countable;
|
||||
use IteratorAggregate;
|
||||
use JsonSerializable;
|
||||
use Traversable;
|
||||
|
||||
class MessageBag implements JsonSerializable, IteratorAggregate, Countable
|
||||
{
|
||||
/**
|
||||
* @var Message[]
|
||||
*/
|
||||
private $messages = [];
|
||||
|
||||
/**
|
||||
* Adds a message to the message bag.
|
||||
*/
|
||||
public function add(Message $message): void
|
||||
{
|
||||
$this->messages[] = $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all messages.
|
||||
*
|
||||
* @return Message[]
|
||||
*/
|
||||
public function all(): array
|
||||
{
|
||||
return $this->messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return array_values($this->messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
return new ArrayIterator($this->messages);
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
<?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\KeyManagement\Analyzer;
|
||||
|
||||
use Jose\Component\Core\JWKSet;
|
||||
|
||||
final class MixedKeyTypes implements KeysetAnalyzer
|
||||
{
|
||||
public function analyze(JWKSet $jwkset, MessageBag $bag): void
|
||||
{
|
||||
if (0 === $jwkset->count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hasSymmetricKeys = false;
|
||||
$hasAsymmetricKeys = false;
|
||||
|
||||
foreach ($jwkset as $jwk) {
|
||||
switch ($jwk->get('kty')) {
|
||||
case 'oct':
|
||||
$hasSymmetricKeys = true;
|
||||
|
||||
break;
|
||||
|
||||
case 'OKP':
|
||||
case 'RSA':
|
||||
case 'EC':
|
||||
$hasAsymmetricKeys = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasAsymmetricKeys && $hasSymmetricKeys) {
|
||||
$bag->add(Message::medium('This key set mixes symmetric and assymetric keys.'));
|
||||
}
|
||||
}
|
||||
}
|
@ -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\KeyManagement\Analyzer;
|
||||
|
||||
use Jose\Component\Core\JWKSet;
|
||||
|
||||
final class MixedPublicAndPrivateKeys implements KeysetAnalyzer
|
||||
{
|
||||
public function analyze(JWKSet $jwkset, MessageBag $bag): void
|
||||
{
|
||||
if (0 === $jwkset->count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hasPublicKeys = false;
|
||||
$hasPrivateKeys = false;
|
||||
|
||||
foreach ($jwkset as $jwk) {
|
||||
switch ($jwk->get('kty')) {
|
||||
case 'OKP':
|
||||
case 'RSA':
|
||||
case 'EC':
|
||||
if ($jwk->has('d')) {
|
||||
$hasPrivateKeys = true;
|
||||
} else {
|
||||
$hasPublicKeys = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasPrivateKeys && $hasPublicKeys) {
|
||||
$bag->add(Message::high('This key set mixes public and private keys.'));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
<?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\KeyManagement\Analyzer;
|
||||
|
||||
use Jose\Component\Core\JWK;
|
||||
|
||||
final class NoneAnalyzer implements KeyAnalyzer
|
||||
{
|
||||
public function analyze(JWK $jwk, MessageBag $bag): void
|
||||
{
|
||||
if ('none' !== $jwk->get('kty')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$bag->add(Message::high('This key is a meant to be used with the algorithm "none". This algorithm is not secured and should be used with care.'));
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
<?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\KeyManagement\Analyzer;
|
||||
|
||||
use Base64Url\Base64Url;
|
||||
use Jose\Component\Core\JWK;
|
||||
|
||||
final class OctAnalyzer implements KeyAnalyzer
|
||||
{
|
||||
public function analyze(JWK $jwk, MessageBag $bag): void
|
||||
{
|
||||
if ('oct' !== $jwk->get('kty')) {
|
||||
return;
|
||||
}
|
||||
$k = Base64Url::decode($jwk->get('k'));
|
||||
$kLength = 8 * mb_strlen($k, '8bit');
|
||||
if ($kLength < 128) {
|
||||
$bag->add(Message::high('The key length is less than 128 bits.'));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
<?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\KeyManagement\Analyzer;
|
||||
|
||||
use Base64Url\Base64Url;
|
||||
use InvalidArgumentException;
|
||||
use function is_array;
|
||||
use Jose\Component\Core\JWK;
|
||||
|
||||
final class RsaAnalyzer implements KeyAnalyzer
|
||||
{
|
||||
public function analyze(JWK $jwk, MessageBag $bag): void
|
||||
{
|
||||
if ('RSA' !== $jwk->get('kty')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->checkExponent($jwk, $bag);
|
||||
$this->checkModulus($jwk, $bag);
|
||||
}
|
||||
|
||||
private function checkExponent(JWK $jwk, MessageBag $bag): void
|
||||
{
|
||||
$exponent = unpack('l', str_pad(Base64Url::decode($jwk->get('e')), 4, "\0"));
|
||||
if (!is_array($exponent) || !isset($exponent[1])) {
|
||||
throw new InvalidArgumentException('Unable to get the private key');
|
||||
}
|
||||
if ($exponent[1] < 65537) {
|
||||
$bag->add(Message::high('The exponent is too low. It should be at least 65537.'));
|
||||
}
|
||||
}
|
||||
|
||||
private function checkModulus(JWK $jwk, MessageBag $bag): void
|
||||
{
|
||||
$n = 8 * mb_strlen(Base64Url::decode($jwk->get('n')), '8bit');
|
||||
if ($n < 2048) {
|
||||
$bag->add(Message::high('The key length is less than 2048 bits.'));
|
||||
}
|
||||
if ($jwk->has('d') && (!$jwk->has('p') || !$jwk->has('q') || !$jwk->has('dp') || !$jwk->has('dq') || !$jwk->has('p') || !$jwk->has('qi'))) {
|
||||
$bag->add(Message::medium('The key is a private RSA key, but Chinese Remainder Theorem primes are missing. These primes are not mandatory, but signatures and decryption processes are faster when available.'));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
<?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\KeyManagement\Analyzer;
|
||||
|
||||
use function in_array;
|
||||
use Jose\Component\Core\JWK;
|
||||
|
||||
final class UsageAnalyzer implements KeyAnalyzer
|
||||
{
|
||||
public function analyze(JWK $jwk, MessageBag $bag): void
|
||||
{
|
||||
if (!$jwk->has('use')) {
|
||||
$bag->add(Message::medium('The parameter "use" should be added.'));
|
||||
} elseif (!in_array($jwk->get('use'), ['sig', 'enc'], true)) {
|
||||
$bag->add(Message::high(sprintf('The parameter "use" has an unsupported value "%s". Please use "sig" (signature) or "enc" (encryption).', $jwk->get('use'))));
|
||||
}
|
||||
if ($jwk->has('key_ops') && !in_array($jwk->get('key_ops'), ['sign', 'verify', 'encrypt', 'decrypt', 'wrapKey', 'unwrapKey'], true)) {
|
||||
$bag->add(Message::high(sprintf('The parameter "key_ops" has an unsupported value "%s". Please use one of the following values: %s.', $jwk->get('use'), implode(', ', ['verify', 'sign', 'encryp', 'decrypt', 'wrapKey', 'unwrapKey']))));
|
||||
}
|
||||
}
|
||||
}
|
@ -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\KeyManagement\Analyzer;
|
||||
|
||||
use Base64Url\Base64Url;
|
||||
use Jose\Component\Core\JWK;
|
||||
use ZxcvbnPhp\Zxcvbn;
|
||||
|
||||
final class ZxcvbnKeyAnalyzer implements KeyAnalyzer
|
||||
{
|
||||
public function analyze(JWK $jwk, MessageBag $bag): void
|
||||
{
|
||||
if ('oct' !== $jwk->get('kty')) {
|
||||
return;
|
||||
}
|
||||
$k = Base64Url::decode($jwk->get('k'));
|
||||
if (class_exists(Zxcvbn::class)) {
|
||||
$zxcvbn = new Zxcvbn();
|
||||
$strength = $zxcvbn->passwordStrength($k);
|
||||
|
||||
switch (true) {
|
||||
case $strength['score'] < 3:
|
||||
$bag->add(Message::high('The octet string is weak and easily guessable. Please change your key as soon as possible.'));
|
||||
|
||||
break;
|
||||
|
||||
case 3 === $strength['score']:
|
||||
$bag->add(Message::medium('The octet string is safe, but a longer key is preferable.'));
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
<?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\KeyManagement;
|
||||
|
||||
use function is_array;
|
||||
use Jose\Component\Core\JWKSet;
|
||||
use Jose\Component\Core\Util\JsonConverter;
|
||||
use RuntimeException;
|
||||
|
||||
class JKUFactory extends UrlKeySetFactory
|
||||
{
|
||||
/**
|
||||
* This method will try to fetch the url a retrieve the key set.
|
||||
* Throws an exception in case of failure.
|
||||
*
|
||||
* @throws RuntimeException if the key cannot be reached
|
||||
*/
|
||||
public function loadFromUrl(string $url, array $header = []): JWKSet
|
||||
{
|
||||
$content = $this->getContent($url, $header);
|
||||
$data = JsonConverter::decode($content);
|
||||
if (!is_array($data)) {
|
||||
throw new RuntimeException('Invalid content.');
|
||||
}
|
||||
|
||||
return JWKSet::createFromKeyData($data);
|
||||
}
|
||||
}
|
@ -0,0 +1,328 @@
|
||||
<?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\KeyManagement;
|
||||
|
||||
use function array_key_exists;
|
||||
use Base64Url\Base64Url;
|
||||
use function extension_loaded;
|
||||
use InvalidArgumentException;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
use Jose\Component\Core\JWK;
|
||||
use Jose\Component\Core\JWKSet;
|
||||
use Jose\Component\Core\Util\ECKey;
|
||||
use Jose\Component\KeyManagement\KeyConverter\KeyConverter;
|
||||
use Jose\Component\KeyManagement\KeyConverter\RSAKey;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
class JWKFactory
|
||||
{
|
||||
/**
|
||||
* Creates a RSA key with the given key size and additional values.
|
||||
*
|
||||
* @param int $size The key size in bits
|
||||
* @param array $values values to configure the key
|
||||
*
|
||||
* @throws InvalidArgumentException if the key has an invalid size
|
||||
* @throws InvalidArgumentException if it is not possible to create the key
|
||||
*/
|
||||
public static function createRSAKey(int $size, array $values = []): JWK
|
||||
{
|
||||
if (0 !== $size % 8) {
|
||||
throw new InvalidArgumentException('Invalid key size.');
|
||||
}
|
||||
if (512 > $size) {
|
||||
throw new InvalidArgumentException('Key length is too short. It needs to be at least 512 bits.');
|
||||
}
|
||||
|
||||
$key = openssl_pkey_new([
|
||||
'private_key_bits' => $size,
|
||||
'private_key_type' => OPENSSL_KEYTYPE_RSA,
|
||||
]);
|
||||
if (false === $key) {
|
||||
throw new InvalidArgumentException('Unable to create the key');
|
||||
}
|
||||
$details = openssl_pkey_get_details($key);
|
||||
if (!is_array($details)) {
|
||||
throw new InvalidArgumentException('Unable to create the key');
|
||||
}
|
||||
$rsa = RSAKey::createFromKeyDetails($details['rsa']);
|
||||
$values = array_merge(
|
||||
$values,
|
||||
$rsa->toArray()
|
||||
);
|
||||
|
||||
return new JWK($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
return ECKey::createECKey($curve, $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a octet key with the given key size and additional values.
|
||||
*
|
||||
* @param int $size The key size in bits
|
||||
* @param array $values values to configure the key
|
||||
*
|
||||
* @throws InvalidArgumentException if the key has an invalid size
|
||||
*/
|
||||
public static function createOctKey(int $size, array $values = []): JWK
|
||||
{
|
||||
if (0 !== $size % 8) {
|
||||
throw new InvalidArgumentException('Invalid key size.');
|
||||
}
|
||||
$values = array_merge(
|
||||
$values,
|
||||
[
|
||||
'kty' => 'oct',
|
||||
'k' => Base64Url::encode(random_bytes($size / 8)),
|
||||
]
|
||||
);
|
||||
|
||||
return new JWK($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a OKP key with the given curve and additional values.
|
||||
*
|
||||
* @param string $curve The curve
|
||||
* @param array $values values to configure the key
|
||||
*
|
||||
* @throws InvalidArgumentException if the extension "sobium" is not available
|
||||
* @throws InvalidArgumentException if the curve is not supported
|
||||
*/
|
||||
public static function createOKPKey(string $curve, array $values = []): JWK
|
||||
{
|
||||
if (!extension_loaded('sodium')) {
|
||||
throw new RuntimeException('The extension "sodium" is not available. Please install it to use this method');
|
||||
}
|
||||
|
||||
switch ($curve) {
|
||||
case 'X25519':
|
||||
$keyPair = sodium_crypto_box_keypair();
|
||||
$secret = sodium_crypto_box_secretkey($keyPair);
|
||||
$x = sodium_crypto_box_publickey($keyPair);
|
||||
|
||||
break;
|
||||
|
||||
case 'Ed25519':
|
||||
$keyPair = sodium_crypto_sign_keypair();
|
||||
$secret = sodium_crypto_sign_secretkey($keyPair);
|
||||
$x = sodium_crypto_sign_publickey($keyPair);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidArgumentException(sprintf('Unsupported "%s" curve', $curve));
|
||||
}
|
||||
$secretLength = mb_strlen($secret, '8bit');
|
||||
$d = mb_substr($secret, 0, -$secretLength / 2, '8bit');
|
||||
|
||||
$values = array_merge(
|
||||
$values,
|
||||
[
|
||||
'kty' => 'OKP',
|
||||
'crv' => $curve,
|
||||
'd' => Base64Url::encode($d),
|
||||
'x' => Base64Url::encode($x),
|
||||
]
|
||||
);
|
||||
|
||||
return new JWK($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a none key with the given additional values.
|
||||
* Please note that this key type is not pat of any specification.
|
||||
* It is used to prevent the use of the "none" algorithm with other key types.
|
||||
*
|
||||
* @param array $values values to configure the key
|
||||
*/
|
||||
public static function createNoneKey(array $values = []): JWK
|
||||
{
|
||||
$values = array_merge(
|
||||
$values,
|
||||
[
|
||||
'kty' => 'none',
|
||||
'alg' => 'none',
|
||||
'use' => 'sig',
|
||||
]
|
||||
);
|
||||
|
||||
return new JWK($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a key from a Json string.
|
||||
*
|
||||
* @throws InvalidArgumentException if the key or keyset is not valid
|
||||
*
|
||||
* @return JWK|JWKSet
|
||||
*/
|
||||
public static function createFromJsonObject(string $value)
|
||||
{
|
||||
$json = json_decode($value, true);
|
||||
if (!is_array($json)) {
|
||||
throw new InvalidArgumentException('Invalid key or key set.');
|
||||
}
|
||||
|
||||
return self::createFromValues($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a key or key set from the given input.
|
||||
*
|
||||
* @return JWK|JWKSet
|
||||
*/
|
||||
public static function createFromValues(array $values)
|
||||
{
|
||||
if (array_key_exists('keys', $values) && is_array($values['keys'])) {
|
||||
return JWKSet::createFromKeyData($values);
|
||||
}
|
||||
|
||||
return new JWK($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method create a JWK object using a shared secret.
|
||||
*/
|
||||
public static function createFromSecret(string $secret, array $additional_values = []): JWK
|
||||
{
|
||||
$values = array_merge(
|
||||
$additional_values,
|
||||
[
|
||||
'kty' => 'oct',
|
||||
'k' => Base64Url::encode($secret),
|
||||
]
|
||||
);
|
||||
|
||||
return new JWK($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will try to load a X.509 certificate and convert it into a public key.
|
||||
*/
|
||||
public static function createFromCertificateFile(string $file, array $additional_values = []): JWK
|
||||
{
|
||||
$values = KeyConverter::loadKeyFromCertificateFile($file);
|
||||
$values = array_merge($values, $additional_values);
|
||||
|
||||
return new JWK($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a keyfrom a key set identified by the given index .
|
||||
*
|
||||
* @param int|string $index
|
||||
*/
|
||||
public static function createFromKeySet(JWKSet $jwkset, $index): JWK
|
||||
{
|
||||
return $jwkset->get($index);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will try to load a PKCS#12 file and convert it into a public key.
|
||||
*
|
||||
* @throws InvalidArgumentException if the certificate cannot be loaded
|
||||
*/
|
||||
public static function createFromPKCS12CertificateFile(string $file, ?string $secret = '', array $additional_values = []): JWK
|
||||
{
|
||||
try {
|
||||
$content = file_get_contents($file);
|
||||
if (!is_string($content)) {
|
||||
throw new RuntimeException('Unable to read the file.');
|
||||
}
|
||||
openssl_pkcs12_read($content, $certs, $secret);
|
||||
} catch (Throwable $throwable) {
|
||||
throw new RuntimeException('Unable to load the certificates.', $throwable->getCode(), $throwable);
|
||||
}
|
||||
if (!is_array($certs) || !array_key_exists('pkey', $certs)) {
|
||||
throw new RuntimeException('Unable to load the certificates.');
|
||||
}
|
||||
|
||||
return self::createFromKey($certs['pkey'], null, $additional_values);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will try to convert a X.509 certificate into a public key.
|
||||
*/
|
||||
public static function createFromCertificate(string $certificate, array $additional_values = []): JWK
|
||||
{
|
||||
$values = KeyConverter::loadKeyFromCertificate($certificate);
|
||||
$values = array_merge($values, $additional_values);
|
||||
|
||||
return new JWK($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will try to convert a X.509 certificate resource into a public key.
|
||||
*
|
||||
* @param resource $res
|
||||
*/
|
||||
public static function createFromX509Resource($res, array $additional_values = []): JWK
|
||||
{
|
||||
$values = KeyConverter::loadKeyFromX509Resource($res);
|
||||
$values = array_merge($values, $additional_values);
|
||||
|
||||
return new JWK($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will try to load and convert a key file into a JWK object.
|
||||
* If the key is encrypted, the password must be set.
|
||||
*/
|
||||
public static function createFromKeyFile(string $file, ?string $password = null, array $additional_values = []): JWK
|
||||
{
|
||||
$values = KeyConverter::loadFromKeyFile($file, $password);
|
||||
$values = array_merge($values, $additional_values);
|
||||
|
||||
return new JWK($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will try to load and convert a key into a JWK object.
|
||||
* If the key is encrypted, the password must be set.
|
||||
*/
|
||||
public static function createFromKey(string $key, ?string $password = null, array $additional_values = []): JWK
|
||||
{
|
||||
$values = KeyConverter::loadFromKey($key, $password);
|
||||
$values = array_merge($values, $additional_values);
|
||||
|
||||
return new JWK($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will try to load and convert a X.509 certificate chain into a public key.
|
||||
*
|
||||
* Be careful! The certificate chain is loaded, but it is NOT VERIFIED by any mean!
|
||||
* It is mandatory to verify the root CA or intermediate CA are trusted.
|
||||
* If not done, it may lead to potential security issues.
|
||||
*/
|
||||
public static function createFromX5C(array $x5c, array $additional_values = []): JWK
|
||||
{
|
||||
$values = KeyConverter::loadFromX5C($x5c);
|
||||
$values = array_merge($values, $additional_values);
|
||||
|
||||
return new JWK($values);
|
||||
}
|
||||
}
|
@ -0,0 +1,307 @@
|
||||
<?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\KeyManagement\KeyConverter;
|
||||
|
||||
use function array_key_exists;
|
||||
use Base64Url\Base64Url;
|
||||
use function count;
|
||||
use FG\ASN1\ASNObject;
|
||||
use FG\ASN1\Exception\ParserException;
|
||||
use FG\ASN1\ExplicitlyTaggedObject;
|
||||
use FG\ASN1\Universal\BitString;
|
||||
use FG\ASN1\Universal\Integer;
|
||||
use FG\ASN1\Universal\ObjectIdentifier;
|
||||
use FG\ASN1\Universal\OctetString;
|
||||
use FG\ASN1\Universal\Sequence;
|
||||
use InvalidArgumentException;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ECKey
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $values = [];
|
||||
|
||||
private function __construct(array $data)
|
||||
{
|
||||
$this->loadJWK($data);
|
||||
}
|
||||
|
||||
public static function createFromPEM(string $pem): self
|
||||
{
|
||||
$data = self::loadPEM($pem);
|
||||
|
||||
return new self($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ECKey $private
|
||||
*
|
||||
* @return ECKey
|
||||
*/
|
||||
public static function toPublic(self $private): self
|
||||
{
|
||||
$data = $private->toArray();
|
||||
if (array_key_exists('d', $data)) {
|
||||
unset($data['d']);
|
||||
}
|
||||
|
||||
return new self($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the key cannot be loaded
|
||||
* @throws ParserException if the key cannot be loaded
|
||||
*/
|
||||
private static function loadPEM(string $data): array
|
||||
{
|
||||
$data = base64_decode(preg_replace('#-.*-|\r|\n#', '', $data), true);
|
||||
$asnObject = ASNObject::fromBinary($data);
|
||||
if (!$asnObject instanceof Sequence) {
|
||||
throw new InvalidArgumentException('Unable to load the key.');
|
||||
}
|
||||
$children = $asnObject->getChildren();
|
||||
if (self::isPKCS8($children)) {
|
||||
$children = self::loadPKCS8($children);
|
||||
}
|
||||
|
||||
if (4 === count($children)) {
|
||||
return self::loadPrivatePEM($children);
|
||||
}
|
||||
if (2 === count($children)) {
|
||||
return self::loadPublicPEM($children);
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Unable to load the key.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ASNObject[] $children
|
||||
*
|
||||
* @throws InvalidArgumentException if the key cannot be loaded
|
||||
* @throws ParserException if the key cannot be loaded
|
||||
*/
|
||||
private static function loadPKCS8(array $children): array
|
||||
{
|
||||
$binary = hex2bin($children[2]->getContent());
|
||||
$asnObject = ASNObject::fromBinary($binary);
|
||||
if (!$asnObject instanceof Sequence) {
|
||||
throw new InvalidArgumentException('Unable to load the key.');
|
||||
}
|
||||
|
||||
return $asnObject->getChildren();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the key cannot be loaded
|
||||
*/
|
||||
private static function loadPublicPEM(array $children): array
|
||||
{
|
||||
if (!$children[0] instanceof Sequence) {
|
||||
throw new InvalidArgumentException('Unsupported key type.');
|
||||
}
|
||||
|
||||
$sub = $children[0]->getChildren();
|
||||
if (!$sub[0] instanceof ObjectIdentifier) {
|
||||
throw new InvalidArgumentException('Unsupported key type.');
|
||||
}
|
||||
if ('1.2.840.10045.2.1' !== $sub[0]->getContent()) {
|
||||
throw new InvalidArgumentException('Unsupported key type.');
|
||||
}
|
||||
if (!$sub[1] instanceof ObjectIdentifier) {
|
||||
throw new InvalidArgumentException('Unsupported key type.');
|
||||
}
|
||||
if (!$children[1] instanceof BitString) {
|
||||
throw new InvalidArgumentException('Unable to load the key.');
|
||||
}
|
||||
|
||||
$bits = $children[1]->getContent();
|
||||
$bits_length = mb_strlen($bits, '8bit');
|
||||
if (0 !== mb_strpos($bits, '04', 0, '8bit')) {
|
||||
throw new InvalidArgumentException('Unsupported key type');
|
||||
}
|
||||
|
||||
$values = ['kty' => 'EC'];
|
||||
$values['crv'] = self::getCurve($sub[1]->getContent());
|
||||
|
||||
$xBin = hex2bin(mb_substr($bits, 2, ($bits_length - 2) / 2, '8bit'));
|
||||
$yBin = hex2bin(mb_substr($bits, (int) (($bits_length - 2) / 2 + 2), ($bits_length - 2) / 2, '8bit'));
|
||||
if (!is_string($xBin) || !is_string($yBin)) {
|
||||
throw new InvalidArgumentException('Unable to load the key.');
|
||||
}
|
||||
|
||||
$values['x'] = Base64Url::encode($xBin);
|
||||
$values['y'] = Base64Url::encode($yBin);
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the OID is not supported
|
||||
*/
|
||||
private static function getCurve(string $oid): string
|
||||
{
|
||||
$curves = self::getSupportedCurves();
|
||||
$curve = array_search($oid, $curves, true);
|
||||
if (!is_string($curve)) {
|
||||
throw new InvalidArgumentException('Unsupported OID.');
|
||||
}
|
||||
|
||||
return $curve;
|
||||
}
|
||||
|
||||
private static function getSupportedCurves(): array
|
||||
{
|
||||
return [
|
||||
'P-256' => '1.2.840.10045.3.1.7',
|
||||
'P-384' => '1.3.132.0.34',
|
||||
'P-521' => '1.3.132.0.35',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the key cannot be loaded
|
||||
*/
|
||||
private static function verifyVersion(ASNObject $children): void
|
||||
{
|
||||
if (!$children instanceof Integer || '1' !== $children->getContent()) {
|
||||
throw new InvalidArgumentException('Unable to load the key.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the key cannot be loaded
|
||||
*/
|
||||
private static function getXAndY(ASNObject $children, string &$x, string &$y): void
|
||||
{
|
||||
if (!$children instanceof ExplicitlyTaggedObject || !is_array($children->getContent())) {
|
||||
throw new InvalidArgumentException('Unable to load the key.');
|
||||
}
|
||||
if (!$children->getContent()[0] instanceof BitString) {
|
||||
throw new InvalidArgumentException('Unable to load the key.');
|
||||
}
|
||||
|
||||
$bits = $children->getContent()[0]->getContent();
|
||||
$bits_length = mb_strlen($bits, '8bit');
|
||||
|
||||
if (0 !== mb_strpos($bits, '04', 0, '8bit')) {
|
||||
throw new InvalidArgumentException('Unsupported key type');
|
||||
}
|
||||
|
||||
$x = mb_substr($bits, 2, (int) (($bits_length - 2) / 2), '8bit');
|
||||
$y = mb_substr($bits, (int) (($bits_length - 2) / 2 + 2), (int) (($bits_length - 2) / 2), '8bit');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the key cannot be loaded
|
||||
*/
|
||||
private static function getD(ASNObject $children): string
|
||||
{
|
||||
if (!$children instanceof OctetString) {
|
||||
throw new InvalidArgumentException('Unable to load the key.');
|
||||
}
|
||||
|
||||
return $children->getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the key cannot be loaded
|
||||
*/
|
||||
private static function loadPrivatePEM(array $children): array
|
||||
{
|
||||
self::verifyVersion($children[0]);
|
||||
$x = '';
|
||||
$y = '';
|
||||
$d = self::getD($children[1]);
|
||||
self::getXAndY($children[3], $x, $y);
|
||||
|
||||
if (!$children[2] instanceof ExplicitlyTaggedObject || !is_array($children[2]->getContent())) {
|
||||
throw new InvalidArgumentException('Unable to load the key.');
|
||||
}
|
||||
if (!$children[2]->getContent()[0] instanceof ObjectIdentifier) {
|
||||
throw new InvalidArgumentException('Unable to load the key.');
|
||||
}
|
||||
|
||||
$curve = $children[2]->getContent()[0]->getContent();
|
||||
$dBin = hex2bin($d);
|
||||
$xBin = hex2bin($x);
|
||||
$yBin = hex2bin($y);
|
||||
if (!is_string($dBin) || !is_string($xBin) || !is_string($yBin)) {
|
||||
throw new InvalidArgumentException('Unable to load the key.');
|
||||
}
|
||||
|
||||
$values = ['kty' => 'EC'];
|
||||
$values['crv'] = self::getCurve($curve);
|
||||
$values['d'] = Base64Url::encode($dBin);
|
||||
$values['x'] = Base64Url::encode($xBin);
|
||||
$values['y'] = Base64Url::encode($yBin);
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ASNObject[] $children
|
||||
*/
|
||||
private static function isPKCS8(array $children): bool
|
||||
{
|
||||
if (3 !== count($children)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$classes = [0 => Integer::class, 1 => Sequence::class, 2 => OctetString::class];
|
||||
foreach ($classes as $k => $class) {
|
||||
if (!$children[$k] instanceof $class) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the key is invalid
|
||||
*/
|
||||
private function loadJWK(array $jwk): void
|
||||
{
|
||||
$keys = [
|
||||
'kty' => 'The key parameter "kty" is missing.',
|
||||
'crv' => 'Curve parameter is missing',
|
||||
'x' => 'Point parameters are missing.',
|
||||
'y' => 'Point parameters are missing.',
|
||||
];
|
||||
foreach ($keys as $k => $v) {
|
||||
if (!array_key_exists($k, $jwk)) {
|
||||
throw new InvalidArgumentException($v);
|
||||
}
|
||||
}
|
||||
|
||||
if ('EC' !== $jwk['kty']) {
|
||||
throw new InvalidArgumentException('JWK is not an Elliptic Curve key.');
|
||||
}
|
||||
$this->values = $jwk;
|
||||
}
|
||||
}
|
@ -0,0 +1,272 @@
|
||||
<?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\KeyManagement\KeyConverter;
|
||||
|
||||
use function array_key_exists;
|
||||
use Base64Url\Base64Url;
|
||||
use function count;
|
||||
use function extension_loaded;
|
||||
use InvalidArgumentException;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class KeyConverter
|
||||
{
|
||||
/**
|
||||
* @throws InvalidArgumentException if the certificate file cannot be read
|
||||
*/
|
||||
public static function loadKeyFromCertificateFile(string $file): array
|
||||
{
|
||||
if (!file_exists($file)) {
|
||||
throw new InvalidArgumentException(sprintf('File "%s" does not exist.', $file));
|
||||
}
|
||||
$content = file_get_contents($file);
|
||||
if (!is_string($content)) {
|
||||
throw new InvalidArgumentException(sprintf('File "%s" cannot be read.', $file));
|
||||
}
|
||||
|
||||
return self::loadKeyFromCertificate($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the OpenSSL extension is not available
|
||||
* @throws InvalidArgumentException if the certificate is invalid or cannot be loaded
|
||||
*/
|
||||
public static function loadKeyFromCertificate(string $certificate): array
|
||||
{
|
||||
if (!extension_loaded('openssl')) {
|
||||
throw new RuntimeException('Please install the OpenSSL extension');
|
||||
}
|
||||
|
||||
try {
|
||||
$res = openssl_x509_read($certificate);
|
||||
if (false === $res) {
|
||||
throw new InvalidArgumentException('Unable to load the certificate.');
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$certificate = self::convertDerToPem($certificate);
|
||||
$res = openssl_x509_read($certificate);
|
||||
}
|
||||
if (false === $res) {
|
||||
throw new InvalidArgumentException('Unable to load the certificate.');
|
||||
}
|
||||
|
||||
return self::loadKeyFromX509Resource($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $res
|
||||
*
|
||||
* @throws InvalidArgumentException if the OpenSSL extension is not available
|
||||
* @throws InvalidArgumentException if the certificate is invalid or cannot be loaded
|
||||
*/
|
||||
public static function loadKeyFromX509Resource($res): array
|
||||
{
|
||||
if (!extension_loaded('openssl')) {
|
||||
throw new RuntimeException('Please install the OpenSSL extension');
|
||||
}
|
||||
$key = openssl_get_publickey($res);
|
||||
if (false === $key) {
|
||||
throw new InvalidArgumentException('Unable to load the certificate.');
|
||||
}
|
||||
$details = openssl_pkey_get_details($key);
|
||||
if (!is_array($details)) {
|
||||
throw new InvalidArgumentException('Unable to load the certificate');
|
||||
}
|
||||
if (isset($details['key'])) {
|
||||
$values = self::loadKeyFromPEM($details['key']);
|
||||
openssl_x509_export($res, $out);
|
||||
$x5c = preg_replace('#-.*-#', '', $out);
|
||||
$x5c = preg_replace('~\R~', PHP_EOL, $x5c);
|
||||
if (!is_string($x5c)) {
|
||||
throw new InvalidArgumentException('Unable to load the certificate');
|
||||
}
|
||||
$x5c = trim($x5c);
|
||||
|
||||
$x5tsha1 = openssl_x509_fingerprint($res, 'sha1', true);
|
||||
$x5tsha256 = openssl_x509_fingerprint($res, 'sha256', true);
|
||||
if (!is_string($x5tsha1) || !is_string($x5tsha256)) {
|
||||
throw new InvalidArgumentException('Unable to compute the certificate fingerprint');
|
||||
}
|
||||
|
||||
$values['x5c'] = [$x5c];
|
||||
$values['x5t'] = Base64Url::encode($x5tsha1);
|
||||
$values['x5t#256'] = Base64Url::encode($x5tsha256);
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Unable to load the certificate');
|
||||
}
|
||||
|
||||
public static function loadFromKeyFile(string $file, ?string $password = null): array
|
||||
{
|
||||
$content = file_get_contents($file);
|
||||
if (!is_string($content)) {
|
||||
throw new InvalidArgumentException('Unable to load the key from the file.');
|
||||
}
|
||||
|
||||
return self::loadFromKey($content, $password);
|
||||
}
|
||||
|
||||
public static function loadFromKey(string $key, ?string $password = null): array
|
||||
{
|
||||
try {
|
||||
return self::loadKeyFromDER($key, $password);
|
||||
} catch (Throwable $e) {
|
||||
return self::loadKeyFromPEM($key, $password);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Be careful! The certificate chain is loaded, but it is NOT VERIFIED by any mean!
|
||||
* It is mandatory to verify the root CA or intermediate CA are trusted.
|
||||
* If not done, it may lead to potential security issues.
|
||||
*
|
||||
* @throws InvalidArgumentException if the certificate chain is empty
|
||||
* @throws InvalidArgumentException if the OpenSSL extension is not available
|
||||
*/
|
||||
public static function loadFromX5C(array $x5c): array
|
||||
{
|
||||
if (0 === count($x5c)) {
|
||||
throw new InvalidArgumentException('The certificate chain is empty');
|
||||
}
|
||||
foreach ($x5c as $id => $cert) {
|
||||
$x5c[$id] = '-----BEGIN CERTIFICATE-----'.PHP_EOL.chunk_split($cert, 64, PHP_EOL).'-----END CERTIFICATE-----';
|
||||
$x509 = openssl_x509_read($x5c[$id]);
|
||||
if (false === $x509) {
|
||||
throw new InvalidArgumentException('Unable to load the certificate chain');
|
||||
}
|
||||
$parsed = openssl_x509_parse($x509);
|
||||
if (false === $parsed) {
|
||||
throw new InvalidArgumentException('Unable to load the certificate chain');
|
||||
}
|
||||
}
|
||||
|
||||
return self::loadKeyFromCertificate(reset($x5c));
|
||||
}
|
||||
|
||||
private static function loadKeyFromDER(string $der, ?string $password = null): array
|
||||
{
|
||||
$pem = self::convertDerToPem($der);
|
||||
|
||||
return self::loadKeyFromPEM($pem, $password);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the OpenSSL extension is not available
|
||||
* @throws InvalidArgumentException if the key cannot be loaded
|
||||
*/
|
||||
private static function loadKeyFromPEM(string $pem, ?string $password = null): array
|
||||
{
|
||||
if (1 === preg_match('#DEK-Info: (.+),(.+)#', $pem, $matches)) {
|
||||
$pem = self::decodePem($pem, $matches, $password);
|
||||
}
|
||||
|
||||
if (!extension_loaded('openssl')) {
|
||||
throw new RuntimeException('Please install the OpenSSL extension');
|
||||
}
|
||||
self::sanitizePEM($pem);
|
||||
$res = openssl_pkey_get_private($pem);
|
||||
if (false === $res) {
|
||||
$res = openssl_pkey_get_public($pem);
|
||||
}
|
||||
if (false === $res) {
|
||||
throw new InvalidArgumentException('Unable to load the key.');
|
||||
}
|
||||
|
||||
$details = openssl_pkey_get_details($res);
|
||||
if (!is_array($details) || !array_key_exists('type', $details)) {
|
||||
throw new InvalidArgumentException('Unable to get details of the key');
|
||||
}
|
||||
|
||||
switch ($details['type']) {
|
||||
case OPENSSL_KEYTYPE_EC:
|
||||
$ec_key = ECKey::createFromPEM($pem);
|
||||
|
||||
return $ec_key->toArray();
|
||||
|
||||
case OPENSSL_KEYTYPE_RSA:
|
||||
$rsa_key = RSAKey::createFromPEM($pem);
|
||||
|
||||
return $rsa_key->toArray();
|
||||
|
||||
default:
|
||||
throw new InvalidArgumentException('Unsupported key type');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method modifies the PEM to get 64 char lines and fix bug with old OpenSSL versions.
|
||||
*/
|
||||
private static function sanitizePEM(string &$pem): void
|
||||
{
|
||||
preg_match_all('#(-.*-)#', $pem, $matches, PREG_PATTERN_ORDER);
|
||||
$ciphertext = preg_replace('#-.*-|\r|\n| #', '', $pem);
|
||||
|
||||
$pem = $matches[0][0].PHP_EOL;
|
||||
$pem .= chunk_split($ciphertext, 64, PHP_EOL);
|
||||
$pem .= $matches[0][1].PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $matches
|
||||
*
|
||||
* @throws InvalidArgumentException if the password to decrypt the key is not provided
|
||||
* @throws InvalidArgumentException if the key cannot be loaded
|
||||
*/
|
||||
private static function decodePem(string $pem, array $matches, ?string $password = null): string
|
||||
{
|
||||
if (null === $password) {
|
||||
throw new InvalidArgumentException('Password required for encrypted keys.');
|
||||
}
|
||||
|
||||
$iv = pack('H*', trim($matches[2]));
|
||||
$iv_sub = mb_substr($iv, 0, 8, '8bit');
|
||||
$symkey = pack('H*', md5($password.$iv_sub));
|
||||
$symkey .= pack('H*', md5($symkey.$password.$iv_sub));
|
||||
$key = preg_replace('#^(?:Proc-Type|DEK-Info): .*#m', '', $pem);
|
||||
$ciphertext = base64_decode(preg_replace('#-.*-|\r|\n#', '', $key), true);
|
||||
if (!is_string($ciphertext)) {
|
||||
throw new InvalidArgumentException('Unable to encode the data.');
|
||||
}
|
||||
|
||||
$decoded = openssl_decrypt($ciphertext, mb_strtolower($matches[1]), $symkey, OPENSSL_RAW_DATA, $iv);
|
||||
if (false === $decoded) {
|
||||
throw new RuntimeException('Unable to decrypt the key');
|
||||
}
|
||||
$number = preg_match_all('#-{5}.*-{5}#', $pem, $result);
|
||||
if (2 !== $number) {
|
||||
throw new InvalidArgumentException('Unable to load the key');
|
||||
}
|
||||
|
||||
$pem = $result[0][0].PHP_EOL;
|
||||
$pem .= chunk_split(base64_encode($decoded), 64);
|
||||
$pem .= $result[0][1].PHP_EOL;
|
||||
|
||||
return $pem;
|
||||
}
|
||||
|
||||
private static function convertDerToPem(string $der_data): string
|
||||
{
|
||||
$pem = chunk_split(base64_encode($der_data), 64, PHP_EOL);
|
||||
|
||||
return '-----BEGIN CERTIFICATE-----'.PHP_EOL.$pem.'-----END CERTIFICATE-----'.PHP_EOL;
|
||||
}
|
||||
}
|
@ -0,0 +1,263 @@
|
||||
<?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\KeyManagement\KeyConverter;
|
||||
|
||||
use function array_key_exists;
|
||||
use Base64Url\Base64Url;
|
||||
use function extension_loaded;
|
||||
use function in_array;
|
||||
use InvalidArgumentException;
|
||||
use function is_array;
|
||||
use Jose\Component\Core\JWK;
|
||||
use Jose\Component\Core\Util\BigInteger;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class RSAKey
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $values = [];
|
||||
|
||||
/**
|
||||
* RSAKey constructor.
|
||||
*/
|
||||
private function __construct(array $data)
|
||||
{
|
||||
$this->loadJWK($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RSAKey
|
||||
*/
|
||||
public static function createFromKeyDetails(array $details): self
|
||||
{
|
||||
$values = ['kty' => 'RSA'];
|
||||
$keys = [
|
||||
'n' => 'n',
|
||||
'e' => 'e',
|
||||
'd' => 'd',
|
||||
'p' => 'p',
|
||||
'q' => 'q',
|
||||
'dp' => 'dmp1',
|
||||
'dq' => 'dmq1',
|
||||
'qi' => 'iqmp',
|
||||
];
|
||||
foreach ($details as $key => $value) {
|
||||
if (in_array($key, $keys, true)) {
|
||||
$value = Base64Url::encode($value);
|
||||
$values[array_search($key, $keys, true)] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return new self($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RuntimeException if the extension OpenSSL is not available
|
||||
* @throws InvalidArgumentException if the key cannot be loaded
|
||||
*
|
||||
* @return RSAKey
|
||||
*/
|
||||
public static function createFromPEM(string $pem): self
|
||||
{
|
||||
if (!extension_loaded('openssl')) {
|
||||
throw new RuntimeException('Please install the OpenSSL extension');
|
||||
}
|
||||
$res = openssl_pkey_get_private($pem);
|
||||
if (false === $res) {
|
||||
$res = openssl_pkey_get_public($pem);
|
||||
}
|
||||
if (false === $res) {
|
||||
throw new InvalidArgumentException('Unable to load the key.');
|
||||
}
|
||||
|
||||
$details = openssl_pkey_get_details($res);
|
||||
if (!is_array($details) || !isset($details['rsa'])) {
|
||||
throw new InvalidArgumentException('Unable to load the key.');
|
||||
}
|
||||
|
||||
return self::createFromKeyDetails($details['rsa']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RSAKey
|
||||
*/
|
||||
public static function createFromJWK(JWK $jwk): self
|
||||
{
|
||||
return new self($jwk->all());
|
||||
}
|
||||
|
||||
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($data);
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->values;
|
||||
}
|
||||
|
||||
public function toJwk(): JWK
|
||||
{
|
||||
return new JWK($this->values);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will try to add Chinese Remainder Theorem (CRT) parameters.
|
||||
* With those primes, the decryption process is really fast.
|
||||
*/
|
||||
public function optimize(): void
|
||||
{
|
||||
if (array_key_exists('d', $this->values)) {
|
||||
$this->populateCRT();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the key is invalid or not an RSA key
|
||||
*/
|
||||
private function loadJWK(array $jwk): void
|
||||
{
|
||||
if (!array_key_exists('kty', $jwk)) {
|
||||
throw new InvalidArgumentException('The key parameter "kty" is missing.');
|
||||
}
|
||||
if ('RSA' !== $jwk['kty']) {
|
||||
throw new InvalidArgumentException('The JWK is not a RSA key.');
|
||||
}
|
||||
|
||||
$this->values = $jwk;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method adds Chinese Remainder Theorem (CRT) parameters if primes 'p' and 'q' are available.
|
||||
* If 'p' and 'q' are missing, they are computed and added to the key data.
|
||||
*/
|
||||
private function populateCRT(): void
|
||||
{
|
||||
if (!array_key_exists('p', $this->values) && !array_key_exists('q', $this->values)) {
|
||||
$d = BigInteger::createFromBinaryString(Base64Url::decode($this->values['d']));
|
||||
$e = BigInteger::createFromBinaryString(Base64Url::decode($this->values['e']));
|
||||
$n = BigInteger::createFromBinaryString(Base64Url::decode($this->values['n']));
|
||||
|
||||
[$p, $q] = $this->findPrimeFactors($d, $e, $n);
|
||||
$this->values['p'] = Base64Url::encode($p->toBytes());
|
||||
$this->values['q'] = Base64Url::encode($q->toBytes());
|
||||
}
|
||||
|
||||
if (array_key_exists('dp', $this->values) && array_key_exists('dq', $this->values) && array_key_exists('qi', $this->values)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$one = BigInteger::createFromDecimal(1);
|
||||
$d = BigInteger::createFromBinaryString(Base64Url::decode($this->values['d']));
|
||||
$p = BigInteger::createFromBinaryString(Base64Url::decode($this->values['p']));
|
||||
$q = BigInteger::createFromBinaryString(Base64Url::decode($this->values['q']));
|
||||
|
||||
$this->values['dp'] = Base64Url::encode($d->mod($p->subtract($one))->toBytes());
|
||||
$this->values['dq'] = Base64Url::encode($d->mod($q->subtract($one))->toBytes());
|
||||
$this->values['qi'] = Base64Url::encode($q->modInverse($p)->toBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RuntimeException if the prime factors cannot be found
|
||||
*
|
||||
* @return BigInteger[]
|
||||
*/
|
||||
private function findPrimeFactors(BigInteger $d, BigInteger $e, BigInteger $n): array
|
||||
{
|
||||
$zero = BigInteger::createFromDecimal(0);
|
||||
$one = BigInteger::createFromDecimal(1);
|
||||
$two = BigInteger::createFromDecimal(2);
|
||||
|
||||
$k = $d->multiply($e)->subtract($one);
|
||||
|
||||
if ($k->isEven()) {
|
||||
$r = $k;
|
||||
$t = $zero;
|
||||
|
||||
do {
|
||||
$r = $r->divide($two);
|
||||
$t = $t->add($one);
|
||||
} while ($r->isEven());
|
||||
|
||||
$found = false;
|
||||
$y = null;
|
||||
|
||||
for ($i = 1; $i <= 100; ++$i) {
|
||||
$g = BigInteger::random($n->subtract($one));
|
||||
$y = $g->modPow($r, $n);
|
||||
|
||||
if ($y->equals($one) || $y->equals($n->subtract($one))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for ($j = $one; $j->lowerThan($t->subtract($one)); $j = $j->add($one)) {
|
||||
$x = $y->modPow($two, $n);
|
||||
|
||||
if ($x->equals($one)) {
|
||||
$found = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ($x->equals($n->subtract($one))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$y = $x;
|
||||
}
|
||||
|
||||
$x = $y->modPow($two, $n);
|
||||
if ($x->equals($one)) {
|
||||
$found = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (null === $y) {
|
||||
throw new InvalidArgumentException('Unable to find prime factors.');
|
||||
}
|
||||
if (true === $found) {
|
||||
$p = $y->subtract($one)->gcd($n);
|
||||
$q = $n->divide($p);
|
||||
|
||||
return [$p, $q];
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Unable to find prime factors.');
|
||||
}
|
||||
}
|
@ -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.
|
@ -0,0 +1,15 @@
|
||||
PHP JWT Key Management 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).
|
@ -0,0 +1,58 @@
|
||||
<?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\KeyManagement;
|
||||
|
||||
use Psr\Http\Client\ClientInterface;
|
||||
use Psr\Http\Message\RequestFactoryInterface;
|
||||
use RuntimeException;
|
||||
|
||||
abstract class UrlKeySetFactory
|
||||
{
|
||||
/**
|
||||
* @var ClientInterface
|
||||
*/
|
||||
private $client;
|
||||
|
||||
/**
|
||||
* @var RequestFactoryInterface
|
||||
*/
|
||||
private $requestFactory;
|
||||
|
||||
/**
|
||||
* UrlKeySetFactory constructor.
|
||||
*/
|
||||
public function __construct(ClientInterface $client, RequestFactoryInterface $requestFactory)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->requestFactory = $requestFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RuntimeException if the response content is invalid
|
||||
*/
|
||||
protected function getContent(string $url, array $header = []): string
|
||||
{
|
||||
$request = $this->requestFactory->createRequest('GET', $url);
|
||||
foreach ($header as $k => $v) {
|
||||
$request = $request->withHeader($k, $v);
|
||||
}
|
||||
$response = $this->client->sendRequest($request);
|
||||
|
||||
if ($response->getStatusCode() >= 400) {
|
||||
throw new RuntimeException('Unable to get the key set.', $response->getStatusCode());
|
||||
}
|
||||
|
||||
return $response->getBody()->getContents();
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
<?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\KeyManagement;
|
||||
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
use Jose\Component\Core\JWK;
|
||||
use Jose\Component\Core\JWKSet;
|
||||
use Jose\Component\Core\Util\JsonConverter;
|
||||
use Jose\Component\KeyManagement\KeyConverter\KeyConverter;
|
||||
use RuntimeException;
|
||||
|
||||
class X5UFactory extends UrlKeySetFactory
|
||||
{
|
||||
/**
|
||||
* This method will try to fetch the url a retrieve the key set.
|
||||
* Throws an exception in case of failure.
|
||||
*
|
||||
* @throws RuntimeException if the response content is invalid
|
||||
*/
|
||||
public function loadFromUrl(string $url, array $header = []): JWKSet
|
||||
{
|
||||
$content = $this->getContent($url, $header);
|
||||
$data = JsonConverter::decode($content);
|
||||
if (!is_array($data)) {
|
||||
throw new RuntimeException('Invalid content.');
|
||||
}
|
||||
|
||||
$keys = [];
|
||||
foreach ($data as $kid => $cert) {
|
||||
if (false === mb_strpos($cert, '-----BEGIN CERTIFICATE-----')) {
|
||||
$cert = '-----BEGIN CERTIFICATE-----'.PHP_EOL.$cert.PHP_EOL.'-----END CERTIFICATE-----';
|
||||
}
|
||||
$jwk = KeyConverter::loadKeyFromCertificate($cert);
|
||||
if (is_string($kid)) {
|
||||
$jwk['kid'] = $kid;
|
||||
$keys[$kid] = new JWK($jwk);
|
||||
} else {
|
||||
$keys[] = new JWK($jwk);
|
||||
}
|
||||
}
|
||||
|
||||
return new JWKSet($keys);
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "web-token/jwt-key-mgmt",
|
||||
"description": "Key Management 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-key-mgmt/contributors"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Jose\\Component\\KeyManagement\\": ""
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"ext-openssl": "*",
|
||||
"psr/http-factory": "^1.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"web-token/jwt-core": "^2.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-sodium": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys",
|
||||
"web-token/jwt-util-ecc": "To use EC key analyzers.",
|
||||
"php-http/message-factory": "To enable JKU/X5U support.",
|
||||
"php-http/httplug": "To enable JKU/X5U support."
|
||||
}
|
||||
}
|
@ -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.
|
@ -0,0 +1 @@
|
||||
patreon: FlorentMorselli
|
@ -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
|
@ -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\Signature\Algorithm;
|
||||
|
||||
use function defined;
|
||||
use function in_array;
|
||||
use InvalidArgumentException;
|
||||
use Jose\Component\Core\JWK;
|
||||
use Jose\Component\Core\Util\ECKey;
|
||||
use Jose\Component\Core\Util\ECSignature;
|
||||
use LogicException;
|
||||
use Throwable;
|
||||
|
||||
abstract class ECDSA implements SignatureAlgorithm
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
if (!defined('OPENSSL_KEYTYPE_EC')) {
|
||||
throw new LogicException('Elliptic Curve key type not supported by your environment.');
|
||||
}
|
||||
}
|
||||
|
||||
public function allowedKeyTypes(): array
|
||||
{
|
||||
return ['EC'];
|
||||
}
|
||||
|
||||
public function sign(JWK $key, string $input): string
|
||||
{
|
||||
$this->checkKey($key);
|
||||
if (!$key->has('d')) {
|
||||
throw new InvalidArgumentException('The EC key is not private');
|
||||
}
|
||||
$pem = ECKey::convertPrivateKeyToPEM($key);
|
||||
openssl_sign($input, $signature, $pem, $this->getHashAlgorithm());
|
||||
|
||||
return ECSignature::fromAsn1($signature, $this->getSignaturePartLength());
|
||||
}
|
||||
|
||||
public function verify(JWK $key, string $input, string $signature): bool
|
||||
{
|
||||
$this->checkKey($key);
|
||||
|
||||
try {
|
||||
$der = ECSignature::toAsn1($signature, $this->getSignaturePartLength());
|
||||
$pem = ECKey::convertPublicKeyToPEM($key);
|
||||
|
||||
return 1 === openssl_verify($input, $der, $pem, $this->getHashAlgorithm());
|
||||
} catch (Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected function getHashAlgorithm(): string;
|
||||
|
||||
abstract protected function getSignaturePartLength(): int;
|
||||
|
||||
private function checkKey(JWK $key): void
|
||||
{
|
||||
if (!in_array($key->get('kty'), $this->allowedKeyTypes(), true)) {
|
||||
throw new InvalidArgumentException('Wrong key type.');
|
||||
}
|
||||
foreach (['x', 'y', 'crv'] as $k) {
|
||||
if (!$key->has($k)) {
|
||||
throw new InvalidArgumentException(sprintf('The key parameter "%s" is missing.', $k));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
<?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\Signature\Algorithm;
|
||||
|
||||
final class ES256 extends ECDSA
|
||||
{
|
||||
public function name(): string
|
||||
{
|
||||
return 'ES256';
|
||||
}
|
||||
|
||||
protected function getHashAlgorithm(): string
|
||||
{
|
||||
return 'sha256';
|
||||
}
|
||||
|
||||
protected function getSignaturePartLength(): int
|
||||
{
|
||||
return 64;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
<?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\Signature\Algorithm;
|
||||
|
||||
final class ES384 extends ECDSA
|
||||
{
|
||||
public function name(): string
|
||||
{
|
||||
return 'ES384';
|
||||
}
|
||||
|
||||
protected function getHashAlgorithm(): string
|
||||
{
|
||||
return 'sha384';
|
||||
}
|
||||
|
||||
protected function getSignaturePartLength(): int
|
||||
{
|
||||
return 96;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
<?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\Signature\Algorithm;
|
||||
|
||||
final class ES512 extends ECDSA
|
||||
{
|
||||
public function name(): string
|
||||
{
|
||||
return 'ES512';
|
||||
}
|
||||
|
||||
protected function getHashAlgorithm(): string
|
||||
{
|
||||
return 'sha512';
|
||||
}
|
||||
|
||||
protected function getSignaturePartLength(): int
|
||||
{
|
||||
return 132;
|
||||
}
|
||||
}
|
@ -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.
|
@ -0,0 +1,15 @@
|
||||
ECDSA Based Signature Algorithms For JWT-Framework
|
||||
==================================================
|
||||
|
||||
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).
|
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "web-token/jwt-signature-algorithm-ecdsa",
|
||||
"description": "ECDSA Based Signature Algorithms 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\\Signature\\Algorithm\\": ""
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"ext-openssl": "*",
|
||||
"web-token/jwt-signature": "^2.0"
|
||||
}
|
||||
}
|
@ -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.
|
@ -0,0 +1 @@
|
||||
patreon: FlorentMorselli
|
@ -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
|
@ -0,0 +1,107 @@
|
||||
<?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\Signature\Algorithm;
|
||||
|
||||
use Base64Url\Base64Url;
|
||||
use function extension_loaded;
|
||||
use function in_array;
|
||||
use InvalidArgumentException;
|
||||
use Jose\Component\Core\JWK;
|
||||
use RuntimeException;
|
||||
|
||||
final class EdDSA implements SignatureAlgorithm
|
||||
{
|
||||
/**
|
||||
* EdDSA constructor.
|
||||
*
|
||||
* @throws RuntimeException if the extension "sodium" is not available
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
if (!extension_loaded('sodium')) {
|
||||
throw new RuntimeException('The extension "sodium" is not available. Please install it to use this method');
|
||||
}
|
||||
}
|
||||
|
||||
public function allowedKeyTypes(): array
|
||||
{
|
||||
return ['OKP'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the key is not private
|
||||
* @throws InvalidArgumentException if the curve is not supported
|
||||
*/
|
||||
public function sign(JWK $key, string $input): string
|
||||
{
|
||||
$this->checkKey($key);
|
||||
if (!$key->has('d')) {
|
||||
throw new InvalidArgumentException('The EC key is not private');
|
||||
}
|
||||
$x = Base64Url::decode($key->get('x'));
|
||||
$d = Base64Url::decode($key->get('d'));
|
||||
$secret = $d.$x;
|
||||
|
||||
switch ($key->get('crv')) {
|
||||
case 'Ed25519':
|
||||
return sodium_crypto_sign_detached($input, $secret);
|
||||
|
||||
default:
|
||||
throw new InvalidArgumentException('Unsupported curve');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the curve is not supported
|
||||
*/
|
||||
public function verify(JWK $key, string $input, string $signature): bool
|
||||
{
|
||||
$this->checkKey($key);
|
||||
|
||||
$public = Base64Url::decode($key->get('x'));
|
||||
|
||||
switch ($key->get('crv')) {
|
||||
case 'Ed25519':
|
||||
return sodium_crypto_sign_verify_detached($signature, $input, $public);
|
||||
|
||||
default:
|
||||
throw new InvalidArgumentException('Unsupported curve');
|
||||
}
|
||||
}
|
||||
|
||||
public function name(): string
|
||||
{
|
||||
return 'EdDSA';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the key type is not valid
|
||||
* @throws InvalidArgumentException if a mandatory key parameter is missing
|
||||
* @throws InvalidArgumentException if the curve is not suuported
|
||||
*/
|
||||
private function checkKey(JWK $key): void
|
||||
{
|
||||
if (!in_array($key->get('kty'), $this->allowedKeyTypes(), true)) {
|
||||
throw new InvalidArgumentException('Wrong key type.');
|
||||
}
|
||||
foreach (['x', 'crv'] as $k) {
|
||||
if (!$key->has($k)) {
|
||||
throw new InvalidArgumentException(sprintf('The key parameter "%s" is missing.', $k));
|
||||
}
|
||||
}
|
||||
if ('Ed25519' !== $key->get('crv')) {
|
||||
throw new InvalidArgumentException('Unsupported curve.');
|
||||
}
|
||||
}
|
||||
}
|
@ -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.
|
@ -0,0 +1,15 @@
|
||||
EdDSA Based Signature Algorithms For JWT-Framework
|
||||
==================================================
|
||||
|
||||
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).
|
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "web-token/jwt-signature-algorithm-eddsa",
|
||||
"description": "EdDSA Signature Algorithm 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\\Signature\\Algorithm\\": ""
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"ext-sodium": "*",
|
||||
"web-token/jwt-signature": "^2.1"
|
||||
}
|
||||
}
|
@ -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.
|
@ -0,0 +1 @@
|
||||
patreon: FlorentMorselli
|
@ -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
|
@ -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.
|
@ -0,0 +1,27 @@
|
||||
<?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\Signature\Algorithm;
|
||||
|
||||
final class PS256 extends RSAPSS
|
||||
{
|
||||
public function name(): string
|
||||
{
|
||||
return 'PS256';
|
||||
}
|
||||
|
||||
protected function getAlgorithm(): string
|
||||
{
|
||||
return 'sha256';
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?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\Signature\Algorithm;
|
||||
|
||||
final class PS384 extends RSAPSS
|
||||
{
|
||||
public function name(): string
|
||||
{
|
||||
return 'PS384';
|
||||
}
|
||||
|
||||
protected function getAlgorithm(): string
|
||||
{
|
||||
return 'sha384';
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?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\Signature\Algorithm;
|
||||
|
||||
final class PS512 extends RSAPSS
|
||||
{
|
||||
public function name(): string
|
||||
{
|
||||
return 'PS512';
|
||||
}
|
||||
|
||||
protected function getAlgorithm(): string
|
||||
{
|
||||
return 'sha512';
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
RSA Based Signature Algorithms For JWT-Framework
|
||||
================================================
|
||||
|
||||
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).
|
@ -0,0 +1,27 @@
|
||||
<?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\Signature\Algorithm;
|
||||
|
||||
final class RS256 extends RSAPKCS1
|
||||
{
|
||||
public function name(): string
|
||||
{
|
||||
return 'RS256';
|
||||
}
|
||||
|
||||
protected function getAlgorithm(): string
|
||||
{
|
||||
return 'sha256';
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?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\Signature\Algorithm;
|
||||
|
||||
final class RS384 extends RSAPKCS1
|
||||
{
|
||||
public function name(): string
|
||||
{
|
||||
return 'RS384';
|
||||
}
|
||||
|
||||
protected function getAlgorithm(): string
|
||||
{
|
||||
return 'sha384';
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?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\Signature\Algorithm;
|
||||
|
||||
final class RS512 extends RSAPKCS1
|
||||
{
|
||||
public function name(): string
|
||||
{
|
||||
return 'RS512';
|
||||
}
|
||||
|
||||
protected function getAlgorithm(): string
|
||||
{
|
||||
return 'sha512';
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
<?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\Signature\Algorithm;
|
||||
|
||||
use function in_array;
|
||||
use InvalidArgumentException;
|
||||
use Jose\Component\Core\JWK;
|
||||
use Jose\Component\Core\Util\RSAKey;
|
||||
use Jose\Component\Signature\Algorithm\Util\RSA as JoseRSA;
|
||||
|
||||
/**
|
||||
* @deprecated Please use either RSAPSS or RSAPKCS1 depending on the padding mode
|
||||
*/
|
||||
abstract class RSA implements SignatureAlgorithm
|
||||
{
|
||||
public function allowedKeyTypes(): array
|
||||
{
|
||||
return ['RSA'];
|
||||
}
|
||||
|
||||
public function verify(JWK $key, string $input, string $signature): bool
|
||||
{
|
||||
$this->checkKey($key);
|
||||
$pub = RSAKey::createFromJWK($key->toPublic());
|
||||
|
||||
return JoseRSA::verify($pub, $input, $signature, $this->getAlgorithm(), $this->getSignatureMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the key is not private
|
||||
*/
|
||||
public function sign(JWK $key, string $input): string
|
||||
{
|
||||
$this->checkKey($key);
|
||||
if (!$key->has('d')) {
|
||||
throw new InvalidArgumentException('The key is not a private key.');
|
||||
}
|
||||
|
||||
$priv = RSAKey::createFromJWK($key);
|
||||
|
||||
return JoseRSA::sign($priv, $input, $this->getAlgorithm(), $this->getSignatureMethod());
|
||||
}
|
||||
|
||||
abstract protected function getAlgorithm(): string;
|
||||
|
||||
abstract protected function getSignatureMethod(): int;
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the key type is not allowed
|
||||
* @throws InvalidArgumentException if the key is invalid
|
||||
*/
|
||||
private function checkKey(JWK $key): void
|
||||
{
|
||||
if (!in_array($key->get('kty'), $this->allowedKeyTypes(), true)) {
|
||||
throw new InvalidArgumentException('Wrong key type.');
|
||||
}
|
||||
foreach (['n', 'e'] as $k) {
|
||||
if (!$key->has($k)) {
|
||||
throw new InvalidArgumentException(sprintf('The key parameter "%s" is missing.', $k));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
<?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\Signature\Algorithm;
|
||||
|
||||
use function in_array;
|
||||
use InvalidArgumentException;
|
||||
use Jose\Component\Core\JWK;
|
||||
use Jose\Component\Core\Util\RSAKey;
|
||||
use RuntimeException;
|
||||
|
||||
abstract class RSAPKCS1 implements SignatureAlgorithm
|
||||
{
|
||||
public function allowedKeyTypes(): array
|
||||
{
|
||||
return ['RSA'];
|
||||
}
|
||||
|
||||
public function verify(JWK $key, string $input, string $signature): bool
|
||||
{
|
||||
$this->checkKey($key);
|
||||
$pub = RSAKey::createFromJWK($key->toPublic());
|
||||
|
||||
return 1 === openssl_verify($input, $signature, $pub->toPEM(), $this->getAlgorithm());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the key is not private
|
||||
* @throws InvalidArgumentException if the data cannot be signed
|
||||
*/
|
||||
public function sign(JWK $key, string $input): string
|
||||
{
|
||||
$this->checkKey($key);
|
||||
if (!$key->has('d')) {
|
||||
throw new InvalidArgumentException('The key is not a private key.');
|
||||
}
|
||||
|
||||
$priv = RSAKey::createFromJWK($key);
|
||||
|
||||
$result = openssl_sign($input, $signature, $priv->toPEM(), $this->getAlgorithm());
|
||||
if (true !== $result) {
|
||||
throw new RuntimeException('Unable to sign');
|
||||
}
|
||||
|
||||
return $signature;
|
||||
}
|
||||
|
||||
abstract protected function getAlgorithm(): string;
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the key type is not allowed
|
||||
* @throws InvalidArgumentException if the key is not valid
|
||||
*/
|
||||
private function checkKey(JWK $key): void
|
||||
{
|
||||
if (!in_array($key->get('kty'), $this->allowedKeyTypes(), true)) {
|
||||
throw new InvalidArgumentException('Wrong key type.');
|
||||
}
|
||||
foreach (['n', 'e'] as $k) {
|
||||
if (!$key->has($k)) {
|
||||
throw new InvalidArgumentException(sprintf('The key parameter "%s" is missing.', $k));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
<?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\Signature\Algorithm;
|
||||
|
||||
use function in_array;
|
||||
use InvalidArgumentException;
|
||||
use Jose\Component\Core\JWK;
|
||||
use Jose\Component\Core\Util\RSAKey;
|
||||
use Jose\Component\Signature\Algorithm\Util\RSA as JoseRSA;
|
||||
|
||||
abstract class RSAPSS implements SignatureAlgorithm
|
||||
{
|
||||
public function allowedKeyTypes(): array
|
||||
{
|
||||
return ['RSA'];
|
||||
}
|
||||
|
||||
public function verify(JWK $key, string $input, string $signature): bool
|
||||
{
|
||||
$this->checkKey($key);
|
||||
$pub = RSAKey::createFromJWK($key->toPublic());
|
||||
|
||||
return JoseRSA::verify($pub, $input, $signature, $this->getAlgorithm(), JoseRSA::SIGNATURE_PSS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the key is not private
|
||||
*/
|
||||
public function sign(JWK $key, string $input): string
|
||||
{
|
||||
$this->checkKey($key);
|
||||
if (!$key->has('d')) {
|
||||
throw new InvalidArgumentException('The key is not a private key.');
|
||||
}
|
||||
|
||||
$priv = RSAKey::createFromJWK($key);
|
||||
|
||||
return JoseRSA::sign($priv, $input, $this->getAlgorithm(), JoseRSA::SIGNATURE_PSS);
|
||||
}
|
||||
|
||||
abstract protected function getAlgorithm(): string;
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the key type is not allowed
|
||||
* @throws InvalidArgumentException if the key is not valid
|
||||
*/
|
||||
private function checkKey(JWK $key): void
|
||||
{
|
||||
if (!in_array($key->get('kty'), $this->allowedKeyTypes(), true)) {
|
||||
throw new InvalidArgumentException('Wrong key type.');
|
||||
}
|
||||
foreach (['n', 'e'] as $k) {
|
||||
if (!$key->has($k)) {
|
||||
throw new InvalidArgumentException(sprintf('The key parameter "%s" is missing.', $k));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,251 @@
|
||||
<?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\Signature\Algorithm\Util;
|
||||
|
||||
use function chr;
|
||||
use InvalidArgumentException;
|
||||
use Jose\Component\Core\Util\BigInteger;
|
||||
use Jose\Component\Core\Util\Hash;
|
||||
use Jose\Component\Core\Util\RSAKey;
|
||||
use function ord;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class RSA
|
||||
{
|
||||
/**
|
||||
* Probabilistic Signature Scheme.
|
||||
*/
|
||||
public const SIGNATURE_PSS = 1;
|
||||
|
||||
/**
|
||||
* Use the PKCS#1.
|
||||
*/
|
||||
public const SIGNATURE_PKCS1 = 2;
|
||||
|
||||
/**
|
||||
* @throws RuntimeException if the data cannot be signed
|
||||
* @throws InvalidArgumentException if the signature mode is not supported
|
||||
*/
|
||||
public static function sign(RSAKey $key, string $message, string $hash, int $mode): string
|
||||
{
|
||||
switch ($mode) {
|
||||
case self::SIGNATURE_PSS:
|
||||
return self::signWithPSS($key, $message, $hash);
|
||||
|
||||
case self::SIGNATURE_PKCS1:
|
||||
$result = openssl_sign($message, $signature, $key->toPEM(), $hash);
|
||||
if (true !== $result) {
|
||||
throw new RuntimeException('Unable to sign the data');
|
||||
}
|
||||
|
||||
return $signature;
|
||||
|
||||
default:
|
||||
throw new InvalidArgumentException('Unsupported mode.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a signature.
|
||||
*/
|
||||
public static function signWithPSS(RSAKey $key, string $message, string $hash): string
|
||||
{
|
||||
$em = self::encodeEMSAPSS($message, 8 * $key->getModulusLength() - 1, Hash::$hash());
|
||||
$message = BigInteger::createFromBinaryString($em);
|
||||
$signature = RSAKey::exponentiate($key, $message);
|
||||
|
||||
return self::convertIntegerToOctetString($signature, $key->getModulusLength());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a signature.
|
||||
*
|
||||
* @deprecated Please use openssl_sign
|
||||
*/
|
||||
public static function signWithPKCS15(RSAKey $key, string $message, string $hash): string
|
||||
{
|
||||
$em = self::encodeEMSA15($message, $key->getModulusLength(), Hash::$hash());
|
||||
$message = BigInteger::createFromBinaryString($em);
|
||||
$signature = RSAKey::exponentiate($key, $message);
|
||||
|
||||
return self::convertIntegerToOctetString($signature, $key->getModulusLength());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the signature mode is not supported
|
||||
*/
|
||||
public static function verify(RSAKey $key, string $message, string $signature, string $hash, int $mode): bool
|
||||
{
|
||||
switch ($mode) {
|
||||
case self::SIGNATURE_PSS:
|
||||
return self::verifyWithPSS($key, $message, $signature, $hash);
|
||||
|
||||
case self::SIGNATURE_PKCS1:
|
||||
return 1 === openssl_verify($message, $signature, $key->toPEM(), $hash);
|
||||
|
||||
default:
|
||||
throw new InvalidArgumentException('Unsupported mode.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a signature.
|
||||
*
|
||||
* @throws RuntimeException if the signature cannot be verified
|
||||
*/
|
||||
public static function verifyWithPSS(RSAKey $key, string $message, string $signature, string $hash): bool
|
||||
{
|
||||
if (mb_strlen($signature, '8bit') !== $key->getModulusLength()) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
$s2 = BigInteger::createFromBinaryString($signature);
|
||||
$m2 = RSAKey::exponentiate($key, $s2);
|
||||
$em = self::convertIntegerToOctetString($m2, $key->getModulusLength());
|
||||
$modBits = 8 * $key->getModulusLength();
|
||||
|
||||
return self::verifyEMSAPSS($message, $em, $modBits - 1, Hash::$hash());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a signature.
|
||||
*
|
||||
* @deprecated Please use openssl_sign
|
||||
*
|
||||
* @throws RuntimeException if the signature cannot be verified
|
||||
*/
|
||||
public static function verifyWithPKCS15(RSAKey $key, string $message, string $signature, string $hash): bool
|
||||
{
|
||||
if (mb_strlen($signature, '8bit') !== $key->getModulusLength()) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
$signature = BigInteger::createFromBinaryString($signature);
|
||||
$m2 = RSAKey::exponentiate($key, $signature);
|
||||
$em = self::convertIntegerToOctetString($m2, $key->getModulusLength());
|
||||
|
||||
return hash_equals($em, self::encodeEMSA15($message, $key->getModulusLength(), Hash::$hash()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RuntimeException if the value cannot be converted
|
||||
*/
|
||||
private static function convertIntegerToOctetString(BigInteger $x, int $xLen): string
|
||||
{
|
||||
$x = $x->toBytes();
|
||||
if (mb_strlen($x, '8bit') > $xLen) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
return str_pad($x, $xLen, chr(0), STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* MGF1.
|
||||
*/
|
||||
private static function getMGF1(string $mgfSeed, int $maskLen, Hash $mgfHash): string
|
||||
{
|
||||
$t = '';
|
||||
$count = ceil($maskLen / $mgfHash->getLength());
|
||||
for ($i = 0; $i < $count; ++$i) {
|
||||
$c = pack('N', $i);
|
||||
$t .= $mgfHash->hash($mgfSeed.$c);
|
||||
}
|
||||
|
||||
return mb_substr($t, 0, $maskLen, '8bit');
|
||||
}
|
||||
|
||||
/**
|
||||
* EMSA-PSS-ENCODE.
|
||||
*
|
||||
* @throws RuntimeException if the message length is invalid
|
||||
*/
|
||||
private static function encodeEMSAPSS(string $message, int $modulusLength, Hash $hash): string
|
||||
{
|
||||
$emLen = ($modulusLength + 1) >> 3;
|
||||
$sLen = $hash->getLength();
|
||||
$mHash = $hash->hash($message);
|
||||
if ($emLen <= $hash->getLength() + $sLen + 2) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
$salt = random_bytes($sLen);
|
||||
$m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
|
||||
$h = $hash->hash($m2);
|
||||
$ps = str_repeat(chr(0), $emLen - $sLen - $hash->getLength() - 2);
|
||||
$db = $ps.chr(1).$salt;
|
||||
$dbMask = self::getMGF1($h, $emLen - $hash->getLength() - 1, $hash);
|
||||
$maskedDB = $db ^ $dbMask;
|
||||
$maskedDB[0] = ~chr(0xFF << ($modulusLength & 7)) & $maskedDB[0];
|
||||
$em = $maskedDB.$h.chr(0xBC);
|
||||
|
||||
return $em;
|
||||
}
|
||||
|
||||
/**
|
||||
* EMSA-PSS-VERIFY.
|
||||
*
|
||||
* @throws InvalidArgumentException if the signature cannot be verified
|
||||
*/
|
||||
private static function verifyEMSAPSS(string $m, string $em, int $emBits, Hash $hash): bool
|
||||
{
|
||||
$emLen = ($emBits + 1) >> 3;
|
||||
$sLen = $hash->getLength();
|
||||
$mHash = $hash->hash($m);
|
||||
if ($emLen < $hash->getLength() + $sLen + 2) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
if ($em[mb_strlen($em, '8bit') - 1] !== chr(0xBC)) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
$maskedDB = mb_substr($em, 0, -$hash->getLength() - 1, '8bit');
|
||||
$h = mb_substr($em, -$hash->getLength() - 1, $hash->getLength(), '8bit');
|
||||
$temp = chr(0xFF << ($emBits & 7));
|
||||
if ((~$maskedDB[0] & $temp) !== $temp) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
$dbMask = self::getMGF1($h, $emLen - $hash->getLength() - 1, $hash/*MGF*/);
|
||||
$db = $maskedDB ^ $dbMask;
|
||||
$db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0];
|
||||
$temp = $emLen - $hash->getLength() - $sLen - 2;
|
||||
if (mb_substr($db, 0, $temp, '8bit') !== str_repeat(chr(0), $temp)) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
if (1 !== ord($db[$temp])) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
$salt = mb_substr($db, $temp + 1, null, '8bit'); // should be $sLen long
|
||||
$m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
|
||||
$h2 = $hash->hash($m2);
|
||||
|
||||
return hash_equals($h, $h2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RuntimeException if the value cannot be encoded
|
||||
*/
|
||||
private static function encodeEMSA15(string $m, int $emBits, Hash $hash): string
|
||||
{
|
||||
$h = $hash->hash($m);
|
||||
$t = $hash->t();
|
||||
$t .= $h;
|
||||
$tLen = mb_strlen($t, '8bit');
|
||||
if ($emBits < $tLen + 11) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
$ps = str_repeat(chr(0xFF), $emBits - $tLen - 3);
|
||||
|
||||
return "\0\1{$ps}\0{$t}";
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "web-token/jwt-signature-algorithm-rsa",
|
||||
"description": "RSA Based Signature Algorithms 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\\Signature\\Algorithm\\": ""
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"brick/math": "^0.8.17|^0.9",
|
||||
"ext-openssl": "*",
|
||||
"web-token/jwt-signature": "^2.1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-gmp": "GMP or BCMath is highly recommended to improve the library performance",
|
||||
"ext-bcmath": "GMP or BCMath is highly recommended to improve the library performance"
|
||||
}
|
||||
}
|
@ -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.
|
@ -0,0 +1 @@
|
||||
patreon: FlorentMorselli
|
@ -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
|
@ -0,0 +1,37 @@
|
||||
<?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\Signature\Algorithm;
|
||||
|
||||
use Jose\Component\Core\Algorithm;
|
||||
use Jose\Component\Core\JWK;
|
||||
|
||||
interface MacAlgorithm extends Algorithm
|
||||
{
|
||||
/**
|
||||
* Sign the input.
|
||||
*
|
||||
* @param JWK $key The private key used to hash the data
|
||||
* @param string $input The input
|
||||
*/
|
||||
public function hash(JWK $key, string $input): string;
|
||||
|
||||
/**
|
||||
* Verify the signature of data.
|
||||
*
|
||||
* @param JWK $key The private key used to hash the data
|
||||
* @param string $input The input
|
||||
* @param string $signature The signature to verify
|
||||
*/
|
||||
public function verify(JWK $key, string $input, string $signature): bool;
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?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\Signature\Algorithm;
|
||||
|
||||
use Jose\Component\Core\Algorithm;
|
||||
use Jose\Component\Core\JWK;
|
||||
|
||||
interface SignatureAlgorithm extends Algorithm
|
||||
{
|
||||
/**
|
||||
* Sign the input.
|
||||
*
|
||||
* @param JWK $key The private key used to sign the data
|
||||
* @param string $input The input
|
||||
*/
|
||||
public function sign(JWK $key, string $input): string;
|
||||
|
||||
/**
|
||||
* Verify the signature of data.
|
||||
*
|
||||
* @param JWK $key The private key used to sign the data
|
||||
* @param string $input The input
|
||||
* @param string $signature The signature to verify
|
||||
*/
|
||||
public function verify(JWK $key, string $input, string $signature): bool;
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
<?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\Signature;
|
||||
|
||||
use function count;
|
||||
use InvalidArgumentException;
|
||||
use Jose\Component\Core\JWT;
|
||||
|
||||
class JWS implements JWT
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isPayloadDetached = false;
|
||||
|
||||
/**
|
||||
* @var null|string
|
||||
*/
|
||||
private $encodedPayload;
|
||||
|
||||
/**
|
||||
* @var Signature[]
|
||||
*/
|
||||
private $signatures = [];
|
||||
|
||||
/**
|
||||
* @var null|string
|
||||
*/
|
||||
private $payload;
|
||||
|
||||
public function __construct(?string $payload, ?string $encodedPayload = null, bool $isPayloadDetached = false)
|
||||
{
|
||||
$this->payload = $payload;
|
||||
$this->encodedPayload = $encodedPayload;
|
||||
$this->isPayloadDetached = $isPayloadDetached;
|
||||
}
|
||||
|
||||
public function getPayload(): ?string
|
||||
{
|
||||
return $this->payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the payload is detached.
|
||||
*/
|
||||
public function isPayloadDetached(): bool
|
||||
{
|
||||
return $this->isPayloadDetached;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Base64Url encoded payload.
|
||||
* If the payload is detached, this method returns null.
|
||||
*/
|
||||
public function getEncodedPayload(): ?string
|
||||
{
|
||||
if (true === $this->isPayloadDetached()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->encodedPayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the signatures associated with the JWS.
|
||||
*
|
||||
* @return Signature[]
|
||||
*/
|
||||
public function getSignatures(): array
|
||||
{
|
||||
return $this->signatures;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the signature at the given index.
|
||||
*
|
||||
* @throws InvalidArgumentException if the signature index does not exist
|
||||
*/
|
||||
public function getSignature(int $id): Signature
|
||||
{
|
||||
if (isset($this->signatures[$id])) {
|
||||
return $this->signatures[$id];
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('The signature does not exist.');
|
||||
}
|
||||
|
||||
/**
|
||||
* This method adds a signature to the JWS object.
|
||||
* Its returns a new JWS object.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return JWS
|
||||
*/
|
||||
public function addSignature(string $signature, array $protectedHeader, ?string $encodedProtectedHeader, array $header = []): self
|
||||
{
|
||||
$jws = clone $this;
|
||||
$jws->signatures[] = new Signature($signature, $protectedHeader, $encodedProtectedHeader, $header);
|
||||
|
||||
return $jws;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of signature associated with the JWS.
|
||||
*/
|
||||
public function countSignatures(): int
|
||||
{
|
||||
return count($this->signatures);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method splits the JWS into a list of JWSs.
|
||||
* It is only useful when the JWS contains more than one signature (JSON General Serialization).
|
||||
*
|
||||
* @return JWS[]
|
||||
*/
|
||||
public function split(): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($this->signatures as $signature) {
|
||||
$jws = new self(
|
||||
$this->payload,
|
||||
$this->encodedPayload,
|
||||
$this->isPayloadDetached
|
||||
);
|
||||
$jws = $jws->addSignature(
|
||||
$signature->getSignature(),
|
||||
$signature->getProtectedHeader(),
|
||||
$signature->getEncodedProtectedHeader(),
|
||||
$signature->getHeader()
|
||||
);
|
||||
|
||||
$result[] = $jws;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
@ -0,0 +1,235 @@
|
||||
<?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\Signature;
|
||||
|
||||
use function array_key_exists;
|
||||
use Base64Url\Base64Url;
|
||||
use function count;
|
||||
use function in_array;
|
||||
use InvalidArgumentException;
|
||||
use function is_array;
|
||||
use Jose\Component\Core\Algorithm;
|
||||
use Jose\Component\Core\AlgorithmManager;
|
||||
use Jose\Component\Core\JWK;
|
||||
use Jose\Component\Core\Util\JsonConverter;
|
||||
use Jose\Component\Core\Util\KeyChecker;
|
||||
use Jose\Component\Signature\Algorithm\MacAlgorithm;
|
||||
use Jose\Component\Signature\Algorithm\SignatureAlgorithm;
|
||||
use LogicException;
|
||||
use RuntimeException;
|
||||
|
||||
class JWSBuilder
|
||||
{
|
||||
/**
|
||||
* @var null|string
|
||||
*/
|
||||
protected $payload;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $isPayloadDetached;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $signatures = [];
|
||||
|
||||
/**
|
||||
* @var null|bool
|
||||
*/
|
||||
protected $isPayloadEncoded;
|
||||
|
||||
/**
|
||||
* @var AlgorithmManager
|
||||
*/
|
||||
private $signatureAlgorithmManager;
|
||||
|
||||
public function __construct(AlgorithmManager $signatureAlgorithmManager)
|
||||
{
|
||||
$this->signatureAlgorithmManager = $signatureAlgorithmManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the algorithm manager associated to the builder.
|
||||
*/
|
||||
public function getSignatureAlgorithmManager(): AlgorithmManager
|
||||
{
|
||||
return $this->signatureAlgorithmManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the current data.
|
||||
*
|
||||
* @return JWSBuilder
|
||||
*/
|
||||
public function create(): self
|
||||
{
|
||||
$this->payload = null;
|
||||
$this->isPayloadDetached = false;
|
||||
$this->signatures = [];
|
||||
$this->isPayloadEncoded = null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the payload.
|
||||
* This method will return a new JWSBuilder object.
|
||||
*
|
||||
* @throws InvalidArgumentException if the payload is not UTF-8 encoded
|
||||
*
|
||||
* @return JWSBuilder
|
||||
*/
|
||||
public function withPayload(string $payload, bool $isPayloadDetached = false): self
|
||||
{
|
||||
if (false === mb_detect_encoding($payload, 'UTF-8', true)) {
|
||||
throw new InvalidArgumentException('The payload must be encoded in UTF-8');
|
||||
}
|
||||
$clone = clone $this;
|
||||
$clone->payload = $payload;
|
||||
$clone->isPayloadDetached = $isPayloadDetached;
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the information needed to compute the signature.
|
||||
* This method will return a new JWSBuilder object.
|
||||
*
|
||||
* @throws InvalidArgumentException if the payload encoding is inconsistent
|
||||
*
|
||||
* @return JWSBuilder
|
||||
*/
|
||||
public function addSignature(JWK $signatureKey, array $protectedHeader, array $header = []): self
|
||||
{
|
||||
$this->checkB64AndCriticalHeader($protectedHeader);
|
||||
$isPayloadEncoded = $this->checkIfPayloadIsEncoded($protectedHeader);
|
||||
if (null === $this->isPayloadEncoded) {
|
||||
$this->isPayloadEncoded = $isPayloadEncoded;
|
||||
} elseif ($this->isPayloadEncoded !== $isPayloadEncoded) {
|
||||
throw new InvalidArgumentException('Foreign payload encoding detected.');
|
||||
}
|
||||
$this->checkDuplicatedHeaderParameters($protectedHeader, $header);
|
||||
KeyChecker::checkKeyUsage($signatureKey, 'signature');
|
||||
$algorithm = $this->findSignatureAlgorithm($signatureKey, $protectedHeader, $header);
|
||||
KeyChecker::checkKeyAlgorithm($signatureKey, $algorithm->name());
|
||||
$clone = clone $this;
|
||||
$clone->signatures[] = [
|
||||
'signature_algorithm' => $algorithm,
|
||||
'signature_key' => $signatureKey,
|
||||
'protected_header' => $protectedHeader,
|
||||
'header' => $header,
|
||||
];
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes all signatures and return the expected JWS object.
|
||||
*
|
||||
* @throws RuntimeException if the payload is not set
|
||||
* @throws RuntimeException if no signature is defined
|
||||
*/
|
||||
public function build(): JWS
|
||||
{
|
||||
if (null === $this->payload) {
|
||||
throw new RuntimeException('The payload is not set.');
|
||||
}
|
||||
if (0 === count($this->signatures)) {
|
||||
throw new RuntimeException('At least one signature must be set.');
|
||||
}
|
||||
|
||||
$encodedPayload = false === $this->isPayloadEncoded ? $this->payload : Base64Url::encode($this->payload);
|
||||
$jws = new JWS($this->payload, $encodedPayload, $this->isPayloadDetached);
|
||||
foreach ($this->signatures as $signature) {
|
||||
/** @var MacAlgorithm|SignatureAlgorithm $algorithm */
|
||||
$algorithm = $signature['signature_algorithm'];
|
||||
/** @var JWK $signatureKey */
|
||||
$signatureKey = $signature['signature_key'];
|
||||
/** @var array $protectedHeader */
|
||||
$protectedHeader = $signature['protected_header'];
|
||||
/** @var array $header */
|
||||
$header = $signature['header'];
|
||||
$encodedProtectedHeader = 0 === count($protectedHeader) ? null : Base64Url::encode(JsonConverter::encode($protectedHeader));
|
||||
$input = sprintf('%s.%s', $encodedProtectedHeader, $encodedPayload);
|
||||
if ($algorithm instanceof SignatureAlgorithm) {
|
||||
$s = $algorithm->sign($signatureKey, $input);
|
||||
} else {
|
||||
$s = $algorithm->hash($signatureKey, $input);
|
||||
}
|
||||
$jws = $jws->addSignature($s, $protectedHeader, $encodedProtectedHeader, $header);
|
||||
}
|
||||
|
||||
return $jws;
|
||||
}
|
||||
|
||||
private function checkIfPayloadIsEncoded(array $protectedHeader): bool
|
||||
{
|
||||
return !array_key_exists('b64', $protectedHeader) || true === $protectedHeader['b64'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws LogicException if the header parameter "crit" is missing, invalid or does not contain "b64" when "b64" is set
|
||||
*/
|
||||
private function checkB64AndCriticalHeader(array $protectedHeader): void
|
||||
{
|
||||
if (!array_key_exists('b64', $protectedHeader)) {
|
||||
return;
|
||||
}
|
||||
if (!array_key_exists('crit', $protectedHeader)) {
|
||||
throw new LogicException('The protected header parameter "crit" is mandatory when protected header parameter "b64" is set.');
|
||||
}
|
||||
if (!is_array($protectedHeader['crit'])) {
|
||||
throw new LogicException('The protected header parameter "crit" must be an array.');
|
||||
}
|
||||
if (!in_array('b64', $protectedHeader['crit'], true)) {
|
||||
throw new LogicException('The protected header parameter "crit" must contain "b64" when protected header parameter "b64" is set.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the header parameter "alg" is missing or the algorithm is not allowed/not supported
|
||||
*
|
||||
* @return MacAlgorithm|SignatureAlgorithm
|
||||
*/
|
||||
private function findSignatureAlgorithm(JWK $key, array $protectedHeader, array $header): Algorithm
|
||||
{
|
||||
$completeHeader = array_merge($header, $protectedHeader);
|
||||
if (!array_key_exists('alg', $completeHeader)) {
|
||||
throw new InvalidArgumentException('No "alg" parameter set in the header.');
|
||||
}
|
||||
if ($key->has('alg') && $key->get('alg') !== $completeHeader['alg']) {
|
||||
throw new InvalidArgumentException(sprintf('The algorithm "%s" is not allowed with this key.', $completeHeader['alg']));
|
||||
}
|
||||
|
||||
$algorithm = $this->signatureAlgorithmManager->get($completeHeader['alg']);
|
||||
if (!$algorithm instanceof SignatureAlgorithm && !$algorithm instanceof MacAlgorithm) {
|
||||
throw new InvalidArgumentException(sprintf('The algorithm "%s" is not supported.', $completeHeader['alg']));
|
||||
}
|
||||
|
||||
return $algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the header contains duplicated entries
|
||||
*/
|
||||
private function checkDuplicatedHeaderParameters(array $header1, array $header2): void
|
||||
{
|
||||
$inter = array_intersect_key($header1, $header2);
|
||||
if (0 !== count($inter)) {
|
||||
throw new InvalidArgumentException(sprintf('The header contains duplicated entries: %s.', implode(', ', array_keys($inter))));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
<?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\Signature;
|
||||
|
||||
use Jose\Component\Core\AlgorithmManagerFactory;
|
||||
|
||||
class JWSBuilderFactory
|
||||
{
|
||||
/**
|
||||
* @var AlgorithmManagerFactory
|
||||
*/
|
||||
private $signatureAlgorithmManagerFactory;
|
||||
|
||||
public function __construct(AlgorithmManagerFactory $signatureAlgorithmManagerFactory)
|
||||
{
|
||||
$this->signatureAlgorithmManagerFactory = $signatureAlgorithmManagerFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates a JWSBuilder using the given algorithm aliases.
|
||||
*
|
||||
* @param string[] $algorithms
|
||||
*/
|
||||
public function create(array $algorithms): JWSBuilder
|
||||
{
|
||||
$algorithmManager = $this->signatureAlgorithmManagerFactory->create($algorithms);
|
||||
|
||||
return new JWSBuilder($algorithmManager);
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
<?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\Signature;
|
||||
|
||||
use Exception;
|
||||
use Jose\Component\Checker\HeaderCheckerManager;
|
||||
use Jose\Component\Core\JWK;
|
||||
use Jose\Component\Core\JWKSet;
|
||||
use Jose\Component\Signature\Serializer\JWSSerializerManager;
|
||||
use Throwable;
|
||||
|
||||
class JWSLoader
|
||||
{
|
||||
/**
|
||||
* @var JWSVerifier
|
||||
*/
|
||||
private $jwsVerifier;
|
||||
|
||||
/**
|
||||
* @var null|HeaderCheckerManager
|
||||
*/
|
||||
private $headerCheckerManager;
|
||||
|
||||
/**
|
||||
* @var JWSSerializerManager
|
||||
*/
|
||||
private $serializerManager;
|
||||
|
||||
/**
|
||||
* JWSLoader constructor.
|
||||
*/
|
||||
public function __construct(JWSSerializerManager $serializerManager, JWSVerifier $jwsVerifier, ?HeaderCheckerManager $headerCheckerManager)
|
||||
{
|
||||
$this->serializerManager = $serializerManager;
|
||||
$this->jwsVerifier = $jwsVerifier;
|
||||
$this->headerCheckerManager = $headerCheckerManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JWSVerifier associated to the JWSLoader.
|
||||
*/
|
||||
public function getJwsVerifier(): JWSVerifier
|
||||
{
|
||||
return $this->jwsVerifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Header Checker Manager associated to the JWSLoader.
|
||||
*/
|
||||
public function getHeaderCheckerManager(): ?HeaderCheckerManager
|
||||
{
|
||||
return $this->headerCheckerManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JWSSerializer associated to the JWSLoader.
|
||||
*/
|
||||
public function getSerializerManager(): JWSSerializerManager
|
||||
{
|
||||
return $this->serializerManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will try to load and verify the token using the given key.
|
||||
* It returns a JWS and will populate the $signature variable in case of success, otherwise an exception is thrown.
|
||||
*
|
||||
* @throws Exception if the token cannot be loaded or verified
|
||||
*/
|
||||
public function loadAndVerifyWithKey(string $token, JWK $key, ?int &$signature, ?string $payload = null): JWS
|
||||
{
|
||||
$keyset = new JWKSet([$key]);
|
||||
|
||||
return $this->loadAndVerifyWithKeySet($token, $keyset, $signature, $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will try to load and verify the token using the given key set.
|
||||
* It returns a JWS and will populate the $signature variable in case of success, otherwise an exception is thrown.
|
||||
*
|
||||
* @throws Exception if the token cannot be loaded or verified
|
||||
*/
|
||||
public function loadAndVerifyWithKeySet(string $token, JWKSet $keyset, ?int &$signature, ?string $payload = null): JWS
|
||||
{
|
||||
try {
|
||||
$jws = $this->serializerManager->unserialize($token);
|
||||
$nbSignatures = $jws->countSignatures();
|
||||
for ($i = 0; $i < $nbSignatures; ++$i) {
|
||||
if ($this->processSignature($jws, $keyset, $i, $payload)) {
|
||||
$signature = $i;
|
||||
|
||||
return $jws;
|
||||
}
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// Nothing to do. Exception thrown just after
|
||||
}
|
||||
|
||||
throw new Exception('Unable to load and verify the token.');
|
||||
}
|
||||
|
||||
private function processSignature(JWS $jws, JWKSet $keyset, int $signature, ?string $payload): bool
|
||||
{
|
||||
try {
|
||||
if (null !== $this->headerCheckerManager) {
|
||||
$this->headerCheckerManager->check($jws, $signature);
|
||||
}
|
||||
|
||||
return $this->jwsVerifier->verifyWithKeySet($jws, $keyset, $signature, $payload);
|
||||
} catch (Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
<?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\Signature;
|
||||
|
||||
use Jose\Component\Checker\HeaderCheckerManagerFactory;
|
||||
use Jose\Component\Signature\Serializer\JWSSerializerManagerFactory;
|
||||
|
||||
class JWSLoaderFactory
|
||||
{
|
||||
/**
|
||||
* @var JWSVerifierFactory
|
||||
*/
|
||||
private $jwsVerifierFactory;
|
||||
|
||||
/**
|
||||
* @var JWSSerializerManagerFactory
|
||||
*/
|
||||
private $jwsSerializerManagerFactory;
|
||||
|
||||
/**
|
||||
* @var null|HeaderCheckerManagerFactory
|
||||
*/
|
||||
private $headerCheckerManagerFactory;
|
||||
|
||||
public function __construct(JWSSerializerManagerFactory $jwsSerializerManagerFactory, JWSVerifierFactory $jwsVerifierFactory, ?HeaderCheckerManagerFactory $headerCheckerManagerFactory)
|
||||
{
|
||||
$this->jwsSerializerManagerFactory = $jwsSerializerManagerFactory;
|
||||
$this->jwsVerifierFactory = $jwsVerifierFactory;
|
||||
$this->headerCheckerManagerFactory = $headerCheckerManagerFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JWSLoader using the given serializer aliases, signature algorithm aliases and (optionally)
|
||||
* the header checker aliases.
|
||||
*/
|
||||
public function create(array $serializers, array $algorithms, array $headerCheckers = []): JWSLoader
|
||||
{
|
||||
$serializerManager = $this->jwsSerializerManagerFactory->create($serializers);
|
||||
$jwsVerifier = $this->jwsVerifierFactory->create($algorithms);
|
||||
if (null !== $this->headerCheckerManagerFactory) {
|
||||
$headerCheckerManager = $this->headerCheckerManagerFactory->create($headerCheckers);
|
||||
} else {
|
||||
$headerCheckerManager = null;
|
||||
}
|
||||
|
||||
return new JWSLoader($serializerManager, $jwsVerifier, $headerCheckerManager);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
<?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\Signature;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Jose\Component\Checker\TokenTypeSupport;
|
||||
use Jose\Component\Core\JWT;
|
||||
|
||||
final class JWSTokenSupport implements TokenTypeSupport
|
||||
{
|
||||
public function supports(JWT $jwt): bool
|
||||
{
|
||||
return $jwt instanceof JWS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the signature index does not exist
|
||||
*/
|
||||
public function retrieveTokenHeaders(JWT $jwt, int $index, array &$protectedHeader, array &$unprotectedHeader): void
|
||||
{
|
||||
if (!$jwt instanceof JWS) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($index > $jwt->countSignatures()) {
|
||||
throw new InvalidArgumentException('Unknown signature index.');
|
||||
}
|
||||
$protectedHeader = $jwt->getSignature($index)->getProtectedHeader();
|
||||
$unprotectedHeader = $jwt->getSignature($index)->getHeader();
|
||||
}
|
||||
}
|
@ -0,0 +1,170 @@
|
||||
<?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\Signature;
|
||||
|
||||
use Base64Url\Base64Url;
|
||||
use InvalidArgumentException;
|
||||
use Jose\Component\Core\Algorithm;
|
||||
use Jose\Component\Core\AlgorithmManager;
|
||||
use Jose\Component\Core\JWK;
|
||||
use Jose\Component\Core\JWKSet;
|
||||
use Jose\Component\Core\Util\KeyChecker;
|
||||
use Jose\Component\Signature\Algorithm\MacAlgorithm;
|
||||
use Jose\Component\Signature\Algorithm\SignatureAlgorithm;
|
||||
use Throwable;
|
||||
|
||||
class JWSVerifier
|
||||
{
|
||||
/**
|
||||
* @var AlgorithmManager
|
||||
*/
|
||||
private $signatureAlgorithmManager;
|
||||
|
||||
/**
|
||||
* JWSVerifier constructor.
|
||||
*/
|
||||
public function __construct(AlgorithmManager $signatureAlgorithmManager)
|
||||
{
|
||||
$this->signatureAlgorithmManager = $signatureAlgorithmManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the algorithm manager associated to the JWSVerifier.
|
||||
*/
|
||||
public function getSignatureAlgorithmManager(): AlgorithmManager
|
||||
{
|
||||
return $this->signatureAlgorithmManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will try to verify the JWS object using the given key and for the given signature.
|
||||
* It returns true if the signature is verified, otherwise false.
|
||||
*
|
||||
* @return bool true if the verification of the signature succeeded, else false
|
||||
*/
|
||||
public function verifyWithKey(JWS $jws, JWK $jwk, int $signature, ?string $detachedPayload = null): bool
|
||||
{
|
||||
$jwkset = new JWKSet([$jwk]);
|
||||
|
||||
return $this->verifyWithKeySet($jws, $jwkset, $signature, $detachedPayload);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will try to verify the JWS object using the given key set and for the given signature.
|
||||
* It returns true if the signature is verified, otherwise false.
|
||||
*
|
||||
* @param JWS $jws A JWS object
|
||||
* @param JWKSet $jwkset The signature will be verified using keys in the key set
|
||||
* @param JWK $jwk The key used to verify the signature in case of success
|
||||
* @param null|string $detachedPayload If not null, the value must be the detached payload encoded in Base64 URL safe. If the input contains a payload, throws an exception.
|
||||
*
|
||||
* @throws InvalidArgumentException if there is no key in the keyset
|
||||
* @throws InvalidArgumentException if the token does not contain any signature
|
||||
*
|
||||
* @return bool true if the verification of the signature succeeded, else false
|
||||
*/
|
||||
public function verifyWithKeySet(JWS $jws, JWKSet $jwkset, int $signatureIndex, ?string $detachedPayload = null, JWK &$jwk = null): bool
|
||||
{
|
||||
if (0 === $jwkset->count()) {
|
||||
throw new InvalidArgumentException('There is no key in the key set.');
|
||||
}
|
||||
if (0 === $jws->countSignatures()) {
|
||||
throw new InvalidArgumentException('The JWS does not contain any signature.');
|
||||
}
|
||||
$this->checkPayload($jws, $detachedPayload);
|
||||
$signature = $jws->getSignature($signatureIndex);
|
||||
|
||||
return $this->verifySignature($jws, $jwkset, $signature, $detachedPayload, $jwk);
|
||||
}
|
||||
|
||||
private function verifySignature(JWS $jws, JWKSet $jwkset, Signature $signature, ?string $detachedPayload = null, JWK &$successJwk = null): bool
|
||||
{
|
||||
$input = $this->getInputToVerify($jws, $signature, $detachedPayload);
|
||||
$algorithm = $this->getAlgorithm($signature);
|
||||
foreach ($jwkset->all() as $jwk) {
|
||||
try {
|
||||
KeyChecker::checkKeyUsage($jwk, 'verification');
|
||||
KeyChecker::checkKeyAlgorithm($jwk, $algorithm->name());
|
||||
if (true === $algorithm->verify($jwk, $input, $signature->getSignature())) {
|
||||
$successJwk = $jwk;
|
||||
|
||||
return true;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
//We do nothing, we continue with other keys
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getInputToVerify(JWS $jws, Signature $signature, ?string $detachedPayload): string
|
||||
{
|
||||
$isPayloadEmpty = $this->isPayloadEmpty($jws->getPayload());
|
||||
$encodedProtectedHeader = $signature->getEncodedProtectedHeader();
|
||||
if (!$signature->hasProtectedHeaderParameter('b64') || true === $signature->getProtectedHeaderParameter('b64')) {
|
||||
if (null !== $jws->getEncodedPayload()) {
|
||||
return sprintf('%s.%s', $encodedProtectedHeader, $jws->getEncodedPayload());
|
||||
}
|
||||
|
||||
$payload = $isPayloadEmpty ? $detachedPayload : $jws->getPayload();
|
||||
|
||||
return sprintf('%s.%s', $encodedProtectedHeader, Base64Url::encode($payload));
|
||||
}
|
||||
|
||||
$payload = $isPayloadEmpty ? $detachedPayload : $jws->getPayload();
|
||||
|
||||
return sprintf('%s.%s', $encodedProtectedHeader, $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the payload is set when a detached payload is provided or no payload is defined
|
||||
*/
|
||||
private function checkPayload(JWS $jws, ?string $detachedPayload = null): void
|
||||
{
|
||||
$isPayloadEmpty = $this->isPayloadEmpty($jws->getPayload());
|
||||
if (null !== $detachedPayload && !$isPayloadEmpty) {
|
||||
throw new InvalidArgumentException('A detached payload is set, but the JWS already has a payload.');
|
||||
}
|
||||
if ($isPayloadEmpty && null === $detachedPayload) {
|
||||
throw new InvalidArgumentException('The JWS has a detached payload, but no payload is provided.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException if the header parameter "alg" is missing or invalid
|
||||
*
|
||||
* @return MacAlgorithm|SignatureAlgorithm
|
||||
*/
|
||||
private function getAlgorithm(Signature $signature): Algorithm
|
||||
{
|
||||
$completeHeader = array_merge($signature->getProtectedHeader(), $signature->getHeader());
|
||||
if (!isset($completeHeader['alg'])) {
|
||||
throw new InvalidArgumentException('No "alg" parameter set in the header.');
|
||||
}
|
||||
|
||||
$algorithm = $this->signatureAlgorithmManager->get($completeHeader['alg']);
|
||||
if (!$algorithm instanceof SignatureAlgorithm && !$algorithm instanceof MacAlgorithm) {
|
||||
throw new InvalidArgumentException(sprintf('The algorithm "%s" is not supported or is not a signature or MAC algorithm.', $completeHeader['alg']));
|
||||
}
|
||||
|
||||
return $algorithm;
|
||||
}
|
||||
|
||||
private function isPayloadEmpty(?string $payload): bool
|
||||
{
|
||||
return null === $payload || '' === $payload;
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
<?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\Signature;
|
||||
|
||||
use Jose\Component\Core\AlgorithmManagerFactory;
|
||||
|
||||
class JWSVerifierFactory
|
||||
{
|
||||
/**
|
||||
* @var AlgorithmManagerFactory
|
||||
*/
|
||||
private $algorithmManagerFactory;
|
||||
|
||||
public function __construct(AlgorithmManagerFactory $algorithmManagerFactory)
|
||||
{
|
||||
$this->algorithmManagerFactory = $algorithmManagerFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JWSVerifier using the given signature algorithm aliases.
|
||||
*
|
||||
* @param string[] $algorithms
|
||||
*/
|
||||
public function create(array $algorithms): JWSVerifier
|
||||
{
|
||||
$algorithmManager = $this->algorithmManagerFactory->create($algorithms);
|
||||
|
||||
return new JWSVerifier($algorithmManager);
|
||||
}
|
||||
}
|
@ -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.
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user