first commit
This commit is contained in:
21
libraries/vendor/web-auth/cose-lib/LICENSE
vendored
Normal file
21
libraries/vendor/web-auth/cose-lib/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 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.
|
||||
10
libraries/vendor/web-auth/cose-lib/src/Algorithm/Algorithm.php
vendored
Normal file
10
libraries/vendor/web-auth/cose-lib/src/Algorithm/Algorithm.php
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm;
|
||||
|
||||
interface Algorithm
|
||||
{
|
||||
public static function identifier(): int;
|
||||
}
|
||||
30
libraries/vendor/web-auth/cose-lib/src/Algorithm/Mac/HS256.php
vendored
Normal file
30
libraries/vendor/web-auth/cose-lib/src/Algorithm/Mac/HS256.php
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Mac;
|
||||
|
||||
final class HS256 extends Hmac
|
||||
{
|
||||
public const ID = 5;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public static function identifier(): int
|
||||
{
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
protected function getHashAlgorithm(): string
|
||||
{
|
||||
return 'sha256';
|
||||
}
|
||||
|
||||
protected function getSignatureLength(): int
|
||||
{
|
||||
return 256;
|
||||
}
|
||||
}
|
||||
30
libraries/vendor/web-auth/cose-lib/src/Algorithm/Mac/HS256Truncated64.php
vendored
Normal file
30
libraries/vendor/web-auth/cose-lib/src/Algorithm/Mac/HS256Truncated64.php
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Mac;
|
||||
|
||||
final class HS256Truncated64 extends Hmac
|
||||
{
|
||||
public const ID = 4;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public static function identifier(): int
|
||||
{
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
protected function getHashAlgorithm(): string
|
||||
{
|
||||
return 'sha256';
|
||||
}
|
||||
|
||||
protected function getSignatureLength(): int
|
||||
{
|
||||
return 64;
|
||||
}
|
||||
}
|
||||
30
libraries/vendor/web-auth/cose-lib/src/Algorithm/Mac/HS384.php
vendored
Normal file
30
libraries/vendor/web-auth/cose-lib/src/Algorithm/Mac/HS384.php
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Mac;
|
||||
|
||||
final class HS384 extends Hmac
|
||||
{
|
||||
public const ID = 6;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public static function identifier(): int
|
||||
{
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
protected function getHashAlgorithm(): string
|
||||
{
|
||||
return 'sha384';
|
||||
}
|
||||
|
||||
protected function getSignatureLength(): int
|
||||
{
|
||||
return 384;
|
||||
}
|
||||
}
|
||||
30
libraries/vendor/web-auth/cose-lib/src/Algorithm/Mac/HS512.php
vendored
Normal file
30
libraries/vendor/web-auth/cose-lib/src/Algorithm/Mac/HS512.php
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Mac;
|
||||
|
||||
final class HS512 extends Hmac
|
||||
{
|
||||
public const ID = 7;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public static function identifier(): int
|
||||
{
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
protected function getHashAlgorithm(): string
|
||||
{
|
||||
return 'sha512';
|
||||
}
|
||||
|
||||
protected function getSignatureLength(): int
|
||||
{
|
||||
return 512;
|
||||
}
|
||||
}
|
||||
43
libraries/vendor/web-auth/cose-lib/src/Algorithm/Mac/Hmac.php
vendored
Normal file
43
libraries/vendor/web-auth/cose-lib/src/Algorithm/Mac/Hmac.php
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Mac;
|
||||
|
||||
use Cose\Key\Key;
|
||||
use Cose\Key\SymmetricKey;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @see \Cose\Tests\Algorithm\Mac\HmacTest
|
||||
*/
|
||||
abstract class Hmac implements Mac
|
||||
{
|
||||
public function hash(string $data, Key $key): string
|
||||
{
|
||||
$this->checKey($key);
|
||||
$signature = hash_hmac($this->getHashAlgorithm(), $data, (string) $key->get(SymmetricKey::DATA_K), true);
|
||||
|
||||
return mb_substr($signature, 0, intdiv($this->getSignatureLength(), 8), '8bit');
|
||||
}
|
||||
|
||||
public function verify(string $data, Key $key, string $signature): bool
|
||||
{
|
||||
return hash_equals($this->hash($data, $key), $signature);
|
||||
}
|
||||
|
||||
abstract protected function getHashAlgorithm(): string;
|
||||
|
||||
abstract protected function getSignatureLength(): int;
|
||||
|
||||
private function checKey(Key $key): void
|
||||
{
|
||||
if ($key->type() !== Key::TYPE_OCT && $key->type() !== Key::TYPE_NAME_OCT) {
|
||||
throw new InvalidArgumentException('Invalid key. Must be of type symmetric');
|
||||
}
|
||||
|
||||
if (! $key->has(SymmetricKey::DATA_K)) {
|
||||
throw new InvalidArgumentException('Invalid key. The value of the key is missing');
|
||||
}
|
||||
}
|
||||
}
|
||||
15
libraries/vendor/web-auth/cose-lib/src/Algorithm/Mac/Mac.php
vendored
Normal file
15
libraries/vendor/web-auth/cose-lib/src/Algorithm/Mac/Mac.php
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Mac;
|
||||
|
||||
use Cose\Algorithm\Algorithm;
|
||||
use Cose\Key\Key;
|
||||
|
||||
interface Mac extends Algorithm
|
||||
{
|
||||
public function hash(string $data, Key $key): string;
|
||||
|
||||
public function verify(string $data, Key $key, string $signature): bool;
|
||||
}
|
||||
61
libraries/vendor/web-auth/cose-lib/src/Algorithm/Manager.php
vendored
Normal file
61
libraries/vendor/web-auth/cose-lib/src/Algorithm/Manager.php
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use function array_key_exists;
|
||||
|
||||
final class Manager
|
||||
{
|
||||
/**
|
||||
* @var array<int, Algorithm>
|
||||
*/
|
||||
private array $algorithms = [];
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function add(Algorithm ...$algorithms): self
|
||||
{
|
||||
foreach ($algorithms as $algorithm) {
|
||||
$identifier = $algorithm::identifier();
|
||||
$this->algorithms[$identifier] = $algorithm;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<int>
|
||||
*/
|
||||
public function list(): iterable
|
||||
{
|
||||
yield from array_keys($this->algorithms);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<int, Algorithm>
|
||||
*/
|
||||
public function all(): iterable
|
||||
{
|
||||
yield from $this->algorithms;
|
||||
}
|
||||
|
||||
public function has(int $identifier): bool
|
||||
{
|
||||
return array_key_exists($identifier, $this->algorithms);
|
||||
}
|
||||
|
||||
public function get(int $identifier): Algorithm
|
||||
{
|
||||
if (! $this->has($identifier)) {
|
||||
throw new InvalidArgumentException('Unsupported algorithm');
|
||||
}
|
||||
|
||||
return $this->algorithms[$identifier];
|
||||
}
|
||||
}
|
||||
57
libraries/vendor/web-auth/cose-lib/src/Algorithm/ManagerFactory.php
vendored
Normal file
57
libraries/vendor/web-auth/cose-lib/src/Algorithm/ManagerFactory.php
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use function array_key_exists;
|
||||
|
||||
final class ManagerFactory
|
||||
{
|
||||
/**
|
||||
* @var array<string, Algorithm>
|
||||
*/
|
||||
private array $algorithms = [];
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function add(string $alias, Algorithm $algorithm): self
|
||||
{
|
||||
$this->algorithms[$alias] = $algorithm;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function list(): iterable
|
||||
{
|
||||
yield from array_keys($this->algorithms);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Algorithm[]
|
||||
*/
|
||||
public function all(): iterable
|
||||
{
|
||||
yield from $this->algorithms;
|
||||
}
|
||||
|
||||
public function generate(string ...$aliases): Manager
|
||||
{
|
||||
$manager = Manager::create();
|
||||
foreach ($aliases as $alias) {
|
||||
if (! array_key_exists($alias, $this->algorithms)) {
|
||||
throw new InvalidArgumentException(sprintf('The algorithm with alias "%s" is not supported', $alias));
|
||||
}
|
||||
$manager->add($this->algorithms[$alias]);
|
||||
}
|
||||
|
||||
return $manager;
|
||||
}
|
||||
}
|
||||
50
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ECDSA.php
vendored
Normal file
50
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ECDSA.php
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Signature\ECDSA;
|
||||
|
||||
use Cose\Algorithm\Signature\Signature;
|
||||
use Cose\Key\Ec2Key;
|
||||
use Cose\Key\Key;
|
||||
use InvalidArgumentException;
|
||||
use function openssl_sign;
|
||||
use function openssl_verify;
|
||||
|
||||
/**
|
||||
* @see \Cose\Tests\Algorithm\Signature\ECDSA\ECDSATest
|
||||
*/
|
||||
abstract class ECDSA implements Signature
|
||||
{
|
||||
public function sign(string $data, Key $key): string
|
||||
{
|
||||
$key = $this->handleKey($key);
|
||||
openssl_sign($data, $signature, $key->asPEM(), $this->getHashAlgorithm());
|
||||
|
||||
return ECSignature::fromAsn1($signature, $this->getSignaturePartLength());
|
||||
}
|
||||
|
||||
public function verify(string $data, Key $key, string $signature): bool
|
||||
{
|
||||
$key = $this->handleKey($key);
|
||||
$publicKey = $key->toPublic();
|
||||
$signature = ECSignature::toAsn1($signature, $this->getSignaturePartLength());
|
||||
return openssl_verify($data, $signature, $publicKey->asPEM(), $this->getHashAlgorithm()) === 1;
|
||||
}
|
||||
|
||||
abstract protected function getCurve(): int;
|
||||
|
||||
abstract protected function getHashAlgorithm(): int;
|
||||
|
||||
abstract protected function getSignaturePartLength(): int;
|
||||
|
||||
private function handleKey(Key $key): Ec2Key
|
||||
{
|
||||
$key = Ec2Key::create($key->getData());
|
||||
if ($key->curve() !== $this->getCurve()) {
|
||||
throw new InvalidArgumentException('This key cannot be used with this algorithm');
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
132
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ECSignature.php
vendored
Normal file
132
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ECSignature.php
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Signature\ECDSA;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use function bin2hex;
|
||||
use function dechex;
|
||||
use function hex2bin;
|
||||
use function hexdec;
|
||||
use function mb_strlen;
|
||||
use function mb_substr;
|
||||
use function str_pad;
|
||||
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;
|
||||
|
||||
public static function toAsn1(string $signature, int $length): string
|
||||
{
|
||||
$signature = bin2hex($signature);
|
||||
|
||||
if (self::octetLength($signature) !== $length) {
|
||||
throw new InvalidArgumentException('Invalid signature length.');
|
||||
}
|
||||
|
||||
$pointR = self::preparePositiveInteger(mb_substr($signature, 0, $length, '8bit'));
|
||||
$pointS = self::preparePositiveInteger(mb_substr($signature, $length, null, '8bit'));
|
||||
|
||||
$lengthR = self::octetLength($pointR);
|
||||
$lengthS = self::octetLength($pointS);
|
||||
|
||||
$totalLength = $lengthR + $lengthS + self::BYTE_SIZE + self::BYTE_SIZE;
|
||||
$lengthPrefix = $totalLength > self::ASN1_MAX_SINGLE_BYTE ? self::ASN1_LENGTH_2BYTES : '';
|
||||
|
||||
return hex2bin(
|
||||
self::ASN1_SEQUENCE
|
||||
. $lengthPrefix . dechex($totalLength)
|
||||
. self::ASN1_INTEGER . dechex($lengthR) . $pointR
|
||||
. self::ASN1_INTEGER . dechex($lengthS) . $pointS
|
||||
);
|
||||
}
|
||||
|
||||
public static function fromAsn1(string $signature, int $length): string
|
||||
{
|
||||
$message = bin2hex($signature);
|
||||
$position = 0;
|
||||
|
||||
if (self::readAsn1Content($message, $position, self::BYTE_SIZE) !== self::ASN1_SEQUENCE) {
|
||||
throw new InvalidArgumentException('Invalid data. Should start with a sequence.');
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
if (self::readAsn1Content($message, $position, self::BYTE_SIZE) === self::ASN1_LENGTH_2BYTES) {
|
||||
$position += self::BYTE_SIZE;
|
||||
}
|
||||
|
||||
$pointR = self::retrievePositiveInteger(self::readAsn1Integer($message, $position));
|
||||
$pointS = self::retrievePositiveInteger(self::readAsn1Integer($message, $position));
|
||||
|
||||
return hex2bin(str_pad($pointR, $length, '0', STR_PAD_LEFT) . str_pad($pointS, $length, '0', STR_PAD_LEFT));
|
||||
}
|
||||
|
||||
private static function octetLength(string $data): int
|
||||
{
|
||||
return intdiv(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 (
|
||||
mb_strpos($data, self::ASN1_NEGATIVE_INTEGER, 0, '8bit') === 0
|
||||
&& 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;
|
||||
}
|
||||
|
||||
private static function readAsn1Integer(string $message, int &$position): string
|
||||
{
|
||||
if (self::readAsn1Content($message, $position, self::BYTE_SIZE) !== self::ASN1_INTEGER) {
|
||||
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 (
|
||||
mb_strpos($data, self::ASN1_NEGATIVE_INTEGER, 0, '8bit') === 0
|
||||
&& mb_substr($data, 2, self::BYTE_SIZE, '8bit') > self::ASN1_BIG_INTEGER_LIMIT
|
||||
) {
|
||||
$data = mb_substr($data, 2, null, '8bit');
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
38
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256.php
vendored
Normal file
38
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256.php
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Signature\ECDSA;
|
||||
|
||||
use Cose\Key\Ec2Key;
|
||||
use const OPENSSL_ALGO_SHA256;
|
||||
|
||||
final class ES256 extends ECDSA
|
||||
{
|
||||
public const ID = -7;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public static function identifier(): int
|
||||
{
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
protected function getHashAlgorithm(): int
|
||||
{
|
||||
return OPENSSL_ALGO_SHA256;
|
||||
}
|
||||
|
||||
protected function getCurve(): int
|
||||
{
|
||||
return Ec2Key::CURVE_P256;
|
||||
}
|
||||
|
||||
protected function getSignaturePartLength(): int
|
||||
{
|
||||
return 64;
|
||||
}
|
||||
}
|
||||
38
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256K.php
vendored
Normal file
38
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES256K.php
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Signature\ECDSA;
|
||||
|
||||
use Cose\Key\Ec2Key;
|
||||
use const OPENSSL_ALGO_SHA256;
|
||||
|
||||
final class ES256K extends ECDSA
|
||||
{
|
||||
public const ID = -46;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public static function identifier(): int
|
||||
{
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
protected function getHashAlgorithm(): int
|
||||
{
|
||||
return OPENSSL_ALGO_SHA256;
|
||||
}
|
||||
|
||||
protected function getCurve(): int
|
||||
{
|
||||
return Ec2Key::CURVE_P256K;
|
||||
}
|
||||
|
||||
protected function getSignaturePartLength(): int
|
||||
{
|
||||
return 64;
|
||||
}
|
||||
}
|
||||
38
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES384.php
vendored
Normal file
38
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES384.php
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Signature\ECDSA;
|
||||
|
||||
use Cose\Key\Ec2Key;
|
||||
use const OPENSSL_ALGO_SHA384;
|
||||
|
||||
final class ES384 extends ECDSA
|
||||
{
|
||||
public const ID = -35;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public static function identifier(): int
|
||||
{
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
protected function getHashAlgorithm(): int
|
||||
{
|
||||
return OPENSSL_ALGO_SHA384;
|
||||
}
|
||||
|
||||
protected function getCurve(): int
|
||||
{
|
||||
return Ec2Key::CURVE_P384;
|
||||
}
|
||||
|
||||
protected function getSignaturePartLength(): int
|
||||
{
|
||||
return 96;
|
||||
}
|
||||
}
|
||||
38
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES512.php
vendored
Normal file
38
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/ECDSA/ES512.php
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Signature\ECDSA;
|
||||
|
||||
use Cose\Key\Ec2Key;
|
||||
use const OPENSSL_ALGO_SHA512;
|
||||
|
||||
final class ES512 extends ECDSA
|
||||
{
|
||||
public const ID = -36;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public static function identifier(): int
|
||||
{
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
protected function getHashAlgorithm(): int
|
||||
{
|
||||
return OPENSSL_ALGO_SHA512;
|
||||
}
|
||||
|
||||
protected function getCurve(): int
|
||||
{
|
||||
return Ec2Key::CURVE_P521;
|
||||
}
|
||||
|
||||
protected function getSignaturePartLength(): int
|
||||
{
|
||||
return 132;
|
||||
}
|
||||
}
|
||||
20
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed25519.php
vendored
Normal file
20
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed25519.php
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Signature\EdDSA;
|
||||
|
||||
final class Ed25519 extends EdDSA
|
||||
{
|
||||
public const ID = -8;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public static function identifier(): int
|
||||
{
|
||||
return self::ID;
|
||||
}
|
||||
}
|
||||
36
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed256.php
vendored
Normal file
36
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed256.php
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Signature\EdDSA;
|
||||
|
||||
use Cose\Key\Key;
|
||||
|
||||
final class Ed256 extends EdDSA
|
||||
{
|
||||
public const ID = -260;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public static function identifier(): int
|
||||
{
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
public function sign(string $data, Key $key): string
|
||||
{
|
||||
$hashedData = hash('sha256', $data, true);
|
||||
|
||||
return parent::sign($hashedData, $key);
|
||||
}
|
||||
|
||||
public function verify(string $data, Key $key, string $signature): bool
|
||||
{
|
||||
$hashedData = hash('sha256', $data, true);
|
||||
|
||||
return parent::verify($hashedData, $key, $signature);
|
||||
}
|
||||
}
|
||||
36
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed512.php
vendored
Normal file
36
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/Ed512.php
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Signature\EdDSA;
|
||||
|
||||
use Cose\Key\Key;
|
||||
|
||||
final class Ed512 extends EdDSA
|
||||
{
|
||||
public const ID = -261;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public static function identifier(): int
|
||||
{
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
public function sign(string $data, Key $key): string
|
||||
{
|
||||
$hashedData = hash('sha512', $data, true);
|
||||
|
||||
return parent::sign($hashedData, $key);
|
||||
}
|
||||
|
||||
public function verify(string $data, Key $key, string $signature): bool
|
||||
{
|
||||
$hashedData = hash('sha512', $data, true);
|
||||
|
||||
return parent::verify($hashedData, $key, $signature);
|
||||
}
|
||||
}
|
||||
63
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/EdDSA.php
vendored
Normal file
63
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/EdDSA/EdDSA.php
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Signature\EdDSA;
|
||||
|
||||
use Cose\Algorithm\Signature\Signature;
|
||||
use Cose\Algorithms;
|
||||
use Cose\Key\Key;
|
||||
use Cose\Key\OkpKey;
|
||||
use InvalidArgumentException;
|
||||
use Throwable;
|
||||
use function sodium_crypto_sign_detached;
|
||||
use function sodium_crypto_sign_verify_detached;
|
||||
|
||||
/**
|
||||
* @see \Cose\Tests\Algorithm\Signature\EdDSA\EdDSATest
|
||||
*/
|
||||
class EdDSA implements Signature
|
||||
{
|
||||
public function sign(string $data, Key $key): string
|
||||
{
|
||||
$key = $this->handleKey($key);
|
||||
if (! $key->isPrivate()) {
|
||||
throw new InvalidArgumentException('The key is not private.');
|
||||
}
|
||||
|
||||
$x = $key->x();
|
||||
$d = $key->d();
|
||||
$secret = $d . $x;
|
||||
|
||||
return match ($key->curve()) {
|
||||
OkpKey::CURVE_ED25519 => sodium_crypto_sign_detached($data, $secret),
|
||||
OkpKey::CURVE_NAME_ED25519 => sodium_crypto_sign_detached($data, $secret),
|
||||
default => throw new InvalidArgumentException('Unsupported curve'),
|
||||
};
|
||||
}
|
||||
|
||||
public function verify(string $data, Key $key, string $signature): bool
|
||||
{
|
||||
$key = $this->handleKey($key);
|
||||
if ($key->curve() !== OkpKey::CURVE_ED25519 && $key->curve() !== OkpKey::CURVE_NAME_ED25519) {
|
||||
throw new InvalidArgumentException('Unsupported curve');
|
||||
}
|
||||
try {
|
||||
sodium_crypto_sign_verify_detached($signature, $data, $key->x());
|
||||
} catch (Throwable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function identifier(): int
|
||||
{
|
||||
return Algorithms::COSE_ALGORITHM_EDDSA;
|
||||
}
|
||||
|
||||
private function handleKey(Key $key): OkpKey
|
||||
{
|
||||
return OkpKey::create($key->getData());
|
||||
}
|
||||
}
|
||||
27
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS256.php
vendored
Normal file
27
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS256.php
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Signature\RSA;
|
||||
|
||||
use Cose\Hash;
|
||||
|
||||
final class PS256 extends PSSRSA
|
||||
{
|
||||
public const ID = -37;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public static function identifier(): int
|
||||
{
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
protected function getHashAlgorithm(): Hash
|
||||
{
|
||||
return Hash::sha256();
|
||||
}
|
||||
}
|
||||
27
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS384.php
vendored
Normal file
27
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS384.php
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Signature\RSA;
|
||||
|
||||
use Cose\Hash;
|
||||
|
||||
final class PS384 extends PSSRSA
|
||||
{
|
||||
public const ID = -38;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public static function identifier(): int
|
||||
{
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
protected function getHashAlgorithm(): Hash
|
||||
{
|
||||
return Hash::sha384();
|
||||
}
|
||||
}
|
||||
27
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS512.php
vendored
Normal file
27
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/RSA/PS512.php
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Signature\RSA;
|
||||
|
||||
use Cose\Hash;
|
||||
|
||||
final class PS512 extends PSSRSA
|
||||
{
|
||||
public const ID = -39;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public static function identifier(): int
|
||||
{
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
protected function getHashAlgorithm(): Hash
|
||||
{
|
||||
return Hash::sha512();
|
||||
}
|
||||
}
|
||||
183
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/RSA/PSSRSA.php
vendored
Normal file
183
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/RSA/PSSRSA.php
vendored
Normal file
@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Signature\RSA;
|
||||
|
||||
use Cose\Algorithm\Signature\Signature;
|
||||
use Cose\BigInteger;
|
||||
use Cose\Hash;
|
||||
use Cose\Key\Key;
|
||||
use Cose\Key\RsaKey;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use function ceil;
|
||||
use function chr;
|
||||
use function hash_equals;
|
||||
use function mb_strlen;
|
||||
use function mb_substr;
|
||||
use function ord;
|
||||
use function pack;
|
||||
use function random_bytes;
|
||||
use function str_pad;
|
||||
use function str_repeat;
|
||||
use const STR_PAD_LEFT;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class PSSRSA implements Signature
|
||||
{
|
||||
public function sign(string $data, Key $key): string
|
||||
{
|
||||
$key = $this->handleKey($key);
|
||||
$modulusLength = mb_strlen($key->n(), '8bit');
|
||||
|
||||
$em = $this->encodeEMSAPSS($data, 8 * $modulusLength - 1, $this->getHashAlgorithm());
|
||||
$message = BigInteger::createFromBinaryString($em);
|
||||
$signature = $this->exponentiate($key, $message);
|
||||
|
||||
return $this->convertIntegerToOctetString($signature, $modulusLength);
|
||||
}
|
||||
|
||||
public function verify(string $data, Key $key, string $signature): bool
|
||||
{
|
||||
$key = $this->handleKey($key);
|
||||
$modulusLength = mb_strlen($key->n(), '8bit');
|
||||
if (mb_strlen($signature, '8bit') !== $modulusLength) {
|
||||
throw new InvalidArgumentException('Invalid modulus length');
|
||||
}
|
||||
$s2 = BigInteger::createFromBinaryString($signature);
|
||||
$m2 = $this->exponentiate($key, $s2);
|
||||
$em = $this->convertIntegerToOctetString($m2, $modulusLength);
|
||||
$modBits = 8 * $modulusLength;
|
||||
|
||||
return $this->verifyEMSAPSS($data, $em, $modBits - 1, $this->getHashAlgorithm());
|
||||
}
|
||||
|
||||
/**
|
||||
* Exponentiate with or without Chinese Remainder Theorem. Operation with primes 'p' and 'q' is appox. 2x faster.
|
||||
*/
|
||||
public function exponentiate(RsaKey $key, BigInteger $c): BigInteger
|
||||
{
|
||||
if ($c->compare(BigInteger::createFromDecimal(0)) < 0 || $c->compare(
|
||||
BigInteger::createFromBinaryString($key->n())
|
||||
) > 0) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
if ($key->isPublic() || ! $key->hasPrimes() || ! $key->hasExponents() || ! $key->hasCoefficient()) {
|
||||
return $c->modPow(
|
||||
BigInteger::createFromBinaryString($key->e()),
|
||||
BigInteger::createFromBinaryString($key->n())
|
||||
);
|
||||
}
|
||||
|
||||
[$pS, $qS] = $key->primes();
|
||||
[$dPS, $dQS] = $key->exponents();
|
||||
$qInv = BigInteger::createFromBinaryString($key->QInv());
|
||||
$p = BigInteger::createFromBinaryString($pS);
|
||||
$q = BigInteger::createFromBinaryString($qS);
|
||||
$dP = BigInteger::createFromBinaryString($dPS);
|
||||
$dQ = BigInteger::createFromBinaryString($dQS);
|
||||
|
||||
$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));
|
||||
}
|
||||
|
||||
abstract protected function getHashAlgorithm(): Hash;
|
||||
|
||||
private function handleKey(Key $key): RsaKey
|
||||
{
|
||||
return RsaKey::create($key->getData());
|
||||
}
|
||||
|
||||
private function convertIntegerToOctetString(BigInteger $x, int $xLen): string
|
||||
{
|
||||
$xB = $x->toBytes();
|
||||
if (mb_strlen($xB, '8bit') > $xLen) {
|
||||
throw new RuntimeException('Unable to convert the integer');
|
||||
}
|
||||
|
||||
return str_pad($xB, $xLen, chr(0), STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* MGF1.
|
||||
*/
|
||||
private 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.
|
||||
*/
|
||||
private 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 = $this->getMGF1($h, $emLen - $hash->getLength() - 1, $hash);
|
||||
$maskedDB = $db ^ $dbMask;
|
||||
$maskedDB[0] = ~chr(0xFF << ($modulusLength & 7)) & $maskedDB[0];
|
||||
|
||||
return $maskedDB . $h . chr(0xBC);
|
||||
}
|
||||
|
||||
/**
|
||||
* EMSA-PSS-VERIFY.
|
||||
*/
|
||||
private 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 = $this->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_strpos($db, str_repeat(chr(0), $temp), 0, '8bit') !== 0) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
if (ord($db[$temp]) !== 1) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
27
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS1.php
vendored
Normal file
27
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS1.php
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Signature\RSA;
|
||||
|
||||
use const OPENSSL_ALGO_SHA1;
|
||||
|
||||
final class RS1 extends RSA
|
||||
{
|
||||
public const ID = -65535;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public static function identifier(): int
|
||||
{
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
protected function getHashAlgorithm(): int
|
||||
{
|
||||
return OPENSSL_ALGO_SHA1;
|
||||
}
|
||||
}
|
||||
27
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS256.php
vendored
Normal file
27
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS256.php
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Signature\RSA;
|
||||
|
||||
use const OPENSSL_ALGO_SHA256;
|
||||
|
||||
final class RS256 extends RSA
|
||||
{
|
||||
public const ID = -257;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public static function identifier(): int
|
||||
{
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
protected function getHashAlgorithm(): int
|
||||
{
|
||||
return OPENSSL_ALGO_SHA256;
|
||||
}
|
||||
}
|
||||
27
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS384.php
vendored
Normal file
27
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS384.php
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Signature\RSA;
|
||||
|
||||
use const OPENSSL_ALGO_SHA384;
|
||||
|
||||
final class RS384 extends RSA
|
||||
{
|
||||
public const ID = -258;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public static function identifier(): int
|
||||
{
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
protected function getHashAlgorithm(): int
|
||||
{
|
||||
return OPENSSL_ALGO_SHA384;
|
||||
}
|
||||
}
|
||||
27
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS512.php
vendored
Normal file
27
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/RSA/RS512.php
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Signature\RSA;
|
||||
|
||||
use const OPENSSL_ALGO_SHA512;
|
||||
|
||||
final class RS512 extends RSA
|
||||
{
|
||||
public const ID = -259;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public static function identifier(): int
|
||||
{
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
protected function getHashAlgorithm(): int
|
||||
{
|
||||
return OPENSSL_ALGO_SHA512;
|
||||
}
|
||||
}
|
||||
49
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/RSA/RSA.php
vendored
Normal file
49
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/RSA/RSA.php
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Signature\RSA;
|
||||
|
||||
use Cose\Algorithm\Signature\Signature;
|
||||
use Cose\Key\Key;
|
||||
use Cose\Key\RsaKey;
|
||||
use InvalidArgumentException;
|
||||
use Throwable;
|
||||
use function openssl_sign;
|
||||
use function openssl_verify;
|
||||
|
||||
/**
|
||||
* @see \Cose\Tests\Algorithm\Signature\RSA\RSATest
|
||||
*/
|
||||
abstract class RSA implements Signature
|
||||
{
|
||||
public function sign(string $data, Key $key): string
|
||||
{
|
||||
$key = $this->handleKey($key);
|
||||
if (! $key->isPrivate()) {
|
||||
throw new InvalidArgumentException('The key is not private.');
|
||||
}
|
||||
|
||||
try {
|
||||
openssl_sign($data, $signature, $key->asPem(), $this->getHashAlgorithm());
|
||||
} catch (Throwable $e) {
|
||||
throw new InvalidArgumentException('Unable to sign the data', 0, $e);
|
||||
}
|
||||
|
||||
return $signature;
|
||||
}
|
||||
|
||||
public function verify(string $data, Key $key, string $signature): bool
|
||||
{
|
||||
$key = $this->handleKey($key);
|
||||
|
||||
return openssl_verify($data, $signature, $key->toPublic()->asPem(), $this->getHashAlgorithm()) === 1;
|
||||
}
|
||||
|
||||
abstract protected function getHashAlgorithm(): int;
|
||||
|
||||
private function handleKey(Key $key): RsaKey
|
||||
{
|
||||
return RsaKey::create($key->getData());
|
||||
}
|
||||
}
|
||||
15
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/Signature.php
vendored
Normal file
15
libraries/vendor/web-auth/cose-lib/src/Algorithm/Signature/Signature.php
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Algorithm\Signature;
|
||||
|
||||
use Cose\Algorithm\Algorithm;
|
||||
use Cose\Key\Key;
|
||||
|
||||
interface Signature extends Algorithm
|
||||
{
|
||||
public function sign(string $data, Key $key): string;
|
||||
|
||||
public function verify(string $data, Key $key, string $signature): bool;
|
||||
}
|
||||
175
libraries/vendor/web-auth/cose-lib/src/Algorithms.php
vendored
Normal file
175
libraries/vendor/web-auth/cose-lib/src/Algorithms.php
vendored
Normal file
@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use function array_key_exists;
|
||||
use const OPENSSL_ALGO_SHA1;
|
||||
use const OPENSSL_ALGO_SHA256;
|
||||
use const OPENSSL_ALGO_SHA384;
|
||||
use const OPENSSL_ALGO_SHA512;
|
||||
|
||||
/**
|
||||
* @see https://www.iana.org/assignments/cose/cose.xhtml#algorithms
|
||||
*/
|
||||
abstract class Algorithms
|
||||
{
|
||||
final public const COSE_ALGORITHM_AES_CCM_64_128_256 = 33;
|
||||
|
||||
final public const COSE_ALGORITHM_AES_CCM_64_128_128 = 32;
|
||||
|
||||
final public const COSE_ALGORITHM_AES_CCM_16_128_256 = 31;
|
||||
|
||||
final public const COSE_ALGORITHM_AES_CCM_16_128_128 = 30;
|
||||
|
||||
final public const COSE_ALGORITHM_AES_MAC_256_128 = 26;
|
||||
|
||||
final public const COSE_ALGORITHM_AES_MAC_128_128 = 25;
|
||||
|
||||
final public const COSE_ALGORITHM_CHACHA20_POLY1305 = 24;
|
||||
|
||||
final public const COSE_ALGORITHM_AES_MAC_256_64 = 15;
|
||||
|
||||
final public const COSE_ALGORITHM_AES_MAC_128_64 = 14;
|
||||
|
||||
final public const COSE_ALGORITHM_AES_CCM_64_64_256 = 13;
|
||||
|
||||
final public const COSE_ALGORITHM_AES_CCM_64_64_128 = 12;
|
||||
|
||||
final public const COSE_ALGORITHM_AES_CCM_16_64_256 = 11;
|
||||
|
||||
final public const COSE_ALGORITHM_AES_CCM_16_64_128 = 10;
|
||||
|
||||
final public const COSE_ALGORITHM_HS512 = 7;
|
||||
|
||||
final public const COSE_ALGORITHM_HS384 = 6;
|
||||
|
||||
final public const COSE_ALGORITHM_HS256 = 5;
|
||||
|
||||
final public const COSE_ALGORITHM_HS256_64 = 4;
|
||||
|
||||
final public const COSE_ALGORITHM_A256GCM = 3;
|
||||
|
||||
final public const COSE_ALGORITHM_A192GCM = 2;
|
||||
|
||||
final public const COSE_ALGORITHM_A128GCM = 1;
|
||||
|
||||
final public const COSE_ALGORITHM_A128KW = -3;
|
||||
|
||||
final public const COSE_ALGORITHM_A192KW = -4;
|
||||
|
||||
final public const COSE_ALGORITHM_A256KW = -5;
|
||||
|
||||
final public const COSE_ALGORITHM_DIRECT = -6;
|
||||
|
||||
final public const COSE_ALGORITHM_ES256 = -7;
|
||||
|
||||
/**
|
||||
* @deprecated since v4.0.6. Please use COSE_ALGORITHM_EDDSA instead. Will be removed in v5.0.0
|
||||
*/
|
||||
final public const COSE_ALGORITHM_EdDSA = -8;
|
||||
|
||||
final public const COSE_ALGORITHM_EDDSA = -8;
|
||||
|
||||
final public const COSE_ALGORITHM_ED256 = -260;
|
||||
|
||||
final public const COSE_ALGORITHM_ED512 = -261;
|
||||
|
||||
final public const COSE_ALGORITHM_DIRECT_HKDF_SHA_256 = -10;
|
||||
|
||||
final public const COSE_ALGORITHM_DIRECT_HKDF_SHA_512 = -11;
|
||||
|
||||
final public const COSE_ALGORITHM_DIRECT_HKDF_AES_128 = -12;
|
||||
|
||||
final public const COSE_ALGORITHM_DIRECT_HKDF_AES_256 = -13;
|
||||
|
||||
final public const COSE_ALGORITHM_ECDH_ES_HKDF_256 = -25;
|
||||
|
||||
final public const COSE_ALGORITHM_ECDH_ES_HKDF_512 = -26;
|
||||
|
||||
final public const COSE_ALGORITHM_ECDH_SS_HKDF_256 = -27;
|
||||
|
||||
final public const COSE_ALGORITHM_ECDH_SS_HKDF_512 = -28;
|
||||
|
||||
final public const COSE_ALGORITHM_ECDH_ES_A128KW = -29;
|
||||
|
||||
final public const COSE_ALGORITHM_ECDH_ES_A192KW = -30;
|
||||
|
||||
final public const COSE_ALGORITHM_ECDH_ES_A256KW = -31;
|
||||
|
||||
final public const COSE_ALGORITHM_ECDH_SS_A128KW = -32;
|
||||
|
||||
final public const COSE_ALGORITHM_ECDH_SS_A192KW = -33;
|
||||
|
||||
final public const COSE_ALGORITHM_ECDH_SS_A256KW = -34;
|
||||
|
||||
final public const COSE_ALGORITHM_ES384 = -35;
|
||||
|
||||
final public const COSE_ALGORITHM_ES512 = -36;
|
||||
|
||||
final public const COSE_ALGORITHM_PS256 = -37;
|
||||
|
||||
final public const COSE_ALGORITHM_PS384 = -38;
|
||||
|
||||
final public const COSE_ALGORITHM_PS512 = -39;
|
||||
|
||||
final public const COSE_ALGORITHM_RSAES_OAEP = -40;
|
||||
|
||||
final public const COSE_ALGORITHM_RSAES_OAEP_256 = -41;
|
||||
|
||||
final public const COSE_ALGORITHM_RSAES_OAEP_512 = -42;
|
||||
|
||||
final public const COSE_ALGORITHM_ES256K = -46;
|
||||
|
||||
final public const COSE_ALGORITHM_RS256 = -257;
|
||||
|
||||
final public const COSE_ALGORITHM_RS384 = -258;
|
||||
|
||||
final public const COSE_ALGORITHM_RS512 = -259;
|
||||
|
||||
final public const COSE_ALGORITHM_RS1 = -65535;
|
||||
|
||||
final public const COSE_ALGORITHM_MAP = [
|
||||
self::COSE_ALGORITHM_ES256 => OPENSSL_ALGO_SHA256,
|
||||
self::COSE_ALGORITHM_ES384 => OPENSSL_ALGO_SHA384,
|
||||
self::COSE_ALGORITHM_ES512 => OPENSSL_ALGO_SHA512,
|
||||
self::COSE_ALGORITHM_RS256 => OPENSSL_ALGO_SHA256,
|
||||
self::COSE_ALGORITHM_RS384 => OPENSSL_ALGO_SHA384,
|
||||
self::COSE_ALGORITHM_RS512 => OPENSSL_ALGO_SHA512,
|
||||
self::COSE_ALGORITHM_RS1 => OPENSSL_ALGO_SHA1,
|
||||
];
|
||||
|
||||
final public const COSE_HASH_MAP = [
|
||||
self::COSE_ALGORITHM_ES256K => 'sha256',
|
||||
self::COSE_ALGORITHM_ES256 => 'sha256',
|
||||
self::COSE_ALGORITHM_ES384 => 'sha384',
|
||||
self::COSE_ALGORITHM_ES512 => 'sha512',
|
||||
self::COSE_ALGORITHM_RS256 => 'sha256',
|
||||
self::COSE_ALGORITHM_RS384 => 'sha384',
|
||||
self::COSE_ALGORITHM_RS512 => 'sha512',
|
||||
self::COSE_ALGORITHM_PS256 => 'sha256',
|
||||
self::COSE_ALGORITHM_PS384 => 'sha384',
|
||||
self::COSE_ALGORITHM_PS512 => 'sha512',
|
||||
self::COSE_ALGORITHM_RS1 => 'sha1',
|
||||
];
|
||||
|
||||
public static function getOpensslAlgorithmFor(int $algorithmIdentifier): int
|
||||
{
|
||||
if (! array_key_exists($algorithmIdentifier, self::COSE_ALGORITHM_MAP)) {
|
||||
throw new InvalidArgumentException('The specified algorithm identifier is not supported');
|
||||
}
|
||||
|
||||
return self::COSE_ALGORITHM_MAP[$algorithmIdentifier];
|
||||
}
|
||||
|
||||
public static function getHashAlgorithmFor(int $algorithmIdentifier): string
|
||||
{
|
||||
if (! array_key_exists($algorithmIdentifier, self::COSE_HASH_MAP)) {
|
||||
throw new InvalidArgumentException('The specified algorithm identifier is not supported');
|
||||
}
|
||||
|
||||
return self::COSE_HASH_MAP[$algorithmIdentifier];
|
||||
}
|
||||
}
|
||||
108
libraries/vendor/web-auth/cose-lib/src/BigInteger.php
vendored
Normal file
108
libraries/vendor/web-auth/cose-lib/src/BigInteger.php
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose;
|
||||
|
||||
use Brick\Math\BigInteger as BrickBigInteger;
|
||||
use function chr;
|
||||
use function hex2bin;
|
||||
use function unpack;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BigInteger
|
||||
{
|
||||
private function __construct(
|
||||
private readonly BrickBigInteger $value
|
||||
) {
|
||||
}
|
||||
|
||||
public static function createFromBinaryString(string $value): self
|
||||
{
|
||||
$res = unpack('H*', $value);
|
||||
$data = current($res);
|
||||
|
||||
return new self(BrickBigInteger::fromBase($data, 16));
|
||||
}
|
||||
|
||||
public static function createFromDecimal(int $value): self
|
||||
{
|
||||
return new self(BrickBigInteger::of($value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a BigInteger to a binary string.
|
||||
*/
|
||||
public function toBytes(): string
|
||||
{
|
||||
if ($this->value->isEqualTo(BrickBigInteger::zero())) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$temp = $this->value->toBase(16);
|
||||
$temp = 0 !== (mb_strlen($temp, '8bit') & 1) ? '0' . $temp : $temp;
|
||||
$temp = hex2bin($temp);
|
||||
|
||||
return ltrim($temp, chr(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds two BigIntegers.
|
||||
*/
|
||||
public function add(self $y): self
|
||||
{
|
||||
$value = $this->value->plus($y->value);
|
||||
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtracts two BigIntegers.
|
||||
*/
|
||||
public function subtract(self $y): self
|
||||
{
|
||||
$value = $this->value->minus($y->value);
|
||||
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies two BigIntegers.
|
||||
*/
|
||||
public function multiply(self $x): self
|
||||
{
|
||||
$value = $this->value->multipliedBy($x->value);
|
||||
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs modular exponentiation.
|
||||
*/
|
||||
public function modPow(self $e, self $n): self
|
||||
{
|
||||
$value = $this->value->modPow($e->value, $n->value);
|
||||
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs modular exponentiation.
|
||||
*/
|
||||
public function mod(self $d): self
|
||||
{
|
||||
$value = $this->value->mod($d->value);
|
||||
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two numbers.
|
||||
*/
|
||||
public function compare(self $y): int
|
||||
{
|
||||
return $this->value->compareTo($y->value);
|
||||
}
|
||||
}
|
||||
61
libraries/vendor/web-auth/cose-lib/src/Hash.php
vendored
Normal file
61
libraries/vendor/web-auth/cose-lib/src/Hash.php
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Hash
|
||||
{
|
||||
private function __construct(
|
||||
private readonly string $hash,
|
||||
private readonly int $length,
|
||||
private readonly string $t
|
||||
) {
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
195
libraries/vendor/web-auth/cose-lib/src/Key/Ec2Key.php
vendored
Normal file
195
libraries/vendor/web-auth/cose-lib/src/Key/Ec2Key.php
vendored
Normal file
@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Key;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use SpomkyLabs\Pki\ASN1\Type\Constructed\Sequence;
|
||||
use SpomkyLabs\Pki\ASN1\Type\Primitive\BitString;
|
||||
use SpomkyLabs\Pki\ASN1\Type\Primitive\Integer;
|
||||
use SpomkyLabs\Pki\ASN1\Type\Primitive\ObjectIdentifier;
|
||||
use SpomkyLabs\Pki\ASN1\Type\Primitive\OctetString;
|
||||
use SpomkyLabs\Pki\ASN1\Type\Tagged\ExplicitlyTaggedType;
|
||||
use function array_key_exists;
|
||||
use function in_array;
|
||||
use function is_int;
|
||||
|
||||
/**
|
||||
* @final
|
||||
* @see \Cose\Tests\Key\Ec2KeyTest
|
||||
*/
|
||||
class Ec2Key extends Key
|
||||
{
|
||||
final public const CURVE_P256 = 1;
|
||||
|
||||
final public const CURVE_P256K = 8;
|
||||
|
||||
final public const CURVE_P384 = 2;
|
||||
|
||||
final public const CURVE_P521 = 3;
|
||||
|
||||
final public const CURVE_NAME_P256 = 'P-256';
|
||||
|
||||
final public const CURVE_NAME_P256K = 'P-256K';
|
||||
|
||||
final public const CURVE_NAME_P384 = 'P-384';
|
||||
|
||||
final public const CURVE_NAME_P521 = 'P-521';
|
||||
|
||||
final public const DATA_CURVE = -1;
|
||||
|
||||
final public const DATA_X = -2;
|
||||
|
||||
final public const DATA_Y = -3;
|
||||
|
||||
final public const DATA_D = -4;
|
||||
|
||||
private const SUPPORTED_CURVES_INT = [self::CURVE_P256, self::CURVE_P256K, self::CURVE_P384, self::CURVE_P521];
|
||||
|
||||
private const SUPPORTED_CURVES_NAMES = [
|
||||
self::CURVE_NAME_P256,
|
||||
self::CURVE_NAME_P256K,
|
||||
self::CURVE_NAME_P384,
|
||||
self::CURVE_NAME_P521,
|
||||
];
|
||||
|
||||
private const NAMED_CURVE_OID = [
|
||||
self::CURVE_P256 => '1.2.840.10045.3.1.7',
|
||||
// NIST P-256 / secp256r1
|
||||
self::CURVE_P256K => '1.3.132.0.10',
|
||||
// NIST P-256K / secp256k1
|
||||
self::CURVE_P384 => '1.3.132.0.34',
|
||||
// NIST P-384 / secp384r1
|
||||
self::CURVE_P521 => '1.3.132.0.35',
|
||||
// NIST P-521 / secp521r1
|
||||
];
|
||||
|
||||
private const CURVE_KEY_LENGTH = [
|
||||
self::CURVE_P256 => 32,
|
||||
self::CURVE_P256K => 32,
|
||||
self::CURVE_P384 => 48,
|
||||
self::CURVE_P521 => 66,
|
||||
self::CURVE_NAME_P256 => 32,
|
||||
self::CURVE_NAME_P256K => 32,
|
||||
self::CURVE_NAME_P384 => 48,
|
||||
self::CURVE_NAME_P521 => 66,
|
||||
];
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $data
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
foreach ([self::DATA_CURVE, self::TYPE] as $key) {
|
||||
if (is_numeric($data[$key])) {
|
||||
$data[$key] = (int) $data[$key];
|
||||
}
|
||||
}
|
||||
parent::__construct($data);
|
||||
if ($data[self::TYPE] !== self::TYPE_EC2 && $data[self::TYPE] !== self::TYPE_NAME_EC2) {
|
||||
throw new InvalidArgumentException('Invalid EC2 key. The key type does not correspond to an EC2 key');
|
||||
}
|
||||
if (! isset($data[self::DATA_CURVE], $data[self::DATA_X], $data[self::DATA_Y])) {
|
||||
throw new InvalidArgumentException('Invalid EC2 key. The curve or the "x/y" coordinates are missing');
|
||||
}
|
||||
if (mb_strlen((string) $data[self::DATA_X], '8bit') !== self::CURVE_KEY_LENGTH[$data[self::DATA_CURVE]]) {
|
||||
throw new InvalidArgumentException('Invalid length for x coordinate');
|
||||
}
|
||||
if (mb_strlen((string) $data[self::DATA_Y], '8bit') !== self::CURVE_KEY_LENGTH[$data[self::DATA_CURVE]]) {
|
||||
throw new InvalidArgumentException('Invalid length for y coordinate');
|
||||
}
|
||||
if (is_int($data[self::DATA_CURVE])) {
|
||||
if (! in_array($data[self::DATA_CURVE], self::SUPPORTED_CURVES_INT, true)) {
|
||||
throw new InvalidArgumentException('The curve is not supported');
|
||||
}
|
||||
} elseif (! in_array($data[self::DATA_CURVE], self::SUPPORTED_CURVES_NAMES, true)) {
|
||||
throw new InvalidArgumentException('The curve is not supported');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $data
|
||||
*/
|
||||
public static function create(array $data): self
|
||||
{
|
||||
return new self($data);
|
||||
}
|
||||
|
||||
public function toPublic(): self
|
||||
{
|
||||
$data = $this->getData();
|
||||
unset($data[self::DATA_D]);
|
||||
|
||||
return new self($data);
|
||||
}
|
||||
|
||||
public function x(): string
|
||||
{
|
||||
return $this->get(self::DATA_X);
|
||||
}
|
||||
|
||||
public function y(): string
|
||||
{
|
||||
return $this->get(self::DATA_Y);
|
||||
}
|
||||
|
||||
public function isPrivate(): bool
|
||||
{
|
||||
return array_key_exists(self::DATA_D, $this->getData());
|
||||
}
|
||||
|
||||
public function d(): string
|
||||
{
|
||||
if (! $this->isPrivate()) {
|
||||
throw new InvalidArgumentException('The key is not private.');
|
||||
}
|
||||
return $this->get(self::DATA_D);
|
||||
}
|
||||
|
||||
public function curve(): int|string
|
||||
{
|
||||
return $this->get(self::DATA_CURVE);
|
||||
}
|
||||
|
||||
public function asPEM(): string
|
||||
{
|
||||
if ($this->isPrivate()) {
|
||||
$der = Sequence::create(
|
||||
Integer::create(1),
|
||||
OctetString::create($this->d()),
|
||||
ExplicitlyTaggedType::create(0, ObjectIdentifier::create($this->getCurveOid())),
|
||||
ExplicitlyTaggedType::create(1, BitString::create($this->getUncompressedCoordinates())),
|
||||
);
|
||||
|
||||
return $this->pem('EC PRIVATE KEY', $der->toDER());
|
||||
}
|
||||
|
||||
$der = Sequence::create(
|
||||
Sequence::create(
|
||||
ObjectIdentifier::create('1.2.840.10045.2.1'),
|
||||
ObjectIdentifier::create($this->getCurveOid())
|
||||
),
|
||||
BitString::create($this->getUncompressedCoordinates())
|
||||
);
|
||||
|
||||
return $this->pem('PUBLIC KEY', $der->toDER());
|
||||
}
|
||||
|
||||
public function getUncompressedCoordinates(): string
|
||||
{
|
||||
return "\x04" . $this->x() . $this->y();
|
||||
}
|
||||
|
||||
private function getCurveOid(): string
|
||||
{
|
||||
return self::NAMED_CURVE_OID[$this->curve()];
|
||||
}
|
||||
|
||||
private function pem(string $type, string $der): string
|
||||
{
|
||||
return sprintf("-----BEGIN %s-----\n", mb_strtoupper($type)) .
|
||||
chunk_split(base64_encode($der), 64, "\n") .
|
||||
sprintf("-----END %s-----\n", mb_strtoupper($type));
|
||||
}
|
||||
}
|
||||
111
libraries/vendor/web-auth/cose-lib/src/Key/Key.php
vendored
Normal file
111
libraries/vendor/web-auth/cose-lib/src/Key/Key.php
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Key;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use function array_key_exists;
|
||||
|
||||
class Key
|
||||
{
|
||||
public const TYPE = 1;
|
||||
|
||||
public const TYPE_OKP = 1;
|
||||
|
||||
public const TYPE_EC2 = 2;
|
||||
|
||||
public const TYPE_RSA = 3;
|
||||
|
||||
public const TYPE_OCT = 4;
|
||||
|
||||
public const TYPE_NAME_OKP = 'OKP';
|
||||
|
||||
public const TYPE_NAME_EC2 = 'EC';
|
||||
|
||||
public const TYPE_NAME_RSA = 'RSA';
|
||||
|
||||
public const TYPE_NAME_OCT = 'oct';
|
||||
|
||||
public const KID = 2;
|
||||
|
||||
public const ALG = 3;
|
||||
|
||||
public const KEY_OPS = 4;
|
||||
|
||||
public const BASE_IV = 5;
|
||||
|
||||
/**
|
||||
* @var array<int|string, mixed>
|
||||
*/
|
||||
private readonly array $data;
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $data
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
if (! array_key_exists(self::TYPE, $data)) {
|
||||
throw new InvalidArgumentException('Invalid key: the type is not defined');
|
||||
}
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $data
|
||||
*/
|
||||
public static function create(array $data): self
|
||||
{
|
||||
return new self($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, mixed> $data
|
||||
*/
|
||||
public static function createFromData(array $data): self
|
||||
{
|
||||
if (! array_key_exists(self::TYPE, $data)) {
|
||||
throw new InvalidArgumentException('Invalid key: the type is not defined');
|
||||
}
|
||||
|
||||
return match ($data[self::TYPE]) {
|
||||
'1' => new OkpKey($data),
|
||||
'2' => new Ec2Key($data),
|
||||
'3' => new RsaKey($data),
|
||||
'4' => new SymmetricKey($data),
|
||||
default => self::create($data),
|
||||
};
|
||||
}
|
||||
|
||||
public function type(): int|string
|
||||
{
|
||||
return $this->data[self::TYPE];
|
||||
}
|
||||
|
||||
public function alg(): int
|
||||
{
|
||||
return (int) $this->get(self::ALG);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int|string, mixed>
|
||||
*/
|
||||
public function getData(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function has(int|string $key): bool
|
||||
{
|
||||
return array_key_exists($key, $this->data);
|
||||
}
|
||||
|
||||
public function get(int|string $key): mixed
|
||||
{
|
||||
if (! array_key_exists($key, $this->data)) {
|
||||
throw new InvalidArgumentException(sprintf('The key has no data at index %d', $key));
|
||||
}
|
||||
|
||||
return $this->data[$key];
|
||||
}
|
||||
}
|
||||
110
libraries/vendor/web-auth/cose-lib/src/Key/OkpKey.php
vendored
Normal file
110
libraries/vendor/web-auth/cose-lib/src/Key/OkpKey.php
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Key;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use function array_key_exists;
|
||||
use function in_array;
|
||||
|
||||
/**
|
||||
* @final
|
||||
* @see \Cose\Tests\Key\OkpKeyTest
|
||||
*/
|
||||
class OkpKey extends Key
|
||||
{
|
||||
final public const CURVE_X25519 = 4;
|
||||
|
||||
final public const CURVE_X448 = 5;
|
||||
|
||||
final public const CURVE_ED25519 = 6;
|
||||
|
||||
final public const CURVE_ED448 = 7;
|
||||
|
||||
final public const CURVE_NAME_X25519 = 'X25519';
|
||||
|
||||
final public const CURVE_NAME_X448 = 'X448';
|
||||
|
||||
final public const CURVE_NAME_ED25519 = 'Ed25519';
|
||||
|
||||
final public const CURVE_NAME_ED448 = 'Ed448';
|
||||
|
||||
final public const DATA_CURVE = -1;
|
||||
|
||||
final public const DATA_X = -2;
|
||||
|
||||
final public const DATA_D = -4;
|
||||
|
||||
private const SUPPORTED_CURVES_INT = [
|
||||
self::CURVE_X25519,
|
||||
self::CURVE_X448,
|
||||
self::CURVE_ED25519,
|
||||
self::CURVE_ED448,
|
||||
];
|
||||
|
||||
private const SUPPORTED_CURVES_NAME = [
|
||||
self::CURVE_NAME_X25519,
|
||||
self::CURVE_NAME_X448,
|
||||
self::CURVE_NAME_ED25519,
|
||||
self::CURVE_NAME_ED448,
|
||||
];
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $data
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
foreach ([self::DATA_CURVE, self::TYPE] as $key) {
|
||||
if (is_numeric($data[$key])) {
|
||||
$data[$key] = (int) $data[$key];
|
||||
}
|
||||
}
|
||||
parent::__construct($data);
|
||||
if ($data[self::TYPE] !== self::TYPE_OKP && $data[self::TYPE] !== self::TYPE_NAME_OKP) {
|
||||
throw new InvalidArgumentException('Invalid OKP key. The key type does not correspond to an OKP key');
|
||||
}
|
||||
if (! isset($data[self::DATA_CURVE], $data[self::DATA_X])) {
|
||||
throw new InvalidArgumentException('Invalid EC2 key. The curve or the "x" coordinate is missing');
|
||||
}
|
||||
if (is_numeric($data[self::DATA_CURVE])) {
|
||||
if (! in_array((int) $data[self::DATA_CURVE], self::SUPPORTED_CURVES_INT, true)) {
|
||||
throw new InvalidArgumentException('The curve is not supported');
|
||||
}
|
||||
} elseif (! in_array($data[self::DATA_CURVE], self::SUPPORTED_CURVES_NAME, true)) {
|
||||
throw new InvalidArgumentException('The curve is not supported');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $data
|
||||
*/
|
||||
public static function create(array $data): self
|
||||
{
|
||||
return new self($data);
|
||||
}
|
||||
|
||||
public function x(): string
|
||||
{
|
||||
return $this->get(self::DATA_X);
|
||||
}
|
||||
|
||||
public function isPrivate(): bool
|
||||
{
|
||||
return array_key_exists(self::DATA_D, $this->getData());
|
||||
}
|
||||
|
||||
public function d(): string
|
||||
{
|
||||
if (! $this->isPrivate()) {
|
||||
throw new InvalidArgumentException('The key is not private.');
|
||||
}
|
||||
|
||||
return $this->get(self::DATA_D);
|
||||
}
|
||||
|
||||
public function curve(): int|string
|
||||
{
|
||||
return $this->get(self::DATA_CURVE);
|
||||
}
|
||||
}
|
||||
262
libraries/vendor/web-auth/cose-lib/src/Key/RsaKey.php
vendored
Normal file
262
libraries/vendor/web-auth/cose-lib/src/Key/RsaKey.php
vendored
Normal file
@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Key;
|
||||
|
||||
use Brick\Math\BigInteger;
|
||||
use InvalidArgumentException;
|
||||
use SpomkyLabs\Pki\CryptoTypes\Asymmetric\PublicKeyInfo;
|
||||
use SpomkyLabs\Pki\CryptoTypes\Asymmetric\RSA\RSAPrivateKey;
|
||||
use SpomkyLabs\Pki\CryptoTypes\Asymmetric\RSA\RSAPublicKey;
|
||||
use function array_key_exists;
|
||||
use function in_array;
|
||||
|
||||
/**
|
||||
* @final
|
||||
* @see \Cose\Tests\Key\RsaKeyTest
|
||||
*/
|
||||
class RsaKey extends Key
|
||||
{
|
||||
final public const DATA_N = -1;
|
||||
|
||||
final public const DATA_E = -2;
|
||||
|
||||
final public const DATA_D = -3;
|
||||
|
||||
final public const DATA_P = -4;
|
||||
|
||||
final public const DATA_Q = -5;
|
||||
|
||||
final public const DATA_DP = -6;
|
||||
|
||||
final public const DATA_DQ = -7;
|
||||
|
||||
final public const DATA_QI = -8;
|
||||
|
||||
final public const DATA_OTHER = -9;
|
||||
|
||||
final public const DATA_RI = -10;
|
||||
|
||||
final public const DATA_DI = -11;
|
||||
|
||||
final public const DATA_TI = -12;
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $data
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
foreach ([self::TYPE] as $key) {
|
||||
if (is_numeric($data[$key])) {
|
||||
$data[$key] = (int) $data[$key];
|
||||
}
|
||||
}
|
||||
parent::__construct($data);
|
||||
if ($data[self::TYPE] !== self::TYPE_RSA && $data[self::TYPE] !== self::TYPE_NAME_RSA) {
|
||||
throw new InvalidArgumentException('Invalid RSA key. The key type does not correspond to a RSA key');
|
||||
}
|
||||
if (! isset($data[self::DATA_N], $data[self::DATA_E])) {
|
||||
throw new InvalidArgumentException('Invalid RSA key. The modulus or the exponent is missing');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $data
|
||||
*/
|
||||
public static function create(array $data): self
|
||||
{
|
||||
return new self($data);
|
||||
}
|
||||
|
||||
public function n(): string
|
||||
{
|
||||
return $this->get(self::DATA_N);
|
||||
}
|
||||
|
||||
public function e(): string
|
||||
{
|
||||
return $this->get(self::DATA_E);
|
||||
}
|
||||
|
||||
public function d(): string
|
||||
{
|
||||
$this->checkKeyIsPrivate();
|
||||
|
||||
return $this->get(self::DATA_D);
|
||||
}
|
||||
|
||||
public function p(): string
|
||||
{
|
||||
$this->checkKeyIsPrivate();
|
||||
|
||||
return $this->get(self::DATA_P);
|
||||
}
|
||||
|
||||
public function q(): string
|
||||
{
|
||||
$this->checkKeyIsPrivate();
|
||||
|
||||
return $this->get(self::DATA_Q);
|
||||
}
|
||||
|
||||
public function dP(): string
|
||||
{
|
||||
$this->checkKeyIsPrivate();
|
||||
|
||||
return $this->get(self::DATA_DP);
|
||||
}
|
||||
|
||||
public function dQ(): string
|
||||
{
|
||||
$this->checkKeyIsPrivate();
|
||||
|
||||
return $this->get(self::DATA_DQ);
|
||||
}
|
||||
|
||||
public function QInv(): string
|
||||
{
|
||||
$this->checkKeyIsPrivate();
|
||||
|
||||
return $this->get(self::DATA_QI);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function other(): array
|
||||
{
|
||||
$this->checkKeyIsPrivate();
|
||||
|
||||
return $this->get(self::DATA_OTHER);
|
||||
}
|
||||
|
||||
public function rI(): string
|
||||
{
|
||||
$this->checkKeyIsPrivate();
|
||||
|
||||
return $this->get(self::DATA_RI);
|
||||
}
|
||||
|
||||
public function dI(): string
|
||||
{
|
||||
$this->checkKeyIsPrivate();
|
||||
|
||||
return $this->get(self::DATA_DI);
|
||||
}
|
||||
|
||||
public function tI(): string
|
||||
{
|
||||
$this->checkKeyIsPrivate();
|
||||
|
||||
return $this->get(self::DATA_TI);
|
||||
}
|
||||
|
||||
public function hasPrimes(): bool
|
||||
{
|
||||
return $this->has(self::DATA_P) && $this->has(self::DATA_Q);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function primes(): array
|
||||
{
|
||||
return [$this->p(), $this->q()];
|
||||
}
|
||||
|
||||
public function hasExponents(): bool
|
||||
{
|
||||
return $this->has(self::DATA_DP) && $this->has(self::DATA_DQ);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function exponents(): array
|
||||
{
|
||||
return [$this->dP(), $this->dQ()];
|
||||
}
|
||||
|
||||
public function hasCoefficient(): bool
|
||||
{
|
||||
return $this->has(self::DATA_QI);
|
||||
}
|
||||
|
||||
public function isPublic(): bool
|
||||
{
|
||||
return ! $this->isPrivate();
|
||||
}
|
||||
|
||||
public function isPrivate(): bool
|
||||
{
|
||||
return array_key_exists(self::DATA_D, $this->getData());
|
||||
}
|
||||
|
||||
public function asPem(): string
|
||||
{
|
||||
if ($this->isPrivate()) {
|
||||
$privateKey = RSAPrivateKey::create(
|
||||
$this->binaryToBigInteger($this->n()),
|
||||
$this->binaryToBigInteger($this->e()),
|
||||
$this->binaryToBigInteger($this->d()),
|
||||
$this->binaryToBigInteger($this->p()),
|
||||
$this->binaryToBigInteger($this->q()),
|
||||
$this->binaryToBigInteger($this->dP()),
|
||||
$this->binaryToBigInteger($this->dQ()),
|
||||
$this->binaryToBigInteger($this->QInv())
|
||||
);
|
||||
|
||||
return $privateKey->toPEM()
|
||||
->string();
|
||||
}
|
||||
|
||||
$publicKey = RSAPublicKey::create(
|
||||
$this->binaryToBigInteger($this->n()),
|
||||
$this->binaryToBigInteger($this->e())
|
||||
);
|
||||
$rsaKey = PublicKeyInfo::fromPublicKey($publicKey);
|
||||
|
||||
return $rsaKey->toPEM()
|
||||
->string();
|
||||
}
|
||||
|
||||
public function toPublic(): static
|
||||
{
|
||||
$toBeRemoved = [
|
||||
self::DATA_D,
|
||||
self::DATA_P,
|
||||
self::DATA_Q,
|
||||
self::DATA_DP,
|
||||
self::DATA_DQ,
|
||||
self::DATA_QI,
|
||||
self::DATA_OTHER,
|
||||
self::DATA_RI,
|
||||
self::DATA_DI,
|
||||
self::DATA_TI,
|
||||
];
|
||||
$data = $this->getData();
|
||||
foreach ($data as $k => $v) {
|
||||
if (in_array($k, $toBeRemoved, true)) {
|
||||
unset($data[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
return new static($data);
|
||||
}
|
||||
|
||||
private function checkKeyIsPrivate(): void
|
||||
{
|
||||
if (! $this->isPrivate()) {
|
||||
throw new InvalidArgumentException('The key is not private.');
|
||||
}
|
||||
}
|
||||
|
||||
private function binaryToBigInteger(string $data): string
|
||||
{
|
||||
$res = unpack('H*', $data);
|
||||
$res = current($res);
|
||||
|
||||
return BigInteger::fromBase($res, 16)->toBase(10);
|
||||
}
|
||||
}
|
||||
44
libraries/vendor/web-auth/cose-lib/src/Key/SymmetricKey.php
vendored
Normal file
44
libraries/vendor/web-auth/cose-lib/src/Key/SymmetricKey.php
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cose\Key;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class SymmetricKey extends Key
|
||||
{
|
||||
final public const DATA_K = -1;
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $data
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
parent::__construct($data);
|
||||
if (! isset($data[self::TYPE]) || (int) $data[self::TYPE] !== self::TYPE_OCT) {
|
||||
throw new InvalidArgumentException(
|
||||
'Invalid symmetric key. The key type does not correspond to a symmetric key'
|
||||
);
|
||||
}
|
||||
if (! isset($data[self::DATA_K])) {
|
||||
throw new InvalidArgumentException('Invalid symmetric key. The parameter "k" is missing');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $data
|
||||
*/
|
||||
public static function create(array $data): self
|
||||
{
|
||||
return new self($data);
|
||||
}
|
||||
|
||||
public function k(): string
|
||||
{
|
||||
return $this->get(self::DATA_K);
|
||||
}
|
||||
}
|
||||
21
libraries/vendor/web-auth/metadata-service/LICENSE
vendored
Normal file
21
libraries/vendor/web-auth/metadata-service/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2022 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.
|
||||
12
libraries/vendor/web-auth/metadata-service/src/CanLogData.php
vendored
Normal file
12
libraries/vendor/web-auth/metadata-service/src/CanLogData.php
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
interface CanLogData
|
||||
{
|
||||
public function setLogger(LoggerInterface $logger): void;
|
||||
}
|
||||
14
libraries/vendor/web-auth/metadata-service/src/CertificateChain/CertificateChainValidator.php
vendored
Normal file
14
libraries/vendor/web-auth/metadata-service/src/CertificateChain/CertificateChainValidator.php
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\CertificateChain;
|
||||
|
||||
interface CertificateChainValidator
|
||||
{
|
||||
/**
|
||||
* @param string[] $untrustedCertificates
|
||||
* @param string[] $trustedCertificates
|
||||
*/
|
||||
public function check(array $untrustedCertificates, array $trustedCertificates): void;
|
||||
}
|
||||
95
libraries/vendor/web-auth/metadata-service/src/CertificateChain/CertificateToolbox.php
vendored
Normal file
95
libraries/vendor/web-auth/metadata-service/src/CertificateChain/CertificateToolbox.php
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\CertificateChain;
|
||||
|
||||
use function in_array;
|
||||
use ParagonIE\ConstantTime\Base64;
|
||||
use const PHP_EOL;
|
||||
use function preg_replace;
|
||||
|
||||
class CertificateToolbox
|
||||
{
|
||||
/**
|
||||
* @param string[] $data
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function fixPEMStructures(array $data, string $type = 'CERTIFICATE'): array
|
||||
{
|
||||
return array_map(static fn ($d): string => self::fixPEMStructure($d, $type), $data);
|
||||
}
|
||||
|
||||
public static function fixPEMStructure(string $data, string $type = 'CERTIFICATE'): string
|
||||
{
|
||||
if (str_contains($data, '-----BEGIN')) {
|
||||
return trim($data);
|
||||
}
|
||||
$pem = '-----BEGIN ' . $type . '-----' . PHP_EOL;
|
||||
$pem .= chunk_split($data, 64, PHP_EOL);
|
||||
|
||||
return $pem . ('-----END ' . $type . '-----' . PHP_EOL);
|
||||
}
|
||||
|
||||
public static function convertPEMToDER(string $data): string
|
||||
{
|
||||
if (! str_contains($data, '-----BEGIN')) {
|
||||
return $data;
|
||||
}
|
||||
$data = preg_replace('/[\-]{5}.*[\-]{5}[\r\n]*/', '', $data);
|
||||
$data = preg_replace("/[\r\n]*/", '', $data);
|
||||
|
||||
return Base64::decode(trim($data), true);
|
||||
}
|
||||
|
||||
public static function convertDERToPEM(string $data, string $type = 'CERTIFICATE'): string
|
||||
{
|
||||
if (str_contains($data, '-----BEGIN')) {
|
||||
return $data;
|
||||
}
|
||||
$der = self::unusedBytesFix($data);
|
||||
|
||||
return self::fixPEMStructure(base64_encode($der), $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $data
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function convertAllDERToPEM(iterable $data, string $type = 'CERTIFICATE'): array
|
||||
{
|
||||
$certificates = [];
|
||||
foreach ($data as $d) {
|
||||
$certificates[] = self::convertDERToPEM($d, $type);
|
||||
}
|
||||
|
||||
return $certificates;
|
||||
}
|
||||
|
||||
private static function unusedBytesFix(string $data): string
|
||||
{
|
||||
$hash = hash('sha256', $data);
|
||||
if (in_array($hash, self::getCertificateHashes(), true)) {
|
||||
$data[mb_strlen($data, '8bit') - 257] = "\0";
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private static function getCertificateHashes(): array
|
||||
{
|
||||
return [
|
||||
'349bca1031f8c82c4ceca38b9cebf1a69df9fb3b94eed99eb3fb9aa3822d26e8',
|
||||
'dd574527df608e47ae45fbba75a2afdd5c20fd94a02419381813cd55a2a3398f',
|
||||
'1d8764f0f7cd1352df6150045c8f638e517270e8b5dda1c63ade9c2280240cae',
|
||||
'd0edc9a91a1677435a953390865d208c55b3183c6759c9b5a7ff494c322558eb',
|
||||
'6073c436dcd064a48127ddbf6032ac1a66fd59a0c24434f070d4e564c124c897',
|
||||
'ca993121846c464d666096d35f13bf44c1b05af205f9b4a1e00cf6cc10c5e511',
|
||||
];
|
||||
}
|
||||
}
|
||||
271
libraries/vendor/web-auth/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php
vendored
Normal file
271
libraries/vendor/web-auth/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php
vendored
Normal file
@ -0,0 +1,271 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\CertificateChain;
|
||||
|
||||
use function count;
|
||||
use DateTimeZone;
|
||||
use function in_array;
|
||||
use Lcobucci\Clock\Clock;
|
||||
use Lcobucci\Clock\SystemClock;
|
||||
use function parse_url;
|
||||
use const PHP_EOL;
|
||||
use const PHP_URL_SCHEME;
|
||||
use Psr\Clock\ClockInterface;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Http\Client\ClientInterface;
|
||||
use Psr\Http\Message\RequestFactoryInterface;
|
||||
use SpomkyLabs\Pki\ASN1\Type\UnspecifiedType;
|
||||
use SpomkyLabs\Pki\CryptoEncoding\PEM;
|
||||
use SpomkyLabs\Pki\X509\Certificate\Certificate;
|
||||
use SpomkyLabs\Pki\X509\CertificationPath\CertificationPath;
|
||||
use SpomkyLabs\Pki\X509\CertificationPath\PathValidation\PathValidationConfig;
|
||||
use Throwable;
|
||||
use Webauthn\MetadataService\Event\BeforeCertificateChainValidation;
|
||||
use Webauthn\MetadataService\Event\CanDispatchEvents;
|
||||
use Webauthn\MetadataService\Event\CertificateChainValidationFailed;
|
||||
use Webauthn\MetadataService\Event\CertificateChainValidationSucceeded;
|
||||
use Webauthn\MetadataService\Event\NullEventDispatcher;
|
||||
use Webauthn\MetadataService\Exception\CertificateChainException;
|
||||
use Webauthn\MetadataService\Exception\CertificateRevocationListException;
|
||||
use Webauthn\MetadataService\Exception\InvalidCertificateException;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class PhpCertificateChainValidator implements CertificateChainValidator, CanDispatchEvents
|
||||
{
|
||||
private const MAX_VALIDATION_LENGTH = 5;
|
||||
|
||||
private readonly Clock|ClockInterface $clock;
|
||||
|
||||
private EventDispatcherInterface $dispatcher;
|
||||
|
||||
public function __construct(
|
||||
private readonly ClientInterface $client,
|
||||
private readonly RequestFactoryInterface $requestFactory,
|
||||
null|Clock|ClockInterface $clock = null,
|
||||
private readonly bool $allowFailures = true
|
||||
) {
|
||||
if ($clock === null) {
|
||||
trigger_deprecation(
|
||||
'web-auth/metadata-service',
|
||||
'4.5.0',
|
||||
'The parameter "$clock" will become mandatory in 5.0.0. Please set a valid PSR Clock implementation instead of "null".'
|
||||
);
|
||||
$clock = new SystemClock(new DateTimeZone('UTC'));
|
||||
}
|
||||
$this->clock = $clock;
|
||||
$this->dispatcher = new NullEventDispatcher();
|
||||
}
|
||||
|
||||
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void
|
||||
{
|
||||
$this->dispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $untrustedCertificates
|
||||
* @param string[] $trustedCertificates
|
||||
*/
|
||||
public function check(array $untrustedCertificates, array $trustedCertificates): void
|
||||
{
|
||||
foreach ($trustedCertificates as $trustedCertificate) {
|
||||
$this->dispatcher->dispatch(
|
||||
BeforeCertificateChainValidation::create($untrustedCertificates, $trustedCertificate)
|
||||
);
|
||||
try {
|
||||
if ($this->validateChain($untrustedCertificates, $trustedCertificate)) {
|
||||
$this->dispatcher->dispatch(
|
||||
CertificateChainValidationSucceeded::create($untrustedCertificates, $trustedCertificate)
|
||||
);
|
||||
return;
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
$this->dispatcher->dispatch(
|
||||
CertificateChainValidationFailed::create($untrustedCertificates, $trustedCertificate)
|
||||
);
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
throw CertificateChainException::create($untrustedCertificates, $trustedCertificates);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $untrustedCertificates
|
||||
*/
|
||||
private function validateChain(array $untrustedCertificates, string $trustedCertificate): bool
|
||||
{
|
||||
$untrustedCertificates = array_map(
|
||||
static fn (string $cert): Certificate => Certificate::fromPEM(PEM::fromString($cert)),
|
||||
array_reverse($untrustedCertificates)
|
||||
);
|
||||
$trustedCertificate = Certificate::fromPEM(PEM::fromString($trustedCertificate));
|
||||
|
||||
// The trust path and the authenticator certificate are the same
|
||||
if (count(
|
||||
$untrustedCertificates
|
||||
) === 1 && $untrustedCertificates[0]->toPEM()->string() === $trustedCertificate->toPEM()->string()) {
|
||||
return true;
|
||||
}
|
||||
$uniqueCertificates = array_map(
|
||||
static fn (Certificate $cert): string => $cert->toPEM()
|
||||
->string(),
|
||||
array_merge($untrustedCertificates, [$trustedCertificate])
|
||||
);
|
||||
count(array_unique($uniqueCertificates)) === count(
|
||||
$uniqueCertificates
|
||||
) || throw CertificateChainException::create(
|
||||
$untrustedCertificates,
|
||||
[$trustedCertificate],
|
||||
'Invalid certificate chain with duplicated certificates.'
|
||||
);
|
||||
|
||||
if (! $this->validateCertificates($trustedCertificate, ...$untrustedCertificates)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$certificates = array_merge([$trustedCertificate], $untrustedCertificates);
|
||||
$numCerts = count($certificates);
|
||||
for ($i = 1; $i < $numCerts; $i++) {
|
||||
if ($this->isRevoked($certificates[$i])) {
|
||||
throw CertificateChainException::create(
|
||||
$untrustedCertificates,
|
||||
[$trustedCertificate],
|
||||
'Unable to validate the certificate chain. Revoked certificate found.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function isRevoked(Certificate $subject): bool
|
||||
{
|
||||
try {
|
||||
$csn = $subject->tbsCertificate()
|
||||
->serialNumber();
|
||||
} catch (Throwable $e) {
|
||||
throw InvalidCertificateException::create(
|
||||
$subject->toPEM()
|
||||
->string(),
|
||||
sprintf('Failed to parse certificate: %s', $e->getMessage()),
|
||||
$e
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$urls = $this->getCrlUrlList($subject);
|
||||
} catch (Throwable $e) {
|
||||
if ($this->allowFailures) {
|
||||
return false;
|
||||
}
|
||||
throw InvalidCertificateException::create(
|
||||
$subject->toPEM()
|
||||
->string(),
|
||||
'Failed to get CRL distribution points: ' . $e->getMessage(),
|
||||
$e
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($urls as $url) {
|
||||
try {
|
||||
$revokedCertificates = $this->retrieveRevokedSerialNumbers($url);
|
||||
|
||||
if (in_array($csn, $revokedCertificates, true)) {
|
||||
return true;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
if ($this->allowFailures) {
|
||||
return false;
|
||||
}
|
||||
throw CertificateRevocationListException::create($url, sprintf(
|
||||
'Failed to retrieve the CRL:' . PHP_EOL . '%s',
|
||||
$e->getMessage()
|
||||
), $e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function validateCertificates(Certificate ...$certificates): bool
|
||||
{
|
||||
try {
|
||||
$config = PathValidationConfig::create($this->clock->now(), self::MAX_VALIDATION_LENGTH);
|
||||
CertificationPath::create(...$certificates)->validate($config);
|
||||
|
||||
return true;
|
||||
} catch (Throwable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function retrieveRevokedSerialNumbers(string $url): array
|
||||
{
|
||||
try {
|
||||
$request = $this->requestFactory->createRequest('GET', $url);
|
||||
$response = $this->client->sendRequest($request);
|
||||
if ($response->getStatusCode() !== 200) {
|
||||
throw CertificateRevocationListException::create($url, 'Failed to download the CRL');
|
||||
}
|
||||
$crlData = $response->getBody()
|
||||
->getContents();
|
||||
$crl = UnspecifiedType::fromDER($crlData)->asSequence();
|
||||
count($crl) === 3 || throw CertificateRevocationListException::create($url, 'Invalid CRL.');
|
||||
$tbsCertList = $crl->at(0)
|
||||
->asSequence();
|
||||
count($tbsCertList) >= 6 || throw CertificateRevocationListException::create($url, 'Invalid CRL.');
|
||||
$list = $tbsCertList->at(5)
|
||||
->asSequence();
|
||||
|
||||
return array_map(static function (UnspecifiedType $r) use ($url): string {
|
||||
$sequence = $r->asSequence();
|
||||
count($sequence) >= 1 || throw CertificateRevocationListException::create($url, 'Invalid CRL.');
|
||||
return $sequence->at(0)
|
||||
->asInteger()
|
||||
->number();
|
||||
}, $list->elements());
|
||||
} catch (Throwable $e) {
|
||||
throw CertificateRevocationListException::create($url, 'Failed to download the CRL', $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getCrlUrlList(Certificate $subject): array
|
||||
{
|
||||
try {
|
||||
$urls = [];
|
||||
|
||||
$extensions = $subject->tbsCertificate()
|
||||
->extensions();
|
||||
if ($extensions->hasCRLDistributionPoints()) {
|
||||
$crlDists = $extensions->crlDistributionPoints();
|
||||
foreach ($crlDists->distributionPoints() as $dist) {
|
||||
$url = $dist->fullName()
|
||||
->names()
|
||||
->firstURI();
|
||||
$scheme = parse_url($url, PHP_URL_SCHEME);
|
||||
if (! in_array($scheme, ['http', 'https'], true)) {
|
||||
continue;
|
||||
}
|
||||
$urls[] = $url;
|
||||
}
|
||||
}
|
||||
return $urls;
|
||||
} catch (Throwable $e) {
|
||||
throw InvalidCertificateException::create(
|
||||
$subject->toPEM()
|
||||
->string(),
|
||||
'Failed to get CRL distribution points from certificate: ' . $e->getMessage(),
|
||||
$e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
libraries/vendor/web-auth/metadata-service/src/Event/BeforeCertificateChainValidation.php
vendored
Normal file
25
libraries/vendor/web-auth/metadata-service/src/Event/BeforeCertificateChainValidation.php
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Event;
|
||||
|
||||
final class BeforeCertificateChainValidation implements WebauthnEvent
|
||||
{
|
||||
/**
|
||||
* @param string[] $untrustedCertificates
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly array $untrustedCertificates,
|
||||
public readonly string $trustedCertificate
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $untrustedCertificates
|
||||
*/
|
||||
public static function create(array $untrustedCertificates, string $trustedCertificate): self
|
||||
{
|
||||
return new self($untrustedCertificates, $trustedCertificate);
|
||||
}
|
||||
}
|
||||
12
libraries/vendor/web-auth/metadata-service/src/Event/CanDispatchEvents.php
vendored
Normal file
12
libraries/vendor/web-auth/metadata-service/src/Event/CanDispatchEvents.php
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Event;
|
||||
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
interface CanDispatchEvents
|
||||
{
|
||||
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void;
|
||||
}
|
||||
25
libraries/vendor/web-auth/metadata-service/src/Event/CertificateChainValidationFailed.php
vendored
Normal file
25
libraries/vendor/web-auth/metadata-service/src/Event/CertificateChainValidationFailed.php
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Event;
|
||||
|
||||
final class CertificateChainValidationFailed implements WebauthnEvent
|
||||
{
|
||||
/**
|
||||
* @param string[] $untrustedCertificates
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly array $untrustedCertificates,
|
||||
public readonly string $trustedCertificate
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $untrustedCertificates
|
||||
*/
|
||||
public static function create(array $untrustedCertificates, string $trustedCertificate): self
|
||||
{
|
||||
return new self($untrustedCertificates, $trustedCertificate);
|
||||
}
|
||||
}
|
||||
25
libraries/vendor/web-auth/metadata-service/src/Event/CertificateChainValidationSucceeded.php
vendored
Normal file
25
libraries/vendor/web-auth/metadata-service/src/Event/CertificateChainValidationSucceeded.php
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Event;
|
||||
|
||||
final class CertificateChainValidationSucceeded implements WebauthnEvent
|
||||
{
|
||||
/**
|
||||
* @param string[] $untrustedCertificates
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly array $untrustedCertificates,
|
||||
public readonly string $trustedCertificate
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $untrustedCertificates
|
||||
*/
|
||||
public static function create(array $untrustedCertificates, string $trustedCertificate): self
|
||||
{
|
||||
return new self($untrustedCertificates, $trustedCertificate);
|
||||
}
|
||||
}
|
||||
20
libraries/vendor/web-auth/metadata-service/src/Event/MetadataStatementFound.php
vendored
Normal file
20
libraries/vendor/web-auth/metadata-service/src/Event/MetadataStatementFound.php
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Event;
|
||||
|
||||
use Webauthn\MetadataService\Statement\MetadataStatement;
|
||||
|
||||
final class MetadataStatementFound implements WebauthnEvent
|
||||
{
|
||||
public function __construct(
|
||||
public readonly MetadataStatement $metadataStatement
|
||||
) {
|
||||
}
|
||||
|
||||
public static function create(MetadataStatement $metadataStatement): self
|
||||
{
|
||||
return new self($metadataStatement);
|
||||
}
|
||||
}
|
||||
15
libraries/vendor/web-auth/metadata-service/src/Event/NullEventDispatcher.php
vendored
Normal file
15
libraries/vendor/web-auth/metadata-service/src/Event/NullEventDispatcher.php
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Event;
|
||||
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
final class NullEventDispatcher implements EventDispatcherInterface
|
||||
{
|
||||
public function dispatch(object $event): object
|
||||
{
|
||||
return $event;
|
||||
}
|
||||
}
|
||||
9
libraries/vendor/web-auth/metadata-service/src/Event/WebauthnEvent.php
vendored
Normal file
9
libraries/vendor/web-auth/metadata-service/src/Event/WebauthnEvent.php
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Event;
|
||||
|
||||
interface WebauthnEvent
|
||||
{
|
||||
}
|
||||
36
libraries/vendor/web-auth/metadata-service/src/Exception/CertificateChainException.php
vendored
Normal file
36
libraries/vendor/web-auth/metadata-service/src/Exception/CertificateChainException.php
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Exception;
|
||||
|
||||
use Throwable;
|
||||
|
||||
class CertificateChainException extends MetadataServiceException
|
||||
{
|
||||
/**
|
||||
* @param array<string> $untrustedCertificates
|
||||
* @param array<string> $trustedCertificates
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly array $untrustedCertificates,
|
||||
public readonly array $trustedCertificates,
|
||||
string $message,
|
||||
?Throwable $previous = null
|
||||
) {
|
||||
parent::__construct($message, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $untrustedCertificates
|
||||
* @param array<string> $trustedCertificates
|
||||
*/
|
||||
public static function create(
|
||||
array $untrustedCertificates,
|
||||
array $trustedCertificates,
|
||||
string $message = 'Unable to validate the certificate chain.',
|
||||
?Throwable $previous = null
|
||||
): self {
|
||||
return new self($untrustedCertificates, $trustedCertificates, $message, $previous);
|
||||
}
|
||||
}
|
||||
18
libraries/vendor/web-auth/metadata-service/src/Exception/CertificateException.php
vendored
Normal file
18
libraries/vendor/web-auth/metadata-service/src/Exception/CertificateException.php
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Exception;
|
||||
|
||||
use Throwable;
|
||||
|
||||
class CertificateException extends MetadataServiceException
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $certificate,
|
||||
string $message,
|
||||
?Throwable $previous = null
|
||||
) {
|
||||
parent::__construct($message, $previous);
|
||||
}
|
||||
}
|
||||
23
libraries/vendor/web-auth/metadata-service/src/Exception/CertificateRevocationListException.php
vendored
Normal file
23
libraries/vendor/web-auth/metadata-service/src/Exception/CertificateRevocationListException.php
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Exception;
|
||||
|
||||
use Throwable;
|
||||
|
||||
final class CertificateRevocationListException extends MetadataServiceException
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $url,
|
||||
string $message,
|
||||
?Throwable $previous = null
|
||||
) {
|
||||
parent::__construct($message, $previous);
|
||||
}
|
||||
|
||||
public static function create(string $url, string $message, ?Throwable $previous = null): self
|
||||
{
|
||||
return new self($url, $message, $previous);
|
||||
}
|
||||
}
|
||||
18
libraries/vendor/web-auth/metadata-service/src/Exception/ExpiredCertificateException.php
vendored
Normal file
18
libraries/vendor/web-auth/metadata-service/src/Exception/ExpiredCertificateException.php
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Exception;
|
||||
|
||||
use Throwable;
|
||||
|
||||
final class ExpiredCertificateException extends CertificateException
|
||||
{
|
||||
public static function create(
|
||||
string $certificate,
|
||||
string $message = 'Expired certificate',
|
||||
?Throwable $previous = null
|
||||
): self {
|
||||
return new self($certificate, $message, $previous);
|
||||
}
|
||||
}
|
||||
23
libraries/vendor/web-auth/metadata-service/src/Exception/InvalidCertificateException.php
vendored
Normal file
23
libraries/vendor/web-auth/metadata-service/src/Exception/InvalidCertificateException.php
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Exception;
|
||||
|
||||
use Throwable;
|
||||
|
||||
final class InvalidCertificateException extends MetadataServiceException
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $certificate,
|
||||
string $message,
|
||||
?Throwable $previous = null
|
||||
) {
|
||||
parent::__construct($message, $previous);
|
||||
}
|
||||
|
||||
public static function create(string $certificate, string $message, ?Throwable $previous = null): self
|
||||
{
|
||||
return new self($certificate, $message, $previous);
|
||||
}
|
||||
}
|
||||
16
libraries/vendor/web-auth/metadata-service/src/Exception/MetadataServiceException.php
vendored
Normal file
16
libraries/vendor/web-auth/metadata-service/src/Exception/MetadataServiceException.php
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Exception;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class MetadataServiceException extends Exception
|
||||
{
|
||||
public function __construct(string $message, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, 0, $previous);
|
||||
}
|
||||
}
|
||||
9
libraries/vendor/web-auth/metadata-service/src/Exception/MetadataStatementException.php
vendored
Normal file
9
libraries/vendor/web-auth/metadata-service/src/Exception/MetadataStatementException.php
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Exception;
|
||||
|
||||
class MetadataStatementException extends MetadataServiceException
|
||||
{
|
||||
}
|
||||
15
libraries/vendor/web-auth/metadata-service/src/Exception/MetadataStatementLoadingException.php
vendored
Normal file
15
libraries/vendor/web-auth/metadata-service/src/Exception/MetadataStatementLoadingException.php
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Exception;
|
||||
|
||||
use Throwable;
|
||||
|
||||
final class MetadataStatementLoadingException extends MetadataStatementException
|
||||
{
|
||||
public static function create(string $message, ?Throwable $previous = null): self
|
||||
{
|
||||
return new self($message, $previous);
|
||||
}
|
||||
}
|
||||
26
libraries/vendor/web-auth/metadata-service/src/Exception/MissingMetadataStatementException.php
vendored
Normal file
26
libraries/vendor/web-auth/metadata-service/src/Exception/MissingMetadataStatementException.php
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Exception;
|
||||
|
||||
use Throwable;
|
||||
|
||||
final class MissingMetadataStatementException extends MetadataStatementException
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $aaguid,
|
||||
string $message,
|
||||
?Throwable $previous = null
|
||||
) {
|
||||
parent::__construct($message, $previous);
|
||||
}
|
||||
|
||||
public static function create(
|
||||
string $aaguid,
|
||||
string $message = 'The Metadata Statement is missing',
|
||||
?Throwable $previous = null
|
||||
): self {
|
||||
return new self($aaguid, $message, $previous);
|
||||
}
|
||||
}
|
||||
9
libraries/vendor/web-auth/metadata-service/src/Exception/RevokedCertificateException.php
vendored
Normal file
9
libraries/vendor/web-auth/metadata-service/src/Exception/RevokedCertificateException.php
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Exception;
|
||||
|
||||
final class RevokedCertificateException extends CertificateException
|
||||
{
|
||||
}
|
||||
12
libraries/vendor/web-auth/metadata-service/src/MetadataStatementRepository.php
vendored
Normal file
12
libraries/vendor/web-auth/metadata-service/src/MetadataStatementRepository.php
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService;
|
||||
|
||||
use Webauthn\MetadataService\Statement\MetadataStatement;
|
||||
|
||||
interface MetadataStatementRepository
|
||||
{
|
||||
public function findOneByAAGUID(string $aaguid): ?MetadataStatement;
|
||||
}
|
||||
66
libraries/vendor/web-auth/metadata-service/src/Service/ChainedMetadataServices.php
vendored
Normal file
66
libraries/vendor/web-auth/metadata-service/src/Service/ChainedMetadataServices.php
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Service;
|
||||
|
||||
use Webauthn\MetadataService\Exception\MissingMetadataStatementException;
|
||||
use Webauthn\MetadataService\Statement\MetadataStatement;
|
||||
|
||||
final class ChainedMetadataServices implements MetadataService
|
||||
{
|
||||
/**
|
||||
* @var MetadataService[]
|
||||
*/
|
||||
private array $services = [];
|
||||
|
||||
public function __construct(MetadataService ...$services)
|
||||
{
|
||||
foreach ($services as $service) {
|
||||
$this->addServices($service);
|
||||
}
|
||||
}
|
||||
|
||||
public static function create(MetadataService ...$services): self
|
||||
{
|
||||
return new self(...$services);
|
||||
}
|
||||
|
||||
public function addServices(MetadataService ...$services): self
|
||||
{
|
||||
foreach ($services as $service) {
|
||||
$this->services[] = $service;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function list(): iterable
|
||||
{
|
||||
foreach ($this->services as $service) {
|
||||
yield from $service->list();
|
||||
}
|
||||
}
|
||||
|
||||
public function has(string $aaguid): bool
|
||||
{
|
||||
foreach ($this->services as $service) {
|
||||
if ($service->has($aaguid)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function get(string $aaguid): MetadataStatement
|
||||
{
|
||||
foreach ($this->services as $service) {
|
||||
if ($service->has($aaguid)) {
|
||||
return $service->get($aaguid);
|
||||
}
|
||||
}
|
||||
|
||||
throw MissingMetadataStatementException::create($aaguid);
|
||||
}
|
||||
}
|
||||
130
libraries/vendor/web-auth/metadata-service/src/Service/DistantResourceMetadataService.php
vendored
Normal file
130
libraries/vendor/web-auth/metadata-service/src/Service/DistantResourceMetadataService.php
vendored
Normal file
@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Service;
|
||||
|
||||
use ParagonIE\ConstantTime\Base64;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Http\Client\ClientInterface;
|
||||
use Psr\Http\Message\RequestFactoryInterface;
|
||||
use function sprintf;
|
||||
use Webauthn\MetadataService\Event\CanDispatchEvents;
|
||||
use Webauthn\MetadataService\Event\MetadataStatementFound;
|
||||
use Webauthn\MetadataService\Event\NullEventDispatcher;
|
||||
use Webauthn\MetadataService\Exception\MetadataStatementLoadingException;
|
||||
use Webauthn\MetadataService\Exception\MissingMetadataStatementException;
|
||||
use Webauthn\MetadataService\Statement\MetadataStatement;
|
||||
|
||||
final class DistantResourceMetadataService implements MetadataService, CanDispatchEvents
|
||||
{
|
||||
private ?MetadataStatement $statement = null;
|
||||
|
||||
private EventDispatcherInterface $dispatcher;
|
||||
|
||||
/**
|
||||
* @param array<string, string> $additionalHeaderParameters
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly RequestFactoryInterface $requestFactory,
|
||||
private readonly ClientInterface $httpClient,
|
||||
private readonly string $uri,
|
||||
private readonly bool $isBase64Encoded = false,
|
||||
private readonly array $additionalHeaderParameters = [],
|
||||
) {
|
||||
$this->dispatcher = new NullEventDispatcher();
|
||||
}
|
||||
|
||||
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void
|
||||
{
|
||||
$this->dispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $additionalHeaderParameters
|
||||
*/
|
||||
public static function create(
|
||||
RequestFactoryInterface $requestFactory,
|
||||
ClientInterface $httpClient,
|
||||
string $uri,
|
||||
bool $isBase64Encoded = false,
|
||||
array $additionalHeaderParameters = []
|
||||
): self {
|
||||
return new self($requestFactory, $httpClient, $uri, $isBase64Encoded, $additionalHeaderParameters);
|
||||
}
|
||||
|
||||
public function list(): iterable
|
||||
{
|
||||
$this->loadData();
|
||||
$this->statement !== null || throw MetadataStatementLoadingException::create(
|
||||
'Unable to load the metadata statement'
|
||||
);
|
||||
$aaguid = $this->statement->getAaguid();
|
||||
if ($aaguid === null) {
|
||||
yield from [];
|
||||
} else {
|
||||
yield from [$aaguid];
|
||||
}
|
||||
}
|
||||
|
||||
public function has(string $aaguid): bool
|
||||
{
|
||||
$this->loadData();
|
||||
$this->statement !== null || throw MetadataStatementLoadingException::create(
|
||||
'Unable to load the metadata statement'
|
||||
);
|
||||
|
||||
return $aaguid === $this->statement->getAaguid();
|
||||
}
|
||||
|
||||
public function get(string $aaguid): MetadataStatement
|
||||
{
|
||||
$this->loadData();
|
||||
$this->statement !== null || throw MetadataStatementLoadingException::create(
|
||||
'Unable to load the metadata statement'
|
||||
);
|
||||
|
||||
if ($aaguid === $this->statement->getAaguid()) {
|
||||
$this->dispatcher->dispatch(MetadataStatementFound::create($this->statement));
|
||||
|
||||
return $this->statement;
|
||||
}
|
||||
|
||||
throw MissingMetadataStatementException::create($aaguid);
|
||||
}
|
||||
|
||||
private function loadData(): void
|
||||
{
|
||||
if ($this->statement !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$content = $this->fetch();
|
||||
if ($this->isBase64Encoded) {
|
||||
$content = Base64::decode($content, true);
|
||||
}
|
||||
$this->statement = MetadataStatement::createFromString($content);
|
||||
}
|
||||
|
||||
private function fetch(): string
|
||||
{
|
||||
$request = $this->requestFactory->createRequest('GET', $this->uri);
|
||||
foreach ($this->additionalHeaderParameters as $k => $v) {
|
||||
$request = $request->withHeader($k, $v);
|
||||
}
|
||||
$response = $this->httpClient->sendRequest($request);
|
||||
$response->getStatusCode() === 200 || throw MetadataStatementLoadingException::create(sprintf(
|
||||
'Unable to contact the server. Response code is %d',
|
||||
$response->getStatusCode()
|
||||
));
|
||||
$response->getBody()
|
||||
->rewind();
|
||||
$content = $response->getBody()
|
||||
->getContents();
|
||||
$content !== '' || throw MetadataStatementLoadingException::create(
|
||||
'Unable to contact the server. The response has no content'
|
||||
);
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
226
libraries/vendor/web-auth/metadata-service/src/Service/FidoAllianceCompliantMetadataService.php
vendored
Normal file
226
libraries/vendor/web-auth/metadata-service/src/Service/FidoAllianceCompliantMetadataService.php
vendored
Normal file
@ -0,0 +1,226 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Service;
|
||||
|
||||
use function array_key_exists;
|
||||
use function is_array;
|
||||
use Jose\Component\Core\AlgorithmManager;
|
||||
use Jose\Component\KeyManagement\JWKFactory;
|
||||
use Jose\Component\Signature\Algorithm\ES256;
|
||||
use Jose\Component\Signature\Algorithm\RS256;
|
||||
use Jose\Component\Signature\JWSVerifier;
|
||||
use Jose\Component\Signature\Serializer\CompactSerializer;
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Http\Client\ClientInterface;
|
||||
use Psr\Http\Message\RequestFactoryInterface;
|
||||
use function sprintf;
|
||||
use Throwable;
|
||||
use Webauthn\MetadataService\CertificateChain\CertificateChainValidator;
|
||||
use Webauthn\MetadataService\CertificateChain\CertificateToolbox;
|
||||
use Webauthn\MetadataService\Event\CanDispatchEvents;
|
||||
use Webauthn\MetadataService\Event\MetadataStatementFound;
|
||||
use Webauthn\MetadataService\Event\NullEventDispatcher;
|
||||
use Webauthn\MetadataService\Exception\MetadataStatementLoadingException;
|
||||
use Webauthn\MetadataService\Exception\MissingMetadataStatementException;
|
||||
use Webauthn\MetadataService\Statement\MetadataStatement;
|
||||
use Webauthn\MetadataService\Statement\StatusReport;
|
||||
|
||||
final class FidoAllianceCompliantMetadataService implements MetadataService, CanDispatchEvents
|
||||
{
|
||||
private bool $loaded = false;
|
||||
|
||||
/**
|
||||
* @var MetadataStatement[]
|
||||
*/
|
||||
private array $statements = [];
|
||||
|
||||
/**
|
||||
* @var array<string, array<int, StatusReport>>
|
||||
*/
|
||||
private array $statusReports = [];
|
||||
|
||||
private EventDispatcherInterface $dispatcher;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $additionalHeaderParameters
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly RequestFactoryInterface $requestFactory,
|
||||
private readonly ClientInterface $httpClient,
|
||||
private readonly string $uri,
|
||||
private readonly array $additionalHeaderParameters = [],
|
||||
private readonly ?CertificateChainValidator $certificateChainValidator = null,
|
||||
private readonly ?string $rootCertificateUri = null,
|
||||
) {
|
||||
$this->dispatcher = new NullEventDispatcher();
|
||||
}
|
||||
|
||||
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void
|
||||
{
|
||||
$this->dispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $additionalHeaderParameters
|
||||
*/
|
||||
public static function create(
|
||||
RequestFactoryInterface $requestFactory,
|
||||
ClientInterface $httpClient,
|
||||
string $uri,
|
||||
array $additionalHeaderParameters = [],
|
||||
?CertificateChainValidator $certificateChainValidator = null,
|
||||
?string $rootCertificateUri = null,
|
||||
): self {
|
||||
return new self(
|
||||
$requestFactory,
|
||||
$httpClient,
|
||||
$uri,
|
||||
$additionalHeaderParameters,
|
||||
$certificateChainValidator,
|
||||
$rootCertificateUri
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function list(): iterable
|
||||
{
|
||||
$this->loadData();
|
||||
|
||||
yield from array_keys($this->statements);
|
||||
}
|
||||
|
||||
public function has(string $aaguid): bool
|
||||
{
|
||||
$this->loadData();
|
||||
|
||||
return array_key_exists($aaguid, $this->statements);
|
||||
}
|
||||
|
||||
public function get(string $aaguid): MetadataStatement
|
||||
{
|
||||
$this->loadData();
|
||||
array_key_exists($aaguid, $this->statements) || throw MissingMetadataStatementException::create($aaguid);
|
||||
$mds = $this->statements[$aaguid];
|
||||
$this->dispatcher->dispatch(MetadataStatementFound::create($mds));
|
||||
|
||||
return $mds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return StatusReport[]
|
||||
*/
|
||||
public function getStatusReports(string $aaguid): iterable
|
||||
{
|
||||
$this->loadData();
|
||||
|
||||
return $this->statusReports[$aaguid] ?? [];
|
||||
}
|
||||
|
||||
private function loadData(): void
|
||||
{
|
||||
if ($this->loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
$content = $this->fetch($this->uri, $this->additionalHeaderParameters);
|
||||
$jwtCertificates = [];
|
||||
try {
|
||||
$payload = $this->getJwsPayload($content, $jwtCertificates);
|
||||
$data = json_decode($payload, true, 512, JSON_THROW_ON_ERROR);
|
||||
$this->validateCertificates(...$jwtCertificates);
|
||||
|
||||
foreach ($data['entries'] as $datum) {
|
||||
$entry = MetadataBLOBPayloadEntry::createFromArray($datum);
|
||||
|
||||
$mds = $entry->getMetadataStatement();
|
||||
if ($mds !== null && $entry->getAaguid() !== null) {
|
||||
$this->statements[$entry->getAaguid()] = $mds;
|
||||
$this->statusReports[$entry->getAaguid()] = $entry->getStatusReports();
|
||||
}
|
||||
}
|
||||
} catch (Throwable) {
|
||||
}
|
||||
|
||||
$this->loaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $headerParameters
|
||||
*/
|
||||
private function fetch(string $uri, array $headerParameters): string
|
||||
{
|
||||
$request = $this->requestFactory->createRequest('GET', $uri);
|
||||
foreach ($headerParameters as $k => $v) {
|
||||
$request = $request->withHeader($k, $v);
|
||||
}
|
||||
$response = $this->httpClient->sendRequest($request);
|
||||
$response->getStatusCode() === 200 || throw MetadataStatementLoadingException::create(sprintf(
|
||||
'Unable to contact the server. Response code is %d',
|
||||
$response->getStatusCode()
|
||||
));
|
||||
$response->getBody()
|
||||
->rewind();
|
||||
$content = $response->getBody()
|
||||
->getContents();
|
||||
$content !== '' || throw MetadataStatementLoadingException::create(
|
||||
'Unable to contact the server. The response has no content'
|
||||
);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $rootCertificates
|
||||
*/
|
||||
private function getJwsPayload(string $token, array &$rootCertificates): string
|
||||
{
|
||||
$jws = (new CompactSerializer())->unserialize($token);
|
||||
$jws->countSignatures() === 1 || throw MetadataStatementLoadingException::create(
|
||||
'Invalid response from the metadata service. Only one signature shall be present.'
|
||||
);
|
||||
$signature = $jws->getSignature(0);
|
||||
$payload = $jws->getPayload();
|
||||
$payload !== '' || throw MetadataStatementLoadingException::create(
|
||||
'Invalid response from the metadata service. The token payload is empty.'
|
||||
);
|
||||
$header = $signature->getProtectedHeader();
|
||||
array_key_exists('alg', $header) || throw MetadataStatementLoadingException::create(
|
||||
'The "alg" parameter is missing.'
|
||||
);
|
||||
array_key_exists('x5c', $header) || throw MetadataStatementLoadingException::create(
|
||||
'The "x5c" parameter is missing.'
|
||||
);
|
||||
is_array($header['x5c']) || throw MetadataStatementLoadingException::create(
|
||||
'The "x5c" parameter should be an array.'
|
||||
);
|
||||
$key = JWKFactory::createFromX5C($header['x5c']);
|
||||
$rootCertificates = $header['x5c'];
|
||||
|
||||
$verifier = new JWSVerifier(new AlgorithmManager([new ES256(), new RS256()]));
|
||||
$isValid = $verifier->verifyWithKey($jws, $key, 0);
|
||||
$isValid || throw MetadataStatementLoadingException::create(
|
||||
'Invalid response from the metadata service. The token signature is invalid.'
|
||||
);
|
||||
$payload = $jws->getPayload();
|
||||
$payload !== null || throw MetadataStatementLoadingException::create(
|
||||
'Invalid response from the metadata service. The payload is missing.'
|
||||
);
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
private function validateCertificates(string ...$untrustedCertificates): void
|
||||
{
|
||||
if ($this->certificateChainValidator === null || $this->rootCertificateUri === null) {
|
||||
return;
|
||||
}
|
||||
$untrustedCertificates = CertificateToolbox::fixPEMStructures($untrustedCertificates);
|
||||
$rootCertificate = CertificateToolbox::convertDERToPEM($this->fetch($this->rootCertificateUri, []));
|
||||
$this->certificateChainValidator->check($untrustedCertificates, [$rootCertificate]);
|
||||
}
|
||||
}
|
||||
61
libraries/vendor/web-auth/metadata-service/src/Service/FolderResourceMetadataService.php
vendored
Normal file
61
libraries/vendor/web-auth/metadata-service/src/Service/FolderResourceMetadataService.php
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Service;
|
||||
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
use function file_get_contents;
|
||||
use InvalidArgumentException;
|
||||
use function is_array;
|
||||
use RuntimeException;
|
||||
use function sprintf;
|
||||
use Webauthn\MetadataService\Statement\MetadataStatement;
|
||||
|
||||
final class FolderResourceMetadataService implements MetadataService
|
||||
{
|
||||
private readonly string $rootPath;
|
||||
|
||||
public function __construct(string $rootPath)
|
||||
{
|
||||
$this->rootPath = rtrim($rootPath, DIRECTORY_SEPARATOR);
|
||||
is_dir($this->rootPath) || throw new InvalidArgumentException('The given parameter is not a valid folder.');
|
||||
is_readable($this->rootPath) || throw new InvalidArgumentException(
|
||||
'The given parameter is not a valid folder.'
|
||||
);
|
||||
}
|
||||
|
||||
public function list(): iterable
|
||||
{
|
||||
$files = glob($this->rootPath . DIRECTORY_SEPARATOR . '*');
|
||||
is_array($files) || throw new RuntimeException('Unable to read files.');
|
||||
foreach ($files as $file) {
|
||||
if (is_dir($file) || ! is_readable($file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield basename($file);
|
||||
}
|
||||
}
|
||||
|
||||
public function has(string $aaguid): bool
|
||||
{
|
||||
$filename = $this->rootPath . DIRECTORY_SEPARATOR . $aaguid;
|
||||
|
||||
return is_file($filename) && is_readable($filename);
|
||||
}
|
||||
|
||||
public function get(string $aaguid): MetadataStatement
|
||||
{
|
||||
$this->has($aaguid) || throw new InvalidArgumentException(sprintf(
|
||||
'The MDS with the AAGUID "%s" does not exist.',
|
||||
$aaguid
|
||||
));
|
||||
$filename = $this->rootPath . DIRECTORY_SEPARATOR . $aaguid;
|
||||
$data = trim(file_get_contents($filename));
|
||||
$mds = MetadataStatement::createFromString($data);
|
||||
$mds->getAaguid() !== null || throw new RuntimeException('Invalid Metadata Statement.');
|
||||
|
||||
return $mds;
|
||||
}
|
||||
}
|
||||
73
libraries/vendor/web-auth/metadata-service/src/Service/InMemoryMetadataService.php
vendored
Normal file
73
libraries/vendor/web-auth/metadata-service/src/Service/InMemoryMetadataService.php
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Service;
|
||||
|
||||
use function array_key_exists;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Webauthn\MetadataService\Event\CanDispatchEvents;
|
||||
use Webauthn\MetadataService\Event\MetadataStatementFound;
|
||||
use Webauthn\MetadataService\Event\NullEventDispatcher;
|
||||
use Webauthn\MetadataService\Exception\MissingMetadataStatementException;
|
||||
use Webauthn\MetadataService\Statement\MetadataStatement;
|
||||
|
||||
final class InMemoryMetadataService implements MetadataService, CanDispatchEvents
|
||||
{
|
||||
/**
|
||||
* @var MetadataStatement[]
|
||||
*/
|
||||
private array $statements = [];
|
||||
|
||||
private EventDispatcherInterface $dispatcher;
|
||||
|
||||
public function __construct(MetadataStatement ...$statements)
|
||||
{
|
||||
foreach ($statements as $statement) {
|
||||
$this->addStatements($statement);
|
||||
}
|
||||
$this->dispatcher = new NullEventDispatcher();
|
||||
}
|
||||
|
||||
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void
|
||||
{
|
||||
$this->dispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
public static function create(MetadataStatement ...$statements): self
|
||||
{
|
||||
return new self(...$statements);
|
||||
}
|
||||
|
||||
public function addStatements(MetadataStatement ...$statements): self
|
||||
{
|
||||
foreach ($statements as $statement) {
|
||||
$aaguid = $statement->getAaguid();
|
||||
if ($aaguid === null) {
|
||||
continue;
|
||||
}
|
||||
$this->statements[$aaguid] = $statement;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function list(): iterable
|
||||
{
|
||||
yield from array_keys($this->statements);
|
||||
}
|
||||
|
||||
public function has(string $aaguid): bool
|
||||
{
|
||||
return array_key_exists($aaguid, $this->statements);
|
||||
}
|
||||
|
||||
public function get(string $aaguid): MetadataStatement
|
||||
{
|
||||
array_key_exists($aaguid, $this->statements) || throw MissingMetadataStatementException::create($aaguid);
|
||||
$mds = $this->statements[$aaguid];
|
||||
$this->dispatcher->dispatch(MetadataStatementFound::create($mds));
|
||||
|
||||
return $mds;
|
||||
}
|
||||
}
|
||||
92
libraries/vendor/web-auth/metadata-service/src/Service/LocalResourceMetadataService.php
vendored
Normal file
92
libraries/vendor/web-auth/metadata-service/src/Service/LocalResourceMetadataService.php
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Service;
|
||||
|
||||
use function file_get_contents;
|
||||
use ParagonIE\ConstantTime\Base64;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Webauthn\MetadataService\Event\CanDispatchEvents;
|
||||
use Webauthn\MetadataService\Event\MetadataStatementFound;
|
||||
use Webauthn\MetadataService\Event\NullEventDispatcher;
|
||||
use Webauthn\MetadataService\Exception\MetadataStatementLoadingException;
|
||||
use Webauthn\MetadataService\Exception\MissingMetadataStatementException;
|
||||
use Webauthn\MetadataService\Statement\MetadataStatement;
|
||||
|
||||
final class LocalResourceMetadataService implements MetadataService, CanDispatchEvents
|
||||
{
|
||||
private ?MetadataStatement $statement = null;
|
||||
|
||||
private EventDispatcherInterface $dispatcher;
|
||||
|
||||
public function __construct(
|
||||
private readonly string $filename,
|
||||
private readonly bool $isBase64Encoded = false,
|
||||
) {
|
||||
$this->dispatcher = new NullEventDispatcher();
|
||||
}
|
||||
|
||||
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void
|
||||
{
|
||||
$this->dispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
public static function create(string $filename, bool $isBase64Encoded = false): self
|
||||
{
|
||||
return new self($filename, $isBase64Encoded);
|
||||
}
|
||||
|
||||
public function list(): iterable
|
||||
{
|
||||
$this->loadData();
|
||||
$this->statement !== null || throw MetadataStatementLoadingException::create(
|
||||
'Unable to load the metadata statement'
|
||||
);
|
||||
$aaguid = $this->statement->getAaguid();
|
||||
if ($aaguid === null) {
|
||||
yield from [];
|
||||
} else {
|
||||
yield from [$aaguid];
|
||||
}
|
||||
}
|
||||
|
||||
public function has(string $aaguid): bool
|
||||
{
|
||||
$this->loadData();
|
||||
$this->statement !== null || throw MetadataStatementLoadingException::create(
|
||||
'Unable to load the metadata statement'
|
||||
);
|
||||
|
||||
return $aaguid === $this->statement->getAaguid();
|
||||
}
|
||||
|
||||
public function get(string $aaguid): MetadataStatement
|
||||
{
|
||||
$this->loadData();
|
||||
$this->statement !== null || throw MetadataStatementLoadingException::create(
|
||||
'Unable to load the metadata statement'
|
||||
);
|
||||
|
||||
if ($aaguid === $this->statement->getAaguid()) {
|
||||
$this->dispatcher->dispatch(MetadataStatementFound::create($this->statement));
|
||||
|
||||
return $this->statement;
|
||||
}
|
||||
|
||||
throw MissingMetadataStatementException::create($aaguid);
|
||||
}
|
||||
|
||||
private function loadData(): void
|
||||
{
|
||||
if ($this->statement !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$content = file_get_contents($this->filename);
|
||||
if ($this->isBase64Encoded) {
|
||||
$content = Base64::decode($content, true);
|
||||
}
|
||||
$this->statement = MetadataStatement::createFromString($content);
|
||||
}
|
||||
}
|
||||
131
libraries/vendor/web-auth/metadata-service/src/Service/MetadataBLOBPayload.php
vendored
Normal file
131
libraries/vendor/web-auth/metadata-service/src/Service/MetadataBLOBPayload.php
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Service;
|
||||
|
||||
use function array_key_exists;
|
||||
use function is_array;
|
||||
use function is_int;
|
||||
use function is_string;
|
||||
use JsonSerializable;
|
||||
use Webauthn\MetadataService\Exception\MetadataStatementLoadingException;
|
||||
use Webauthn\MetadataService\Utils;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class MetadataBLOBPayload implements JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var MetadataBLOBPayloadEntry[]
|
||||
*/
|
||||
private array $entries = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private array $rootCertificates = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly int $no,
|
||||
private readonly string $nextUpdate,
|
||||
private readonly ?string $legalHeader = null
|
||||
) {
|
||||
}
|
||||
|
||||
public function addEntry(MetadataBLOBPayloadEntry $entry): self
|
||||
{
|
||||
$this->entries[] = $entry;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLegalHeader(): ?string
|
||||
{
|
||||
return $this->legalHeader;
|
||||
}
|
||||
|
||||
public function getNo(): int
|
||||
{
|
||||
return $this->no;
|
||||
}
|
||||
|
||||
public function getNextUpdate(): string
|
||||
{
|
||||
return $this->nextUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MetadataBLOBPayloadEntry[]
|
||||
*/
|
||||
public function getEntries(): array
|
||||
{
|
||||
return $this->entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public static function createFromArray(array $data): self
|
||||
{
|
||||
$data = Utils::filterNullValues($data);
|
||||
foreach (['no', 'nextUpdate', 'entries'] as $key) {
|
||||
array_key_exists($key, $data) || throw MetadataStatementLoadingException::create(sprintf(
|
||||
'Invalid data. The parameter "%s" is missing',
|
||||
$key
|
||||
));
|
||||
}
|
||||
is_int($data['no']) || throw MetadataStatementLoadingException::create(
|
||||
'Invalid data. The parameter "no" shall be an integer'
|
||||
);
|
||||
is_string($data['nextUpdate']) || throw MetadataStatementLoadingException::create(
|
||||
'Invalid data. The parameter "nextUpdate" shall be a string'
|
||||
);
|
||||
is_array($data['entries']) || throw MetadataStatementLoadingException::create(
|
||||
'Invalid data. The parameter "entries" shall be a n array of entries'
|
||||
);
|
||||
$object = new self($data['no'], $data['nextUpdate'], $data['legalHeader'] ?? null);
|
||||
foreach ($data['entries'] as $entry) {
|
||||
$object->addEntry(MetadataBLOBPayloadEntry::createFromArray($entry));
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$data = [
|
||||
'legalHeader' => $this->legalHeader,
|
||||
'nextUpdate' => $this->nextUpdate,
|
||||
'no' => $this->no,
|
||||
'entries' => array_map(
|
||||
static fn (MetadataBLOBPayloadEntry $object): array => $object->jsonSerialize(),
|
||||
$this->entries
|
||||
),
|
||||
];
|
||||
|
||||
return Utils::filterNullValues($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getRootCertificates(): array
|
||||
{
|
||||
return $this->rootCertificates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $rootCertificates
|
||||
*/
|
||||
public function setRootCertificates(array $rootCertificates): self
|
||||
{
|
||||
$this->rootCertificates = $rootCertificates;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
200
libraries/vendor/web-auth/metadata-service/src/Service/MetadataBLOBPayloadEntry.php
vendored
Normal file
200
libraries/vendor/web-auth/metadata-service/src/Service/MetadataBLOBPayloadEntry.php
vendored
Normal file
@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Service;
|
||||
|
||||
use function array_key_exists;
|
||||
use function count;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
use JsonSerializable;
|
||||
use Webauthn\MetadataService\Exception\MetadataStatementLoadingException;
|
||||
use Webauthn\MetadataService\Statement\BiometricStatusReport;
|
||||
use Webauthn\MetadataService\Statement\MetadataStatement;
|
||||
use Webauthn\MetadataService\Statement\StatusReport;
|
||||
use Webauthn\MetadataService\Utils;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class MetadataBLOBPayloadEntry implements JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private array $attestationCertificateKeyIdentifiers = [];
|
||||
|
||||
/**
|
||||
* @var BiometricStatusReport[]
|
||||
*/
|
||||
private array $biometricStatusReports = [];
|
||||
|
||||
/**
|
||||
* @var StatusReport[]
|
||||
*/
|
||||
private array $statusReports = [];
|
||||
|
||||
/**
|
||||
* @param string[] $attestationCertificateKeyIdentifiers
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly ?string $aaid,
|
||||
private readonly ?string $aaguid,
|
||||
array $attestationCertificateKeyIdentifiers,
|
||||
private readonly ?MetadataStatement $metadataStatement,
|
||||
private readonly string $timeOfLastStatusChange,
|
||||
private readonly ?string $rogueListURL,
|
||||
private readonly ?string $rogueListHash
|
||||
) {
|
||||
if ($aaid !== null && $aaguid !== null) {
|
||||
throw MetadataStatementLoadingException::create('Authenticators cannot support both AAID and AAGUID');
|
||||
}
|
||||
if ($aaid === null && $aaguid === null && count($attestationCertificateKeyIdentifiers) === 0) {
|
||||
throw MetadataStatementLoadingException::create(
|
||||
'If neither AAID nor AAGUID are set, the attestation certificate identifier list shall not be empty'
|
||||
);
|
||||
}
|
||||
foreach ($attestationCertificateKeyIdentifiers as $attestationCertificateKeyIdentifier) {
|
||||
is_string($attestationCertificateKeyIdentifier) || throw MetadataStatementLoadingException::create(
|
||||
'Invalid attestation certificate identifier. Shall be a list of strings'
|
||||
);
|
||||
preg_match(
|
||||
'/^[0-9a-f]+$/',
|
||||
$attestationCertificateKeyIdentifier
|
||||
) === 1 || throw MetadataStatementLoadingException::create(
|
||||
'Invalid attestation certificate identifier. Shall be a list of strings'
|
||||
);
|
||||
}
|
||||
$this->attestationCertificateKeyIdentifiers = $attestationCertificateKeyIdentifiers;
|
||||
}
|
||||
|
||||
public function getAaid(): ?string
|
||||
{
|
||||
return $this->aaid;
|
||||
}
|
||||
|
||||
public function getAaguid(): ?string
|
||||
{
|
||||
return $this->aaguid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttestationCertificateKeyIdentifiers(): array
|
||||
{
|
||||
return $this->attestationCertificateKeyIdentifiers;
|
||||
}
|
||||
|
||||
public function getMetadataStatement(): ?MetadataStatement
|
||||
{
|
||||
return $this->metadataStatement;
|
||||
}
|
||||
|
||||
public function addBiometricStatusReports(BiometricStatusReport ...$biometricStatusReports): self
|
||||
{
|
||||
foreach ($biometricStatusReports as $biometricStatusReport) {
|
||||
$this->biometricStatusReports[] = $biometricStatusReport;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BiometricStatusReport[]
|
||||
*/
|
||||
public function getBiometricStatusReports(): array
|
||||
{
|
||||
return $this->biometricStatusReports;
|
||||
}
|
||||
|
||||
public function addStatusReports(StatusReport ...$statusReports): self
|
||||
{
|
||||
foreach ($statusReports as $statusReport) {
|
||||
$this->statusReports[] = $statusReport;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return StatusReport[]
|
||||
*/
|
||||
public function getStatusReports(): array
|
||||
{
|
||||
return $this->statusReports;
|
||||
}
|
||||
|
||||
public function getTimeOfLastStatusChange(): string
|
||||
{
|
||||
return $this->timeOfLastStatusChange;
|
||||
}
|
||||
|
||||
public function getRogueListURL(): string|null
|
||||
{
|
||||
return $this->rogueListURL;
|
||||
}
|
||||
|
||||
public function getRogueListHash(): string|null
|
||||
{
|
||||
return $this->rogueListHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public static function createFromArray(array $data): self
|
||||
{
|
||||
$data = Utils::filterNullValues($data);
|
||||
array_key_exists('timeOfLastStatusChange', $data) || throw MetadataStatementLoadingException::create(
|
||||
'Invalid data. The parameter "timeOfLastStatusChange" is missing'
|
||||
);
|
||||
array_key_exists('statusReports', $data) || throw MetadataStatementLoadingException::create(
|
||||
'Invalid data. The parameter "statusReports" is missing'
|
||||
);
|
||||
is_array($data['statusReports']) || throw MetadataStatementLoadingException::create(
|
||||
'Invalid data. The parameter "statusReports" shall be an array of StatusReport objects'
|
||||
);
|
||||
$object = new self(
|
||||
$data['aaid'] ?? null,
|
||||
$data['aaguid'] ?? null,
|
||||
$data['attestationCertificateKeyIdentifiers'] ?? [],
|
||||
isset($data['metadataStatement']) ? MetadataStatement::createFromArray($data['metadataStatement']) : null,
|
||||
$data['timeOfLastStatusChange'],
|
||||
$data['rogueListURL'] ?? null,
|
||||
$data['rogueListHash'] ?? null
|
||||
);
|
||||
foreach ($data['statusReports'] as $statusReport) {
|
||||
$object->addStatusReports(StatusReport::createFromArray($statusReport));
|
||||
}
|
||||
if (array_key_exists('biometricStatusReport', $data)) {
|
||||
foreach ($data['biometricStatusReport'] as $biometricStatusReport) {
|
||||
$object->addBiometricStatusReports(BiometricStatusReport::createFromArray($biometricStatusReport));
|
||||
}
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$data = [
|
||||
'aaid' => $this->aaid,
|
||||
'aaguid' => $this->aaguid,
|
||||
'attestationCertificateKeyIdentifiers' => $this->attestationCertificateKeyIdentifiers,
|
||||
'statusReports' => array_map(
|
||||
static fn (StatusReport $object): array => $object->jsonSerialize(),
|
||||
$this->statusReports
|
||||
),
|
||||
'timeOfLastStatusChange' => $this->timeOfLastStatusChange,
|
||||
'rogueListURL' => $this->rogueListURL,
|
||||
'rogueListHash' => $this->rogueListHash,
|
||||
];
|
||||
|
||||
return Utils::filterNullValues($data);
|
||||
}
|
||||
}
|
||||
19
libraries/vendor/web-auth/metadata-service/src/Service/MetadataService.php
vendored
Normal file
19
libraries/vendor/web-auth/metadata-service/src/Service/MetadataService.php
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Service;
|
||||
|
||||
use Webauthn\MetadataService\Statement\MetadataStatement;
|
||||
|
||||
interface MetadataService
|
||||
{
|
||||
/**
|
||||
* @return string[] The list of AAGUID supported by the service
|
||||
*/
|
||||
public function list(): iterable;
|
||||
|
||||
public function has(string $aaguid): bool;
|
||||
|
||||
public function get(string $aaguid): MetadataStatement;
|
||||
}
|
||||
73
libraries/vendor/web-auth/metadata-service/src/Service/StringMetadataService.php
vendored
Normal file
73
libraries/vendor/web-auth/metadata-service/src/Service/StringMetadataService.php
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Service;
|
||||
|
||||
use function array_key_exists;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Webauthn\MetadataService\Event\CanDispatchEvents;
|
||||
use Webauthn\MetadataService\Event\MetadataStatementFound;
|
||||
use Webauthn\MetadataService\Event\NullEventDispatcher;
|
||||
use Webauthn\MetadataService\Exception\MissingMetadataStatementException;
|
||||
use Webauthn\MetadataService\Statement\MetadataStatement;
|
||||
|
||||
final class StringMetadataService implements MetadataService, CanDispatchEvents
|
||||
{
|
||||
/**
|
||||
* @var MetadataStatement[]
|
||||
*/
|
||||
private array $statements = [];
|
||||
|
||||
private EventDispatcherInterface $dispatcher;
|
||||
|
||||
public function __construct(string ...$statements)
|
||||
{
|
||||
foreach ($statements as $statement) {
|
||||
$this->addStatements(MetadataStatement::createFromString($statement));
|
||||
}
|
||||
$this->dispatcher = new NullEventDispatcher();
|
||||
}
|
||||
|
||||
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void
|
||||
{
|
||||
$this->dispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
public static function create(string ...$statements): self
|
||||
{
|
||||
return new self(...$statements);
|
||||
}
|
||||
|
||||
public function addStatements(MetadataStatement ...$statements): self
|
||||
{
|
||||
foreach ($statements as $statement) {
|
||||
$aaguid = $statement->getAaguid();
|
||||
if ($aaguid === null) {
|
||||
continue;
|
||||
}
|
||||
$this->statements[$aaguid] = $statement;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function list(): iterable
|
||||
{
|
||||
yield from array_keys($this->statements);
|
||||
}
|
||||
|
||||
public function has(string $aaguid): bool
|
||||
{
|
||||
return array_key_exists($aaguid, $this->statements);
|
||||
}
|
||||
|
||||
public function get(string $aaguid): MetadataStatement
|
||||
{
|
||||
array_key_exists($aaguid, $this->statements) || throw MissingMetadataStatementException::create($aaguid);
|
||||
$mds = $this->statements[$aaguid];
|
||||
$this->dispatcher->dispatch(MetadataStatementFound::create($mds));
|
||||
|
||||
return $mds;
|
||||
}
|
||||
}
|
||||
38
libraries/vendor/web-auth/metadata-service/src/Statement/AbstractDescriptor.php
vendored
Normal file
38
libraries/vendor/web-auth/metadata-service/src/Statement/AbstractDescriptor.php
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Statement;
|
||||
|
||||
use JsonSerializable;
|
||||
use Webauthn\MetadataService\Exception\MetadataStatementLoadingException;
|
||||
|
||||
abstract class AbstractDescriptor implements JsonSerializable
|
||||
{
|
||||
private readonly ?int $maxRetries;
|
||||
|
||||
private readonly ?int $blockSlowdown;
|
||||
|
||||
public function __construct(?int $maxRetries = null, ?int $blockSlowdown = null)
|
||||
{
|
||||
$maxRetries >= 0 || throw MetadataStatementLoadingException::create(
|
||||
'Invalid data. The value of "maxRetries" must be a positive integer'
|
||||
);
|
||||
$blockSlowdown >= 0 || throw MetadataStatementLoadingException::create(
|
||||
'Invalid data. The value of "blockSlowdown" must be a positive integer'
|
||||
);
|
||||
|
||||
$this->maxRetries = $maxRetries;
|
||||
$this->blockSlowdown = $blockSlowdown;
|
||||
}
|
||||
|
||||
public function getMaxRetries(): ?int
|
||||
{
|
||||
return $this->maxRetries;
|
||||
}
|
||||
|
||||
public function getBlockSlowdown(): ?int
|
||||
{
|
||||
return $this->blockSlowdown;
|
||||
}
|
||||
}
|
||||
54
libraries/vendor/web-auth/metadata-service/src/Statement/AlternativeDescriptions.php
vendored
Normal file
54
libraries/vendor/web-auth/metadata-service/src/Statement/AlternativeDescriptions.php
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Statement;
|
||||
|
||||
use JsonSerializable;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class AlternativeDescriptions implements JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private array $descriptions = [];
|
||||
|
||||
/**
|
||||
* @param array<string, string> $descriptions
|
||||
*/
|
||||
public static function create(array $descriptions = []): self
|
||||
{
|
||||
$object = new self();
|
||||
foreach ($descriptions as $k => $v) {
|
||||
$object->add($k, $v);
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function all(): array
|
||||
{
|
||||
return $this->descriptions;
|
||||
}
|
||||
|
||||
public function add(string $locale, string $description): self
|
||||
{
|
||||
$this->descriptions[$locale] = $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return $this->descriptions;
|
||||
}
|
||||
}
|
||||
46
libraries/vendor/web-auth/metadata-service/src/Statement/AuthenticatorGetInfo.php
vendored
Normal file
46
libraries/vendor/web-auth/metadata-service/src/Statement/AuthenticatorGetInfo.php
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Statement;
|
||||
|
||||
use JsonSerializable;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class AuthenticatorGetInfo implements JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private array $info = [];
|
||||
|
||||
/**
|
||||
* @param array<string|int, mixed> $data
|
||||
*/
|
||||
public static function create(array $data = []): self
|
||||
{
|
||||
$object = new self();
|
||||
foreach ($data as $k => $v) {
|
||||
$object->add($k, $v);
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
public function add(string|int $key, mixed $value): self
|
||||
{
|
||||
$this->info[$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return $this->info;
|
||||
}
|
||||
}
|
||||
68
libraries/vendor/web-auth/metadata-service/src/Statement/AuthenticatorStatus.php
vendored
Normal file
68
libraries/vendor/web-auth/metadata-service/src/Statement/AuthenticatorStatus.php
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Statement;
|
||||
|
||||
abstract class AuthenticatorStatus
|
||||
{
|
||||
final public const NOT_FIDO_CERTIFIED = 'NOT_FIDO_CERTIFIED';
|
||||
|
||||
final public const FIDO_CERTIFIED = 'FIDO_CERTIFIED';
|
||||
|
||||
final public const USER_VERIFICATION_BYPASS = 'USER_VERIFICATION_BYPASS';
|
||||
|
||||
final public const ATTESTATION_KEY_COMPROMISE = 'ATTESTATION_KEY_COMPROMISE';
|
||||
|
||||
final public const USER_KEY_REMOTE_COMPROMISE = 'USER_KEY_REMOTE_COMPROMISE';
|
||||
|
||||
final public const USER_KEY_PHYSICAL_COMPROMISE = 'USER_KEY_PHYSICAL_COMPROMISE';
|
||||
|
||||
final public const UPDATE_AVAILABLE = 'UPDATE_AVAILABLE';
|
||||
|
||||
final public const REVOKED = 'REVOKED';
|
||||
|
||||
final public const SELF_ASSERTION_SUBMITTED = 'SELF_ASSERTION_SUBMITTED';
|
||||
|
||||
final public const FIDO_CERTIFIED_L1 = 'FIDO_CERTIFIED_L1';
|
||||
|
||||
final public const FIDO_CERTIFIED_L1plus = 'FIDO_CERTIFIED_L1plus';
|
||||
|
||||
final public const FIDO_CERTIFIED_L2 = 'FIDO_CERTIFIED_L2';
|
||||
|
||||
final public const FIDO_CERTIFIED_L2plus = 'FIDO_CERTIFIED_L2plus';
|
||||
|
||||
final public const FIDO_CERTIFIED_L3 = 'FIDO_CERTIFIED_L3';
|
||||
|
||||
final public const FIDO_CERTIFIED_L3plus = 'FIDO_CERTIFIED_L3plus';
|
||||
|
||||
final public const FIDO_CERTIFIED_L4 = 'FIDO_CERTIFIED_L4';
|
||||
|
||||
final public const FIDO_CERTIFIED_L5 = 'FIDO_CERTIFIED_L5';
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public static function list(): array
|
||||
{
|
||||
return [
|
||||
self::NOT_FIDO_CERTIFIED,
|
||||
self::FIDO_CERTIFIED,
|
||||
self::USER_VERIFICATION_BYPASS,
|
||||
self::ATTESTATION_KEY_COMPROMISE,
|
||||
self::USER_KEY_REMOTE_COMPROMISE,
|
||||
self::USER_KEY_PHYSICAL_COMPROMISE,
|
||||
self::UPDATE_AVAILABLE,
|
||||
self::REVOKED,
|
||||
self::SELF_ASSERTION_SUBMITTED,
|
||||
self::FIDO_CERTIFIED_L1,
|
||||
self::FIDO_CERTIFIED_L1plus,
|
||||
self::FIDO_CERTIFIED_L2,
|
||||
self::FIDO_CERTIFIED_L2plus,
|
||||
self::FIDO_CERTIFIED_L3,
|
||||
self::FIDO_CERTIFIED_L3plus,
|
||||
self::FIDO_CERTIFIED_L4,
|
||||
self::FIDO_CERTIFIED_L5,
|
||||
];
|
||||
}
|
||||
}
|
||||
68
libraries/vendor/web-auth/metadata-service/src/Statement/BiometricAccuracyDescriptor.php
vendored
Normal file
68
libraries/vendor/web-auth/metadata-service/src/Statement/BiometricAccuracyDescriptor.php
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Statement;
|
||||
|
||||
use Webauthn\MetadataService\Utils;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class BiometricAccuracyDescriptor extends AbstractDescriptor
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ?float $selfAttestedFRR,
|
||||
private readonly ?float $selfAttestedFAR,
|
||||
private readonly ?float $maxTemplates,
|
||||
?int $maxRetries = null,
|
||||
?int $blockSlowdown = null
|
||||
) {
|
||||
parent::__construct($maxRetries, $blockSlowdown);
|
||||
}
|
||||
|
||||
public function getSelfAttestedFRR(): ?float
|
||||
{
|
||||
return $this->selfAttestedFRR;
|
||||
}
|
||||
|
||||
public function getSelfAttestedFAR(): ?float
|
||||
{
|
||||
return $this->selfAttestedFAR;
|
||||
}
|
||||
|
||||
public function getMaxTemplates(): ?float
|
||||
{
|
||||
return $this->maxTemplates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public static function createFromArray(array $data): self
|
||||
{
|
||||
return new self(
|
||||
$data['selfAttestedFRR'] ?? null,
|
||||
$data['selfAttestedFAR'] ?? null,
|
||||
$data['maxTemplates'] ?? null,
|
||||
$data['maxRetries'] ?? null,
|
||||
$data['blockSlowdown'] ?? null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$data = [
|
||||
'selfAttestedFRR' => $this->selfAttestedFRR,
|
||||
'selfAttestedFAR' => $this->selfAttestedFAR,
|
||||
'maxTemplates' => $this->maxTemplates,
|
||||
'maxRetries' => $this->getMaxRetries(),
|
||||
'blockSlowdown' => $this->getBlockSlowdown(),
|
||||
];
|
||||
|
||||
return Utils::filterNullValues($data);
|
||||
}
|
||||
}
|
||||
97
libraries/vendor/web-auth/metadata-service/src/Statement/BiometricStatusReport.php
vendored
Normal file
97
libraries/vendor/web-auth/metadata-service/src/Statement/BiometricStatusReport.php
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Statement;
|
||||
|
||||
use JsonSerializable;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class BiometricStatusReport implements JsonSerializable
|
||||
{
|
||||
private ?int $certLevel = null;
|
||||
|
||||
private ?int $modality = null;
|
||||
|
||||
private ?string $effectiveDate = null;
|
||||
|
||||
private ?string $certificationDescriptor = null;
|
||||
|
||||
private ?string $certificateNumber = null;
|
||||
|
||||
private ?string $certificationPolicyVersion = null;
|
||||
|
||||
private ?string $certificationRequirementsVersion = null;
|
||||
|
||||
public function getCertLevel(): int|null
|
||||
{
|
||||
return $this->certLevel;
|
||||
}
|
||||
|
||||
public function getModality(): int|null
|
||||
{
|
||||
return $this->modality;
|
||||
}
|
||||
|
||||
public function getEffectiveDate(): ?string
|
||||
{
|
||||
return $this->effectiveDate;
|
||||
}
|
||||
|
||||
public function getCertificationDescriptor(): ?string
|
||||
{
|
||||
return $this->certificationDescriptor;
|
||||
}
|
||||
|
||||
public function getCertificateNumber(): ?string
|
||||
{
|
||||
return $this->certificateNumber;
|
||||
}
|
||||
|
||||
public function getCertificationPolicyVersion(): ?string
|
||||
{
|
||||
return $this->certificationPolicyVersion;
|
||||
}
|
||||
|
||||
public function getCertificationRequirementsVersion(): ?string
|
||||
{
|
||||
return $this->certificationRequirementsVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public static function createFromArray(array $data): self
|
||||
{
|
||||
$object = new self();
|
||||
$object->certLevel = $data['certLevel'] ?? null;
|
||||
$object->modality = $data['modality'] ?? null;
|
||||
$object->effectiveDate = $data['effectiveDate'] ?? null;
|
||||
$object->certificationDescriptor = $data['certificationDescriptor'] ?? null;
|
||||
$object->certificateNumber = $data['certificateNumber'] ?? null;
|
||||
$object->certificationPolicyVersion = $data['certificationPolicyVersion'] ?? null;
|
||||
$object->certificationRequirementsVersion = $data['certificationRequirementsVersion'] ?? null;
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$data = [
|
||||
'certLevel' => $this->certLevel,
|
||||
'modality' => $this->modality,
|
||||
'effectiveDate' => $this->effectiveDate,
|
||||
'certificationDescriptor' => $this->certificationDescriptor,
|
||||
'certificateNumber' => $this->certificateNumber,
|
||||
'certificationPolicyVersion' => $this->certificationPolicyVersion,
|
||||
'certificationRequirementsVersion' => $this->certificationRequirementsVersion,
|
||||
];
|
||||
|
||||
return array_filter($data, static fn ($var): bool => $var !== null);
|
||||
}
|
||||
}
|
||||
77
libraries/vendor/web-auth/metadata-service/src/Statement/CodeAccuracyDescriptor.php
vendored
Normal file
77
libraries/vendor/web-auth/metadata-service/src/Statement/CodeAccuracyDescriptor.php
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Statement;
|
||||
|
||||
use function array_key_exists;
|
||||
use Webauthn\MetadataService\Exception\MetadataStatementLoadingException;
|
||||
use Webauthn\MetadataService\Utils;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class CodeAccuracyDescriptor extends AbstractDescriptor
|
||||
{
|
||||
private readonly int $base;
|
||||
|
||||
private readonly int $minLength;
|
||||
|
||||
public function __construct(int $base, int $minLength, ?int $maxRetries = null, ?int $blockSlowdown = null)
|
||||
{
|
||||
$base >= 0 || throw MetadataStatementLoadingException::create(
|
||||
'Invalid data. The value of "base" must be a positive integer'
|
||||
);
|
||||
$minLength >= 0 || throw MetadataStatementLoadingException::create(
|
||||
'Invalid data. The value of "minLength" must be a positive integer'
|
||||
);
|
||||
$this->base = $base;
|
||||
$this->minLength = $minLength;
|
||||
parent::__construct($maxRetries, $blockSlowdown);
|
||||
}
|
||||
|
||||
public function getBase(): int
|
||||
{
|
||||
return $this->base;
|
||||
}
|
||||
|
||||
public function getMinLength(): int
|
||||
{
|
||||
return $this->minLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public static function createFromArray(array $data): self
|
||||
{
|
||||
array_key_exists('base', $data) || throw MetadataStatementLoadingException::create(
|
||||
'The parameter "base" is missing'
|
||||
);
|
||||
array_key_exists('minLength', $data) || throw MetadataStatementLoadingException::create(
|
||||
'The parameter "minLength" is missing'
|
||||
);
|
||||
|
||||
return new self(
|
||||
$data['base'],
|
||||
$data['minLength'],
|
||||
$data['maxRetries'] ?? null,
|
||||
$data['blockSlowdown'] ?? null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$data = [
|
||||
'base' => $this->base,
|
||||
'minLength' => $this->minLength,
|
||||
'maxRetries' => $this->getMaxRetries(),
|
||||
'blockSlowdown' => $this->getBlockSlowdown(),
|
||||
];
|
||||
|
||||
return Utils::filterNullValues($data);
|
||||
}
|
||||
}
|
||||
180
libraries/vendor/web-auth/metadata-service/src/Statement/DisplayPNGCharacteristicsDescriptor.php
vendored
Normal file
180
libraries/vendor/web-auth/metadata-service/src/Statement/DisplayPNGCharacteristicsDescriptor.php
vendored
Normal file
@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Statement;
|
||||
|
||||
use function array_key_exists;
|
||||
use function is_array;
|
||||
use JsonSerializable;
|
||||
use Webauthn\MetadataService\Exception\MetadataStatementLoadingException;
|
||||
use Webauthn\MetadataService\Utils;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class DisplayPNGCharacteristicsDescriptor implements JsonSerializable
|
||||
{
|
||||
private readonly int $width;
|
||||
|
||||
private readonly int $height;
|
||||
|
||||
private readonly int $bitDepth;
|
||||
|
||||
private readonly int $colorType;
|
||||
|
||||
private readonly int $compression;
|
||||
|
||||
private readonly int $filter;
|
||||
|
||||
private readonly int $interlace;
|
||||
|
||||
/**
|
||||
* @var RgbPaletteEntry[]
|
||||
*/
|
||||
private array $plte = [];
|
||||
|
||||
public function __construct(
|
||||
int $width,
|
||||
int $height,
|
||||
int $bitDepth,
|
||||
int $colorType,
|
||||
int $compression,
|
||||
int $filter,
|
||||
int $interlace
|
||||
) {
|
||||
$width >= 0 || throw MetadataStatementLoadingException::create('Invalid width');
|
||||
$height >= 0 || throw MetadataStatementLoadingException::create('Invalid height');
|
||||
($bitDepth >= 0 && $bitDepth <= 254) || throw MetadataStatementLoadingException::create('Invalid bit depth');
|
||||
($colorType >= 0 && $colorType <= 254) || throw MetadataStatementLoadingException::create(
|
||||
'Invalid color type'
|
||||
);
|
||||
($compression >= 0 && $compression <= 254) || throw MetadataStatementLoadingException::create(
|
||||
'Invalid compression'
|
||||
);
|
||||
($filter >= 0 && $filter <= 254) || throw MetadataStatementLoadingException::create('Invalid filter');
|
||||
($interlace >= 0 && $interlace <= 254) || throw MetadataStatementLoadingException::create(
|
||||
'Invalid interlace'
|
||||
);
|
||||
|
||||
$this->width = $width;
|
||||
$this->height = $height;
|
||||
$this->bitDepth = $bitDepth;
|
||||
$this->colorType = $colorType;
|
||||
$this->compression = $compression;
|
||||
$this->filter = $filter;
|
||||
$this->interlace = $interlace;
|
||||
}
|
||||
|
||||
public function addPalettes(RgbPaletteEntry ...$rgbPaletteEntries): self
|
||||
{
|
||||
foreach ($rgbPaletteEntries as $rgbPaletteEntry) {
|
||||
$this->plte[] = $rgbPaletteEntry;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getWidth(): int
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
public function getHeight(): int
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
public function getBitDepth(): int
|
||||
{
|
||||
return $this->bitDepth;
|
||||
}
|
||||
|
||||
public function getColorType(): int
|
||||
{
|
||||
return $this->colorType;
|
||||
}
|
||||
|
||||
public function getCompression(): int
|
||||
{
|
||||
return $this->compression;
|
||||
}
|
||||
|
||||
public function getFilter(): int
|
||||
{
|
||||
return $this->filter;
|
||||
}
|
||||
|
||||
public function getInterlace(): int
|
||||
{
|
||||
return $this->interlace;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RgbPaletteEntry[]
|
||||
*/
|
||||
public function getPaletteEntries(): array
|
||||
{
|
||||
return $this->plte;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public static function createFromArray(array $data): self
|
||||
{
|
||||
$data = Utils::filterNullValues($data);
|
||||
foreach ([
|
||||
'width',
|
||||
'compression',
|
||||
'height',
|
||||
'bitDepth',
|
||||
'colorType',
|
||||
'compression',
|
||||
'filter',
|
||||
'interlace',
|
||||
] as $key) {
|
||||
array_key_exists($key, $data) || throw MetadataStatementLoadingException::create(sprintf(
|
||||
'Invalid data. The key "%s" is missing',
|
||||
$key
|
||||
));
|
||||
}
|
||||
$object = new self(
|
||||
$data['width'],
|
||||
$data['height'],
|
||||
$data['bitDepth'],
|
||||
$data['colorType'],
|
||||
$data['compression'],
|
||||
$data['filter'],
|
||||
$data['interlace']
|
||||
);
|
||||
if (isset($data['plte'])) {
|
||||
$plte = $data['plte'];
|
||||
is_array($plte) || throw MetadataStatementLoadingException::create('Invalid "plte" parameter');
|
||||
foreach ($plte as $item) {
|
||||
$object->addPalettes(RgbPaletteEntry::createFromArray($item));
|
||||
}
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$data = [
|
||||
'width' => $this->width,
|
||||
'height' => $this->height,
|
||||
'bitDepth' => $this->bitDepth,
|
||||
'colorType' => $this->colorType,
|
||||
'compression' => $this->compression,
|
||||
'filter' => $this->filter,
|
||||
'interlace' => $this->interlace,
|
||||
'plte' => $this->plte,
|
||||
];
|
||||
|
||||
return Utils::filterNullValues($data);
|
||||
}
|
||||
}
|
||||
98
libraries/vendor/web-auth/metadata-service/src/Statement/EcdaaTrustAnchor.php
vendored
Normal file
98
libraries/vendor/web-auth/metadata-service/src/Statement/EcdaaTrustAnchor.php
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Statement;
|
||||
|
||||
use function array_key_exists;
|
||||
use JsonSerializable;
|
||||
use ParagonIE\ConstantTime\Base64UrlSafe;
|
||||
use Webauthn\MetadataService\Exception\MetadataStatementLoadingException;
|
||||
use Webauthn\MetadataService\Utils;
|
||||
|
||||
/**
|
||||
* @deprecated since 4.2.0 and will be removed in 5.0.0. The ECDAA Trust Anchor does no longer exist in Webauthn specification.
|
||||
* @final
|
||||
*/
|
||||
class EcdaaTrustAnchor implements JsonSerializable
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $X,
|
||||
private readonly string $Y,
|
||||
private readonly string $c,
|
||||
private readonly string $sx,
|
||||
private readonly string $sy,
|
||||
private readonly string $G1Curve
|
||||
) {
|
||||
}
|
||||
|
||||
public function getX(): string
|
||||
{
|
||||
return $this->X;
|
||||
}
|
||||
|
||||
public function getY(): string
|
||||
{
|
||||
return $this->Y;
|
||||
}
|
||||
|
||||
public function getC(): string
|
||||
{
|
||||
return $this->c;
|
||||
}
|
||||
|
||||
public function getSx(): string
|
||||
{
|
||||
return $this->sx;
|
||||
}
|
||||
|
||||
public function getSy(): string
|
||||
{
|
||||
return $this->sy;
|
||||
}
|
||||
|
||||
public function getG1Curve(): string
|
||||
{
|
||||
return $this->G1Curve;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public static function createFromArray(array $data): self
|
||||
{
|
||||
$data = Utils::filterNullValues($data);
|
||||
foreach (['X', 'Y', 'c', 'sx', 'sy', 'G1Curve'] as $key) {
|
||||
array_key_exists($key, $data) || throw MetadataStatementLoadingException::create(sprintf(
|
||||
'Invalid data. The key "%s" is missing',
|
||||
$key
|
||||
));
|
||||
}
|
||||
|
||||
return new self(
|
||||
Base64UrlSafe::decode($data['X']),
|
||||
Base64UrlSafe::decode($data['Y']),
|
||||
Base64UrlSafe::decode($data['c']),
|
||||
Base64UrlSafe::decode($data['sx']),
|
||||
Base64UrlSafe::decode($data['sy']),
|
||||
$data['G1Curve']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$data = [
|
||||
'X' => Base64UrlSafe::encodeUnpadded($this->X),
|
||||
'Y' => Base64UrlSafe::encodeUnpadded($this->Y),
|
||||
'c' => Base64UrlSafe::encodeUnpadded($this->c),
|
||||
'sx' => Base64UrlSafe::encodeUnpadded($this->sx),
|
||||
'sy' => Base64UrlSafe::encodeUnpadded($this->sy),
|
||||
'G1Curve' => $this->G1Curve,
|
||||
];
|
||||
|
||||
return Utils::filterNullValues($data);
|
||||
}
|
||||
}
|
||||
83
libraries/vendor/web-auth/metadata-service/src/Statement/ExtensionDescriptor.php
vendored
Normal file
83
libraries/vendor/web-auth/metadata-service/src/Statement/ExtensionDescriptor.php
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Statement;
|
||||
|
||||
use function array_key_exists;
|
||||
use JsonSerializable;
|
||||
use Webauthn\MetadataService\Exception\MetadataStatementLoadingException;
|
||||
use Webauthn\MetadataService\Utils;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class ExtensionDescriptor implements JsonSerializable
|
||||
{
|
||||
private readonly ?int $tag;
|
||||
|
||||
public function __construct(
|
||||
private readonly string $id,
|
||||
?int $tag,
|
||||
private readonly ?string $data,
|
||||
private readonly bool $failIfUnknown
|
||||
) {
|
||||
if ($tag !== null) {
|
||||
$tag >= 0 || throw MetadataStatementLoadingException::create(
|
||||
'Invalid data. The parameter "tag" shall be a positive integer'
|
||||
);
|
||||
}
|
||||
$this->tag = $tag;
|
||||
}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getTag(): ?int
|
||||
{
|
||||
return $this->tag;
|
||||
}
|
||||
|
||||
public function getData(): ?string
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function isFailIfUnknown(): bool
|
||||
{
|
||||
return $this->failIfUnknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public static function createFromArray(array $data): self
|
||||
{
|
||||
$data = Utils::filterNullValues($data);
|
||||
array_key_exists('id', $data) || throw MetadataStatementLoadingException::create(
|
||||
'Invalid data. The parameter "id" is missing'
|
||||
);
|
||||
array_key_exists('fail_if_unknown', $data) || throw MetadataStatementLoadingException::create(
|
||||
'Invalid data. The parameter "fail_if_unknown" is missing'
|
||||
);
|
||||
|
||||
return new self($data['id'], $data['tag'] ?? null, $data['data'] ?? null, $data['fail_if_unknown']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$result = [
|
||||
'id' => $this->id,
|
||||
'tag' => $this->tag,
|
||||
'data' => $this->data,
|
||||
'fail_if_unknown' => $this->failIfUnknown,
|
||||
];
|
||||
|
||||
return Utils::filterNullValues($result);
|
||||
}
|
||||
}
|
||||
556
libraries/vendor/web-auth/metadata-service/src/Statement/MetadataStatement.php
vendored
Normal file
556
libraries/vendor/web-auth/metadata-service/src/Statement/MetadataStatement.php
vendored
Normal file
@ -0,0 +1,556 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Statement;
|
||||
|
||||
use function array_key_exists;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
use JsonSerializable;
|
||||
use Webauthn\MetadataService\CertificateChain\CertificateToolbox;
|
||||
use Webauthn\MetadataService\Exception\MetadataStatementLoadingException;
|
||||
use Webauthn\MetadataService\Utils;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class MetadataStatement implements JsonSerializable
|
||||
{
|
||||
final public const KEY_PROTECTION_SOFTWARE = 'software';
|
||||
|
||||
final public const KEY_PROTECTION_HARDWARE = 'hardware';
|
||||
|
||||
final public const KEY_PROTECTION_TEE = 'tee';
|
||||
|
||||
final public const KEY_PROTECTION_SECURE_ELEMENT = 'secure_element';
|
||||
|
||||
final public const KEY_PROTECTION_REMOTE_HANDLE = 'remote_handle';
|
||||
|
||||
final public const MATCHER_PROTECTION_SOFTWARE = 'software';
|
||||
|
||||
final public const MATCHER_PROTECTION_TEE = 'tee';
|
||||
|
||||
final public const MATCHER_PROTECTION_ON_CHIP = 'on_chip';
|
||||
|
||||
final public const ATTACHMENT_HINT_INTERNAL = 'internal';
|
||||
|
||||
final public const ATTACHMENT_HINT_EXTERNAL = 'external';
|
||||
|
||||
final public const ATTACHMENT_HINT_WIRED = 'wired';
|
||||
|
||||
final public const ATTACHMENT_HINT_WIRELESS = 'wireless';
|
||||
|
||||
final public const ATTACHMENT_HINT_NFC = 'nfc';
|
||||
|
||||
final public const ATTACHMENT_HINT_BLUETOOTH = 'bluetooth';
|
||||
|
||||
final public const ATTACHMENT_HINT_NETWORK = 'network';
|
||||
|
||||
final public const ATTACHMENT_HINT_READY = 'ready';
|
||||
|
||||
final public const ATTACHMENT_HINT_WIFI_DIRECT = 'wifi_direct';
|
||||
|
||||
final public const TRANSACTION_CONFIRMATION_DISPLAY_ANY = 'any';
|
||||
|
||||
final public const TRANSACTION_CONFIRMATION_DISPLAY_PRIVILEGED_SOFTWARE = 'privileged_software';
|
||||
|
||||
final public const TRANSACTION_CONFIRMATION_DISPLAY_TEE = 'tee';
|
||||
|
||||
final public const TRANSACTION_CONFIRMATION_DISPLAY_HARDWARE = 'hardware';
|
||||
|
||||
final public const TRANSACTION_CONFIRMATION_DISPLAY_REMOTE = 'remote';
|
||||
|
||||
final public const ALG_SIGN_SECP256R1_ECDSA_SHA256_RAW = 'secp256r1_ecdsa_sha256_raw';
|
||||
|
||||
final public const ALG_SIGN_SECP256R1_ECDSA_SHA256_DER = 'secp256r1_ecdsa_sha256_der';
|
||||
|
||||
final public const ALG_SIGN_RSASSA_PSS_SHA256_RAW = 'rsassa_pss_sha256_raw';
|
||||
|
||||
final public const ALG_SIGN_RSASSA_PSS_SHA256_DER = 'rsassa_pss_sha256_der';
|
||||
|
||||
final public const ALG_SIGN_SECP256K1_ECDSA_SHA256_RAW = 'secp256k1_ecdsa_sha256_raw';
|
||||
|
||||
final public const ALG_SIGN_SECP256K1_ECDSA_SHA256_DER = 'secp256k1_ecdsa_sha256_der';
|
||||
|
||||
final public const ALG_SIGN_SM2_SM3_RAW = 'sm2_sm3_raw';
|
||||
|
||||
final public const ALG_SIGN_RSA_EMSA_PKCS1_SHA256_RAW = 'rsa_emsa_pkcs1_sha256_raw';
|
||||
|
||||
final public const ALG_SIGN_RSA_EMSA_PKCS1_SHA256_DER = 'rsa_emsa_pkcs1_sha256_der';
|
||||
|
||||
final public const ALG_SIGN_RSASSA_PSS_SHA384_RAW = 'rsassa_pss_sha384_raw';
|
||||
|
||||
final public const ALG_SIGN_RSASSA_PSS_SHA512_RAW = 'rsassa_pss_sha256_raw';
|
||||
|
||||
final public const ALG_SIGN_RSASSA_PKCSV15_SHA256_RAW = 'rsassa_pkcsv15_sha256_raw';
|
||||
|
||||
final public const ALG_SIGN_RSASSA_PKCSV15_SHA384_RAW = 'rsassa_pkcsv15_sha384_raw';
|
||||
|
||||
final public const ALG_SIGN_RSASSA_PKCSV15_SHA512_RAW = 'rsassa_pkcsv15_sha512_raw';
|
||||
|
||||
final public const ALG_SIGN_RSASSA_PKCSV15_SHA1_RAW = 'rsassa_pkcsv15_sha1_raw';
|
||||
|
||||
final public const ALG_SIGN_SECP384R1_ECDSA_SHA384_RAW = 'secp384r1_ecdsa_sha384_raw';
|
||||
|
||||
final public const ALG_SIGN_SECP521R1_ECDSA_SHA512_RAW = 'secp512r1_ecdsa_sha256_raw';
|
||||
|
||||
final public const ALG_SIGN_ED25519_EDDSA_SHA256_RAW = 'ed25519_eddsa_sha512_raw';
|
||||
|
||||
final public const ALG_KEY_ECC_X962_RAW = 'ecc_x962_raw';
|
||||
|
||||
final public const ALG_KEY_ECC_X962_DER = 'ecc_x962_der';
|
||||
|
||||
final public const ALG_KEY_RSA_2048_RAW = 'rsa_2048_raw';
|
||||
|
||||
final public const ALG_KEY_RSA_2048_DER = 'rsa_2048_der';
|
||||
|
||||
final public const ALG_KEY_COSE = 'cose';
|
||||
|
||||
final public const ATTESTATION_BASIC_FULL = 'basic_full';
|
||||
|
||||
final public const ATTESTATION_BASIC_SURROGATE = 'basic_surrogate';
|
||||
|
||||
/**
|
||||
* @deprecated since 4.2.0 and will be removed in 5.0.0. The ECDAA Trust Anchor does no longer exist in Webauthn specification.
|
||||
*/
|
||||
final public const ATTESTATION_ECDAA = 'ecdaa';
|
||||
|
||||
final public const ATTESTATION_ATTCA = 'attca';
|
||||
|
||||
final public const ATTESTATION_ANONCA = 'anonca';
|
||||
|
||||
private ?string $legalHeader = null;
|
||||
|
||||
private ?string $aaid = null;
|
||||
|
||||
private ?string $aaguid = null;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private array $attestationCertificateKeyIdentifiers = [];
|
||||
|
||||
private AlternativeDescriptions $alternativeDescriptions;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private array $keyProtection = [];
|
||||
|
||||
private ?bool $isKeyRestricted = null;
|
||||
|
||||
private ?bool $isFreshUserVerificationRequired = null;
|
||||
|
||||
private ?int $cryptoStrength = null;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private array $attachmentHint = [];
|
||||
|
||||
private ?string $tcDisplayContentType = null;
|
||||
|
||||
/**
|
||||
* @var DisplayPNGCharacteristicsDescriptor[]
|
||||
*/
|
||||
private array $tcDisplayPNGCharacteristics = [];
|
||||
|
||||
/**
|
||||
* @var EcdaaTrustAnchor[]
|
||||
*/
|
||||
private array $ecdaaTrustAnchors = [];
|
||||
|
||||
private ?string $icon = null;
|
||||
|
||||
/**
|
||||
* @var ExtensionDescriptor[]
|
||||
*/
|
||||
private array $supportedExtensions = [];
|
||||
|
||||
private null|AuthenticatorGetInfo $authenticatorGetInfo = null;
|
||||
|
||||
/**
|
||||
* @param Version[] $upv
|
||||
* @param string[] $authenticationAlgorithms
|
||||
* @param string[] $publicKeyAlgAndEncodings
|
||||
* @param string[] $attestationTypes
|
||||
* @param VerificationMethodANDCombinations[] $userVerificationDetails
|
||||
* @param string[] $matcherProtection
|
||||
* @param string[] $tcDisplay
|
||||
* @param string[] $attestationRootCertificates
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly string $description,
|
||||
private readonly int $authenticatorVersion,
|
||||
private readonly string $protocolFamily,
|
||||
private readonly int $schema,
|
||||
private readonly array $upv,
|
||||
private readonly array $authenticationAlgorithms,
|
||||
private readonly array $publicKeyAlgAndEncodings,
|
||||
private readonly array $attestationTypes,
|
||||
private readonly array $userVerificationDetails,
|
||||
private readonly array $matcherProtection,
|
||||
private readonly array $tcDisplay,
|
||||
private readonly array $attestationRootCertificates,
|
||||
) {
|
||||
$this->alternativeDescriptions = new AlternativeDescriptions();
|
||||
$this->authenticatorGetInfo = new AuthenticatorGetInfo();
|
||||
}
|
||||
|
||||
public static function createFromString(string $statement): self
|
||||
{
|
||||
$data = json_decode($statement, true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
return self::createFromArray($data);
|
||||
}
|
||||
|
||||
public function getLegalHeader(): ?string
|
||||
{
|
||||
return $this->legalHeader;
|
||||
}
|
||||
|
||||
public function getAaid(): ?string
|
||||
{
|
||||
return $this->aaid;
|
||||
}
|
||||
|
||||
public function getAaguid(): ?string
|
||||
{
|
||||
return $this->aaguid;
|
||||
}
|
||||
|
||||
public function isKeyRestricted(): ?bool
|
||||
{
|
||||
return $this->isKeyRestricted;
|
||||
}
|
||||
|
||||
public function isFreshUserVerificationRequired(): ?bool
|
||||
{
|
||||
return $this->isFreshUserVerificationRequired;
|
||||
}
|
||||
|
||||
public function getAuthenticatorGetInfo(): AuthenticatorGetInfo|null
|
||||
{
|
||||
return $this->authenticatorGetInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttestationCertificateKeyIdentifiers(): array
|
||||
{
|
||||
return $this->attestationCertificateKeyIdentifiers;
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function getAlternativeDescriptions(): AlternativeDescriptions
|
||||
{
|
||||
return $this->alternativeDescriptions;
|
||||
}
|
||||
|
||||
public function getAuthenticatorVersion(): int
|
||||
{
|
||||
return $this->authenticatorVersion;
|
||||
}
|
||||
|
||||
public function getProtocolFamily(): string
|
||||
{
|
||||
return $this->protocolFamily;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Version[]
|
||||
*/
|
||||
public function getUpv(): array
|
||||
{
|
||||
return $this->upv;
|
||||
}
|
||||
|
||||
public function getSchema(): ?int
|
||||
{
|
||||
return $this->schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAuthenticationAlgorithms(): array
|
||||
{
|
||||
return $this->authenticationAlgorithms;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getPublicKeyAlgAndEncodings(): array
|
||||
{
|
||||
return $this->publicKeyAlgAndEncodings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttestationTypes(): array
|
||||
{
|
||||
return $this->attestationTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return VerificationMethodANDCombinations[]
|
||||
*/
|
||||
public function getUserVerificationDetails(): array
|
||||
{
|
||||
return $this->userVerificationDetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getKeyProtection(): array
|
||||
{
|
||||
return $this->keyProtection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getMatcherProtection(): array
|
||||
{
|
||||
return $this->matcherProtection;
|
||||
}
|
||||
|
||||
public function getCryptoStrength(): ?int
|
||||
{
|
||||
return $this->cryptoStrength;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttachmentHint(): array
|
||||
{
|
||||
return $this->attachmentHint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getTcDisplay(): array
|
||||
{
|
||||
return $this->tcDisplay;
|
||||
}
|
||||
|
||||
public function getTcDisplayContentType(): ?string
|
||||
{
|
||||
return $this->tcDisplayContentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DisplayPNGCharacteristicsDescriptor[]
|
||||
*/
|
||||
public function getTcDisplayPNGCharacteristics(): array
|
||||
{
|
||||
return $this->tcDisplayPNGCharacteristics;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttestationRootCertificates(): array
|
||||
{
|
||||
return $this->attestationRootCertificates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EcdaaTrustAnchor[]
|
||||
*
|
||||
* @deprecated since 4.2.0 and will be removed in 5.0.0. The ECDAA Trust Anchor does no longer exist in Webauthn specification.
|
||||
*/
|
||||
public function getEcdaaTrustAnchors(): array
|
||||
{
|
||||
return $this->ecdaaTrustAnchors;
|
||||
}
|
||||
|
||||
public function getIcon(): ?string
|
||||
{
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ExtensionDescriptor[]
|
||||
*/
|
||||
public function getSupportedExtensions(): array
|
||||
{
|
||||
return $this->supportedExtensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public static function createFromArray(array $data): self
|
||||
{
|
||||
$requiredKeys = [
|
||||
'description',
|
||||
'authenticatorVersion',
|
||||
'protocolFamily',
|
||||
'schema',
|
||||
'upv',
|
||||
'authenticationAlgorithms',
|
||||
'publicKeyAlgAndEncodings',
|
||||
'attestationTypes',
|
||||
'userVerificationDetails',
|
||||
'matcherProtection',
|
||||
'tcDisplay',
|
||||
'attestationRootCertificates',
|
||||
];
|
||||
foreach ($requiredKeys as $key) {
|
||||
array_key_exists($key, $data) || throw MetadataStatementLoadingException::create(sprintf(
|
||||
'Invalid data. The key "%s" is missing',
|
||||
$key
|
||||
));
|
||||
}
|
||||
$subObjects = [
|
||||
'authenticationAlgorithms',
|
||||
'publicKeyAlgAndEncodings',
|
||||
'attestationTypes',
|
||||
'matcherProtection',
|
||||
'tcDisplay',
|
||||
'attestationRootCertificates',
|
||||
];
|
||||
foreach ($subObjects as $subObject) {
|
||||
is_array($data[$subObject]) || throw MetadataStatementLoadingException::create(sprintf(
|
||||
'Invalid Metadata Statement. The parameter "%s" shall be a list of strings.',
|
||||
$subObject
|
||||
));
|
||||
foreach ($data[$subObject] as $datum) {
|
||||
is_string($datum) || throw MetadataStatementLoadingException::create(sprintf(
|
||||
'Invalid Metadata Statement. The parameter "%s" shall be a list of strings.',
|
||||
$subObject
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$object = new self(
|
||||
$data['description'],
|
||||
$data['authenticatorVersion'],
|
||||
$data['protocolFamily'],
|
||||
$data['schema'],
|
||||
array_map(static function ($upv): Version {
|
||||
is_array($upv) || throw MetadataStatementLoadingException::create('Invalid Metadata Statement');
|
||||
|
||||
return Version::createFromArray($upv);
|
||||
}, $data['upv']),
|
||||
$data['authenticationAlgorithms'],
|
||||
$data['publicKeyAlgAndEncodings'],
|
||||
$data['attestationTypes'],
|
||||
array_map(static function ($userVerificationDetails): VerificationMethodANDCombinations {
|
||||
is_array($userVerificationDetails) || throw MetadataStatementLoadingException::create(
|
||||
'Invalid Metadata Statement'
|
||||
);
|
||||
|
||||
return VerificationMethodANDCombinations::createFromArray($userVerificationDetails);
|
||||
}, $data['userVerificationDetails']),
|
||||
$data['matcherProtection'],
|
||||
$data['tcDisplay'],
|
||||
CertificateToolbox::fixPEMStructures($data['attestationRootCertificates'])
|
||||
);
|
||||
|
||||
$object->legalHeader = $data['legalHeader'] ?? null;
|
||||
$object->aaid = $data['aaid'] ?? null;
|
||||
$object->aaguid = $data['aaguid'] ?? null;
|
||||
$object->attestationCertificateKeyIdentifiers = $data['attestationCertificateKeyIdentifiers'] ?? [];
|
||||
$object->alternativeDescriptions = AlternativeDescriptions::create($data['alternativeDescriptions'] ?? []);
|
||||
$object->authenticatorGetInfo = isset($data['attestationTypes']) ? AuthenticatorGetInfo::create(
|
||||
$data['attestationTypes']
|
||||
) : null;
|
||||
$object->keyProtection = $data['keyProtection'] ?? [];
|
||||
$object->isKeyRestricted = $data['isKeyRestricted'] ?? null;
|
||||
$object->isFreshUserVerificationRequired = $data['isFreshUserVerificationRequired'] ?? null;
|
||||
$object->cryptoStrength = $data['cryptoStrength'] ?? null;
|
||||
$object->attachmentHint = $data['attachmentHint'] ?? [];
|
||||
$object->tcDisplayContentType = $data['tcDisplayContentType'] ?? null;
|
||||
if (isset($data['tcDisplayPNGCharacteristics'])) {
|
||||
$tcDisplayPNGCharacteristics = $data['tcDisplayPNGCharacteristics'];
|
||||
is_array($tcDisplayPNGCharacteristics) || throw MetadataStatementLoadingException::create(
|
||||
'Invalid Metadata Statement'
|
||||
);
|
||||
foreach ($tcDisplayPNGCharacteristics as $tcDisplayPNGCharacteristic) {
|
||||
is_array($tcDisplayPNGCharacteristic) || throw MetadataStatementLoadingException::create(
|
||||
'Invalid Metadata Statement'
|
||||
);
|
||||
$object->tcDisplayPNGCharacteristics[] = DisplayPNGCharacteristicsDescriptor::createFromArray(
|
||||
$tcDisplayPNGCharacteristic
|
||||
);
|
||||
}
|
||||
}
|
||||
$object->ecdaaTrustAnchors = $data['ecdaaTrustAnchors'] ?? [];
|
||||
$object->icon = $data['icon'] ?? null;
|
||||
if (isset($data['supportedExtensions'])) {
|
||||
$supportedExtensions = $data['supportedExtensions'];
|
||||
is_array($supportedExtensions) || throw MetadataStatementLoadingException::create(
|
||||
'Invalid Metadata Statement'
|
||||
);
|
||||
foreach ($supportedExtensions as $supportedExtension) {
|
||||
is_array($supportedExtension) || throw MetadataStatementLoadingException::create(
|
||||
'Invalid Metadata Statement'
|
||||
);
|
||||
$object->supportedExtensions[] = ExtensionDescriptor::createFromArray($supportedExtension);
|
||||
}
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$data = [
|
||||
'legalHeader' => $this->legalHeader,
|
||||
'aaid' => $this->aaid,
|
||||
'aaguid' => $this->aaguid,
|
||||
'attestationCertificateKeyIdentifiers' => $this->attestationCertificateKeyIdentifiers,
|
||||
'description' => $this->description,
|
||||
'alternativeDescriptions' => $this->alternativeDescriptions,
|
||||
'authenticatorVersion' => $this->authenticatorVersion,
|
||||
'protocolFamily' => $this->protocolFamily,
|
||||
'upv' => $this->upv,
|
||||
'authenticationAlgorithms' => $this->authenticationAlgorithms,
|
||||
'publicKeyAlgAndEncodings' => $this->publicKeyAlgAndEncodings,
|
||||
'attestationTypes' => $this->attestationTypes,
|
||||
'userVerificationDetails' => $this->userVerificationDetails,
|
||||
'keyProtection' => $this->keyProtection,
|
||||
'isKeyRestricted' => $this->isKeyRestricted,
|
||||
'isFreshUserVerificationRequired' => $this->isFreshUserVerificationRequired,
|
||||
'matcherProtection' => $this->matcherProtection,
|
||||
'cryptoStrength' => $this->cryptoStrength,
|
||||
'attachmentHint' => $this->attachmentHint,
|
||||
'tcDisplay' => $this->tcDisplay,
|
||||
'tcDisplayContentType' => $this->tcDisplayContentType,
|
||||
'tcDisplayPNGCharacteristics' => array_map(
|
||||
static fn (DisplayPNGCharacteristicsDescriptor $object): array => $object->jsonSerialize(),
|
||||
$this->tcDisplayPNGCharacteristics
|
||||
),
|
||||
'attestationRootCertificates' => CertificateToolbox::fixPEMStructures($this->attestationRootCertificates),
|
||||
'ecdaaTrustAnchors' => array_map(
|
||||
static fn (EcdaaTrustAnchor $object): array => $object->jsonSerialize(),
|
||||
$this->ecdaaTrustAnchors
|
||||
),
|
||||
'icon' => $this->icon,
|
||||
'authenticatorGetInfo' => $this->authenticatorGetInfo,
|
||||
'supportedExtensions' => array_map(
|
||||
static fn (ExtensionDescriptor $object): array => $object->jsonSerialize(),
|
||||
$this->supportedExtensions
|
||||
),
|
||||
];
|
||||
|
||||
return Utils::filterNullValues($data);
|
||||
}
|
||||
}
|
||||
66
libraries/vendor/web-auth/metadata-service/src/Statement/PatternAccuracyDescriptor.php
vendored
Normal file
66
libraries/vendor/web-auth/metadata-service/src/Statement/PatternAccuracyDescriptor.php
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Statement;
|
||||
|
||||
use function array_key_exists;
|
||||
use function is_int;
|
||||
use Webauthn\MetadataService\Exception\MetadataStatementLoadingException;
|
||||
use Webauthn\MetadataService\Utils;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class PatternAccuracyDescriptor extends AbstractDescriptor
|
||||
{
|
||||
private readonly int $minComplexity;
|
||||
|
||||
public function __construct(int $minComplexity, ?int $maxRetries = null, ?int $blockSlowdown = null)
|
||||
{
|
||||
$minComplexity >= 0 || throw MetadataStatementLoadingException::create(
|
||||
'Invalid data. The value of "minComplexity" must be a positive integer'
|
||||
);
|
||||
$this->minComplexity = $minComplexity;
|
||||
parent::__construct($maxRetries, $blockSlowdown);
|
||||
}
|
||||
|
||||
public function getMinComplexity(): int
|
||||
{
|
||||
return $this->minComplexity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public static function createFromArray(array $data): self
|
||||
{
|
||||
$data = Utils::filterNullValues($data);
|
||||
array_key_exists('minComplexity', $data) || throw MetadataStatementLoadingException::create(
|
||||
'The key "minComplexity" is missing'
|
||||
);
|
||||
foreach (['minComplexity', 'maxRetries', 'blockSlowdown'] as $key) {
|
||||
if (array_key_exists($key, $data)) {
|
||||
is_int($data[$key]) || throw MetadataStatementLoadingException::create(
|
||||
sprintf('Invalid data. The value of "%s" must be a positive integer', $key)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new self($data['minComplexity'], $data['maxRetries'] ?? null, $data['blockSlowdown'] ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, int|null>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$data = [
|
||||
'minComplexity' => $this->minComplexity,
|
||||
'maxRetries' => $this->getMaxRetries(),
|
||||
'blockSlowdown' => $this->getBlockSlowdown(),
|
||||
];
|
||||
|
||||
return Utils::filterNullValues($data);
|
||||
}
|
||||
}
|
||||
77
libraries/vendor/web-auth/metadata-service/src/Statement/RgbPaletteEntry.php
vendored
Normal file
77
libraries/vendor/web-auth/metadata-service/src/Statement/RgbPaletteEntry.php
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Statement;
|
||||
|
||||
use function array_key_exists;
|
||||
use function is_int;
|
||||
use JsonSerializable;
|
||||
use Webauthn\MetadataService\Exception\MetadataStatementLoadingException;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class RgbPaletteEntry implements JsonSerializable
|
||||
{
|
||||
private readonly int $r;
|
||||
|
||||
private readonly int $g;
|
||||
|
||||
private readonly int $b;
|
||||
|
||||
public function __construct(int $r, int $g, int $b)
|
||||
{
|
||||
($r >= 0 && $r <= 255) || throw MetadataStatementLoadingException::create('The key "r" is invalid');
|
||||
($g >= 0 && $g <= 255) || throw MetadataStatementLoadingException::create('The key "g" is invalid');
|
||||
($b >= 0 && $b <= 255) || throw MetadataStatementLoadingException::create('The key "b" is invalid');
|
||||
$this->r = $r;
|
||||
$this->g = $g;
|
||||
$this->b = $b;
|
||||
}
|
||||
|
||||
public function getR(): int
|
||||
{
|
||||
return $this->r;
|
||||
}
|
||||
|
||||
public function getG(): int
|
||||
{
|
||||
return $this->g;
|
||||
}
|
||||
|
||||
public function getB(): int
|
||||
{
|
||||
return $this->b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public static function createFromArray(array $data): self
|
||||
{
|
||||
foreach (['r', 'g', 'b'] as $key) {
|
||||
array_key_exists($key, $data) || throw MetadataStatementLoadingException::create(sprintf(
|
||||
'The key "%s" is missing',
|
||||
$key
|
||||
));
|
||||
is_int($data[$key]) || throw MetadataStatementLoadingException::create(
|
||||
sprintf('The key "%s" is invalid', $key)
|
||||
);
|
||||
}
|
||||
|
||||
return new self($data['r'], $data['g'], $data['b']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, int>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'r' => $this->r,
|
||||
'g' => $this->g,
|
||||
'b' => $this->b,
|
||||
];
|
||||
}
|
||||
}
|
||||
58
libraries/vendor/web-auth/metadata-service/src/Statement/RogueListEntry.php
vendored
Normal file
58
libraries/vendor/web-auth/metadata-service/src/Statement/RogueListEntry.php
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Statement;
|
||||
|
||||
use function array_key_exists;
|
||||
use function is_string;
|
||||
use JsonSerializable;
|
||||
use Webauthn\MetadataService\Exception\MetadataStatementLoadingException;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class RogueListEntry implements JsonSerializable
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $sk,
|
||||
private readonly string $date
|
||||
) {
|
||||
}
|
||||
|
||||
public function getSk(): string
|
||||
{
|
||||
return $this->sk;
|
||||
}
|
||||
|
||||
public function getDate(): ?string
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public static function createFromArray(array $data): self
|
||||
{
|
||||
array_key_exists('sk', $data) || throw MetadataStatementLoadingException::create('The key "sk" is missing');
|
||||
is_string($data['sk']) || throw MetadataStatementLoadingException::create('The key "date" is invalid');
|
||||
array_key_exists('date', $data) || throw MetadataStatementLoadingException::create(
|
||||
'The key "date" is missing'
|
||||
);
|
||||
is_string($data['date']) || throw MetadataStatementLoadingException::create('The key "date" is invalid');
|
||||
|
||||
return new self($data['sk'], $data['date']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'sk' => $this->sk,
|
||||
'date' => $this->date,
|
||||
];
|
||||
}
|
||||
}
|
||||
148
libraries/vendor/web-auth/metadata-service/src/Statement/StatusReport.php
vendored
Normal file
148
libraries/vendor/web-auth/metadata-service/src/Statement/StatusReport.php
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Statement;
|
||||
|
||||
use function array_key_exists;
|
||||
use function in_array;
|
||||
use function is_string;
|
||||
use JsonSerializable;
|
||||
use Webauthn\MetadataService\Exception\MetadataStatementLoadingException;
|
||||
use Webauthn\MetadataService\Utils;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class StatusReport implements JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @see AuthenticatorStatus
|
||||
*/
|
||||
private readonly string $status;
|
||||
|
||||
public function __construct(
|
||||
string $status,
|
||||
private readonly ?string $effectiveDate,
|
||||
private readonly ?string $certificate,
|
||||
private readonly ?string $url,
|
||||
private readonly ?string $certificationDescriptor,
|
||||
private readonly ?string $certificateNumber,
|
||||
private readonly ?string $certificationPolicyVersion,
|
||||
private readonly ?string $certificationRequirementsVersion
|
||||
) {
|
||||
in_array($status, AuthenticatorStatus::list(), true) || throw MetadataStatementLoadingException::create(
|
||||
'The value of the key "status" is not acceptable'
|
||||
);
|
||||
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
public function isCompromised(): bool
|
||||
{
|
||||
return in_array($this->status, [
|
||||
AuthenticatorStatus::ATTESTATION_KEY_COMPROMISE,
|
||||
AuthenticatorStatus::USER_KEY_PHYSICAL_COMPROMISE,
|
||||
AuthenticatorStatus::USER_KEY_REMOTE_COMPROMISE,
|
||||
AuthenticatorStatus::USER_VERIFICATION_BYPASS,
|
||||
], true);
|
||||
}
|
||||
|
||||
public function getStatus(): string
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
public function getEffectiveDate(): ?string
|
||||
{
|
||||
return $this->effectiveDate;
|
||||
}
|
||||
|
||||
public function getCertificate(): ?string
|
||||
{
|
||||
return $this->certificate;
|
||||
}
|
||||
|
||||
public function getUrl(): ?string
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
public function getCertificationDescriptor(): ?string
|
||||
{
|
||||
return $this->certificationDescriptor;
|
||||
}
|
||||
|
||||
public function getCertificateNumber(): ?string
|
||||
{
|
||||
return $this->certificateNumber;
|
||||
}
|
||||
|
||||
public function getCertificationPolicyVersion(): ?string
|
||||
{
|
||||
return $this->certificationPolicyVersion;
|
||||
}
|
||||
|
||||
public function getCertificationRequirementsVersion(): ?string
|
||||
{
|
||||
return $this->certificationRequirementsVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public static function createFromArray(array $data): self
|
||||
{
|
||||
$data = Utils::filterNullValues($data);
|
||||
array_key_exists('status', $data) || throw MetadataStatementLoadingException::create(
|
||||
'The key "status" is missing'
|
||||
);
|
||||
foreach ([
|
||||
'effectiveDate',
|
||||
'certificate',
|
||||
'url',
|
||||
'certificationDescriptor',
|
||||
'certificateNumber',
|
||||
'certificationPolicyVersion',
|
||||
'certificationRequirementsVersion',
|
||||
] as $key) {
|
||||
if (isset($data[$key])) {
|
||||
$value = $data[$key];
|
||||
$value === null || is_string($value) || throw MetadataStatementLoadingException::create(sprintf(
|
||||
'The value of the key "%s" is invalid',
|
||||
$key
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return new self(
|
||||
$data['status'],
|
||||
$data['effectiveDate'] ?? null,
|
||||
$data['certificate'] ?? null,
|
||||
$data['url'] ?? null,
|
||||
$data['certificationDescriptor'] ?? null,
|
||||
$data['certificateNumber'] ?? null,
|
||||
$data['certificationPolicyVersion'] ?? null,
|
||||
$data['certificationRequirementsVersion'] ?? null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$data = [
|
||||
'status' => $this->status,
|
||||
'effectiveDate' => $this->effectiveDate,
|
||||
'certificate' => $this->certificate,
|
||||
'url' => $this->url,
|
||||
'certificationDescriptor' => $this->certificationDescriptor,
|
||||
'certificateNumber' => $this->certificateNumber,
|
||||
'certificationPolicyVersion' => $this->certificationPolicyVersion,
|
||||
'certificationRequirementsVersion' => $this->certificationRequirementsVersion,
|
||||
];
|
||||
|
||||
return Utils::filterNullValues($data);
|
||||
}
|
||||
}
|
||||
61
libraries/vendor/web-auth/metadata-service/src/Statement/VerificationMethodANDCombinations.php
vendored
Normal file
61
libraries/vendor/web-auth/metadata-service/src/Statement/VerificationMethodANDCombinations.php
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Statement;
|
||||
|
||||
use function is_array;
|
||||
use JsonSerializable;
|
||||
use Webauthn\MetadataService\Exception\MetadataStatementLoadingException;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class VerificationMethodANDCombinations implements JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var VerificationMethodDescriptor[]
|
||||
*/
|
||||
private array $verificationMethods = [];
|
||||
|
||||
public function addVerificationMethodDescriptor(VerificationMethodDescriptor $verificationMethodDescriptor): self
|
||||
{
|
||||
$this->verificationMethods[] = $verificationMethodDescriptor;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return VerificationMethodDescriptor[]
|
||||
*/
|
||||
public function getVerificationMethods(): array
|
||||
{
|
||||
return $this->verificationMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public static function createFromArray(array $data): self
|
||||
{
|
||||
$object = new self();
|
||||
|
||||
foreach ($data as $datum) {
|
||||
is_array($datum) || throw MetadataStatementLoadingException::create('Invalid data');
|
||||
$object->addVerificationMethodDescriptor(VerificationMethodDescriptor::createFromArray($datum));
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<array<mixed>>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return array_map(
|
||||
static fn (VerificationMethodDescriptor $object): array => $object->jsonSerialize(),
|
||||
$this->verificationMethods
|
||||
);
|
||||
}
|
||||
}
|
||||
186
libraries/vendor/web-auth/metadata-service/src/Statement/VerificationMethodDescriptor.php
vendored
Normal file
186
libraries/vendor/web-auth/metadata-service/src/Statement/VerificationMethodDescriptor.php
vendored
Normal file
@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Statement;
|
||||
|
||||
use function array_key_exists;
|
||||
use function is_array;
|
||||
use JsonSerializable;
|
||||
use Webauthn\MetadataService\Exception\MetadataStatementLoadingException;
|
||||
use Webauthn\MetadataService\Utils;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class VerificationMethodDescriptor implements JsonSerializable
|
||||
{
|
||||
final public const USER_VERIFY_PRESENCE_INTERNAL = 'presence_internal';
|
||||
|
||||
final public const USER_VERIFY_FINGERPRINT_INTERNAL = 'fingerprint_internal';
|
||||
|
||||
final public const USER_VERIFY_PASSCODE_INTERNAL = 'passcode_internal';
|
||||
|
||||
final public const USER_VERIFY_VOICEPRINT_INTERNAL = 'voiceprint_internal';
|
||||
|
||||
final public const USER_VERIFY_FACEPRINT_INTERNAL = 'faceprint_internal';
|
||||
|
||||
final public const USER_VERIFY_LOCATION_INTERNAL = 'location_internal';
|
||||
|
||||
final public const USER_VERIFY_EYEPRINT_INTERNAL = 'eyeprint_internal';
|
||||
|
||||
final public const USER_VERIFY_PATTERN_INTERNAL = 'pattern_internal';
|
||||
|
||||
final public const USER_VERIFY_HANDPRINT_INTERNAL = 'handprint_internal';
|
||||
|
||||
final public const USER_VERIFY_PASSCODE_EXTERNAL = 'passcode_external';
|
||||
|
||||
final public const USER_VERIFY_PATTERN_EXTERNAL = 'pattern_external';
|
||||
|
||||
final public const USER_VERIFY_NONE = 'none';
|
||||
|
||||
final public const USER_VERIFY_ALL = 'all';
|
||||
|
||||
private readonly string $userVerificationMethod;
|
||||
|
||||
public function __construct(
|
||||
string $userVerificationMethod,
|
||||
private readonly ?CodeAccuracyDescriptor $caDesc = null,
|
||||
private readonly ?BiometricAccuracyDescriptor $baDesc = null,
|
||||
private readonly ?PatternAccuracyDescriptor $paDesc = null
|
||||
) {
|
||||
$userVerificationMethod >= 0 || throw MetadataStatementLoadingException::create(
|
||||
'The parameter "userVerificationMethod" is invalid'
|
||||
);
|
||||
$this->userVerificationMethod = $userVerificationMethod;
|
||||
}
|
||||
|
||||
public function getUserVerificationMethod(): string
|
||||
{
|
||||
return $this->userVerificationMethod;
|
||||
}
|
||||
|
||||
public function userPresence(): bool
|
||||
{
|
||||
return $this->userVerificationMethod === self::USER_VERIFY_PRESENCE_INTERNAL;
|
||||
}
|
||||
|
||||
public function fingerprint(): bool
|
||||
{
|
||||
return $this->userVerificationMethod === self::USER_VERIFY_FINGERPRINT_INTERNAL;
|
||||
}
|
||||
|
||||
public function passcodeInternal(): bool
|
||||
{
|
||||
return $this->userVerificationMethod === self::USER_VERIFY_PASSCODE_INTERNAL;
|
||||
}
|
||||
|
||||
public function voicePrint(): bool
|
||||
{
|
||||
return $this->userVerificationMethod === self::USER_VERIFY_VOICEPRINT_INTERNAL;
|
||||
}
|
||||
|
||||
public function facePrint(): bool
|
||||
{
|
||||
return $this->userVerificationMethod === self::USER_VERIFY_FACEPRINT_INTERNAL;
|
||||
}
|
||||
|
||||
public function location(): bool
|
||||
{
|
||||
return $this->userVerificationMethod === self::USER_VERIFY_LOCATION_INTERNAL;
|
||||
}
|
||||
|
||||
public function eyePrint(): bool
|
||||
{
|
||||
return $this->userVerificationMethod === self::USER_VERIFY_EYEPRINT_INTERNAL;
|
||||
}
|
||||
|
||||
public function patternInternal(): bool
|
||||
{
|
||||
return $this->userVerificationMethod === self::USER_VERIFY_PATTERN_INTERNAL;
|
||||
}
|
||||
|
||||
public function handprint(): bool
|
||||
{
|
||||
return $this->userVerificationMethod === self::USER_VERIFY_HANDPRINT_INTERNAL;
|
||||
}
|
||||
|
||||
public function passcodeExternal(): bool
|
||||
{
|
||||
return $this->userVerificationMethod === self::USER_VERIFY_PASSCODE_EXTERNAL;
|
||||
}
|
||||
|
||||
public function patternExternal(): bool
|
||||
{
|
||||
return $this->userVerificationMethod === self::USER_VERIFY_PATTERN_EXTERNAL;
|
||||
}
|
||||
|
||||
public function none(): bool
|
||||
{
|
||||
return $this->userVerificationMethod === self::USER_VERIFY_NONE;
|
||||
}
|
||||
|
||||
public function all(): bool
|
||||
{
|
||||
return $this->userVerificationMethod === self::USER_VERIFY_ALL;
|
||||
}
|
||||
|
||||
public function getCaDesc(): ?CodeAccuracyDescriptor
|
||||
{
|
||||
return $this->caDesc;
|
||||
}
|
||||
|
||||
public function getBaDesc(): ?BiometricAccuracyDescriptor
|
||||
{
|
||||
return $this->baDesc;
|
||||
}
|
||||
|
||||
public function getPaDesc(): ?PatternAccuracyDescriptor
|
||||
{
|
||||
return $this->paDesc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public static function createFromArray(array $data): self
|
||||
{
|
||||
$data = Utils::filterNullValues($data);
|
||||
if (isset($data['userVerification']) && ! isset($data['userVerificationMethod'])) {
|
||||
$data['userVerificationMethod'] = $data['userVerification'];
|
||||
unset($data['userVerification']);
|
||||
}
|
||||
array_key_exists('userVerificationMethod', $data) || throw MetadataStatementLoadingException::create(
|
||||
'The parameters "userVerificationMethod" is missing'
|
||||
);
|
||||
|
||||
foreach (['caDesc', 'baDesc', 'paDesc'] as $key) {
|
||||
if (isset($data[$key])) {
|
||||
is_array($data[$key]) || throw MetadataStatementLoadingException::create(
|
||||
sprintf('Invalid parameter "%s"', $key)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$caDesc = isset($data['caDesc']) ? CodeAccuracyDescriptor::createFromArray($data['caDesc']) : null;
|
||||
$baDesc = isset($data['baDesc']) ? BiometricAccuracyDescriptor::createFromArray($data['baDesc']) : null;
|
||||
$paDesc = isset($data['paDesc']) ? PatternAccuracyDescriptor::createFromArray($data['paDesc']) : null;
|
||||
|
||||
return new self($data['userVerificationMethod'], $caDesc, $baDesc, $paDesc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$data = [
|
||||
'userVerificationMethod' => $this->userVerificationMethod,
|
||||
'caDesc' => $this->caDesc?->jsonSerialize(),
|
||||
'baDesc' => $this->baDesc?->jsonSerialize(),
|
||||
'paDesc' => $this->paDesc?->jsonSerialize(),
|
||||
];
|
||||
|
||||
return Utils::filterNullValues($data);
|
||||
}
|
||||
}
|
||||
73
libraries/vendor/web-auth/metadata-service/src/Statement/Version.php
vendored
Normal file
73
libraries/vendor/web-auth/metadata-service/src/Statement/Version.php
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService\Statement;
|
||||
|
||||
use function array_key_exists;
|
||||
use function is_int;
|
||||
use JsonSerializable;
|
||||
use Webauthn\MetadataService\Exception\MetadataStatementLoadingException;
|
||||
use Webauthn\MetadataService\Utils;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class Version implements JsonSerializable
|
||||
{
|
||||
private readonly ?int $major;
|
||||
|
||||
private readonly ?int $minor;
|
||||
|
||||
public function __construct(?int $major, ?int $minor)
|
||||
{
|
||||
if ($major === null && $minor === null) {
|
||||
throw MetadataStatementLoadingException::create('Invalid data. Must contain at least one item');
|
||||
}
|
||||
$major >= 0 || throw MetadataStatementLoadingException::create('Invalid argument "major"');
|
||||
$minor >= 0 || throw MetadataStatementLoadingException::create('Invalid argument "minor"');
|
||||
|
||||
$this->major = $major;
|
||||
$this->minor = $minor;
|
||||
}
|
||||
|
||||
public function getMajor(): ?int
|
||||
{
|
||||
return $this->major;
|
||||
}
|
||||
|
||||
public function getMinor(): ?int
|
||||
{
|
||||
return $this->minor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public static function createFromArray(array $data): self
|
||||
{
|
||||
$data = Utils::filterNullValues($data);
|
||||
foreach (['major', 'minor'] as $key) {
|
||||
if (array_key_exists($key, $data)) {
|
||||
is_int($data[$key]) || throw MetadataStatementLoadingException::create(
|
||||
sprintf('Invalid value for key "%s"', $key)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new self($data['major'] ?? null, $data['minor'] ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, int|null>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$data = [
|
||||
'major' => $this->major,
|
||||
'minor' => $this->minor,
|
||||
];
|
||||
|
||||
return Utils::filterNullValues($data);
|
||||
}
|
||||
}
|
||||
15
libraries/vendor/web-auth/metadata-service/src/StatusReportRepository.php
vendored
Normal file
15
libraries/vendor/web-auth/metadata-service/src/StatusReportRepository.php
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService;
|
||||
|
||||
use Webauthn\MetadataService\Statement\StatusReport;
|
||||
|
||||
interface StatusReportRepository
|
||||
{
|
||||
/**
|
||||
* @return StatusReport[]
|
||||
*/
|
||||
public function findStatusReportsByAAGUID(string $aaguid): array;
|
||||
}
|
||||
21
libraries/vendor/web-auth/metadata-service/src/Utils.php
vendored
Normal file
21
libraries/vendor/web-auth/metadata-service/src/Utils.php
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\MetadataService;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class Utils
|
||||
{
|
||||
/**
|
||||
* @param array<mixed> $data
|
||||
*
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public static function filterNullValues(array $data): array
|
||||
{
|
||||
return array_filter($data, static fn ($var): bool => $var !== null);
|
||||
}
|
||||
}
|
||||
21
libraries/vendor/web-auth/webauthn-lib/LICENSE
vendored
Normal file
21
libraries/vendor/web-auth/webauthn-lib/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2022 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,231 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\AttestationStatement;
|
||||
|
||||
use function array_key_exists;
|
||||
use CBOR\Decoder;
|
||||
use CBOR\Normalizable;
|
||||
use Cose\Algorithms;
|
||||
use Cose\Key\Ec2Key;
|
||||
use Cose\Key\Key;
|
||||
use Cose\Key\RsaKey;
|
||||
use function count;
|
||||
use function is_array;
|
||||
use function openssl_pkey_get_public;
|
||||
use function openssl_verify;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use SpomkyLabs\Pki\ASN1\Type\Constructed\Sequence;
|
||||
use SpomkyLabs\Pki\ASN1\Type\Primitive\OctetString;
|
||||
use SpomkyLabs\Pki\ASN1\Type\Tagged\ExplicitTagging;
|
||||
use Webauthn\AuthenticatorData;
|
||||
use Webauthn\Event\AttestationStatementLoaded;
|
||||
use Webauthn\Exception\AttestationStatementLoadingException;
|
||||
use Webauthn\Exception\AttestationStatementVerificationException;
|
||||
use Webauthn\Exception\InvalidAttestationStatementException;
|
||||
use Webauthn\MetadataService\CertificateChain\CertificateToolbox;
|
||||
use Webauthn\MetadataService\Event\CanDispatchEvents;
|
||||
use Webauthn\MetadataService\Event\NullEventDispatcher;
|
||||
use Webauthn\StringStream;
|
||||
use Webauthn\TrustPath\CertificateTrustPath;
|
||||
|
||||
final class AndroidKeyAttestationStatementSupport implements AttestationStatementSupport, CanDispatchEvents
|
||||
{
|
||||
private readonly Decoder $decoder;
|
||||
|
||||
private EventDispatcherInterface $dispatcher;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->decoder = Decoder::create();
|
||||
$this->dispatcher = new NullEventDispatcher();
|
||||
}
|
||||
|
||||
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void
|
||||
{
|
||||
$this->dispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function name(): string
|
||||
{
|
||||
return 'android-key';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @param array<string, mixed> $attestation
|
||||
*/
|
||||
public function load(array $attestation): AttestationStatement
|
||||
{
|
||||
array_key_exists('attStmt', $attestation) || throw AttestationStatementLoadingException::create($attestation);
|
||||
foreach (['sig', 'x5c', 'alg'] as $key) {
|
||||
array_key_exists($key, $attestation['attStmt']) || throw AttestationStatementLoadingException::create(
|
||||
$attestation,
|
||||
sprintf('The attestation statement value "%s" is missing.', $key)
|
||||
);
|
||||
}
|
||||
$certificates = $attestation['attStmt']['x5c'];
|
||||
(is_countable($certificates) ? count(
|
||||
$certificates
|
||||
) : 0) > 0 || throw AttestationStatementLoadingException::create(
|
||||
$attestation,
|
||||
'The attestation statement value "x5c" must be a list with at least one certificate.'
|
||||
);
|
||||
$certificates = CertificateToolbox::convertAllDERToPEM($certificates);
|
||||
|
||||
$attestationStatement = AttestationStatement::createBasic(
|
||||
$attestation['fmt'],
|
||||
$attestation['attStmt'],
|
||||
new CertificateTrustPath($certificates)
|
||||
);
|
||||
$this->dispatcher->dispatch(AttestationStatementLoaded::create($attestationStatement));
|
||||
|
||||
return $attestationStatement;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function isValid(
|
||||
string $clientDataJSONHash,
|
||||
AttestationStatement $attestationStatement,
|
||||
AuthenticatorData $authenticatorData
|
||||
): bool {
|
||||
$trustPath = $attestationStatement->getTrustPath();
|
||||
$trustPath instanceof CertificateTrustPath || throw InvalidAttestationStatementException::create(
|
||||
$attestationStatement,
|
||||
'Invalid trust path. Shall contain certificates.'
|
||||
);
|
||||
|
||||
$certificates = $trustPath->getCertificates();
|
||||
|
||||
//Decode leaf attestation certificate
|
||||
$leaf = $certificates[0];
|
||||
$this->checkCertificate($leaf, $clientDataJSONHash, $authenticatorData);
|
||||
|
||||
$signedData = $authenticatorData->getAuthData() . $clientDataJSONHash;
|
||||
$alg = $attestationStatement->get('alg');
|
||||
|
||||
return openssl_verify(
|
||||
$signedData,
|
||||
$attestationStatement->get('sig'),
|
||||
$leaf,
|
||||
Algorithms::getOpensslAlgorithmFor((int) $alg)
|
||||
) === 1;
|
||||
}
|
||||
|
||||
private function checkCertificate(
|
||||
string $certificate,
|
||||
string $clientDataHash,
|
||||
AuthenticatorData $authenticatorData
|
||||
): void {
|
||||
$resource = openssl_pkey_get_public($certificate);
|
||||
$details = openssl_pkey_get_details($resource);
|
||||
is_array($details) || throw AttestationStatementVerificationException::create(
|
||||
'Unable to read the certificate'
|
||||
);
|
||||
|
||||
//Check that authData publicKey matches the public key in the attestation certificate
|
||||
$attestedCredentialData = $authenticatorData->getAttestedCredentialData();
|
||||
$attestedCredentialData !== null || throw AttestationStatementVerificationException::create(
|
||||
'No attested credential data found'
|
||||
);
|
||||
$publicKeyData = $attestedCredentialData->getCredentialPublicKey();
|
||||
$publicKeyData !== null || throw AttestationStatementVerificationException::create(
|
||||
'No attested public key found'
|
||||
);
|
||||
$publicDataStream = new StringStream($publicKeyData);
|
||||
$coseKey = $this->decoder->decode($publicDataStream);
|
||||
$coseKey instanceof Normalizable || throw AttestationStatementVerificationException::create(
|
||||
'Invalid attested public key found'
|
||||
);
|
||||
|
||||
$publicDataStream->isEOF() || throw AttestationStatementVerificationException::create(
|
||||
'Invalid public key data. Presence of extra bytes.'
|
||||
);
|
||||
$publicDataStream->close();
|
||||
$publicKey = Key::createFromData($coseKey->normalize());
|
||||
|
||||
($publicKey instanceof Ec2Key) || ($publicKey instanceof RsaKey) || throw AttestationStatementVerificationException::create(
|
||||
'Unsupported key type'
|
||||
);
|
||||
$publicKey->asPEM() === $details['key'] || throw AttestationStatementVerificationException::create(
|
||||
'Invalid key'
|
||||
);
|
||||
|
||||
/*---------------------------*/
|
||||
$certDetails = openssl_x509_parse($certificate);
|
||||
|
||||
//Find Android KeyStore Extension with OID "1.3.6.1.4.1.11129.2.1.17" in certificate extensions
|
||||
is_array(
|
||||
$certDetails
|
||||
) || throw AttestationStatementVerificationException::create('The certificate is not valid');
|
||||
array_key_exists('extensions', $certDetails) || throw AttestationStatementVerificationException::create(
|
||||
'The certificate has no extension'
|
||||
);
|
||||
is_array($certDetails['extensions']) || throw AttestationStatementVerificationException::create(
|
||||
'The certificate has no extension'
|
||||
);
|
||||
array_key_exists(
|
||||
'1.3.6.1.4.1.11129.2.1.17',
|
||||
$certDetails['extensions']
|
||||
) || throw AttestationStatementVerificationException::create(
|
||||
'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is missing'
|
||||
);
|
||||
$extension = $certDetails['extensions']['1.3.6.1.4.1.11129.2.1.17'];
|
||||
$extensionAsAsn1 = Sequence::fromDER($extension);
|
||||
$extensionAsAsn1->has(4);
|
||||
|
||||
//Check that attestationChallenge is set to the clientDataHash.
|
||||
$extensionAsAsn1->has(4) || throw AttestationStatementVerificationException::create(
|
||||
'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'
|
||||
);
|
||||
$ext = $extensionAsAsn1->at(4)
|
||||
->asElement();
|
||||
$ext instanceof OctetString || throw AttestationStatementVerificationException::create(
|
||||
'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'
|
||||
);
|
||||
$clientDataHash === $ext->string() || throw AttestationStatementVerificationException::create(
|
||||
'The client data hash is not valid'
|
||||
);
|
||||
|
||||
//Check that both teeEnforced and softwareEnforced structures don't contain allApplications(600) tag.
|
||||
$extensionAsAsn1->has(6) || throw AttestationStatementVerificationException::create(
|
||||
'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'
|
||||
);
|
||||
|
||||
$softwareEnforcedFlags = $extensionAsAsn1->at(6)
|
||||
->asElement();
|
||||
$softwareEnforcedFlags instanceof Sequence || throw AttestationStatementVerificationException::create(
|
||||
'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'
|
||||
);
|
||||
$this->checkAbsenceOfAllApplicationsTag($softwareEnforcedFlags);
|
||||
|
||||
$extensionAsAsn1->has(7) || throw AttestationStatementVerificationException::create(
|
||||
'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'
|
||||
);
|
||||
$teeEnforcedFlags = $extensionAsAsn1->at(7)
|
||||
->asElement();
|
||||
$teeEnforcedFlags instanceof Sequence || throw AttestationStatementVerificationException::create(
|
||||
'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'
|
||||
);
|
||||
$this->checkAbsenceOfAllApplicationsTag($teeEnforcedFlags);
|
||||
}
|
||||
|
||||
private function checkAbsenceOfAllApplicationsTag(Sequence $sequence): void
|
||||
{
|
||||
foreach ($sequence->elements() as $tag) {
|
||||
$tag->asElement() instanceof ExplicitTagging || throw AttestationStatementVerificationException::create(
|
||||
'Invalid tag'
|
||||
);
|
||||
$tag->asElement()
|
||||
->tag() !== 600 || throw AttestationStatementVerificationException::create('Forbidden tag 600 found');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,359 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\AttestationStatement;
|
||||
|
||||
use function array_key_exists;
|
||||
use function count;
|
||||
use function is_array;
|
||||
use function is_int;
|
||||
use function is_string;
|
||||
use Jose\Component\Core\Algorithm as AlgorithmInterface;
|
||||
use Jose\Component\Core\AlgorithmManager;
|
||||
use Jose\Component\KeyManagement\JWKFactory;
|
||||
use Jose\Component\Signature\Algorithm\EdDSA;
|
||||
use Jose\Component\Signature\Algorithm\ES256;
|
||||
use Jose\Component\Signature\Algorithm\ES384;
|
||||
use Jose\Component\Signature\Algorithm\ES512;
|
||||
use Jose\Component\Signature\Algorithm\PS256;
|
||||
use Jose\Component\Signature\Algorithm\PS384;
|
||||
use Jose\Component\Signature\Algorithm\PS512;
|
||||
use Jose\Component\Signature\Algorithm\RS256;
|
||||
use Jose\Component\Signature\Algorithm\RS384;
|
||||
use Jose\Component\Signature\Algorithm\RS512;
|
||||
use Jose\Component\Signature\JWS;
|
||||
use Jose\Component\Signature\JWSVerifier;
|
||||
use Jose\Component\Signature\Serializer\CompactSerializer;
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Http\Client\ClientInterface;
|
||||
use Psr\Http\Message\RequestFactoryInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Webauthn\AuthenticatorData;
|
||||
use Webauthn\Event\AttestationStatementLoaded;
|
||||
use Webauthn\Exception\AttestationStatementLoadingException;
|
||||
use Webauthn\Exception\AttestationStatementVerificationException;
|
||||
use Webauthn\Exception\InvalidAttestationStatementException;
|
||||
use Webauthn\Exception\UnsupportedFeatureException;
|
||||
use Webauthn\MetadataService\CertificateChain\CertificateToolbox;
|
||||
use Webauthn\MetadataService\Event\CanDispatchEvents;
|
||||
use Webauthn\MetadataService\Event\NullEventDispatcher;
|
||||
use Webauthn\TrustPath\CertificateTrustPath;
|
||||
|
||||
final class AndroidSafetyNetAttestationStatementSupport implements AttestationStatementSupport, CanDispatchEvents
|
||||
{
|
||||
private ?string $apiKey = null;
|
||||
|
||||
private ?ClientInterface $client = null;
|
||||
|
||||
private readonly CompactSerializer $jwsSerializer;
|
||||
|
||||
private ?JWSVerifier $jwsVerifier = null;
|
||||
|
||||
private ?RequestFactoryInterface $requestFactory = null;
|
||||
|
||||
private int $leeway = 0;
|
||||
|
||||
private int $maxAge = 60000;
|
||||
|
||||
private EventDispatcherInterface $dispatcher;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (! class_exists(RS256::class)) {
|
||||
throw UnsupportedFeatureException::create(
|
||||
'The algorithm RS256 is missing. Did you forget to install the package web-token/jwt-signature-algorithm-rsa?'
|
||||
);
|
||||
}
|
||||
if (! class_exists(JWKFactory::class)) {
|
||||
throw UnsupportedFeatureException::create(
|
||||
'The class Jose\Component\KeyManagement\JWKFactory is missing. Did you forget to install the package web-token/jwt-key-mgmt?'
|
||||
);
|
||||
}
|
||||
$this->jwsSerializer = new CompactSerializer();
|
||||
$this->initJwsVerifier();
|
||||
$this->dispatcher = new NullEventDispatcher();
|
||||
}
|
||||
|
||||
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void
|
||||
{
|
||||
$this->dispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function enableApiVerification(
|
||||
ClientInterface $client,
|
||||
string $apiKey,
|
||||
RequestFactoryInterface $requestFactory
|
||||
): self {
|
||||
$this->apiKey = $apiKey;
|
||||
$this->client = $client;
|
||||
$this->requestFactory = $requestFactory;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMaxAge(int $maxAge): self
|
||||
{
|
||||
$this->maxAge = $maxAge;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setLeeway(int $leeway): self
|
||||
{
|
||||
$this->leeway = $leeway;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function name(): string
|
||||
{
|
||||
return 'android-safetynet';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attestation
|
||||
*/
|
||||
public function load(array $attestation): AttestationStatement
|
||||
{
|
||||
array_key_exists('attStmt', $attestation) || throw AttestationStatementLoadingException::create(
|
||||
$attestation,
|
||||
'Invalid attestation object'
|
||||
);
|
||||
foreach (['ver', 'response'] as $key) {
|
||||
array_key_exists($key, $attestation['attStmt']) || throw AttestationStatementLoadingException::create(
|
||||
$attestation,
|
||||
sprintf('The attestation statement value "%s" is missing.', $key)
|
||||
);
|
||||
$attestation['attStmt'][$key] !== '' || throw AttestationStatementLoadingException::create(
|
||||
$attestation,
|
||||
sprintf('The attestation statement value "%s" is empty.', $key)
|
||||
);
|
||||
}
|
||||
$jws = $this->jwsSerializer->unserialize($attestation['attStmt']['response']);
|
||||
$jwsHeader = $jws->getSignature(0)
|
||||
->getProtectedHeader();
|
||||
array_key_exists('x5c', $jwsHeader) || throw AttestationStatementLoadingException::create(
|
||||
$attestation,
|
||||
'The response in the attestation statement must contain a "x5c" header.'
|
||||
);
|
||||
(is_countable($jwsHeader['x5c']) ? count(
|
||||
$jwsHeader['x5c']
|
||||
) : 0) > 0 || throw AttestationStatementLoadingException::create(
|
||||
$attestation,
|
||||
'The "x5c" parameter in the attestation statement response must contain at least one certificate.'
|
||||
);
|
||||
$certificates = $this->convertCertificatesToPem($jwsHeader['x5c']);
|
||||
$attestation['attStmt']['jws'] = $jws;
|
||||
|
||||
$attestationStatement = AttestationStatement::createBasic(
|
||||
$this->name(),
|
||||
$attestation['attStmt'],
|
||||
new CertificateTrustPath($certificates)
|
||||
);
|
||||
$this->dispatcher->dispatch(AttestationStatementLoaded::create($attestationStatement));
|
||||
|
||||
return $attestationStatement;
|
||||
}
|
||||
|
||||
public function isValid(
|
||||
string $clientDataJSONHash,
|
||||
AttestationStatement $attestationStatement,
|
||||
AuthenticatorData $authenticatorData
|
||||
): bool {
|
||||
$trustPath = $attestationStatement->getTrustPath();
|
||||
$trustPath instanceof CertificateTrustPath || throw InvalidAttestationStatementException::create(
|
||||
$attestationStatement,
|
||||
'Invalid trust path'
|
||||
);
|
||||
$certificates = $trustPath->getCertificates();
|
||||
$firstCertificate = current($certificates);
|
||||
is_string($firstCertificate) || throw InvalidAttestationStatementException::create(
|
||||
$attestationStatement,
|
||||
'No certificate'
|
||||
);
|
||||
|
||||
$parsedCertificate = openssl_x509_parse($firstCertificate);
|
||||
is_array($parsedCertificate) || throw InvalidAttestationStatementException::create(
|
||||
$attestationStatement,
|
||||
'Invalid attestation object'
|
||||
);
|
||||
array_key_exists('subject', $parsedCertificate) || throw InvalidAttestationStatementException::create(
|
||||
$attestationStatement,
|
||||
'Invalid attestation object'
|
||||
);
|
||||
array_key_exists('CN', $parsedCertificate['subject']) || throw InvalidAttestationStatementException::create(
|
||||
$attestationStatement,
|
||||
'Invalid attestation object'
|
||||
);
|
||||
$parsedCertificate['subject']['CN'] === 'attest.android.com' || throw InvalidAttestationStatementException::create(
|
||||
$attestationStatement,
|
||||
'Invalid attestation object'
|
||||
);
|
||||
|
||||
/** @var JWS $jws */
|
||||
$jws = $attestationStatement->get('jws');
|
||||
$payload = $jws->getPayload();
|
||||
$this->validatePayload($payload, $clientDataJSONHash, $authenticatorData);
|
||||
|
||||
//Check the signature
|
||||
$this->validateSignature($jws, $trustPath);
|
||||
|
||||
//Check against Google service
|
||||
$this->validateUsingGoogleApi($attestationStatement);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function validatePayload(
|
||||
?string $payload,
|
||||
string $clientDataJSONHash,
|
||||
AuthenticatorData $authenticatorData
|
||||
): void {
|
||||
$payload !== null || throw AttestationStatementVerificationException::create('Invalid attestation object');
|
||||
$payload = json_decode($payload, true, 512, JSON_THROW_ON_ERROR);
|
||||
array_key_exists('nonce', $payload) || throw AttestationStatementVerificationException::create(
|
||||
'Invalid attestation object. "nonce" is missing.'
|
||||
);
|
||||
$payload['nonce'] === base64_encode(
|
||||
hash('sha256', $authenticatorData->getAuthData() . $clientDataJSONHash, true)
|
||||
) || throw AttestationStatementVerificationException::create('Invalid attestation object. Invalid nonce');
|
||||
array_key_exists('ctsProfileMatch', $payload) || throw AttestationStatementVerificationException::create(
|
||||
'Invalid attestation object. "ctsProfileMatch" is missing.'
|
||||
);
|
||||
$payload['ctsProfileMatch'] || throw AttestationStatementVerificationException::create(
|
||||
'Invalid attestation object. "ctsProfileMatch" value is false.'
|
||||
);
|
||||
array_key_exists('timestampMs', $payload) || throw AttestationStatementVerificationException::create(
|
||||
'Invalid attestation object. Timestamp is missing.'
|
||||
);
|
||||
is_int($payload['timestampMs']) || throw AttestationStatementVerificationException::create(
|
||||
'Invalid attestation object. Timestamp shall be an integer.'
|
||||
);
|
||||
$currentTime = time() * 1000;
|
||||
$payload['timestampMs'] <= $currentTime + $this->leeway || throw AttestationStatementVerificationException::create(
|
||||
sprintf(
|
||||
'Invalid attestation object. Issued in the future. Current time: %d. Response time: %d',
|
||||
$currentTime,
|
||||
$payload['timestampMs']
|
||||
)
|
||||
);
|
||||
$currentTime - $payload['timestampMs'] <= $this->maxAge || throw AttestationStatementVerificationException::create(
|
||||
sprintf(
|
||||
'Invalid attestation object. Too old. Current time: %d. Response time: %d',
|
||||
$currentTime,
|
||||
$payload['timestampMs']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function validateSignature(JWS $jws, CertificateTrustPath $trustPath): void
|
||||
{
|
||||
$jwk = JWKFactory::createFromCertificate($trustPath->getCertificates()[0]);
|
||||
$isValid = $this->jwsVerifier?->verifyWithKey($jws, $jwk, 0);
|
||||
$isValid === true || throw AttestationStatementVerificationException::create('Invalid response signature');
|
||||
}
|
||||
|
||||
private function validateUsingGoogleApi(AttestationStatement $attestationStatement): void
|
||||
{
|
||||
if ($this->client === null || $this->apiKey === null || $this->requestFactory === null) {
|
||||
return;
|
||||
}
|
||||
$uri = sprintf(
|
||||
'https://www.googleapis.com/androidcheck/v1/attestations/verify?key=%s',
|
||||
urlencode($this->apiKey)
|
||||
);
|
||||
$requestBody = sprintf('{"signedAttestation":"%s"}', $attestationStatement->get('response'));
|
||||
$request = $this->requestFactory->createRequest('POST', $uri);
|
||||
$request = $request->withHeader('content-type', 'application/json');
|
||||
$request->getBody()
|
||||
->write($requestBody);
|
||||
|
||||
$response = $this->client->sendRequest($request);
|
||||
$this->checkGoogleApiResponse($response);
|
||||
$responseBody = $this->getResponseBody($response);
|
||||
$responseBodyJson = json_decode($responseBody, true, 512, JSON_THROW_ON_ERROR);
|
||||
array_key_exists(
|
||||
'isValidSignature',
|
||||
$responseBodyJson
|
||||
) || throw AttestationStatementVerificationException::create('Invalid response.');
|
||||
$responseBodyJson['isValidSignature'] === true || throw AttestationStatementVerificationException::create(
|
||||
'Invalid response.'
|
||||
);
|
||||
}
|
||||
|
||||
private function getResponseBody(ResponseInterface $response): string
|
||||
{
|
||||
$responseBody = '';
|
||||
$response->getBody()
|
||||
->rewind();
|
||||
do {
|
||||
$tmp = $response->getBody()
|
||||
->read(1024);
|
||||
if ($tmp === '') {
|
||||
break;
|
||||
}
|
||||
$responseBody .= $tmp;
|
||||
} while (true);
|
||||
|
||||
return $responseBody;
|
||||
}
|
||||
|
||||
private function checkGoogleApiResponse(ResponseInterface $response): void
|
||||
{
|
||||
$response->getStatusCode() === 200 || throw AttestationStatementVerificationException::create(
|
||||
'Request did not succeeded'
|
||||
);
|
||||
$response->hasHeader('content-type') || throw AttestationStatementVerificationException::create(
|
||||
'Unrecognized response'
|
||||
);
|
||||
|
||||
foreach ($response->getHeader('content-type') as $header) {
|
||||
if (mb_strpos($header, 'application/json') === 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw AttestationStatementVerificationException::create('Unrecognized response');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $certificates
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function convertCertificatesToPem(array $certificates): array
|
||||
{
|
||||
foreach ($certificates as $k => $v) {
|
||||
$certificates[$k] = CertificateToolbox::fixPEMStructure($v);
|
||||
}
|
||||
|
||||
return $certificates;
|
||||
}
|
||||
|
||||
private function initJwsVerifier(): void
|
||||
{
|
||||
$algorithmClasses = [
|
||||
RS256::class, RS384::class, RS512::class,
|
||||
PS256::class, PS384::class, PS512::class,
|
||||
ES256::class, ES384::class, ES512::class,
|
||||
EdDSA::class,
|
||||
];
|
||||
/** @var AlgorithmInterface[] $algorithms */
|
||||
$algorithms = [];
|
||||
foreach ($algorithmClasses as $algorithm) {
|
||||
if (class_exists($algorithm)) {
|
||||
/** @var AlgorithmInterface $algorithm */
|
||||
$algorithms[] = new $algorithm();
|
||||
}
|
||||
}
|
||||
$algorithmManager = new AlgorithmManager($algorithms);
|
||||
$this->jwsVerifier = new JWSVerifier($algorithmManager);
|
||||
}
|
||||
}
|
||||
177
libraries/vendor/web-auth/webauthn-lib/src/AttestationStatement/AppleAttestationStatementSupport.php
vendored
Normal file
177
libraries/vendor/web-auth/webauthn-lib/src/AttestationStatement/AppleAttestationStatementSupport.php
vendored
Normal file
@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\AttestationStatement;
|
||||
|
||||
use function array_key_exists;
|
||||
use CBOR\Decoder;
|
||||
use CBOR\Normalizable;
|
||||
use Cose\Key\Ec2Key;
|
||||
use Cose\Key\Key;
|
||||
use Cose\Key\RsaKey;
|
||||
use function count;
|
||||
use function is_array;
|
||||
use function openssl_pkey_get_public;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Webauthn\AuthenticatorData;
|
||||
use Webauthn\Event\AttestationStatementLoaded;
|
||||
use Webauthn\Exception\AttestationStatementLoadingException;
|
||||
use Webauthn\Exception\AttestationStatementVerificationException;
|
||||
use Webauthn\Exception\InvalidAttestationStatementException;
|
||||
use Webauthn\MetadataService\CertificateChain\CertificateToolbox;
|
||||
use Webauthn\MetadataService\Event\CanDispatchEvents;
|
||||
use Webauthn\MetadataService\Event\NullEventDispatcher;
|
||||
use Webauthn\StringStream;
|
||||
use Webauthn\TrustPath\CertificateTrustPath;
|
||||
|
||||
final class AppleAttestationStatementSupport implements AttestationStatementSupport, CanDispatchEvents
|
||||
{
|
||||
private readonly Decoder $decoder;
|
||||
|
||||
private EventDispatcherInterface $dispatcher;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->decoder = Decoder::create();
|
||||
$this->dispatcher = new NullEventDispatcher();
|
||||
}
|
||||
|
||||
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void
|
||||
{
|
||||
$this->dispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function name(): string
|
||||
{
|
||||
return 'apple';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attestation
|
||||
*/
|
||||
public function load(array $attestation): AttestationStatement
|
||||
{
|
||||
array_key_exists('attStmt', $attestation) || throw AttestationStatementLoadingException::create(
|
||||
$attestation,
|
||||
'Invalid attestation object'
|
||||
);
|
||||
array_key_exists('x5c', $attestation['attStmt']) || throw AttestationStatementLoadingException::create(
|
||||
$attestation,
|
||||
'The attestation statement value "x5c" is missing.'
|
||||
);
|
||||
$certificates = $attestation['attStmt']['x5c'];
|
||||
(is_countable($certificates) ? count(
|
||||
$certificates
|
||||
) : 0) > 0 || throw AttestationStatementLoadingException::create(
|
||||
$attestation,
|
||||
'The attestation statement value "x5c" must be a list with at least one certificate.'
|
||||
);
|
||||
$certificates = CertificateToolbox::convertAllDERToPEM($certificates);
|
||||
|
||||
$attestationStatement = AttestationStatement::createAnonymizationCA(
|
||||
$attestation['fmt'],
|
||||
$attestation['attStmt'],
|
||||
new CertificateTrustPath($certificates)
|
||||
);
|
||||
$this->dispatcher->dispatch(AttestationStatementLoaded::create($attestationStatement));
|
||||
|
||||
return $attestationStatement;
|
||||
}
|
||||
|
||||
public function isValid(
|
||||
string $clientDataJSONHash,
|
||||
AttestationStatement $attestationStatement,
|
||||
AuthenticatorData $authenticatorData
|
||||
): bool {
|
||||
$trustPath = $attestationStatement->getTrustPath();
|
||||
$trustPath instanceof CertificateTrustPath || throw InvalidAttestationStatementException::create(
|
||||
$attestationStatement,
|
||||
'Invalid trust path'
|
||||
);
|
||||
|
||||
$certificates = $trustPath->getCertificates();
|
||||
|
||||
//Decode leaf attestation certificate
|
||||
$leaf = $certificates[0];
|
||||
|
||||
$this->checkCertificateAndGetPublicKey($leaf, $clientDataJSONHash, $authenticatorData);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function checkCertificateAndGetPublicKey(
|
||||
string $certificate,
|
||||
string $clientDataHash,
|
||||
AuthenticatorData $authenticatorData
|
||||
): void {
|
||||
$resource = openssl_pkey_get_public($certificate);
|
||||
$details = openssl_pkey_get_details($resource);
|
||||
is_array($details) || throw AttestationStatementVerificationException::create(
|
||||
'Unable to read the certificate'
|
||||
);
|
||||
|
||||
//Check that authData publicKey matches the public key in the attestation certificate
|
||||
$attestedCredentialData = $authenticatorData->getAttestedCredentialData();
|
||||
$attestedCredentialData !== null || throw AttestationStatementVerificationException::create(
|
||||
'No attested credential data found'
|
||||
);
|
||||
$publicKeyData = $attestedCredentialData->getCredentialPublicKey();
|
||||
$publicKeyData !== null || throw AttestationStatementVerificationException::create(
|
||||
'No attested public key found'
|
||||
);
|
||||
$publicDataStream = new StringStream($publicKeyData);
|
||||
$coseKey = $this->decoder->decode($publicDataStream);
|
||||
$coseKey instanceof Normalizable || throw AttestationStatementVerificationException::create(
|
||||
'Invalid attested public key found'
|
||||
);
|
||||
$publicDataStream->isEOF() || throw AttestationStatementVerificationException::create(
|
||||
'Invalid public key data. Presence of extra bytes.'
|
||||
);
|
||||
$publicDataStream->close();
|
||||
$publicKey = Key::createFromData($coseKey->normalize());
|
||||
|
||||
($publicKey instanceof Ec2Key) || ($publicKey instanceof RsaKey) || throw AttestationStatementVerificationException::create(
|
||||
'Unsupported key type'
|
||||
);
|
||||
|
||||
//We check the attested key corresponds to the key in the certificate
|
||||
$publicKey->asPEM() === $details['key'] || throw AttestationStatementVerificationException::create(
|
||||
'Invalid key'
|
||||
);
|
||||
|
||||
/*---------------------------*/
|
||||
$certDetails = openssl_x509_parse($certificate);
|
||||
|
||||
//Find Apple Extension with OID "1.2.840.113635.100.8.2" in certificate extensions
|
||||
is_array(
|
||||
$certDetails
|
||||
) || throw AttestationStatementVerificationException::create('The certificate is not valid');
|
||||
array_key_exists('extensions', $certDetails) || throw AttestationStatementVerificationException::create(
|
||||
'The certificate has no extension'
|
||||
);
|
||||
is_array($certDetails['extensions']) || throw AttestationStatementVerificationException::create(
|
||||
'The certificate has no extension'
|
||||
);
|
||||
array_key_exists(
|
||||
'1.2.840.113635.100.8.2',
|
||||
$certDetails['extensions']
|
||||
) || throw AttestationStatementVerificationException::create(
|
||||
'The certificate extension "1.2.840.113635.100.8.2" is missing'
|
||||
);
|
||||
$extension = $certDetails['extensions']['1.2.840.113635.100.8.2'];
|
||||
|
||||
$nonceToHash = $authenticatorData->getAuthData() . $clientDataHash;
|
||||
$nonce = hash('sha256', $nonceToHash);
|
||||
|
||||
//'3024a1220420' corresponds to the Sequence+Explicitly Tagged Object + Octet Object
|
||||
'3024a1220420' . $nonce === bin2hex(
|
||||
(string) $extension
|
||||
) || throw AttestationStatementVerificationException::create('The client data hash is not valid');
|
||||
}
|
||||
}
|
||||
52
libraries/vendor/web-auth/webauthn-lib/src/AttestationStatement/AttestationObject.php
vendored
Normal file
52
libraries/vendor/web-auth/webauthn-lib/src/AttestationStatement/AttestationObject.php
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\AttestationStatement;
|
||||
|
||||
use Webauthn\AuthenticatorData;
|
||||
use Webauthn\MetadataService\Statement\MetadataStatement;
|
||||
|
||||
class AttestationObject
|
||||
{
|
||||
private ?MetadataStatement $metadataStatement = null;
|
||||
|
||||
public function __construct(
|
||||
private readonly string $rawAttestationObject,
|
||||
private AttestationStatement $attStmt,
|
||||
private readonly AuthenticatorData $authData
|
||||
) {
|
||||
}
|
||||
|
||||
public function getRawAttestationObject(): string
|
||||
{
|
||||
return $this->rawAttestationObject;
|
||||
}
|
||||
|
||||
public function getAttStmt(): AttestationStatement
|
||||
{
|
||||
return $this->attStmt;
|
||||
}
|
||||
|
||||
public function setAttStmt(AttestationStatement $attStmt): void
|
||||
{
|
||||
$this->attStmt = $attStmt;
|
||||
}
|
||||
|
||||
public function getAuthData(): AuthenticatorData
|
||||
{
|
||||
return $this->authData;
|
||||
}
|
||||
|
||||
public function getMetadataStatement(): ?MetadataStatement
|
||||
{
|
||||
return $this->metadataStatement;
|
||||
}
|
||||
|
||||
public function setMetadataStatement(MetadataStatement $metadataStatement): self
|
||||
{
|
||||
$this->metadataStatement = $metadataStatement;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
180
libraries/vendor/web-auth/webauthn-lib/src/AttestationStatement/AttestationObjectLoader.php
vendored
Normal file
180
libraries/vendor/web-auth/webauthn-lib/src/AttestationStatement/AttestationObjectLoader.php
vendored
Normal file
@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\AttestationStatement;
|
||||
|
||||
use function array_key_exists;
|
||||
use CBOR\Decoder;
|
||||
use CBOR\MapObject;
|
||||
use CBOR\Normalizable;
|
||||
use function is_array;
|
||||
use function ord;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
use Throwable;
|
||||
use function unpack;
|
||||
use Webauthn\AttestedCredentialData;
|
||||
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputsLoader;
|
||||
use Webauthn\AuthenticatorData;
|
||||
use Webauthn\Event\AttestationObjectLoaded;
|
||||
use Webauthn\Exception\InvalidDataException;
|
||||
use Webauthn\MetadataService\CanLogData;
|
||||
use Webauthn\MetadataService\Event\CanDispatchEvents;
|
||||
use Webauthn\MetadataService\Event\NullEventDispatcher;
|
||||
use Webauthn\StringStream;
|
||||
use Webauthn\Util\Base64;
|
||||
|
||||
class AttestationObjectLoader implements CanDispatchEvents, CanLogData
|
||||
{
|
||||
private const FLAG_AT = 0b01000000;
|
||||
|
||||
private const FLAG_ED = 0b10000000;
|
||||
|
||||
private readonly Decoder $decoder;
|
||||
|
||||
private LoggerInterface $logger;
|
||||
|
||||
private EventDispatcherInterface $dispatcher;
|
||||
|
||||
public function __construct(
|
||||
private readonly AttestationStatementSupportManager $attestationStatementSupportManager
|
||||
) {
|
||||
$this->decoder = Decoder::create();
|
||||
$this->logger = new NullLogger();
|
||||
$this->dispatcher = new NullEventDispatcher();
|
||||
}
|
||||
|
||||
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void
|
||||
{
|
||||
$this->dispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
public static function create(AttestationStatementSupportManager $attestationStatementSupportManager): self
|
||||
{
|
||||
return new self($attestationStatementSupportManager);
|
||||
}
|
||||
|
||||
public function load(string $data): AttestationObject
|
||||
{
|
||||
try {
|
||||
$this->logger->info('Trying to load the data', [
|
||||
'data' => $data,
|
||||
]);
|
||||
$decodedData = Base64::decode($data);
|
||||
$stream = new StringStream($decodedData);
|
||||
$parsed = $this->decoder->decode($stream);
|
||||
|
||||
$this->logger->info('Loading the Attestation Statement');
|
||||
$parsed instanceof Normalizable || throw InvalidDataException::create(
|
||||
$parsed,
|
||||
'Invalid attestation object. Unexpected object.'
|
||||
);
|
||||
$attestationObject = $parsed->normalize();
|
||||
$stream->isEOF() || throw InvalidDataException::create(
|
||||
null,
|
||||
'Invalid attestation object. Presence of extra bytes.'
|
||||
);
|
||||
$stream->close();
|
||||
is_array($attestationObject) || throw InvalidDataException::create(
|
||||
$attestationObject,
|
||||
'Invalid attestation object'
|
||||
);
|
||||
array_key_exists('authData', $attestationObject) || throw InvalidDataException::create(
|
||||
$attestationObject,
|
||||
'Invalid attestation object'
|
||||
);
|
||||
array_key_exists('fmt', $attestationObject) || throw InvalidDataException::create(
|
||||
$attestationObject,
|
||||
'Invalid attestation object'
|
||||
);
|
||||
array_key_exists('attStmt', $attestationObject) || throw InvalidDataException::create(
|
||||
$attestationObject,
|
||||
'Invalid attestation object'
|
||||
);
|
||||
$authData = $attestationObject['authData'];
|
||||
|
||||
$attestationStatementSupport = $this->attestationStatementSupportManager->get($attestationObject['fmt']);
|
||||
$attestationStatement = $attestationStatementSupport->load($attestationObject);
|
||||
$this->logger->info('Attestation Statement loaded');
|
||||
$this->logger->debug('Attestation Statement loaded', [
|
||||
'attestationStatement' => $attestationStatement,
|
||||
]);
|
||||
|
||||
$authDataStream = new StringStream($authData);
|
||||
$rp_id_hash = $authDataStream->read(32);
|
||||
$flags = $authDataStream->read(1);
|
||||
$signCount = $authDataStream->read(4);
|
||||
$signCount = unpack('N', $signCount);
|
||||
$this->logger->debug(sprintf('Signature counter: %d', $signCount[1]));
|
||||
|
||||
$attestedCredentialData = null;
|
||||
if (0 !== (ord($flags) & self::FLAG_AT)) {
|
||||
$this->logger->info('Attested Credential Data is present');
|
||||
$aaguid = Uuid::fromBinary($authDataStream->read(16));
|
||||
$credentialLength = $authDataStream->read(2);
|
||||
$credentialLength = unpack('n', $credentialLength);
|
||||
$credentialId = $authDataStream->read($credentialLength[1]);
|
||||
$credentialPublicKey = $this->decoder->decode($authDataStream);
|
||||
$credentialPublicKey instanceof MapObject || throw InvalidDataException::create(
|
||||
$credentialPublicKey,
|
||||
'The data does not contain a valid credential public key.'
|
||||
);
|
||||
$attestedCredentialData = new AttestedCredentialData(
|
||||
$aaguid,
|
||||
$credentialId,
|
||||
(string) $credentialPublicKey
|
||||
);
|
||||
$this->logger->info('Attested Credential Data loaded');
|
||||
$this->logger->debug('Attested Credential Data loaded', [
|
||||
'at' => $attestedCredentialData,
|
||||
]);
|
||||
}
|
||||
|
||||
$extension = null;
|
||||
if (0 !== (ord($flags) & self::FLAG_ED)) {
|
||||
$this->logger->info('Extension Data loaded');
|
||||
$extension = $this->decoder->decode($authDataStream);
|
||||
$extension = AuthenticationExtensionsClientOutputsLoader::load($extension);
|
||||
$this->logger->info('Extension Data loaded');
|
||||
$this->logger->debug('Extension Data loaded', [
|
||||
'ed' => $extension,
|
||||
]);
|
||||
}
|
||||
$authDataStream->isEOF() || throw InvalidDataException::create(
|
||||
null,
|
||||
'Invalid authentication data. Presence of extra bytes.'
|
||||
);
|
||||
$authDataStream->close();
|
||||
|
||||
$authenticatorData = new AuthenticatorData(
|
||||
$authData,
|
||||
$rp_id_hash,
|
||||
$flags,
|
||||
$signCount[1],
|
||||
$attestedCredentialData,
|
||||
$extension
|
||||
);
|
||||
$attestationObject = new AttestationObject($data, $attestationStatement, $authenticatorData);
|
||||
$this->logger->info('Attestation Object loaded');
|
||||
$this->logger->debug('Attestation Object', [
|
||||
'ed' => $attestationObject,
|
||||
]);
|
||||
$this->dispatcher->dispatch(AttestationObjectLoaded::create($attestationObject));
|
||||
|
||||
return $attestationObject;
|
||||
} catch (Throwable $throwable) {
|
||||
$this->logger->error('An error occurred', [
|
||||
'exception' => $throwable,
|
||||
]);
|
||||
throw $throwable;
|
||||
}
|
||||
}
|
||||
|
||||
public function setLogger(LoggerInterface $logger): void
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
}
|
||||
161
libraries/vendor/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatement.php
vendored
Normal file
161
libraries/vendor/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatement.php
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\AttestationStatement;
|
||||
|
||||
use function array_key_exists;
|
||||
use JsonSerializable;
|
||||
use Webauthn\Exception\InvalidDataException;
|
||||
use Webauthn\TrustPath\TrustPath;
|
||||
use Webauthn\TrustPath\TrustPathLoader;
|
||||
|
||||
class AttestationStatement implements JsonSerializable
|
||||
{
|
||||
final public const TYPE_NONE = 'none';
|
||||
|
||||
final public const TYPE_BASIC = 'basic';
|
||||
|
||||
final public const TYPE_SELF = 'self';
|
||||
|
||||
final public const TYPE_ATTCA = 'attca';
|
||||
|
||||
/**
|
||||
* @deprecated since 4.2.0 and will be removed in 5.0.0. The ECDAA Trust Anchor does no longer exist in Webauthn specification.
|
||||
*/
|
||||
final public const TYPE_ECDAA = 'ecdaa';
|
||||
|
||||
final public const TYPE_ANONCA = 'anonca';
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attStmt
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly string $fmt,
|
||||
private readonly array $attStmt,
|
||||
private readonly string $type,
|
||||
private readonly TrustPath $trustPath
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attStmt
|
||||
*/
|
||||
public static function createNone(string $fmt, array $attStmt, TrustPath $trustPath): self
|
||||
{
|
||||
return new self($fmt, $attStmt, self::TYPE_NONE, $trustPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attStmt
|
||||
*/
|
||||
public static function createBasic(string $fmt, array $attStmt, TrustPath $trustPath): self
|
||||
{
|
||||
return new self($fmt, $attStmt, self::TYPE_BASIC, $trustPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attStmt
|
||||
*/
|
||||
public static function createSelf(string $fmt, array $attStmt, TrustPath $trustPath): self
|
||||
{
|
||||
return new self($fmt, $attStmt, self::TYPE_SELF, $trustPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attStmt
|
||||
*/
|
||||
public static function createAttCA(string $fmt, array $attStmt, TrustPath $trustPath): self
|
||||
{
|
||||
return new self($fmt, $attStmt, self::TYPE_ATTCA, $trustPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attStmt
|
||||
*
|
||||
* @deprecated since 4.2.0 and will be removed in 5.0.0. The ECDAA Trust Anchor does no longer exist in Webauthn specification.
|
||||
*/
|
||||
public static function createEcdaa(string $fmt, array $attStmt, TrustPath $trustPath): self
|
||||
{
|
||||
return new self($fmt, $attStmt, self::TYPE_ECDAA, $trustPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attStmt
|
||||
*/
|
||||
public static function createAnonymizationCA(string $fmt, array $attStmt, TrustPath $trustPath): self
|
||||
{
|
||||
return new self($fmt, $attStmt, self::TYPE_ANONCA, $trustPath);
|
||||
}
|
||||
|
||||
public function getFmt(): string
|
||||
{
|
||||
return $this->fmt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function getAttStmt(): array
|
||||
{
|
||||
return $this->attStmt;
|
||||
}
|
||||
|
||||
public function has(string $key): bool
|
||||
{
|
||||
return array_key_exists($key, $this->attStmt);
|
||||
}
|
||||
|
||||
public function get(string $key): mixed
|
||||
{
|
||||
$this->has($key) || throw InvalidDataException::create($this->attStmt, sprintf(
|
||||
'The attestation statement has no key "%s".',
|
||||
$key
|
||||
));
|
||||
|
||||
return $this->attStmt[$key];
|
||||
}
|
||||
|
||||
public function getTrustPath(): TrustPath
|
||||
{
|
||||
return $this->trustPath;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $data
|
||||
*/
|
||||
public static function createFromArray(array $data): self
|
||||
{
|
||||
foreach (['fmt', 'attStmt', 'trustPath', 'type'] as $key) {
|
||||
array_key_exists($key, $data) || throw InvalidDataException::create($data, sprintf(
|
||||
'The key "%s" is missing',
|
||||
$key
|
||||
));
|
||||
}
|
||||
|
||||
return new self(
|
||||
$data['fmt'],
|
||||
$data['attStmt'],
|
||||
$data['type'],
|
||||
TrustPathLoader::loadTrustPath($data['trustPath'])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'fmt' => $this->fmt,
|
||||
'attStmt' => $this->attStmt,
|
||||
'trustPath' => $this->trustPath->jsonSerialize(),
|
||||
'type' => $this->type,
|
||||
];
|
||||
}
|
||||
}
|
||||
23
libraries/vendor/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatementSupport.php
vendored
Normal file
23
libraries/vendor/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatementSupport.php
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\AttestationStatement;
|
||||
|
||||
use Webauthn\AuthenticatorData;
|
||||
|
||||
interface AttestationStatementSupport
|
||||
{
|
||||
public function name(): string;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attestation
|
||||
*/
|
||||
public function load(array $attestation): AttestationStatement;
|
||||
|
||||
public function isValid(
|
||||
string $clientDataJSONHash,
|
||||
AttestationStatement $attestationStatement,
|
||||
AuthenticatorData $authenticatorData
|
||||
): bool;
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Webauthn\AttestationStatement;
|
||||
|
||||
use function array_key_exists;
|
||||
use Webauthn\Exception\InvalidDataException;
|
||||
|
||||
class AttestationStatementSupportManager
|
||||
{
|
||||
/**
|
||||
* @var AttestationStatementSupport[]
|
||||
*/
|
||||
private array $attestationStatementSupports = [];
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function add(AttestationStatementSupport $attestationStatementSupport): void
|
||||
{
|
||||
$this->attestationStatementSupports[$attestationStatementSupport->name()] = $attestationStatementSupport;
|
||||
}
|
||||
|
||||
public function has(string $name): bool
|
||||
{
|
||||
return array_key_exists($name, $this->attestationStatementSupports);
|
||||
}
|
||||
|
||||
public function get(string $name): AttestationStatementSupport
|
||||
{
|
||||
$this->has($name) || throw InvalidDataException::create($name, sprintf(
|
||||
'The attestation statement format "%s" is not supported.',
|
||||
$name
|
||||
));
|
||||
|
||||
return $this->attestationStatementSupports[$name];
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user