updated plugin WP-WebAuthn version 1.3.1

This commit is contained in:
2023-10-22 22:21:36 +00:00
committed by Gitium
parent 959829cf69
commit c7746517a0
931 changed files with 5408 additions and 1937 deletions

View File

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

View File

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

View File

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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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))));
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

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

View File

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

View File

@ -0,0 +1,96 @@
<?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\Serializer;
use Base64Url\Base64Url;
use function count;
use InvalidArgumentException;
use Jose\Component\Core\Util\JsonConverter;
use Jose\Component\Signature\JWS;
use LogicException;
use Throwable;
final class CompactSerializer extends Serializer
{
public const NAME = 'jws_compact';
public function displayName(): string
{
return 'JWS Compact';
}
public function name(): string
{
return self::NAME;
}
/**
* @throws LogicException if the JWS has unprotected header (invalid for compact JSON)
* @throws LogicException if the payload is not encoded but contains unauthorized characters
*/
public function serialize(JWS $jws, ?int $signatureIndex = null): string
{
if (null === $signatureIndex) {
$signatureIndex = 0;
}
$signature = $jws->getSignature($signatureIndex);
if (0 !== count($signature->getHeader())) {
throw new LogicException('The signature contains unprotected header parameters and cannot be converted into compact JSON.');
}
$isEmptyPayload = null === $jws->getEncodedPayload() || '' === $jws->getEncodedPayload();
if (!$this->isPayloadEncoded($signature->getProtectedHeader()) && !$isEmptyPayload) {
if (1 !== preg_match('/^[\x{20}-\x{2d}|\x{2f}-\x{7e}]*$/u', $jws->getPayload())) {
throw new LogicException('Unable to convert the JWS with non-encoded payload.');
}
}
return sprintf(
'%s.%s.%s',
$signature->getEncodedProtectedHeader(),
$jws->getEncodedPayload(),
Base64Url::encode($signature->getSignature())
);
}
/**
* @throws InvalidArgumentException if the input is invalid
*/
public function unserialize(string $input): JWS
{
$parts = explode('.', $input);
if (3 !== count($parts)) {
throw new InvalidArgumentException('Unsupported input');
}
try {
$encodedProtectedHeader = $parts[0];
$protectedHeader = JsonConverter::decode(Base64Url::decode($parts[0]));
$hasPayload = '' !== $parts[1];
if (!$hasPayload) {
$payload = null;
$encodedPayload = null;
} else {
$encodedPayload = $parts[1];
$payload = $this->isPayloadEncoded($protectedHeader) ? Base64Url::decode($encodedPayload) : $encodedPayload;
}
$signature = Base64Url::decode($parts[2]);
$jws = new JWS($payload, $encodedPayload, !$hasPayload);
return $jws->addSignature($signature, $protectedHeader, $encodedProtectedHeader);
} catch (Throwable $throwable) {
throw new InvalidArgumentException('Unsupported input', $throwable->getCode(), $throwable);
}
}
}

View File

@ -0,0 +1,110 @@
<?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\Serializer;
use Base64Url\Base64Url;
use function count;
use InvalidArgumentException;
use function is_array;
use Jose\Component\Core\Util\JsonConverter;
use Jose\Component\Signature\JWS;
final class JSONFlattenedSerializer extends Serializer
{
public const NAME = 'jws_json_flattened';
public function displayName(): string
{
return 'JWS JSON Flattened';
}
public function name(): string
{
return self::NAME;
}
public function serialize(JWS $jws, ?int $signatureIndex = null): string
{
if (null === $signatureIndex) {
$signatureIndex = 0;
}
$signature = $jws->getSignature($signatureIndex);
$data = [];
$values = [
'payload' => $jws->getEncodedPayload(),
'protected' => $signature->getEncodedProtectedHeader(),
'header' => $signature->getHeader(),
];
$encodedPayload = $jws->getEncodedPayload();
if (null !== $encodedPayload && '' !== $encodedPayload) {
$data['payload'] = $encodedPayload;
}
$encodedProtectedHeader = $signature->getEncodedProtectedHeader();
if (null !== $encodedProtectedHeader && '' !== $encodedProtectedHeader) {
$data['protected'] = $encodedProtectedHeader;
}
$header = $signature->getHeader();
if (0 !== count($header)) {
$data['header'] = $header;
}
$data['signature'] = Base64Url::encode($signature->getSignature());
return JsonConverter::encode($data);
}
/**
* @throws InvalidArgumentException if the input is not supported
* @throws InvalidArgumentException if the JWS header is invalid
*/
public function unserialize(string $input): JWS
{
$data = JsonConverter::decode($input);
if (!is_array($data)) {
throw new InvalidArgumentException('Unsupported input.');
}
if (!isset($data['signature'])) {
throw new InvalidArgumentException('Unsupported input.');
}
$signature = Base64Url::decode($data['signature']);
if (isset($data['protected'])) {
$encodedProtectedHeader = $data['protected'];
$protectedHeader = JsonConverter::decode(Base64Url::decode($data['protected']));
} else {
$encodedProtectedHeader = null;
$protectedHeader = [];
}
if (isset($data['header'])) {
if (!is_array($data['header'])) {
throw new InvalidArgumentException('Bad header.');
}
$header = $data['header'];
} else {
$header = [];
}
if (isset($data['payload'])) {
$encodedPayload = $data['payload'];
$payload = $this->isPayloadEncoded($protectedHeader) ? Base64Url::decode($encodedPayload) : $encodedPayload;
} else {
$payload = null;
$encodedPayload = null;
}
$jws = new JWS($payload, $encodedPayload, null === $encodedPayload);
return $jws->addSignature($signature, $protectedHeader, $encodedProtectedHeader, $header);
}
}

View File

@ -0,0 +1,167 @@
<?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\Serializer;
use function array_key_exists;
use Base64Url\Base64Url;
use function count;
use InvalidArgumentException;
use function is_array;
use function is_string;
use Jose\Component\Core\Util\JsonConverter;
use Jose\Component\Signature\JWS;
use LogicException;
final class JSONGeneralSerializer extends Serializer
{
public const NAME = 'jws_json_general';
public function displayName(): string
{
return 'JWS JSON General';
}
public function name(): string
{
return self::NAME;
}
/**
* @throws LogicException if no signature is attached
*/
public function serialize(JWS $jws, ?int $signatureIndex = null): string
{
if (0 === $jws->countSignatures()) {
throw new LogicException('No signature.');
}
$data = [];
$this->checkPayloadEncoding($jws);
if (false === $jws->isPayloadDetached()) {
$data['payload'] = $jws->getEncodedPayload();
}
$data['signatures'] = [];
foreach ($jws->getSignatures() as $signature) {
$tmp = ['signature' => Base64Url::encode($signature->getSignature())];
$values = [
'protected' => $signature->getEncodedProtectedHeader(),
'header' => $signature->getHeader(),
];
foreach ($values as $key => $value) {
if ((is_string($value) && '' !== $value) || (is_array($value) && 0 !== count($value))) {
$tmp[$key] = $value;
}
}
$data['signatures'][] = $tmp;
}
return JsonConverter::encode($data);
}
/**
* @throws InvalidArgumentException if the input is not supported
*/
public function unserialize(string $input): JWS
{
$data = JsonConverter::decode($input);
if (!isset($data['signatures'])) {
throw new InvalidArgumentException('Unsupported input.');
}
$isPayloadEncoded = null;
$rawPayload = $data['payload'] ?? null;
$signatures = [];
foreach ($data['signatures'] as $signature) {
if (!isset($signature['signature'])) {
throw new InvalidArgumentException('Unsupported input.');
}
[$encodedProtectedHeader, $protectedHeader, $header] = $this->processHeaders($signature);
$signatures[] = [
'signature' => Base64Url::decode($signature['signature']),
'protected' => $protectedHeader,
'encoded_protected' => $encodedProtectedHeader,
'header' => $header,
];
$isPayloadEncoded = $this->processIsPayloadEncoded($isPayloadEncoded, $protectedHeader);
}
$payload = $this->processPayload($rawPayload, $isPayloadEncoded);
$jws = new JWS($payload, $rawPayload);
foreach ($signatures as $signature) {
$jws = $jws->addSignature(
$signature['signature'],
$signature['protected'],
$signature['encoded_protected'],
$signature['header']
);
}
return $jws;
}
/**
* @throws InvalidArgumentException if the payload encoding is invalid
*/
private function processIsPayloadEncoded(?bool $isPayloadEncoded, array $protectedHeader): bool
{
if (null === $isPayloadEncoded) {
return $this->isPayloadEncoded($protectedHeader);
}
if ($this->isPayloadEncoded($protectedHeader) !== $isPayloadEncoded) {
throw new InvalidArgumentException('Foreign payload encoding detected.');
}
return $isPayloadEncoded;
}
private function processHeaders(array $signature): array
{
$encodedProtectedHeader = $signature['protected'] ?? null;
$protectedHeader = null === $encodedProtectedHeader ? [] : JsonConverter::decode(Base64Url::decode($encodedProtectedHeader));
$header = array_key_exists('header', $signature) ? $signature['header'] : [];
return [$encodedProtectedHeader, $protectedHeader, $header];
}
private function processPayload(?string $rawPayload, ?bool $isPayloadEncoded): ?string
{
if (null === $rawPayload) {
return null;
}
return false === $isPayloadEncoded ? $rawPayload : Base64Url::decode($rawPayload);
}
// @throws LogicException if the payload encoding is invalid
private function checkPayloadEncoding(JWS $jws): void
{
if ($jws->isPayloadDetached()) {
return;
}
$is_encoded = null;
foreach ($jws->getSignatures() as $signature) {
if (null === $is_encoded) {
$is_encoded = $this->isPayloadEncoded($signature->getProtectedHeader());
}
if (false === $jws->isPayloadDetached()) {
if ($is_encoded !== $this->isPayloadEncoded($signature->getProtectedHeader())) {
throw new LogicException('Foreign payload encoding detected.');
}
}
}
}
}

View File

@ -0,0 +1,38 @@
<?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\Serializer;
use Jose\Component\Signature\JWS;
interface JWSSerializer
{
/**
* The name of the serialization.
*/
public function name(): string;
public function displayName(): string;
/**
* Converts a JWS into a string.
*/
public function serialize(JWS $jws, ?int $signatureIndex = null): string;
/**
* Loads data and return a JWS object.
*
* @param string $input A string that represents a JWS
*/
public function unserialize(string $input): JWS;
}

View File

@ -0,0 +1,86 @@
<?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\Serializer;
use InvalidArgumentException;
use Jose\Component\Signature\JWS;
class JWSSerializerManager
{
/**
* @var JWSSerializer[]
*/
private $serializers = [];
/**
* @param JWSSerializer[] $serializers
*/
public function __construct(array $serializers)
{
foreach ($serializers as $serializer) {
$this->add($serializer);
}
}
/**
* @return string[]
*/
public function list(): array
{
return array_keys($this->serializers);
}
/**
* Converts a JWS into a string.
*
* @throws InvalidArgumentException if the serializer is not supported
*/
public function serialize(string $name, JWS $jws, ?int $signatureIndex = null): string
{
if (!isset($this->serializers[$name])) {
throw new InvalidArgumentException(sprintf('Unsupported serializer "%s".', $name));
}
return $this->serializers[$name]->serialize($jws, $signatureIndex);
}
/**
* Loads data and return a JWS object.
*
* @param string $input A string that represents a JWS
* @param null|string $name the name of the serializer if the input is unserialized
*
* @throws InvalidArgumentException if the input is not supported
*/
public function unserialize(string $input, ?string &$name = null): JWS
{
foreach ($this->serializers as $serializer) {
try {
$jws = $serializer->unserialize($input);
$name = $serializer->name();
return $jws;
} catch (InvalidArgumentException $e) {
continue;
}
}
throw new InvalidArgumentException('Unsupported input.');
}
private function add(JWSSerializer $serializer): void
{
$this->serializers[$serializer->name()] = $serializer;
}
}

View File

@ -0,0 +1,63 @@
<?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\Serializer;
use InvalidArgumentException;
class JWSSerializerManagerFactory
{
/**
* @var JWSSerializer[]
*/
private $serializers = [];
/**
* @param string[] $names
*
* @throws InvalidArgumentException if the serializer is not supported
*/
public function create(array $names): JWSSerializerManager
{
$serializers = [];
foreach ($names as $name) {
if (!isset($this->serializers[$name])) {
throw new InvalidArgumentException(sprintf('Unsupported serializer "%s".', $name));
}
$serializers[] = $this->serializers[$name];
}
return new JWSSerializerManager($serializers);
}
/**
* @return string[]
*/
public function names(): array
{
return array_keys($this->serializers);
}
/**
* @return JWSSerializer[]
*/
public function all(): array
{
return $this->serializers;
}
public function add(JWSSerializer $serializer): void
{
$this->serializers[$serializer->name()] = $serializer;
}
}

View File

@ -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\Signature\Serializer;
use function array_key_exists;
abstract class Serializer implements JWSSerializer
{
protected function isPayloadEncoded(array $protectedHeader): bool
{
return !array_key_exists('b64', $protectedHeader) || true === $protectedHeader['b64'];
}
}

View File

@ -0,0 +1,134 @@
<?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 InvalidArgumentException;
class Signature
{
/**
* @var null|string
*/
private $encodedProtectedHeader;
/**
* @var array
*/
private $protectedHeader;
/**
* @var array
*/
private $header;
/**
* @var string
*/
private $signature;
public function __construct(string $signature, array $protectedHeader, ?string $encodedProtectedHeader, array $header)
{
$this->protectedHeader = null === $encodedProtectedHeader ? [] : $protectedHeader;
$this->encodedProtectedHeader = $encodedProtectedHeader;
$this->signature = $signature;
$this->header = $header;
}
/**
* The protected header associated with the signature.
*/
public function getProtectedHeader(): array
{
return $this->protectedHeader;
}
/**
* The unprotected header associated with the signature.
*/
public function getHeader(): array
{
return $this->header;
}
/**
* The protected header associated with the signature.
*/
public function getEncodedProtectedHeader(): ?string
{
return $this->encodedProtectedHeader;
}
/**
* Returns the value of the protected header of the specified key.
*
* @param string $key The key
*
* @throws InvalidArgumentException if the header parameter does not exist
*
* @return null|mixed Header value
*/
public function getProtectedHeaderParameter(string $key)
{
if ($this->hasProtectedHeaderParameter($key)) {
return $this->getProtectedHeader()[$key];
}
throw new InvalidArgumentException(sprintf('The protected header "%s" does not exist', $key));
}
/**
* Returns true if the protected header has the given parameter.
*
* @param string $key The key
*/
public function hasProtectedHeaderParameter(string $key): bool
{
return array_key_exists($key, $this->getProtectedHeader());
}
/**
* Returns the value of the unprotected header of the specified key.
*
* @param string $key The key
*
* @return null|mixed Header value
*/
public function getHeaderParameter(string $key)
{
if ($this->hasHeaderParameter($key)) {
return $this->header[$key];
}
throw new InvalidArgumentException(sprintf('The header "%s" does not exist', $key));
}
/**
* Returns true if the unprotected header has the given parameter.
*
* @param string $key The key
*/
public function hasHeaderParameter(string $key): bool
{
return array_key_exists($key, $this->header);
}
/**
* Returns the value of the signature.
*/
public function getSignature(): string
{
return $this->signature;
}
}

View File

@ -0,0 +1,33 @@
{
"name": "web-token/jwt-signature",
"description": "Signature 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-signature/contributors"
}
],
"autoload": {
"psr-4": {
"Jose\\Component\\Signature\\": ""
}
},
"require": {
"web-token/jwt-core": "^2.1"
},
"suggest": {
"web-token/jwt-signature-algorithm-ecdsa": "ECDSA Based Signature Algorithms",
"web-token/jwt-signature-algorithm-eddsa": "EdDSA Based Signature Algorithms",
"web-token/jwt-signature-algorithm-hmac": "HMAC Based Signature Algorithms",
"web-token/jwt-signature-algorithm-none": "None Signature Algorithm",
"web-token/jwt-signature-algorithm-rsa": "RSA Based Signature Algorithms",
"web-token/jwt-signature-algorithm-experimental": "Experimental Signature Algorithms"
}
}