Update website

This commit is contained in:
Guilhem Lavaux 2024-11-19 09:35:33 +01:00
parent bb4b0f9be8
commit 011b183e28
4263 changed files with 3014 additions and 720369 deletions

View file

@ -1,21 +0,0 @@
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.

View file

@ -1,39 +0,0 @@
{
"name": "web-auth/metadata-service",
"type": "library",
"license": "MIT",
"description": "Metadata Service for FIDO2/Webauthn",
"keywords": ["FIDO", "FIDO2", "webauthn"],
"homepage": "https://github.com/web-auth",
"authors": [
{
"name": "Florent Morselli",
"homepage": "https://github.com/Spomky"
},
{
"name": "All contributors",
"homepage": "https://github.com/web-auth/metadata-service/contributors"
}
],
"require": {
"php": ">=7.2",
"ext-json": "*",
"beberlei/assert": "^3.2",
"league/uri": "^6.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"psr/log": "^1.1"
},
"suggest": {
"psr/log-implementation": "Recommended to receive logs from the library"
},
"autoload": {
"psr-4": {
"Webauthn\\MetadataService\\": "src/"
}
},
"suggest": {
"web-token/jwt-key-mgmt": "Mandatory for fetching Metadata Statement from distant sources",
"web-token/jwt-signature-algorithm-ecdsa": "Mandatory for fetching Metadata Statement from distant sources"
}
}

View file

@ -1,49 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use Assert\Assertion;
use JsonSerializable;
abstract class AbstractDescriptor implements JsonSerializable
{
/**
* @var int|null
*/
private $maxRetries;
/**
* @var int|null
*/
private $blockSlowdown;
public function __construct(?int $maxRetries = null, ?int $blockSlowdown = null)
{
Assertion::greaterOrEqualThan($maxRetries, 0, Utils::logicException('Invalid data. The value of "maxRetries" must be a positive integer'));
Assertion::greaterOrEqualThan($blockSlowdown, 0, Utils::logicException('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;
}
}

View file

@ -1,58 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
abstract class AuthenticatorStatus
{
public const NOT_FIDO_CERTIFIED = 'NOT_FIDO_CERTIFIED';
public const FIDO_CERTIFIED = 'FIDO_CERTIFIED';
public const USER_VERIFICATION_BYPASS = 'USER_VERIFICATION_BYPASS';
public const ATTESTATION_KEY_COMPROMISE = 'ATTESTATION_KEY_COMPROMISE';
public const USER_KEY_REMOTE_COMPROMISE = 'USER_KEY_REMOTE_COMPROMISE';
public const USER_KEY_PHYSICAL_COMPROMISE = 'USER_KEY_PHYSICAL_COMPROMISE';
public const UPDATE_AVAILABLE = 'UPDATE_AVAILABLE';
public const REVOKED = 'REVOKED';
public const SELF_ASSERTION_SUBMITTED = 'SELF_ASSERTION_SUBMITTED';
public const FIDO_CERTIFIED_L1 = 'FIDO_CERTIFIED_L1';
public const FIDO_CERTIFIED_L1plus = 'FIDO_CERTIFIED_L1plus';
public const FIDO_CERTIFIED_L2 = 'FIDO_CERTIFIED_L2';
public const FIDO_CERTIFIED_L2plus = 'FIDO_CERTIFIED_L2plus';
public const FIDO_CERTIFIED_L3 = 'FIDO_CERTIFIED_L3';
public const FIDO_CERTIFIED_L3plus = 'FIDO_CERTIFIED_L3plus';
public const FIDO_CERTIFIED_L4 = 'FIDO_CERTIFIED_L4';
public const FIDO_CERTIFIED_L5 = 'FIDO_CERTIFIED_L5';
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,
];
}
}

View file

@ -1,108 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use Assert\Assertion;
class BiometricAccuracyDescriptor extends AbstractDescriptor
{
/**
* @var float|null
*/
private $FAR;
/**
* @var float|null
*/
private $FRR;
/**
* @var float|null
*/
private $EER;
/**
* @var float|null
*/
private $FAAR;
/**
* @var int|null
*/
private $maxReferenceDataSets;
public function __construct(?float $FAR, ?float $FRR, ?float $EER, ?float $FAAR, ?int $maxReferenceDataSets, ?int $maxRetries = null, ?int $blockSlowdown = null)
{
Assertion::greaterOrEqualThan($maxReferenceDataSets, 0, Utils::logicException('Invalid data. The value of "maxReferenceDataSets" must be a positive integer'));
$this->FRR = $FRR;
$this->FAR = $FAR;
$this->EER = $EER;
$this->FAAR = $FAAR;
$this->maxReferenceDataSets = $maxReferenceDataSets;
parent::__construct($maxRetries, $blockSlowdown);
}
public function getFAR(): ?float
{
return $this->FAR;
}
public function getFRR(): ?float
{
return $this->FRR;
}
public function getEER(): ?float
{
return $this->EER;
}
public function getFAAR(): ?float
{
return $this->FAAR;
}
public function getMaxReferenceDataSets(): ?int
{
return $this->maxReferenceDataSets;
}
public static function createFromArray(array $data): self
{
return new self(
$data['FAR'] ?? null,
$data['FRR'] ?? null,
$data['EER'] ?? null,
$data['FAAR'] ?? null,
$data['maxReferenceDataSets'] ?? null,
$data['maxRetries'] ?? null,
$data['blockSlowdown'] ?? null
);
}
public function jsonSerialize(): array
{
$data = [
'FAR' => $this->FAR,
'FRR' => $this->FRR,
'EER' => $this->EER,
'FAAR' => $this->FAAR,
'maxReferenceDataSets' => $this->maxReferenceDataSets,
'maxRetries' => $this->getMaxRetries(),
'blockSlowdown' => $this->getBlockSlowdown(),
];
return Utils::filterNullValues($data);
}
}

View file

@ -1,118 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use JsonSerializable;
class BiometricStatusReport implements JsonSerializable
{
/**
* @var int
*/
private $certLevel;
/**
* @var int
*/
private $modality;
/**
* @var string|null
*/
private $effectiveDate;
/**
* @var string|null
*/
private $certificationDescriptor;
/**
* @var string|null
*/
private $certificateNumber;
/**
* @var string|null
*/
private $certificationPolicyVersion;
/**
* @var string|null
*/
private $certificationRequirementsVersion;
public function getCertLevel(): int
{
return $this->certLevel;
}
public function getModality(): int
{
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;
}
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;
}
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 function ($var): bool {return null !== $var; });
}
}

View file

@ -1,73 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use Assert\Assertion;
class CodeAccuracyDescriptor extends AbstractDescriptor
{
/**
* @var int
*/
private $base;
/**
* @var int
*/
private $minLength;
public function __construct(int $base, int $minLength, ?int $maxRetries = null, ?int $blockSlowdown = null)
{
Assertion::greaterOrEqualThan($base, 0, Utils::logicException('Invalid data. The value of "base" must be a positive integer'));
Assertion::greaterOrEqualThan($minLength, 0, Utils::logicException('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;
}
public static function createFromArray(array $data): self
{
Assertion::keyExists($data, 'base', Utils::logicException('The parameter "base" is missing'));
Assertion::keyExists($data, 'minLength', Utils::logicException('The parameter "minLength" is missing'));
return new self(
$data['base'],
$data['minLength'],
$data['maxRetries'] ?? null,
$data['blockSlowdown'] ?? null
);
}
public function jsonSerialize(): array
{
$data = [
'base' => $this->base,
'minLength' => $this->minLength,
'maxRetries' => $this->getMaxRetries(),
'blockSlowdown' => $this->getBlockSlowdown(),
];
return Utils::filterNullValues($data);
}
}

View file

@ -1,172 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use Assert\Assertion;
use JsonSerializable;
use function Safe\sprintf;
class DisplayPNGCharacteristicsDescriptor implements JsonSerializable
{
/**
* @var int
*/
private $width;
/**
* @var int
*/
private $height;
/**
* @var int
*/
private $bitDepth;
/**
* @var int
*/
private $colorType;
/**
* @var int
*/
private $compression;
/**
* @var int
*/
private $filter;
/**
* @var int
*/
private $interlace;
/**
* @var RgbPaletteEntry[]
*/
private $plte = [];
public function __construct(int $width, int $height, int $bitDepth, int $colorType, int $compression, int $filter, int $interlace)
{
Assertion::greaterOrEqualThan($width, 0, Utils::logicException('Invalid width'));
Assertion::greaterOrEqualThan($height, 0, Utils::logicException('Invalid height'));
Assertion::range($bitDepth, 0, 254, Utils::logicException('Invalid bit depth'));
Assertion::range($colorType, 0, 254, Utils::logicException('Invalid color type'));
Assertion::range($compression, 0, 254, Utils::logicException('Invalid compression'));
Assertion::range($filter, 0, 254, Utils::logicException('Invalid filter'));
Assertion::range($interlace, 0, 254, Utils::logicException('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 addPalette(RgbPaletteEntry $rgbPaletteEntry): self
{
$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 getPlte(): array
{
return $this->plte;
}
public static function createFromArray(array $data): self
{
$data = Utils::filterNullValues($data);
foreach (['width', 'compression', 'height', 'bitDepth', 'colorType', 'compression', 'filter', 'interlace'] as $key) {
Assertion::keyExists($data, $key, 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'];
Assertion::isArray($plte, Utils::logicException('Invalid "plte" parameter'));
foreach ($plte as $item) {
$object->addPalette(RgbPaletteEntry::createFromArray($item));
}
}
return $object;
}
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);
}
}

View file

@ -1,82 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use Assert\Assertion;
use Base64Url\Base64Url;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use function Safe\json_decode;
use function Safe\sprintf;
class DistantSingleMetadata extends SingleMetadata
{
/**
* @var ClientInterface
*/
private $httpClient;
/**
* @var RequestFactoryInterface
*/
private $requestFactory;
/**
* @var array
*/
private $additionalHeaders;
/**
* @var string
*/
private $uri;
/**
* @var bool
*/
private $isBase64Encoded;
public function __construct(string $uri, bool $isBase64Encoded, ClientInterface $httpClient, RequestFactoryInterface $requestFactory, array $additionalHeaders = [])
{
parent::__construct($uri, $isBase64Encoded); //Useless
$this->uri = $uri;
$this->isBase64Encoded = $isBase64Encoded;
$this->httpClient = $httpClient;
$this->requestFactory = $requestFactory;
$this->additionalHeaders = $additionalHeaders;
}
public function getMetadataStatement(): MetadataStatement
{
$payload = $this->fetch();
$json = $this->isBase64Encoded ? Base64Url::decode($payload) : $payload;
$data = json_decode($json, true);
return MetadataStatement::createFromArray($data);
}
private function fetch(): string
{
$request = $this->requestFactory->createRequest('GET', $this->uri);
foreach ($this->additionalHeaders as $k => $v) {
$request = $request->withHeader($k, $v);
}
$response = $this->httpClient->sendRequest($request);
Assertion::eq(200, $response->getStatusCode(), sprintf('Unable to contact the server. Response code is %d', $response->getStatusCode()));
$content = $response->getBody()->getContents();
Assertion::notEmpty($content, 'Unable to contact the server. The response has no content');
return $content;
}
}

View file

@ -1,123 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use Assert\Assertion;
use Base64Url\Base64Url;
use JsonSerializable;
use function Safe\sprintf;
class EcdaaTrustAnchor implements JsonSerializable
{
/**
* @var string
*/
private $X;
/**
* @var string
*/
private $Y;
/**
* @var string
*/
private $c;
/**
* @var string
*/
private $sx;
/**
* @var string
*/
private $sy;
/**
* @var string
*/
private $G1Curve;
public function __construct(string $X, string $Y, string $c, string $sx, string $sy, string $G1Curve)
{
$this->X = $X;
$this->Y = $Y;
$this->c = $c;
$this->sx = $sx;
$this->sy = $sy;
$this->G1Curve = $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;
}
public static function createFromArray(array $data): self
{
$data = Utils::filterNullValues($data);
foreach (['X', 'Y', 'c', 'sx', 'sy', 'G1Curve'] as $key) {
Assertion::keyExists($data, $key, sprintf('Invalid data. The key "%s" is missing', $key));
}
return new self(
Base64Url::decode($data['X']),
Base64Url::decode($data['Y']),
Base64Url::decode($data['c']),
Base64Url::decode($data['sx']),
Base64Url::decode($data['sy']),
$data['G1Curve']
);
}
public function jsonSerialize(): array
{
$data = [
'X' => Base64Url::encode($this->X),
'Y' => Base64Url::encode($this->Y),
'c' => Base64Url::encode($this->c),
'sx' => Base64Url::encode($this->sx),
'sy' => Base64Url::encode($this->sy),
'G1Curve' => $this->G1Curve,
];
return Utils::filterNullValues($data);
}
}

View file

@ -1,106 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use function array_key_exists;
use Assert\Assertion;
use JsonSerializable;
class ExtensionDescriptor implements JsonSerializable
{
/**
* @var string
*/
private $id;
/**
* @var int|null
*/
private $tag;
/**
* @var string|null
*/
private $data;
/**
* @var bool
*/
private $fail_if_unknown;
public function __construct(string $id, ?int $tag, ?string $data, bool $fail_if_unknown)
{
if (null !== $tag) {
Assertion::greaterOrEqualThan($tag, 0, Utils::logicException('Invalid data. The parameter "tag" shall be a positive integer'));
}
$this->id = $id;
$this->tag = $tag;
$this->data = $data;
$this->fail_if_unknown = $fail_if_unknown;
}
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->fail_if_unknown;
}
public static function createFromArray(array $data): self
{
$data = Utils::filterNullValues($data);
Assertion::keyExists($data, 'id', Utils::logicException('Invalid data. The parameter "id" is missing'));
Assertion::string($data['id'], Utils::logicException('Invalid data. The parameter "id" shall be a string'));
Assertion::keyExists($data, 'fail_if_unknown', Utils::logicException('Invalid data. The parameter "fail_if_unknown" is missing'));
Assertion::boolean($data['fail_if_unknown'], Utils::logicException('Invalid data. The parameter "fail_if_unknown" shall be a boolean'));
if (array_key_exists('tag', $data)) {
Assertion::integer($data['tag'], Utils::logicException('Invalid data. The parameter "tag" shall be a positive integer'));
}
if (array_key_exists('data', $data)) {
Assertion::string($data['data'], Utils::logicException('Invalid data. The parameter "data" shall be a string'));
}
return new self(
$data['id'],
$data['tag'] ?? null,
$data['data'] ?? null,
$data['fail_if_unknown']
);
}
public function jsonSerialize(): array
{
$result = [
'id' => $this->id,
'tag' => $this->tag,
'data' => $this->data,
'fail_if_unknown' => $this->fail_if_unknown,
];
return Utils::filterNullValues($result);
}
}

View file

@ -1,283 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use Assert\Assertion;
use Base64Url\Base64Url;
use function count;
use InvalidArgumentException;
use function is_array;
use Jose\Component\KeyManagement\JWKFactory;
use Jose\Component\Signature\Algorithm\ES256;
use Jose\Component\Signature\Serializer\CompactSerializer;
use League\Uri\UriString;
use LogicException;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use function Safe\json_decode;
use function Safe\sprintf;
use Throwable;
use Webauthn\CertificateToolbox;
class MetadataService
{
/**
* @var ClientInterface
*/
private $httpClient;
/**
* @var RequestFactoryInterface
*/
private $requestFactory;
/**
* @var array
*/
private $additionalQueryStringValues;
/**
* @var array
*/
private $additionalHeaders;
/**
* @var string
*/
private $serviceUri;
/**
* @var LoggerInterface
*/
private $logger;
public function __construct(string $serviceUri, ClientInterface $httpClient, RequestFactoryInterface $requestFactory, array $additionalQueryStringValues = [], array $additionalHeaders = [], ?LoggerInterface $logger = null)
{
if (0 !== count($additionalQueryStringValues)) {
@trigger_error('The argument "additionalQueryStringValues" is deprecated since version 3.3 and will be removed in 4.0. Please set an empty array instead and us the method `addQueryStringValues`.', E_USER_DEPRECATED);
}
if (0 !== count($additionalQueryStringValues)) {
@trigger_error('The argument "additionalHeaders" is deprecated since version 3.3 and will be removed in 4.0. Please set an empty array instead and us the method `addHeaders`.', E_USER_DEPRECATED);
}
if (null !== $logger) {
@trigger_error('The argument "logger" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setLogger" instead.', E_USER_DEPRECATED);
}
$this->serviceUri = $serviceUri;
$this->httpClient = $httpClient;
$this->requestFactory = $requestFactory;
$this->additionalQueryStringValues = $additionalQueryStringValues;
$this->additionalHeaders = $additionalHeaders;
$this->logger = $logger ?? new NullLogger();
}
public function addQueryStringValues(array $additionalQueryStringValues): self
{
$this->additionalQueryStringValues = $additionalQueryStringValues;
return $this;
}
public function addHeaders(array $additionalHeaders): self
{
$this->additionalHeaders = $additionalHeaders;
return $this;
}
public function setLogger(LoggerInterface $logger): self
{
$this->logger = $logger;
return $this;
}
public function has(string $aaguid): bool
{
try {
$toc = $this->fetchMetadataTOCPayload();
} catch (Throwable $e) {
return false;
}
foreach ($toc->getEntries() as $entry) {
if ($entry->getAaguid() === $aaguid && null !== $entry->getUrl()) {
return true;
}
}
return false;
}
public function get(string $aaguid): MetadataStatement
{
$toc = $this->fetchMetadataTOCPayload();
foreach ($toc->getEntries() as $entry) {
if ($entry->getAaguid() === $aaguid && null !== $entry->getUrl()) {
$mds = $this->fetchMetadataStatementFor($entry);
$mds
->setStatusReports($entry->getStatusReports())
->setRootCertificates($toc->getRootCertificates())
;
return $mds;
}
}
throw new InvalidArgumentException(sprintf('The Metadata Statement with AAGUID "%s" is missing', $aaguid));
}
/**
* @deprecated This method is deprecated since v3.3 and will be removed in v4.0
*/
public function getMetadataStatementFor(MetadataTOCPayloadEntry $entry, string $hashingFunction = 'sha256'): MetadataStatement
{
return $this->fetchMetadataStatementFor($entry, $hashingFunction);
}
public function fetchMetadataStatementFor(MetadataTOCPayloadEntry $entry, string $hashingFunction = 'sha256'): MetadataStatement
{
$this->logger->info('Trying to get the metadata statement for a given entry', ['entry' => $entry]);
try {
$hash = $entry->getHash();
$url = $entry->getUrl();
if (null === $hash || null === $url) {
throw new LogicException('The Metadata Statement has not been published');
}
$uri = $this->buildUri($url);
$result = $this->fetchMetadataStatement($uri, true, $hash, $hashingFunction);
$this->logger->info('The metadata statement exists');
$this->logger->debug('Metadata Statement', ['mds' => $result]);
return $result;
} catch (Throwable $throwable) {
$this->logger->error('An error occurred', [
'exception' => $throwable,
]);
throw $throwable;
}
}
/**
* @deprecated This method is deprecated since v3.3 and will be removed in v4.0
*/
public function getMetadataTOCPayload(): MetadataTOCPayload
{
return $this->fetchMetadataTOCPayload();
}
private function fetchMetadataTOCPayload(): MetadataTOCPayload
{
$this->logger->info('Trying to get the metadata service TOC payload');
try {
$uri = $this->buildUri($this->serviceUri);
$toc = $this->fetchTableOfContent($uri);
$this->logger->info('The TOC payload has been received');
$this->logger->debug('TOC payload', ['toc' => $toc]);
return $toc;
} catch (Throwable $throwable) {
$this->logger->error('An error occurred', [
'exception' => $throwable,
]);
throw $throwable;
}
}
private function buildUri(string $uri): string
{
$parsedUri = UriString::parse($uri);
$queryString = $parsedUri['query'];
$query = [];
if (null !== $queryString) {
parse_str($queryString, $query);
}
foreach ($this->additionalQueryStringValues as $k => $v) {
if (!isset($query[$k])) {
$query[$k] = $v;
continue;
}
if (!is_array($query[$k])) {
$query[$k] = [$query[$k], $v];
continue;
}
$query[$k][] = $v;
}
$parsedUri['query'] = 0 === count($query) ? null : http_build_query($query, '', '&', PHP_QUERY_RFC3986);
return UriString::build($parsedUri);
}
private function fetchTableOfContent(string $uri): MetadataTOCPayload
{
$content = $this->fetch($uri);
$rootCertificates = [];
$payload = $this->getJwsPayload($content, $rootCertificates);
$data = json_decode($payload, true);
$toc = MetadataTOCPayload::createFromArray($data);
$toc->setRootCertificates($rootCertificates);
return $toc;
}
private function fetchMetadataStatement(string $uri, bool $isBase64UrlEncoded, string $hash = '', string $hashingFunction = 'sha256'): MetadataStatement
{
$payload = $this->fetch($uri);
if ('' !== $hash) {
Assertion::true(hash_equals($hash, hash($hashingFunction, $payload, true)), 'The hash cannot be verified. The metadata statement shall be rejected');
}
$json = $isBase64UrlEncoded ? Base64Url::decode($payload) : $payload;
$data = json_decode($json, true);
return MetadataStatement::createFromArray($data);
}
private function fetch(string $uri): string
{
$request = $this->requestFactory->createRequest('GET', $uri);
foreach ($this->additionalHeaders as $k => $v) {
$request = $request->withHeader($k, $v);
}
$response = $this->httpClient->sendRequest($request);
Assertion::eq(200, $response->getStatusCode(), sprintf('Unable to contact the server. Response code is %d', $response->getStatusCode()));
$content = $response->getBody()->getContents();
Assertion::notEmpty($content, 'Unable to contact the server. The response has no content');
return $content;
}
private function getJwsPayload(string $token, array &$rootCertificates): string
{
$jws = (new CompactSerializer())->unserialize($token);
Assertion::eq(1, $jws->countSignatures(), 'Invalid response from the metadata service. Only one signature shall be present.');
$signature = $jws->getSignature(0);
$payload = $jws->getPayload();
Assertion::notEmpty($payload, 'Invalid response from the metadata service. The token payload is empty.');
$header = $signature->getProtectedHeader();
Assertion::keyExists($header, 'alg', 'The "alg" parameter is missing.');
Assertion::eq($header['alg'], 'ES256', 'The expected "alg" parameter value should be "ES256".');
Assertion::keyExists($header, 'x5c', 'The "x5c" parameter is missing.');
Assertion::isArray($header['x5c'], 'The "x5c" parameter should be an array.');
$key = JWKFactory::createFromX5C($header['x5c']);
$rootCertificates = array_map(static function (string $x509): string {
return CertificateToolbox::fixPEMStructure($x509);
}, $header['x5c']);
$algorithm = new ES256();
$isValid = $algorithm->verify($key, $signature->getEncodedProtectedHeader().'.'.$jws->getEncodedPayload(), $signature->getSignature());
Assertion::true($isValid, 'Invalid response from the metadata service. The token signature is invalid.');
return $jws->getPayload();
}
}

View file

@ -1,602 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use Assert\Assertion;
use InvalidArgumentException;
use JsonSerializable;
use function Safe\json_decode;
use function Safe\sprintf;
class MetadataStatement implements JsonSerializable
{
public const KEY_PROTECTION_SOFTWARE = 0x0001;
public const KEY_PROTECTION_HARDWARE = 0x0002;
public const KEY_PROTECTION_TEE = 0x0004;
public const KEY_PROTECTION_SECURE_ELEMENT = 0x0008;
public const KEY_PROTECTION_REMOTE_HANDLE = 0x0010;
public const MATCHER_PROTECTION_SOFTWARE = 0x0001;
public const MATCHER_PROTECTION_TEE = 0x0002;
public const MATCHER_PROTECTION_ON_CHIP = 0x0004;
public const ATTACHMENT_HINT_INTERNAL = 0x0001;
public const ATTACHMENT_HINT_EXTERNAL = 0x0002;
public const ATTACHMENT_HINT_WIRED = 0x0004;
public const ATTACHMENT_HINT_WIRELESS = 0x0008;
public const ATTACHMENT_HINT_NFC = 0x0010;
public const ATTACHMENT_HINT_BLUETOOTH = 0x0020;
public const ATTACHMENT_HINT_NETWORK = 0x0040;
public const ATTACHMENT_HINT_READY = 0x0080;
public const ATTACHMENT_HINT_WIFI_DIRECT = 0x0100;
public const TRANSACTION_CONFIRMATION_DISPLAY_ANY = 0x0001;
public const TRANSACTION_CONFIRMATION_DISPLAY_PRIVILEGED_SOFTWARE = 0x0002;
public const TRANSACTION_CONFIRMATION_DISPLAY_TEE = 0x0004;
public const TRANSACTION_CONFIRMATION_DISPLAY_HARDWARE = 0x0008;
public const TRANSACTION_CONFIRMATION_DISPLAY_REMOTE = 0x0010;
public const ALG_SIGN_SECP256R1_ECDSA_SHA256_RAW = 0x0001;
public const ALG_SIGN_SECP256R1_ECDSA_SHA256_DER = 0x0002;
public const ALG_SIGN_RSASSA_PSS_SHA256_RAW = 0x0003;
public const ALG_SIGN_RSASSA_PSS_SHA256_DER = 0x0004;
public const ALG_SIGN_SECP256K1_ECDSA_SHA256_RAW = 0x0005;
public const ALG_SIGN_SECP256K1_ECDSA_SHA256_DER = 0x0006;
public const ALG_SIGN_SM2_SM3_RAW = 0x0007;
public const ALG_SIGN_RSA_EMSA_PKCS1_SHA256_RAW = 0x0008;
public const ALG_SIGN_RSA_EMSA_PKCS1_SHA256_DER = 0x0009;
public const ALG_SIGN_RSASSA_PSS_SHA384_RAW = 0x000A;
public const ALG_SIGN_RSASSA_PSS_SHA512_RAW = 0x000B;
public const ALG_SIGN_RSASSA_PKCSV15_SHA256_RAW = 0x000C;
public const ALG_SIGN_RSASSA_PKCSV15_SHA384_RAW = 0x000D;
public const ALG_SIGN_RSASSA_PKCSV15_SHA512_RAW = 0x000E;
public const ALG_SIGN_RSASSA_PKCSV15_SHA1_RAW = 0x000F;
public const ALG_SIGN_SECP384R1_ECDSA_SHA384_RAW = 0x0010;
public const ALG_SIGN_SECP521R1_ECDSA_SHA512_RAW = 0x0011;
public const ALG_SIGN_ED25519_EDDSA_SHA256_RAW = 0x0012;
public const ALG_KEY_ECC_X962_RAW = 0x0100;
public const ALG_KEY_ECC_X962_DER = 0x0101;
public const ALG_KEY_RSA_2048_RAW = 0x0102;
public const ALG_KEY_RSA_2048_DER = 0x0103;
public const ALG_KEY_COSE = 0x0104;
public const ATTESTATION_BASIC_FULL = 0x3E07;
public const ATTESTATION_BASIC_SURROGATE = 0x3E08;
public const ATTESTATION_ECDAA = 0x3E09;
public const ATTESTATION_ATTCA = 0x3E0A;
/**
* @var string|null
*/
private $legalHeader;
/**
* @var string|null
*/
private $aaid;
/**
* @var string|null
*/
private $aaguid;
/**
* @var string[]
*/
private $attestationCertificateKeyIdentifiers = [];
/**
* @var string
*/
private $description;
/**
* @var string[]
*/
private $alternativeDescriptions = [];
/**
* @var int
*/
private $authenticatorVersion;
/**
* @var string
*/
private $protocolFamily;
/**
* @var Version[]
*/
private $upv = [];
/**
* @var string|null
*/
private $assertionScheme;
/**
* @var int|null
*/
private $authenticationAlgorithm;
/**
* @var int[]
*/
private $authenticationAlgorithms = [];
/**
* @var int|null
*/
private $publicKeyAlgAndEncoding;
/**
* @var int[]
*/
private $publicKeyAlgAndEncodings = [];
/**
* @var int[]
*/
private $attestationTypes = [];
/**
* @var VerificationMethodANDCombinations[]
*/
private $userVerificationDetails = [];
/**
* @var int
*/
private $keyProtection;
/**
* @var bool|null
*/
private $isKeyRestricted;
/**
* @var bool|null
*/
private $isFreshUserVerificationRequired;
/**
* @var int
*/
private $matcherProtection;
/**
* @var int|null
*/
private $cryptoStrength;
/**
* @var string|null
*/
private $operatingEnv;
/**
* @var int
*/
private $attachmentHint = 0;
/**
* @var bool|null
*/
private $isSecondFactorOnly;
/**
* @var int
*/
private $tcDisplay;
/**
* @var string|null
*/
private $tcDisplayContentType;
/**
* @var DisplayPNGCharacteristicsDescriptor[]
*/
private $tcDisplayPNGCharacteristics = [];
/**
* @var string[]
*/
private $attestationRootCertificates = [];
/**
* @var EcdaaTrustAnchor[]
*/
private $ecdaaTrustAnchors = [];
/**
* @var string|null
*/
private $icon;
/**
* @var ExtensionDescriptor[]
*/
private $supportedExtensions = [];
/**
* @var array<int, StatusReport>
*/
private $statusReports = [];
/**
* @var string[]
*/
private $rootCertificates = [];
public static function createFromString(string $statement): self
{
$data = json_decode($statement, true);
Assertion::isArray($data, 'Invalid Metadata Statement');
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;
}
/**
* @return string[]
*/
public function getAttestationCertificateKeyIdentifiers(): array
{
return $this->attestationCertificateKeyIdentifiers;
}
public function getDescription(): string
{
return $this->description;
}
/**
* @return string[]
*/
public function getAlternativeDescriptions(): array
{
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 getAssertionScheme(): ?string
{
return $this->assertionScheme;
}
public function getAuthenticationAlgorithm(): ?int
{
return $this->authenticationAlgorithm;
}
/**
* @return int[]
*/
public function getAuthenticationAlgorithms(): array
{
return $this->authenticationAlgorithms;
}
public function getPublicKeyAlgAndEncoding(): ?int
{
return $this->publicKeyAlgAndEncoding;
}
/**
* @return int[]
*/
public function getPublicKeyAlgAndEncodings(): array
{
return $this->publicKeyAlgAndEncodings;
}
/**
* @return int[]
*/
public function getAttestationTypes(): array
{
return $this->attestationTypes;
}
/**
* @return VerificationMethodANDCombinations[]
*/
public function getUserVerificationDetails(): array
{
return $this->userVerificationDetails;
}
public function getKeyProtection(): int
{
return $this->keyProtection;
}
public function isKeyRestricted(): ?bool
{
return (bool) $this->isKeyRestricted;
}
public function isFreshUserVerificationRequired(): ?bool
{
return (bool) $this->isFreshUserVerificationRequired;
}
public function getMatcherProtection(): int
{
return $this->matcherProtection;
}
public function getCryptoStrength(): ?int
{
return $this->cryptoStrength;
}
public function getOperatingEnv(): ?string
{
return $this->operatingEnv;
}
public function getAttachmentHint(): int
{
return $this->attachmentHint;
}
public function isSecondFactorOnly(): ?bool
{
return (bool) $this->isSecondFactorOnly;
}
public function getTcDisplay(): int
{
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[]
*/
public function getEcdaaTrustAnchors(): array
{
return $this->ecdaaTrustAnchors;
}
public function getIcon(): ?string
{
return $this->icon;
}
/**
* @return ExtensionDescriptor[]
*/
public function getSupportedExtensions(): array
{
return $this->supportedExtensions;
}
public static function createFromArray(array $data): self
{
$object = new self();
foreach (['description', 'protocolFamily'] as $key) {
if (!isset($data[$key])) {
throw new InvalidArgumentException(sprintf('The parameter "%s" is missing', $key));
}
}
$object->legalHeader = $data['legalHeader'] ?? null;
$object->aaid = $data['aaid'] ?? null;
$object->aaguid = $data['aaguid'] ?? null;
$object->attestationCertificateKeyIdentifiers = $data['attestationCertificateKeyIdentifiers'] ?? [];
$object->description = $data['description'];
$object->alternativeDescriptions = $data['alternativeDescriptions'] ?? [];
$object->authenticatorVersion = $data['authenticatorVersion'] ?? 0;
$object->protocolFamily = $data['protocolFamily'];
if (isset($data['upv'])) {
$upv = $data['upv'];
Assertion::isArray($upv, 'Invalid Metadata Statement');
foreach ($upv as $value) {
Assertion::isArray($value, 'Invalid Metadata Statement');
$object->upv[] = Version::createFromArray($value);
}
}
$object->assertionScheme = $data['assertionScheme'] ?? null;
$object->authenticationAlgorithm = $data['authenticationAlgorithm'] ?? null;
$object->authenticationAlgorithms = $data['authenticationAlgorithms'] ?? [];
$object->publicKeyAlgAndEncoding = $data['publicKeyAlgAndEncoding'] ?? null;
$object->publicKeyAlgAndEncodings = $data['publicKeyAlgAndEncodings'] ?? [];
$object->attestationTypes = $data['attestationTypes'] ?? [];
if (isset($data['userVerificationDetails'])) {
$userVerificationDetails = $data['userVerificationDetails'];
Assertion::isArray($userVerificationDetails, 'Invalid Metadata Statement');
foreach ($userVerificationDetails as $value) {
Assertion::isArray($value, 'Invalid Metadata Statement');
$object->userVerificationDetails[] = VerificationMethodANDCombinations::createFromArray($value);
}
}
$object->keyProtection = $data['keyProtection'] ?? 0;
$object->isKeyRestricted = $data['isKeyRestricted'] ?? null;
$object->isFreshUserVerificationRequired = $data['isFreshUserVerificationRequired'] ?? null;
$object->matcherProtection = $data['matcherProtection'] ?? 0;
$object->cryptoStrength = $data['cryptoStrength'] ?? null;
$object->operatingEnv = $data['operatingEnv'] ?? null;
$object->attachmentHint = $data['attachmentHint'] ?? 0;
$object->isSecondFactorOnly = $data['isSecondFactorOnly'] ?? null;
$object->tcDisplay = $data['tcDisplay'] ?? 0;
$object->tcDisplayContentType = $data['tcDisplayContentType'] ?? null;
if (isset($data['tcDisplayPNGCharacteristics'])) {
$tcDisplayPNGCharacteristics = $data['tcDisplayPNGCharacteristics'];
Assertion::isArray($tcDisplayPNGCharacteristics, 'Invalid Metadata Statement');
foreach ($tcDisplayPNGCharacteristics as $tcDisplayPNGCharacteristic) {
Assertion::isArray($tcDisplayPNGCharacteristic, 'Invalid Metadata Statement');
$object->tcDisplayPNGCharacteristics[] = DisplayPNGCharacteristicsDescriptor::createFromArray($tcDisplayPNGCharacteristic);
}
}
$object->attestationRootCertificates = $data['attestationRootCertificates'] ?? [];
$object->ecdaaTrustAnchors = $data['ecdaaTrustAnchors'] ?? [];
$object->icon = $data['icon'] ?? null;
if (isset($data['supportedExtensions'])) {
$supportedExtensions = $data['supportedExtensions'];
Assertion::isArray($supportedExtensions, 'Invalid Metadata Statement');
foreach ($supportedExtensions as $supportedExtension) {
Assertion::isArray($supportedExtension, 'Invalid Metadata Statement');
$object->supportedExtensions[] = ExtensionDescriptor::createFromArray($supportedExtension);
}
}
$object->rootCertificates = $data['rootCertificates'] ?? [];
if (isset($data['statusReports'])) {
$reports = $data['statusReports'];
Assertion::isArray($reports, 'Invalid Metadata Statement');
foreach ($reports as $report) {
Assertion::isArray($report, 'Invalid Metadata Statement');
$object->statusReports[] = StatusReport::createFromArray($report);
}
}
return $object;
}
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,
'assertionScheme' => $this->assertionScheme,
'authenticationAlgorithm' => $this->authenticationAlgorithm,
'authenticationAlgorithms' => $this->authenticationAlgorithms,
'publicKeyAlgAndEncoding' => $this->publicKeyAlgAndEncoding,
'publicKeyAlgAndEncodings' => $this->publicKeyAlgAndEncodings,
'attestationTypes' => $this->attestationTypes,
'userVerificationDetails' => $this->userVerificationDetails,
'keyProtection' => $this->keyProtection,
'isKeyRestricted' => $this->isKeyRestricted,
'isFreshUserVerificationRequired' => $this->isFreshUserVerificationRequired,
'matcherProtection' => $this->matcherProtection,
'cryptoStrength' => $this->cryptoStrength,
'operatingEnv' => $this->operatingEnv,
'attachmentHint' => $this->attachmentHint,
'isSecondFactorOnly' => $this->isSecondFactorOnly,
'tcDisplay' => $this->tcDisplay,
'tcDisplayContentType' => $this->tcDisplayContentType,
'tcDisplayPNGCharacteristics' => array_map(static function (DisplayPNGCharacteristicsDescriptor $object): array {
return $object->jsonSerialize();
}, $this->tcDisplayPNGCharacteristics),
'attestationRootCertificates' => $this->attestationRootCertificates,
'ecdaaTrustAnchors' => array_map(static function (EcdaaTrustAnchor $object): array {
return $object->jsonSerialize();
}, $this->ecdaaTrustAnchors),
'icon' => $this->icon,
'supportedExtensions' => array_map(static function (ExtensionDescriptor $object): array {
return $object->jsonSerialize();
}, $this->supportedExtensions),
'rootCertificates' => $this->rootCertificates,
'statusReports' => $this->statusReports,
];
return Utils::filterNullValues($data);
}
/**
* @return StatusReport[]
*/
public function getStatusReports(): array
{
return $this->statusReports;
}
/**
* @param StatusReport[] $statusReports
*/
public function setStatusReports(array $statusReports): self
{
$this->statusReports = $statusReports;
return $this;
}
/**
* @return string[]
*/
public function getRootCertificates(): array
{
return $this->rootCertificates;
}
/**
* @param string[] $rootCertificates
*/
public function setRootCertificates(array $rootCertificates): self
{
$this->rootCertificates = $rootCertificates;
return $this;
}
}

View file

@ -1,85 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use Assert\Assertion;
use Base64Url\Base64Url;
use Jose\Component\KeyManagement\JWKFactory;
use Jose\Component\Signature\Algorithm\ES256;
use Jose\Component\Signature\Serializer\CompactSerializer;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use function Safe\json_decode;
use function Safe\sprintf;
/**
* @deprecated This class is deprecated since v3.3 and will be removed in v4.0
*/
class MetadataStatementFetcher
{
public static function fetchTableOfContent(string $uri, ClientInterface $client, RequestFactoryInterface $requestFactory, array $additionalHeaders = []): MetadataTOCPayload
{
$content = self::fetch($uri, $client, $requestFactory, $additionalHeaders);
$payload = self::getJwsPayload($content);
$data = json_decode($payload, true);
return MetadataTOCPayload::createFromArray($data);
}
public static function fetchMetadataStatement(string $uri, bool $isBase64UrlEncoded, ClientInterface $client, RequestFactoryInterface $requestFactory, array $additionalHeaders = [], string $hash = '', string $hashingFunction = 'sha256'): MetadataStatement
{
$payload = self::fetch($uri, $client, $requestFactory, $additionalHeaders);
if ('' !== $hash) {
Assertion::true(hash_equals($hash, hash($hashingFunction, $payload, true)), 'The hash cannot be verified. The metadata statement shall be rejected');
}
$json = $isBase64UrlEncoded ? Base64Url::decode($payload) : $payload;
$data = json_decode($json, true);
return MetadataStatement::createFromArray($data);
}
private static function fetch(string $uri, ClientInterface $client, RequestFactoryInterface $requestFactory, array $additionalHeaders = []): string
{
$request = $requestFactory->createRequest('GET', $uri);
foreach ($additionalHeaders as $k => $v) {
$request = $request->withHeader($k, $v);
}
$response = $client->sendRequest($request);
Assertion::eq(200, $response->getStatusCode(), sprintf('Unable to contact the server. Response code is %d', $response->getStatusCode()));
$content = $response->getBody()->getContents();
Assertion::notEmpty($content, 'Unable to contact the server. The response has no content');
return $content;
}
private static function getJwsPayload(string $token): string
{
$jws = (new CompactSerializer())->unserialize($token);
Assertion::eq(1, $jws->countSignatures(), 'Invalid response from the metadata service. Only one signature shall be present.');
$signature = $jws->getSignature(0);
$payload = $jws->getPayload();
Assertion::notEmpty($payload, 'Invalid response from the metadata service. The token payload is empty.');
$header = $signature->getProtectedHeader();
Assertion::keyExists($header, 'alg', 'The "alg" parameter is missing.');
Assertion::eq($header['alg'], 'ES256', 'The expected "alg" parameter value should be "ES256".');
Assertion::keyExists($header, 'x5c', 'The "x5c" parameter is missing.');
Assertion::isArray($header['x5c'], 'The "x5c" parameter should be an array.');
$key = JWKFactory::createFromX5C($header['x5c']);
$algorithm = new ES256();
$isValid = $algorithm->verify($key, $signature->getEncodedProtectedHeader().'.'.$jws->getEncodedPayload(), $signature->getSignature());
Assertion::true($isValid, 'Invalid response from the metadata service. The token signature is invalid.');
return $jws->getPayload();
}
}

View file

@ -1,26 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
interface MetadataStatementRepository
{
public function findOneByAAGUID(string $aaguid): ?MetadataStatement;
/**
* @deprecated This method is deprecated since v3.3 and will be removed in v4.0. Please use the method "getStatusReports()" provided by the MetadataStatement object
*
* @return StatusReport[]
*/
public function findStatusReportsByAAGUID(string $aaguid): array;
}

View file

@ -1,142 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use function array_key_exists;
use Assert\Assertion;
use JsonSerializable;
use function Safe\sprintf;
class MetadataTOCPayload implements JsonSerializable
{
/**
* @var string|null
*/
private $legalHeader;
/**
* @var int
*/
private $no;
/**
* @var string
*/
private $nextUpdate;
/**
* @var MetadataTOCPayloadEntry[]
*/
private $entries = [];
/**
* @var string[]
*/
private $rootCertificates;
public function __construct(int $no, string $nextUpdate, ?string $legalHeader = null)
{
$this->no = $no;
$this->nextUpdate = $nextUpdate;
$this->legalHeader = $legalHeader;
}
public function addEntry(MetadataTOCPayloadEntry $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 MetadataTOCPayloadEntry[]
*/
public function getEntries(): array
{
return $this->entries;
}
public static function createFromArray(array $data): self
{
$data = Utils::filterNullValues($data);
foreach (['no', 'nextUpdate', 'entries'] as $key) {
Assertion::keyExists($data, $key, Utils::logicException(sprintf('Invalid data. The parameter "%s" is missing', $key)));
}
Assertion::integer($data['no'], Utils::logicException('Invalid data. The parameter "no" shall be an integer'));
Assertion::string($data['nextUpdate'], Utils::logicException('Invalid data. The parameter "nextUpdate" shall be a string'));
Assertion::isArray($data['entries'], Utils::logicException('Invalid data. The parameter "entries" shall be a n array of entries'));
if (array_key_exists('legalHeader', $data)) {
Assertion::string($data['legalHeader'], Utils::logicException('Invalid data. The parameter "legalHeader" shall be a string'));
}
$object = new self(
$data['no'],
$data['nextUpdate'],
$data['legalHeader'] ?? null
);
foreach ($data['entries'] as $k => $entry) {
$object->addEntry(MetadataTOCPayloadEntry::createFromArray($entry));
}
$object->rootCertificates = $data['rootCertificates'] ?? [];
return $object;
}
public function jsonSerialize(): array
{
$data = [
'legalHeader' => $this->legalHeader,
'nextUpdate' => $this->nextUpdate,
'no' => $this->no,
'entries' => array_map(static function (MetadataTOCPayloadEntry $object): array {
return $object->jsonSerialize();
}, $this->entries),
'rootCertificates' => $this->rootCertificates,
];
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;
}
}

View file

@ -1,188 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use Assert\Assertion;
use Base64Url\Base64Url;
use function count;
use JsonSerializable;
use LogicException;
class MetadataTOCPayloadEntry implements JsonSerializable
{
/**
* @var string|null
*/
private $aaid;
/**
* @var string|null
*/
private $aaguid;
/**
* @var string[]
*/
private $attestationCertificateKeyIdentifiers = [];
/**
* @var string|null
*/
private $hash;
/**
* @var string|null
*/
private $url;
/**
* @var StatusReport[]
*/
private $statusReports = [];
/**
* @var string
*/
private $timeOfLastStatusChange;
/**
* @var string
*/
private $rogueListURL;
/**
* @var string
*/
private $rogueListHash;
public function __construct(?string $aaid, ?string $aaguid, array $attestationCertificateKeyIdentifiers, ?string $hash, ?string $url, string $timeOfLastStatusChange, ?string $rogueListURL, ?string $rogueListHash)
{
if (null !== $aaid && null !== $aaguid) {
throw new LogicException('Authenticators cannot support both AAID and AAGUID');
}
if (null === $aaid && null === $aaguid && 0 === count($attestationCertificateKeyIdentifiers)) {
throw new LogicException('If neither AAID nor AAGUID are set, the attestation certificate identifier list shall not be empty');
}
foreach ($attestationCertificateKeyIdentifiers as $attestationCertificateKeyIdentifier) {
Assertion::string($attestationCertificateKeyIdentifier, Utils::logicException('Invalid attestation certificate identifier. Shall be a list of strings'));
Assertion::notEmpty($attestationCertificateKeyIdentifier, Utils::logicException('Invalid attestation certificate identifier. Shall be a list of strings'));
Assertion::regex($attestationCertificateKeyIdentifier, '/^[0-9a-f]+$/', Utils::logicException('Invalid attestation certificate identifier. Shall be a list of strings'));
}
$this->aaid = $aaid;
$this->aaguid = $aaguid;
$this->attestationCertificateKeyIdentifiers = $attestationCertificateKeyIdentifiers;
$this->hash = Base64Url::decode($hash);
$this->url = $url;
$this->timeOfLastStatusChange = $timeOfLastStatusChange;
$this->rogueListURL = $rogueListURL;
$this->rogueListHash = $rogueListHash;
}
public function getAaid(): ?string
{
return $this->aaid;
}
public function getAaguid(): ?string
{
return $this->aaguid;
}
public function getAttestationCertificateKeyIdentifiers(): array
{
return $this->attestationCertificateKeyIdentifiers;
}
public function getHash(): ?string
{
return $this->hash;
}
public function getUrl(): ?string
{
return $this->url;
}
public function addStatusReports(StatusReport $statusReport): self
{
$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
{
return $this->rogueListURL;
}
public function getRogueListHash(): string
{
return $this->rogueListHash;
}
public static function createFromArray(array $data): self
{
$data = Utils::filterNullValues($data);
Assertion::keyExists($data, 'timeOfLastStatusChange', Utils::logicException('Invalid data. The parameter "timeOfLastStatusChange" is missing'));
Assertion::keyExists($data, 'statusReports', Utils::logicException('Invalid data. The parameter "statusReports" is missing'));
Assertion::isArray($data['statusReports'], Utils::logicException('Invalid data. The parameter "statusReports" shall be an array of StatusReport objects'));
$object = new self(
$data['aaid'] ?? null,
$data['aaguid'] ?? null,
$data['attestationCertificateKeyIdentifiers'] ?? [],
$data['hash'] ?? null,
$data['url'] ?? null,
$data['timeOfLastStatusChange'],
$data['rogueListURL'] ?? null,
$data['rogueListHash'] ?? null
);
foreach ($data['statusReports'] as $statusReport) {
$object->addStatusReports(StatusReport::createFromArray($statusReport));
}
return $object;
}
public function jsonSerialize(): array
{
$data = [
'aaid' => $this->aaid,
'aaguid' => $this->aaguid,
'attestationCertificateKeyIdentifiers' => $this->attestationCertificateKeyIdentifiers,
'hash' => Base64Url::encode($this->hash),
'url' => $this->url,
'statusReports' => array_map(static function (StatusReport $object): array {
return $object->jsonSerialize();
}, $this->statusReports),
'timeOfLastStatusChange' => $this->timeOfLastStatusChange,
'rogueListURL' => $this->rogueListURL,
'rogueListHash' => $this->rogueListHash,
];
return Utils::filterNullValues($data);
}
}

View file

@ -1,66 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use function array_key_exists;
use Assert\Assertion;
use function Safe\sprintf;
class PatternAccuracyDescriptor extends AbstractDescriptor
{
/**
* @var int
*/
private $minComplexity;
public function __construct(int $minComplexity, ?int $maxRetries = null, ?int $blockSlowdown = null)
{
Assertion::greaterOrEqualThan($minComplexity, 0, Utils::logicException('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;
}
public static function createFromArray(array $data): self
{
$data = Utils::filterNullValues($data);
Assertion::keyExists($data, 'minComplexity', Utils::logicException('The key "minComplexity" is missing'));
foreach (['minComplexity', 'maxRetries', 'blockSlowdown'] as $key) {
if (array_key_exists($key, $data)) {
Assertion::integer($data[$key], Utils::logicException(sprintf('Invalid data. The value of "%s" must be a positive integer', $key)));
}
}
return new self(
$data['minComplexity'],
$data['maxRetries'] ?? null,
$data['blockSlowdown'] ?? null
);
}
public function jsonSerialize(): array
{
$data = [
'minComplexity' => $this->minComplexity,
'maxRetries' => $this->getMaxRetries(),
'blockSlowdown' => $this->getBlockSlowdown(),
];
return Utils::filterNullValues($data);
}
}

View file

@ -1,84 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use Assert\Assertion;
use JsonSerializable;
use function Safe\sprintf;
class RgbPaletteEntry implements JsonSerializable
{
/**
* @var int
*/
private $r;
/**
* @var int
*/
private $g;
/**
* @var int
*/
private $b;
public function __construct(int $r, int $g, int $b)
{
Assertion::range($r, 0, 255, Utils::logicException('The key "r" is invalid'));
Assertion::range($g, 0, 255, Utils::logicException('The key "g" is invalid'));
Assertion::range($b, 0, 255, Utils::logicException('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;
}
public static function createFromArray(array $data): self
{
foreach (['r', 'g', 'b'] as $key) {
Assertion::keyExists($data, $key, sprintf('The key "%s" is missing', $key));
Assertion::integer($data[$key], sprintf('The key "%s" is invalid', $key));
}
return new self(
$data['r'],
$data['g'],
$data['b']
);
}
public function jsonSerialize(): array
{
return [
'r' => $this->r,
'g' => $this->g,
'b' => $this->b,
];
}
}

View file

@ -1,67 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use Assert\Assertion;
use JsonSerializable;
class RogueListEntry implements JsonSerializable
{
/**
* @var string
*/
private $sk;
/**
* @var string
*/
private $date;
public function __construct(string $sk, string $date)
{
$this->sk = $sk;
$this->date = $date;
}
public function getSk(): string
{
return $this->sk;
}
public function getDate(): ?string
{
return $this->date;
}
public static function createFromArray(array $data): self
{
Assertion::keyExists($data, 'sk', 'The key "sk" is missing');
Assertion::string($data['sk'], 'The key "sk" is invalid');
Assertion::keyExists($data, 'date', 'The key "date" is missing');
Assertion::string($data['date'], 'The key "date" is invalid');
return new self(
$data['sk'],
$data['date']
);
}
public function jsonSerialize(): array
{
return [
'sk' => $this->sk,
'date' => $this->date,
];
}
}

View file

@ -1,53 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use function Safe\base64_decode;
use function Safe\json_decode;
class SingleMetadata
{
/**
* @var MetadataStatement
*/
private $statement;
/**
* @var string
*/
private $data;
/**
* @var bool
*/
private $isBase64Encoded;
public function __construct(string $data, bool $isBase64Encoded)
{
$this->data = $data;
$this->isBase64Encoded = $isBase64Encoded;
}
public function getMetadataStatement(): MetadataStatement
{
if (null === $this->statement) {
$json = $this->data;
if ($this->isBase64Encoded) {
$json = base64_decode($this->data, true);
}
$statement = json_decode($json, true);
$this->statement = MetadataStatement::createFromArray($statement);
}
return $this->statement;
}
}

View file

@ -1,166 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use Assert\Assertion;
use function in_array;
use JsonSerializable;
use function Safe\sprintf;
class StatusReport implements JsonSerializable
{
/**
* @var string
*
* @see AuthenticatorStatus
*/
private $status;
/**
* @var string|null
*/
private $effectiveDate;
/**
* @var string|null
*/
private $certificate;
/**
* @var string|null
*/
private $url;
/**
* @var string|null
*/
private $certificationDescriptor;
/**
* @var string|null
*/
private $certificateNumber;
/**
* @var string|null
*/
private $certificationPolicyVersion;
/**
* @var string|null
*/
private $certificationRequirementsVersion;
public function __construct(string $status, ?string $effectiveDate, ?string $certificate, ?string $url, ?string $certificationDescriptor, ?string $certificateNumber, ?string $certificationPolicyVersion, ?string $certificationRequirementsVersion)
{
Assertion::inArray($status, AuthenticatorStatus::list(), Utils::logicException('The value of the key "status" is not acceptable'));
$this->status = $status;
$this->effectiveDate = $effectiveDate;
$this->certificate = $certificate;
$this->url = $url;
$this->certificationDescriptor = $certificationDescriptor;
$this->certificateNumber = $certificateNumber;
$this->certificationPolicyVersion = $certificationPolicyVersion;
$this->certificationRequirementsVersion = $certificationRequirementsVersion;
}
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;
}
public static function createFromArray(array $data): self
{
$data = Utils::filterNullValues($data);
Assertion::keyExists($data, 'status', Utils::logicException('The key "status" is missing'));
foreach (['effectiveDate', 'certificate', 'url', 'certificationDescriptor', 'certificateNumber', 'certificationPolicyVersion', 'certificationRequirementsVersion'] as $key) {
if (isset($data[$key])) {
Assertion::nullOrString($data[$key], Utils::logicException(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
);
}
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);
}
}

View file

@ -1,35 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use LogicException;
use Throwable;
/**
* @internal
*/
abstract class Utils
{
public static function logicException(string $message, ?Throwable $previousException = null): callable
{
return static function () use ($message, $previousException): LogicException {
return new LogicException($message, 0, $previousException);
};
}
public static function filterNullValues(array $data): array
{
return array_filter($data, static function ($var): bool {return null !== $var; });
}
}

View file

@ -1,59 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use Assert\Assertion;
use JsonSerializable;
class VerificationMethodANDCombinations implements JsonSerializable
{
/**
* @var VerificationMethodDescriptor[]
*/
private $verificationMethods = [];
public function addVerificationMethodDescriptor(VerificationMethodDescriptor $verificationMethodDescriptor): self
{
$this->verificationMethods[] = $verificationMethodDescriptor;
return $this;
}
/**
* @return VerificationMethodDescriptor[]
*/
public function getVerificationMethods(): array
{
return $this->verificationMethods;
}
public static function createFromArray(array $data): self
{
$object = new self();
foreach ($data as $datum) {
Assertion::isArray($datum, Utils::logicException('Invalid data'));
$object->addVerificationMethodDescriptor(VerificationMethodDescriptor::createFromArray($datum));
}
return $object;
}
public function jsonSerialize(): array
{
return array_map(static function (VerificationMethodDescriptor $object): array {
return $object->jsonSerialize();
}, $this->verificationMethods);
}
}

View file

@ -1,168 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use Assert\Assertion;
use JsonSerializable;
use function Safe\sprintf;
class VerificationMethodDescriptor implements JsonSerializable
{
public const USER_VERIFY_PRESENCE = 0x00000001;
public const USER_VERIFY_FINGERPRINT = 0x00000002;
public const USER_VERIFY_PASSCODE = 0x00000004;
public const USER_VERIFY_VOICEPRINT = 0x00000008;
public const USER_VERIFY_FACEPRINT = 0x00000010;
public const USER_VERIFY_LOCATION = 0x00000020;
public const USER_VERIFY_EYEPRINT = 0x00000040;
public const USER_VERIFY_PATTERN = 0x00000080;
public const USER_VERIFY_HANDPRINT = 0x00000100;
public const USER_VERIFY_NONE = 0x00000200;
public const USER_VERIFY_ALL = 0x00000400;
/**
* @var int
*/
private $userVerification;
/**
* @var CodeAccuracyDescriptor|null
*/
private $caDesc;
/**
* @var BiometricAccuracyDescriptor|null
*/
private $baDesc;
/**
* @var PatternAccuracyDescriptor|null
*/
private $paDesc;
public function __construct(int $userVerification, ?CodeAccuracyDescriptor $caDesc = null, ?BiometricAccuracyDescriptor $baDesc = null, ?PatternAccuracyDescriptor $paDesc = null)
{
Assertion::greaterOrEqualThan($userVerification, 0, Utils::logicException('The parameter "userVerification" is invalid'));
$this->userVerification = $userVerification;
$this->caDesc = $caDesc;
$this->baDesc = $baDesc;
$this->paDesc = $paDesc;
}
public function getUserVerification(): int
{
return $this->userVerification;
}
public function userPresence(): bool
{
return 0 !== ($this->userVerification & self::USER_VERIFY_PRESENCE);
}
public function fingerprint(): bool
{
return 0 !== ($this->userVerification & self::USER_VERIFY_FINGERPRINT);
}
public function passcode(): bool
{
return 0 !== ($this->userVerification & self::USER_VERIFY_PASSCODE);
}
public function voicePrint(): bool
{
return 0 !== ($this->userVerification & self::USER_VERIFY_VOICEPRINT);
}
public function facePrint(): bool
{
return 0 !== ($this->userVerification & self::USER_VERIFY_FACEPRINT);
}
public function location(): bool
{
return 0 !== ($this->userVerification & self::USER_VERIFY_LOCATION);
}
public function eyePrint(): bool
{
return 0 !== ($this->userVerification & self::USER_VERIFY_EYEPRINT);
}
public function pattern(): bool
{
return 0 !== ($this->userVerification & self::USER_VERIFY_PATTERN);
}
public function handprint(): bool
{
return 0 !== ($this->userVerification & self::USER_VERIFY_HANDPRINT);
}
public function none(): bool
{
return 0 !== ($this->userVerification & self::USER_VERIFY_NONE);
}
public function all(): bool
{
return 0 !== ($this->userVerification & 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;
}
public static function createFromArray(array $data): self
{
$data = Utils::filterNullValues($data);
Assertion::keyExists($data, 'userVerification', Utils::logicException('The parameter "userVerification" is missing'));
Assertion::integer($data['userVerification'], Utils::logicException('The parameter "userVerification" is invalid'));
foreach (['caDesc', 'baDesc', 'paDesc'] as $key) {
if (isset($data[$key])) {
Assertion::isArray($data[$key], Utils::logicException(sprintf('Invalid parameter "%s"', $key)));
}
}
return new self(
$data['userVerification'],
isset($data['caDesc']) ? CodeAccuracyDescriptor::createFromArray($data['caDesc']) : null,
isset($data['baDesc']) ? BiometricAccuracyDescriptor::createFromArray($data['baDesc']) : null,
isset($data['paDesc']) ? PatternAccuracyDescriptor::createFromArray($data['paDesc']) : null
);
}
public function jsonSerialize(): array
{
$data = [
'userVerification' => $this->userVerification,
'caDesc' => null === $this->caDesc ? null : $this->caDesc->jsonSerialize(),
'baDesc' => null === $this->baDesc ? null : $this->baDesc->jsonSerialize(),
'paDesc' => null === $this->paDesc ? null : $this->paDesc->jsonSerialize(),
];
return Utils::filterNullValues($data);
}
}

View file

@ -1,80 +0,0 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\MetadataService;
use function array_key_exists;
use Assert\Assertion;
use JsonSerializable;
use LogicException;
use function Safe\sprintf;
class Version implements JsonSerializable
{
/**
* @var int|null
*/
private $major;
/**
* @var int|null
*/
private $minor;
public function __construct(?int $major, ?int $minor)
{
if (null === $major && null === $minor) {
throw new LogicException('Invalid data. Must contain at least one item');
}
Assertion::greaterOrEqualThan($major, 0, Utils::logicException('Invalid argument "major"'));
Assertion::greaterOrEqualThan($minor, 0, Utils::logicException('Invalid argument "minor"'));
$this->major = $major;
$this->minor = $minor;
}
public function getMajor(): ?int
{
return $this->major;
}
public function getMinor(): ?int
{
return $this->minor;
}
public static function createFromArray(array $data): self
{
$data = Utils::filterNullValues($data);
foreach (['major', 'minor'] as $key) {
if (array_key_exists($key, $data)) {
Assertion::integer($data[$key], sprintf('Invalid value for key "%s"', $key));
}
}
return new self(
$data['major'] ?? null,
$data['minor'] ?? null
);
}
public function jsonSerialize(): array
{
$data = [
'major' => $this->major,
'minor' => $this->minor,
];
return Utils::filterNullValues($data);
}
}