diff options
Diffstat (limited to 'vendor/web-token/jwt-core/JWKSet.php')
-rw-r--r-- | vendor/web-token/jwt-core/JWKSet.php | 321 |
1 files changed, 321 insertions, 0 deletions
diff --git a/vendor/web-token/jwt-core/JWKSet.php b/vendor/web-token/jwt-core/JWKSet.php new file mode 100644 index 0000000..efcd417 --- /dev/null +++ b/vendor/web-token/jwt-core/JWKSet.php @@ -0,0 +1,321 @@ +<?php + +declare(strict_types=1); + +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2018 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; + +class JWKSet implements \Countable, \IteratorAggregate, \JsonSerializable +{ + /** + * @var array + */ + private $keys = []; + + /** + * JWKSet constructor. + * + * @param JWK[] $keys + */ + 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. + * + * @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 JWK objects. + * + * @deprecated Will be removed in v2.0. Please use constructor instead. + * + * @param JWK[] $keys + * + * @return JWKSet + */ + public static function createFromKeys(array $keys): self + { + return new self($keys); + } + + /** + * Creates a JWKSet object using the given Json string. + * + * @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 + */ + 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 Algorithm|null $algorithm Specifies the algorithm to be used + * @param array $restrictions More restrictions such as 'kid' or 'kty' + */ + 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 (empty($result)) { + return null; + } + + \usort($result, [$this, 'sortKeys']); + + return $result[0]['key']; + } + + /** + * @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')) { + return $type === self::convertKeyOpsToKeyUse($key->get('use')) ? 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; + } + + private static function convertKeyOpsToKeyUse(string $key_ops): string + { + switch ($key_ops) { + case 'verify': + case 'sign': + return 'sig'; + case 'encrypt': + case 'decrypt': + case 'wrapKey': + case 'unwrapKey': + return 'enc'; + default: + throw new InvalidArgumentException(\sprintf('Unsupported key operation value "%s"', $key_ops)); + } + } + + /** + * 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() + { + return new \ArrayIterator($this->keys); + } +} |