Update website
This commit is contained in:
parent
41ce1aa076
commit
ea0eb1c6e0
4222 changed files with 721797 additions and 14 deletions
21
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/LICENSE
vendored
Normal file
21
admin/phpMyAdmin/vendor/web-auth/webauthn-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.
|
49
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/composer.json
vendored
Normal file
49
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/composer.json
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"name": "web-auth/webauthn-lib",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"description": "FIDO2/Webauthn Support For PHP",
|
||||
"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/webauthn-library/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.2",
|
||||
"ext-json": "*",
|
||||
"ext-openssl": "*",
|
||||
"ext-mbstring": "*",
|
||||
"beberlei/assert": "^3.2",
|
||||
"fgrosse/phpasn1": "^2.1",
|
||||
"psr/http-client": "^1.0",
|
||||
"psr/http-factory": "^1.0",
|
||||
"psr/http-message": "^1.0",
|
||||
"psr/log": "^1.1",
|
||||
"ramsey/uuid": "^3.8|^4.0",
|
||||
"spomky-labs/base64url": "^2.0",
|
||||
"spomky-labs/cbor-php": "^1.0|^2.0",
|
||||
"symfony/process": "^3.0|^4.0|^5.0",
|
||||
"thecodingmachine/safe": "^1.1",
|
||||
"web-auth/cose-lib": "self.version",
|
||||
"web-auth/metadata-service": "self.version"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Webauthn\\": "src/"
|
||||
}
|
||||
},
|
||||
"suggest": {
|
||||
"psr/log-implementation": "Recommended to receive logs from the library",
|
||||
"web-token/jwt-key-mgmt": "Mandatory for the AndroidSafetyNet Attestation Statement support",
|
||||
"web-token/jwt-signature-algorithm-rsa": "Mandatory for the AndroidSafetyNet Attestation Statement support",
|
||||
"web-token/jwt-signature-algorithm-ecdsa": "Recommended for the AndroidSafetyNet Attestation Statement support",
|
||||
"web-token/jwt-signature-algorithm-eddsa": "Recommended for the AndroidSafetyNet Attestation Statement support"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
<?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\AttestationStatement;
|
||||
|
||||
use Assert\Assertion;
|
||||
use CBOR\Decoder;
|
||||
use CBOR\OtherObject\OtherObjectManager;
|
||||
use CBOR\Tag\TagObjectManager;
|
||||
use Cose\Algorithms;
|
||||
use Cose\Key\Ec2Key;
|
||||
use Cose\Key\Key;
|
||||
use Cose\Key\RsaKey;
|
||||
use function count;
|
||||
use FG\ASN1\ASNObject;
|
||||
use FG\ASN1\ExplicitlyTaggedObject;
|
||||
use FG\ASN1\Universal\OctetString;
|
||||
use FG\ASN1\Universal\Sequence;
|
||||
use function Safe\hex2bin;
|
||||
use function Safe\openssl_pkey_get_public;
|
||||
use function Safe\sprintf;
|
||||
use Webauthn\AuthenticatorData;
|
||||
use Webauthn\CertificateToolbox;
|
||||
use Webauthn\StringStream;
|
||||
use Webauthn\TrustPath\CertificateTrustPath;
|
||||
|
||||
final class AndroidKeyAttestationStatementSupport implements AttestationStatementSupport
|
||||
{
|
||||
/**
|
||||
* @var Decoder
|
||||
*/
|
||||
private $decoder;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager());
|
||||
}
|
||||
|
||||
public function name(): string
|
||||
{
|
||||
return 'android-key';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $attestation
|
||||
*/
|
||||
public function load(array $attestation): AttestationStatement
|
||||
{
|
||||
Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object');
|
||||
foreach (['sig', 'x5c', 'alg'] as $key) {
|
||||
Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key));
|
||||
}
|
||||
$certificates = $attestation['attStmt']['x5c'];
|
||||
Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.');
|
||||
Assertion::greaterThan(count($certificates), 0, 'The attestation statement value "x5c" must be a list with at least one certificate.');
|
||||
Assertion::allString($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.');
|
||||
$certificates = CertificateToolbox::convertAllDERToPEM($certificates);
|
||||
|
||||
return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates));
|
||||
}
|
||||
|
||||
public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
|
||||
{
|
||||
$trustPath = $attestationStatement->getTrustPath();
|
||||
Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path');
|
||||
|
||||
$certificates = $trustPath->getCertificates();
|
||||
|
||||
//Decode leaf attestation certificate
|
||||
$leaf = $certificates[0];
|
||||
$this->checkCertificateAndGetPublicKey($leaf, $clientDataJSONHash, $authenticatorData);
|
||||
|
||||
$signedData = $authenticatorData->getAuthData().$clientDataJSONHash;
|
||||
$alg = $attestationStatement->get('alg');
|
||||
|
||||
return 1 === openssl_verify($signedData, $attestationStatement->get('sig'), $leaf, Algorithms::getOpensslAlgorithmFor((int) $alg));
|
||||
}
|
||||
|
||||
private function checkCertificateAndGetPublicKey(string $certificate, string $clientDataHash, AuthenticatorData $authenticatorData): void
|
||||
{
|
||||
$resource = openssl_pkey_get_public($certificate);
|
||||
$details = openssl_pkey_get_details($resource);
|
||||
Assertion::isArray($details, 'Unable to read the certificate');
|
||||
|
||||
//Check that authData publicKey matches the public key in the attestation certificate
|
||||
$attestedCredentialData = $authenticatorData->getAttestedCredentialData();
|
||||
Assertion::notNull($attestedCredentialData, 'No attested credential data found');
|
||||
$publicKeyData = $attestedCredentialData->getCredentialPublicKey();
|
||||
Assertion::notNull($publicKeyData, 'No attested public key found');
|
||||
$publicDataStream = new StringStream($publicKeyData);
|
||||
$coseKey = $this->decoder->decode($publicDataStream)->getNormalizedData(false);
|
||||
Assertion::true($publicDataStream->isEOF(), 'Invalid public key data. Presence of extra bytes.');
|
||||
$publicDataStream->close();
|
||||
$publicKey = Key::createFromData($coseKey);
|
||||
|
||||
Assertion::true(($publicKey instanceof Ec2Key) || ($publicKey instanceof RsaKey), 'Unsupported key type');
|
||||
Assertion::eq($publicKey->asPEM(), $details['key'], '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
|
||||
Assertion::isArray($certDetails, 'The certificate is not valid');
|
||||
Assertion::keyExists($certDetails, 'extensions', 'The certificate has no extension');
|
||||
Assertion::isArray($certDetails['extensions'], 'The certificate has no extension');
|
||||
Assertion::keyExists($certDetails['extensions'], '1.3.6.1.4.1.11129.2.1.17', '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 = ASNObject::fromBinary($extension);
|
||||
Assertion::isInstanceOf($extensionAsAsn1, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid');
|
||||
$objects = $extensionAsAsn1->getChildren();
|
||||
|
||||
//Check that attestationChallenge is set to the clientDataHash.
|
||||
Assertion::keyExists($objects, 4, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid');
|
||||
Assertion::isInstanceOf($objects[4], OctetString::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid');
|
||||
Assertion::eq($clientDataHash, hex2bin(($objects[4])->getContent()), 'The client data hash is not valid');
|
||||
|
||||
//Check that both teeEnforced and softwareEnforced structures don’t contain allApplications(600) tag.
|
||||
Assertion::keyExists($objects, 6, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid');
|
||||
$softwareEnforcedFlags = $objects[6];
|
||||
Assertion::isInstanceOf($softwareEnforcedFlags, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid');
|
||||
$this->checkAbsenceOfAllApplicationsTag($softwareEnforcedFlags);
|
||||
|
||||
Assertion::keyExists($objects, 7, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid');
|
||||
$teeEnforcedFlags = $objects[6];
|
||||
Assertion::isInstanceOf($teeEnforcedFlags, Sequence::class, '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->getChildren() as $tag) {
|
||||
Assertion::isInstanceOf($tag, ExplicitlyTaggedObject::class, 'Invalid tag');
|
||||
/* @var ExplicitlyTaggedObject $tag */
|
||||
Assertion::notEq(600, (int) $tag->getTag(), 'Forbidden tag 600 found');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
<?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\AttestationStatement;
|
||||
|
||||
use Assert\Assertion;
|
||||
use InvalidArgumentException;
|
||||
use Jose\Component\Core\Algorithm as AlgorithmInterface;
|
||||
use Jose\Component\Core\AlgorithmManager;
|
||||
use Jose\Component\Core\Util\JsonConverter;
|
||||
use Jose\Component\KeyManagement\JWKFactory;
|
||||
use Jose\Component\Signature\Algorithm;
|
||||
use Jose\Component\Signature\JWS;
|
||||
use Jose\Component\Signature\JWSVerifier;
|
||||
use Jose\Component\Signature\Serializer\CompactSerializer;
|
||||
use Psr\Http\Client\ClientInterface;
|
||||
use Psr\Http\Message\RequestFactoryInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use RuntimeException;
|
||||
use function Safe\json_decode;
|
||||
use function Safe\sprintf;
|
||||
use Webauthn\AuthenticatorData;
|
||||
use Webauthn\CertificateToolbox;
|
||||
use Webauthn\TrustPath\CertificateTrustPath;
|
||||
|
||||
final class AndroidSafetyNetAttestationStatementSupport implements AttestationStatementSupport
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $apiKey;
|
||||
|
||||
/**
|
||||
* @var ClientInterface|null
|
||||
*/
|
||||
private $client;
|
||||
|
||||
/**
|
||||
* @var CompactSerializer
|
||||
*/
|
||||
private $jwsSerializer;
|
||||
|
||||
/**
|
||||
* @var JWSVerifier|null
|
||||
*/
|
||||
private $jwsVerifier;
|
||||
|
||||
/**
|
||||
* @var RequestFactoryInterface|null
|
||||
*/
|
||||
private $requestFactory;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $leeway;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $maxAge;
|
||||
|
||||
public function __construct(?ClientInterface $client = null, ?string $apiKey = null, ?RequestFactoryInterface $requestFactory = null, ?int $leeway = null, ?int $maxAge = null)
|
||||
{
|
||||
if (!class_exists(Algorithm\RS256::class)) {
|
||||
throw new RuntimeException('The algorithm RS256 is missing. Did you forget to install the package web-token/jwt-signature-algorithm-rsa?');
|
||||
}
|
||||
if (!class_exists(JWKFactory::class)) {
|
||||
throw new RuntimeException('The class Jose\Component\KeyManagement\JWKFactory is missing. Did you forget to install the package web-token/jwt-key-mgmt?');
|
||||
}
|
||||
if (null !== $client) {
|
||||
@trigger_error('The argument "client" is deprecated since version 3.3 and will be removed in 4.0. Please set `null` instead and use the method "enableApiVerification".', E_USER_DEPRECATED);
|
||||
}
|
||||
if (null !== $apiKey) {
|
||||
@trigger_error('The argument "apiKey" is deprecated since version 3.3 and will be removed in 4.0. Please set `null` instead and use the method "enableApiVerification".', E_USER_DEPRECATED);
|
||||
}
|
||||
if (null !== $requestFactory) {
|
||||
@trigger_error('The argument "requestFactory" is deprecated since version 3.3 and will be removed in 4.0. Please set `null` instead and use the method "enableApiVerification".', E_USER_DEPRECATED);
|
||||
}
|
||||
if (null !== $maxAge) {
|
||||
@trigger_error('The argument "maxAge" is deprecated since version 3.3 and will be removed in 4.0. Please set `null` instead and use the method "setMaxAge".', E_USER_DEPRECATED);
|
||||
}
|
||||
if (null !== $leeway) {
|
||||
@trigger_error('The argument "leeway" is deprecated since version 3.3 and will be removed in 4.0. Please set `null` instead and use the method "setLeeway".', E_USER_DEPRECATED);
|
||||
}
|
||||
$this->jwsSerializer = new CompactSerializer();
|
||||
$this->initJwsVerifier();
|
||||
|
||||
//To be removed in 4.0
|
||||
$this->leeway = $leeway ?? 0;
|
||||
$this->maxAge = $maxAge ?? 60000;
|
||||
$this->apiKey = $apiKey;
|
||||
$this->client = $client;
|
||||
$this->requestFactory = $requestFactory;
|
||||
}
|
||||
|
||||
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 mixed[] $attestation
|
||||
*/
|
||||
public function load(array $attestation): AttestationStatement
|
||||
{
|
||||
Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object');
|
||||
foreach (['ver', 'response'] as $key) {
|
||||
Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key));
|
||||
Assertion::notEmpty($attestation['attStmt'][$key], sprintf('The attestation statement value "%s" is empty.', $key));
|
||||
}
|
||||
$jws = $this->jwsSerializer->unserialize($attestation['attStmt']['response']);
|
||||
$jwsHeader = $jws->getSignature(0)->getProtectedHeader();
|
||||
Assertion::keyExists($jwsHeader, 'x5c', 'The response in the attestation statement must contain a "x5c" header.');
|
||||
Assertion::notEmpty($jwsHeader['x5c'], 'The "x5c" parameter in the attestation statement response must contain at least one certificate.');
|
||||
$certificates = $this->convertCertificatesToPem($jwsHeader['x5c']);
|
||||
$attestation['attStmt']['jws'] = $jws;
|
||||
|
||||
return AttestationStatement::createBasic(
|
||||
$this->name(),
|
||||
$attestation['attStmt'],
|
||||
new CertificateTrustPath($certificates)
|
||||
);
|
||||
}
|
||||
|
||||
public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
|
||||
{
|
||||
$trustPath = $attestationStatement->getTrustPath();
|
||||
Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path');
|
||||
$certificates = $trustPath->getCertificates();
|
||||
$firstCertificate = current($certificates);
|
||||
Assertion::string($firstCertificate, 'No certificate');
|
||||
|
||||
$parsedCertificate = openssl_x509_parse($firstCertificate);
|
||||
Assertion::isArray($parsedCertificate, 'Invalid attestation object');
|
||||
Assertion::keyExists($parsedCertificate, 'subject', 'Invalid attestation object');
|
||||
Assertion::keyExists($parsedCertificate['subject'], 'CN', 'Invalid attestation object');
|
||||
Assertion::eq($parsedCertificate['subject']['CN'], 'attest.android.com', '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
|
||||
{
|
||||
Assertion::notNull($payload, 'Invalid attestation object');
|
||||
$payload = JsonConverter::decode($payload);
|
||||
Assertion::isArray($payload, 'Invalid attestation object');
|
||||
Assertion::keyExists($payload, 'nonce', 'Invalid attestation object. "nonce" is missing.');
|
||||
Assertion::eq($payload['nonce'], base64_encode(hash('sha256', $authenticatorData->getAuthData().$clientDataJSONHash, true)), 'Invalid attestation object. Invalid nonce');
|
||||
Assertion::keyExists($payload, 'ctsProfileMatch', 'Invalid attestation object. "ctsProfileMatch" is missing.');
|
||||
Assertion::true($payload['ctsProfileMatch'], 'Invalid attestation object. "ctsProfileMatch" value is false.');
|
||||
Assertion::keyExists($payload, 'timestampMs', 'Invalid attestation object. Timestamp is missing.');
|
||||
Assertion::integer($payload['timestampMs'], 'Invalid attestation object. Timestamp shall be an integer.');
|
||||
$currentTime = time() * 1000;
|
||||
Assertion::lessOrEqualThan($payload['timestampMs'], $currentTime + $this->leeway, sprintf('Invalid attestation object. Issued in the future. Current time: %d. Response time: %d', $currentTime, $payload['timestampMs']));
|
||||
Assertion::lessOrEqualThan($currentTime - $payload['timestampMs'], $this->maxAge, 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);
|
||||
Assertion::true($isValid, 'Invalid response signature');
|
||||
}
|
||||
|
||||
private function validateUsingGoogleApi(AttestationStatement $attestationStatement): void
|
||||
{
|
||||
if (null === $this->client || null === $this->apiKey || null === $this->requestFactory) {
|
||||
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);
|
||||
Assertion::keyExists($responseBodyJson, 'isValidSignature', 'Invalid response.');
|
||||
Assertion::boolean($responseBodyJson['isValidSignature'], 'Invalid response.');
|
||||
Assertion::true($responseBodyJson['isValidSignature'], 'Invalid response.');
|
||||
}
|
||||
|
||||
private function getResponseBody(ResponseInterface $response): string
|
||||
{
|
||||
$responseBody = '';
|
||||
$response->getBody()->rewind();
|
||||
while (true) {
|
||||
$tmp = $response->getBody()->read(1024);
|
||||
if ('' === $tmp) {
|
||||
break;
|
||||
}
|
||||
$responseBody .= $tmp;
|
||||
}
|
||||
|
||||
return $responseBody;
|
||||
}
|
||||
|
||||
private function checkGoogleApiResponse(ResponseInterface $response): void
|
||||
{
|
||||
Assertion::eq(200, $response->getStatusCode(), 'Request did not succeeded');
|
||||
Assertion::true($response->hasHeader('content-type'), 'Unrecognized response');
|
||||
|
||||
foreach ($response->getHeader('content-type') as $header) {
|
||||
if (0 === mb_strpos($header, 'application/json')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('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 = [
|
||||
Algorithm\RS256::class, Algorithm\RS384::class, Algorithm\RS512::class,
|
||||
Algorithm\PS256::class, Algorithm\PS384::class, Algorithm\PS512::class,
|
||||
Algorithm\ES256::class, Algorithm\ES384::class, Algorithm\ES512::class,
|
||||
Algorithm\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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
<?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\AttestationStatement;
|
||||
|
||||
use Assert\Assertion;
|
||||
use CBOR\Decoder;
|
||||
use CBOR\OtherObject\OtherObjectManager;
|
||||
use CBOR\Tag\TagObjectManager;
|
||||
use Cose\Key\Ec2Key;
|
||||
use Cose\Key\Key;
|
||||
use Cose\Key\RsaKey;
|
||||
use function count;
|
||||
use function Safe\openssl_pkey_get_public;
|
||||
use function Safe\sprintf;
|
||||
use Webauthn\AuthenticatorData;
|
||||
use Webauthn\CertificateToolbox;
|
||||
use Webauthn\StringStream;
|
||||
use Webauthn\TrustPath\CertificateTrustPath;
|
||||
|
||||
final class AppleAttestationStatementSupport implements AttestationStatementSupport
|
||||
{
|
||||
/**
|
||||
* @var Decoder
|
||||
*/
|
||||
private $decoder;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager());
|
||||
}
|
||||
|
||||
public function name(): string
|
||||
{
|
||||
return 'apple';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $attestation
|
||||
*/
|
||||
public function load(array $attestation): AttestationStatement
|
||||
{
|
||||
Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object');
|
||||
foreach (['x5c'] as $key) {
|
||||
Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key));
|
||||
}
|
||||
$certificates = $attestation['attStmt']['x5c'];
|
||||
Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.');
|
||||
Assertion::greaterThan(count($certificates), 0, 'The attestation statement value "x5c" must be a list with at least one certificate.');
|
||||
Assertion::allString($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.');
|
||||
$certificates = CertificateToolbox::convertAllDERToPEM($certificates);
|
||||
|
||||
return AttestationStatement::createAnonymizationCA($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates));
|
||||
}
|
||||
|
||||
public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
|
||||
{
|
||||
$trustPath = $attestationStatement->getTrustPath();
|
||||
Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, '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);
|
||||
Assertion::isArray($details, 'Unable to read the certificate');
|
||||
|
||||
//Check that authData publicKey matches the public key in the attestation certificate
|
||||
$attestedCredentialData = $authenticatorData->getAttestedCredentialData();
|
||||
Assertion::notNull($attestedCredentialData, 'No attested credential data found');
|
||||
$publicKeyData = $attestedCredentialData->getCredentialPublicKey();
|
||||
Assertion::notNull($publicKeyData, 'No attested public key found');
|
||||
$publicDataStream = new StringStream($publicKeyData);
|
||||
$coseKey = $this->decoder->decode($publicDataStream)->getNormalizedData(false);
|
||||
Assertion::true($publicDataStream->isEOF(), 'Invalid public key data. Presence of extra bytes.');
|
||||
$publicDataStream->close();
|
||||
$publicKey = Key::createFromData($coseKey);
|
||||
|
||||
Assertion::true(($publicKey instanceof Ec2Key) || ($publicKey instanceof RsaKey), 'Unsupported key type');
|
||||
|
||||
//We check the attested key corresponds to the key in the certificate
|
||||
Assertion::eq($publicKey->asPEM(), $details['key'], 'Invalid key');
|
||||
|
||||
/*---------------------------*/
|
||||
$certDetails = openssl_x509_parse($certificate);
|
||||
|
||||
//Find Apple Extension with OID “1.2.840.113635.100.8.2” in certificate extensions
|
||||
Assertion::isArray($certDetails, 'The certificate is not valid');
|
||||
Assertion::keyExists($certDetails, 'extensions', 'The certificate has no extension');
|
||||
Assertion::isArray($certDetails['extensions'], 'The certificate has no extension');
|
||||
Assertion::keyExists($certDetails['extensions'], '1.2.840.113635.100.8.2', '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
|
||||
Assertion::eq('3024a1220420'.$nonce, bin2hex($extension), 'The client data hash is not valid');
|
||||
}
|
||||
}
|
81
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AttestationStatement/AttestationObject.php
vendored
Normal file
81
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AttestationStatement/AttestationObject.php
vendored
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?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\AttestationStatement;
|
||||
|
||||
use Webauthn\AuthenticatorData;
|
||||
use Webauthn\MetadataService\MetadataStatement;
|
||||
|
||||
class AttestationObject
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $rawAttestationObject;
|
||||
/**
|
||||
* @var AttestationStatement
|
||||
*/
|
||||
private $attStmt;
|
||||
/**
|
||||
* @var AuthenticatorData
|
||||
*/
|
||||
private $authData;
|
||||
|
||||
/**
|
||||
* @var MetadataStatement|null
|
||||
*/
|
||||
private $metadataStatement;
|
||||
|
||||
public function __construct(string $rawAttestationObject, AttestationStatement $attStmt, AuthenticatorData $authData, ?MetadataStatement $metadataStatement = null)
|
||||
{
|
||||
if (null !== $metadataStatement) {
|
||||
@trigger_error('The argument "metadataStatement" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setMetadataStatement".', E_USER_DEPRECATED);
|
||||
}
|
||||
$this->rawAttestationObject = $rawAttestationObject;
|
||||
$this->attStmt = $attStmt;
|
||||
$this->authData = $authData;
|
||||
$this->metadataStatement = $metadataStatement;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
148
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AttestationStatement/AttestationObjectLoader.php
vendored
Normal file
148
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AttestationStatement/AttestationObjectLoader.php
vendored
Normal file
|
@ -0,0 +1,148 @@
|
|||
<?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\AttestationStatement;
|
||||
|
||||
use Assert\Assertion;
|
||||
use Base64Url\Base64Url;
|
||||
use CBOR\Decoder;
|
||||
use CBOR\MapObject;
|
||||
use CBOR\OtherObject\OtherObjectManager;
|
||||
use CBOR\Tag\TagObjectManager;
|
||||
use function ord;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use function Safe\sprintf;
|
||||
use function Safe\unpack;
|
||||
use Throwable;
|
||||
use Webauthn\AttestedCredentialData;
|
||||
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputsLoader;
|
||||
use Webauthn\AuthenticatorData;
|
||||
use Webauthn\MetadataService\MetadataStatementRepository;
|
||||
use Webauthn\StringStream;
|
||||
|
||||
class AttestationObjectLoader
|
||||
{
|
||||
private const FLAG_AT = 0b01000000;
|
||||
private const FLAG_ED = 0b10000000;
|
||||
|
||||
/**
|
||||
* @var Decoder
|
||||
*/
|
||||
private $decoder;
|
||||
|
||||
/**
|
||||
* @var AttestationStatementSupportManager
|
||||
*/
|
||||
private $attestationStatementSupportManager;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface|null
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
public function __construct(AttestationStatementSupportManager $attestationStatementSupportManager, ?MetadataStatementRepository $metadataStatementRepository = null, ?LoggerInterface $logger = null)
|
||||
{
|
||||
if (null !== $metadataStatementRepository) {
|
||||
@trigger_error('The argument "metadataStatementRepository" is deprecated since version 3.2 and will be removed in 4.0. Please set `null` instead.', 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->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager());
|
||||
$this->attestationStatementSupportManager = $attestationStatementSupportManager;
|
||||
$this->logger = $logger ?? new NullLogger();
|
||||
}
|
||||
|
||||
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 = Base64Url::decode($data);
|
||||
$stream = new StringStream($decodedData);
|
||||
$parsed = $this->decoder->decode($stream);
|
||||
|
||||
$this->logger->info('Loading the Attestation Statement');
|
||||
$attestationObject = $parsed->getNormalizedData();
|
||||
Assertion::true($stream->isEOF(), 'Invalid attestation object. Presence of extra bytes.');
|
||||
$stream->close();
|
||||
Assertion::isArray($attestationObject, 'Invalid attestation object');
|
||||
Assertion::keyExists($attestationObject, 'authData', 'Invalid attestation object');
|
||||
Assertion::keyExists($attestationObject, 'fmt', 'Invalid attestation object');
|
||||
Assertion::keyExists($attestationObject, 'attStmt', '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)[1];
|
||||
$this->logger->debug(sprintf('Signature counter: %d', $signCount));
|
||||
|
||||
$attestedCredentialData = null;
|
||||
if (0 !== (ord($flags) & self::FLAG_AT)) {
|
||||
$this->logger->info('Attested Credential Data is present');
|
||||
$aaguid = Uuid::fromBytes($authDataStream->read(16));
|
||||
$credentialLength = $authDataStream->read(2);
|
||||
$credentialLength = unpack('n', $credentialLength)[1];
|
||||
$credentialId = $authDataStream->read($credentialLength);
|
||||
$credentialPublicKey = $this->decoder->decode($authDataStream);
|
||||
Assertion::isInstanceOf($credentialPublicKey, MapObject::class, '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]);
|
||||
}
|
||||
Assertion::true($authDataStream->isEOF(), 'Invalid authentication data. Presence of extra bytes.');
|
||||
$authDataStream->close();
|
||||
|
||||
$authenticatorData = new AuthenticatorData($authData, $rp_id_hash, $flags, $signCount, $attestedCredentialData, $extension);
|
||||
$attestationObject = new AttestationObject($data, $attestationStatement, $authenticatorData);
|
||||
$this->logger->info('Attestation Object loaded');
|
||||
$this->logger->debug('Attestation Object', ['ed' => $attestationObject]);
|
||||
|
||||
return $attestationObject;
|
||||
} catch (Throwable $throwable) {
|
||||
$this->logger->error('An error occurred', [
|
||||
'exception' => $throwable,
|
||||
]);
|
||||
throw $throwable;
|
||||
}
|
||||
}
|
||||
|
||||
public function setLogger(LoggerInterface $logger): self
|
||||
{
|
||||
$this->logger = $logger;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
175
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatement.php
vendored
Normal file
175
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AttestationStatement/AttestationStatement.php
vendored
Normal file
|
@ -0,0 +1,175 @@
|
|||
<?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\AttestationStatement;
|
||||
|
||||
use function array_key_exists;
|
||||
use Assert\Assertion;
|
||||
use JsonSerializable;
|
||||
use function Safe\sprintf;
|
||||
use Webauthn\TrustPath\TrustPath;
|
||||
use Webauthn\TrustPath\TrustPathLoader;
|
||||
|
||||
class AttestationStatement implements JsonSerializable
|
||||
{
|
||||
public const TYPE_NONE = 'none';
|
||||
public const TYPE_BASIC = 'basic';
|
||||
public const TYPE_SELF = 'self';
|
||||
public const TYPE_ATTCA = 'attca';
|
||||
public const TYPE_ECDAA = 'ecdaa';
|
||||
public const TYPE_ANONCA = 'anonca';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $fmt;
|
||||
|
||||
/**
|
||||
* @var mixed[]
|
||||
*/
|
||||
private $attStmt;
|
||||
|
||||
/**
|
||||
* @var TrustPath
|
||||
*/
|
||||
private $trustPath;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* @param mixed[] $attStmt
|
||||
*/
|
||||
public function __construct(string $fmt, array $attStmt, string $type, TrustPath $trustPath)
|
||||
{
|
||||
$this->fmt = $fmt;
|
||||
$this->attStmt = $attStmt;
|
||||
$this->type = $type;
|
||||
$this->trustPath = $trustPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $attStmt
|
||||
*/
|
||||
public static function createNone(string $fmt, array $attStmt, TrustPath $trustPath): self
|
||||
{
|
||||
return new self($fmt, $attStmt, self::TYPE_NONE, $trustPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $attStmt
|
||||
*/
|
||||
public static function createBasic(string $fmt, array $attStmt, TrustPath $trustPath): self
|
||||
{
|
||||
return new self($fmt, $attStmt, self::TYPE_BASIC, $trustPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $attStmt
|
||||
*/
|
||||
public static function createSelf(string $fmt, array $attStmt, TrustPath $trustPath): self
|
||||
{
|
||||
return new self($fmt, $attStmt, self::TYPE_SELF, $trustPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $attStmt
|
||||
*/
|
||||
public static function createAttCA(string $fmt, array $attStmt, TrustPath $trustPath): self
|
||||
{
|
||||
return new self($fmt, $attStmt, self::TYPE_ATTCA, $trustPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $attStmt
|
||||
*/
|
||||
public static function createEcdaa(string $fmt, array $attStmt, TrustPath $trustPath): self
|
||||
{
|
||||
return new self($fmt, $attStmt, self::TYPE_ECDAA, $trustPath);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $key)
|
||||
{
|
||||
Assertion::true($this->has($key), 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) {
|
||||
Assertion::keyExists($data, $key, 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,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?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\AttestationStatement;
|
||||
|
||||
use Webauthn\AuthenticatorData;
|
||||
|
||||
interface AttestationStatementSupport
|
||||
{
|
||||
public function name(): string;
|
||||
|
||||
/**
|
||||
* @param mixed[] $attestation
|
||||
*/
|
||||
public function load(array $attestation): AttestationStatement;
|
||||
|
||||
public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool;
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?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\AttestationStatement;
|
||||
|
||||
use function array_key_exists;
|
||||
use Assert\Assertion;
|
||||
use function Safe\sprintf;
|
||||
|
||||
class AttestationStatementSupportManager
|
||||
{
|
||||
/**
|
||||
* @var AttestationStatementSupport[]
|
||||
*/
|
||||
private $attestationStatementSupports = [];
|
||||
|
||||
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
|
||||
{
|
||||
Assertion::true($this->has($name), sprintf('The attestation statement format "%s" is not supported.', $name));
|
||||
|
||||
return $this->attestationStatementSupports[$name];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
<?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\AttestationStatement;
|
||||
|
||||
use Assert\Assertion;
|
||||
use CBOR\Decoder;
|
||||
use CBOR\MapObject;
|
||||
use CBOR\OtherObject\OtherObjectManager;
|
||||
use CBOR\Tag\TagObjectManager;
|
||||
use Cose\Key\Ec2Key;
|
||||
use InvalidArgumentException;
|
||||
use function Safe\openssl_pkey_get_public;
|
||||
use function Safe\sprintf;
|
||||
use Throwable;
|
||||
use Webauthn\AuthenticatorData;
|
||||
use Webauthn\CertificateToolbox;
|
||||
use Webauthn\StringStream;
|
||||
use Webauthn\TrustPath\CertificateTrustPath;
|
||||
|
||||
final class FidoU2FAttestationStatementSupport implements AttestationStatementSupport
|
||||
{
|
||||
/**
|
||||
* @var Decoder
|
||||
*/
|
||||
private $decoder;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager());
|
||||
}
|
||||
|
||||
public function name(): string
|
||||
{
|
||||
return 'fido-u2f';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $attestation
|
||||
*/
|
||||
public function load(array $attestation): AttestationStatement
|
||||
{
|
||||
Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object');
|
||||
foreach (['sig', 'x5c'] as $key) {
|
||||
Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key));
|
||||
}
|
||||
$certificates = $attestation['attStmt']['x5c'];
|
||||
Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with one certificate.');
|
||||
Assertion::count($certificates, 1, 'The attestation statement value "x5c" must be a list with one certificate.');
|
||||
Assertion::allString($certificates, 'The attestation statement value "x5c" must be a list with one certificate.');
|
||||
|
||||
reset($certificates);
|
||||
$certificates = CertificateToolbox::convertAllDERToPEM($certificates);
|
||||
$this->checkCertificate($certificates[0]);
|
||||
|
||||
return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates));
|
||||
}
|
||||
|
||||
public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
|
||||
{
|
||||
Assertion::eq(
|
||||
$authenticatorData->getAttestedCredentialData()->getAaguid()->toString(),
|
||||
'00000000-0000-0000-0000-000000000000',
|
||||
'Invalid AAGUID for fido-u2f attestation statement. Shall be "00000000-0000-0000-0000-000000000000"'
|
||||
);
|
||||
$trustPath = $attestationStatement->getTrustPath();
|
||||
Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path');
|
||||
$dataToVerify = "\0";
|
||||
$dataToVerify .= $authenticatorData->getRpIdHash();
|
||||
$dataToVerify .= $clientDataJSONHash;
|
||||
$dataToVerify .= $authenticatorData->getAttestedCredentialData()->getCredentialId();
|
||||
$dataToVerify .= $this->extractPublicKey($authenticatorData->getAttestedCredentialData()->getCredentialPublicKey());
|
||||
|
||||
return 1 === openssl_verify($dataToVerify, $attestationStatement->get('sig'), $trustPath->getCertificates()[0], OPENSSL_ALGO_SHA256);
|
||||
}
|
||||
|
||||
private function extractPublicKey(?string $publicKey): string
|
||||
{
|
||||
Assertion::notNull($publicKey, 'The attested credential data does not contain a valid public key.');
|
||||
|
||||
$publicKeyStream = new StringStream($publicKey);
|
||||
$coseKey = $this->decoder->decode($publicKeyStream);
|
||||
Assertion::true($publicKeyStream->isEOF(), 'Invalid public key. Presence of extra bytes.');
|
||||
$publicKeyStream->close();
|
||||
Assertion::isInstanceOf($coseKey, MapObject::class, 'The attested credential data does not contain a valid public key.');
|
||||
|
||||
$coseKey = $coseKey->getNormalizedData();
|
||||
$ec2Key = new Ec2Key($coseKey + [Ec2Key::TYPE => 2, Ec2Key::DATA_CURVE => Ec2Key::CURVE_P256]);
|
||||
|
||||
return "\x04".$ec2Key->x().$ec2Key->y();
|
||||
}
|
||||
|
||||
private function checkCertificate(string $publicKey): void
|
||||
{
|
||||
try {
|
||||
$resource = openssl_pkey_get_public($publicKey);
|
||||
$details = openssl_pkey_get_details($resource);
|
||||
} catch (Throwable $throwable) {
|
||||
throw new InvalidArgumentException('Invalid certificate or certificate chain', 0, $throwable);
|
||||
}
|
||||
Assertion::isArray($details, 'Invalid certificate or certificate chain');
|
||||
Assertion::keyExists($details, 'ec', 'Invalid certificate or certificate chain');
|
||||
Assertion::keyExists($details['ec'], 'curve_name', 'Invalid certificate or certificate chain');
|
||||
Assertion::eq($details['ec']['curve_name'], 'prime256v1', 'Invalid certificate or certificate chain');
|
||||
Assertion::keyExists($details['ec'], 'curve_oid', 'Invalid certificate or certificate chain');
|
||||
Assertion::eq($details['ec']['curve_oid'], '1.2.840.10045.3.1.7', 'Invalid certificate or certificate chain');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?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\AttestationStatement;
|
||||
|
||||
use Assert\Assertion;
|
||||
use function count;
|
||||
use Webauthn\AuthenticatorData;
|
||||
use Webauthn\TrustPath\EmptyTrustPath;
|
||||
|
||||
final class NoneAttestationStatementSupport implements AttestationStatementSupport
|
||||
{
|
||||
public function name(): string
|
||||
{
|
||||
return 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $attestation
|
||||
*/
|
||||
public function load(array $attestation): AttestationStatement
|
||||
{
|
||||
Assertion::noContent($attestation['attStmt'], 'Invalid attestation object');
|
||||
|
||||
return AttestationStatement::createNone($attestation['fmt'], $attestation['attStmt'], new EmptyTrustPath());
|
||||
}
|
||||
|
||||
public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
|
||||
{
|
||||
return 0 === count($attestationStatement->getAttStmt());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
<?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\AttestationStatement;
|
||||
|
||||
use function array_key_exists;
|
||||
use Assert\Assertion;
|
||||
use CBOR\Decoder;
|
||||
use CBOR\MapObject;
|
||||
use CBOR\OtherObject\OtherObjectManager;
|
||||
use CBOR\Tag\TagObjectManager;
|
||||
use Cose\Algorithm\Manager;
|
||||
use Cose\Algorithm\Signature\Signature;
|
||||
use Cose\Algorithms;
|
||||
use Cose\Key\Key;
|
||||
use function in_array;
|
||||
use InvalidArgumentException;
|
||||
use function is_array;
|
||||
use RuntimeException;
|
||||
use Webauthn\AuthenticatorData;
|
||||
use Webauthn\CertificateToolbox;
|
||||
use Webauthn\StringStream;
|
||||
use Webauthn\TrustPath\CertificateTrustPath;
|
||||
use Webauthn\TrustPath\EcdaaKeyIdTrustPath;
|
||||
use Webauthn\TrustPath\EmptyTrustPath;
|
||||
use Webauthn\Util\CoseSignatureFixer;
|
||||
|
||||
final class PackedAttestationStatementSupport implements AttestationStatementSupport
|
||||
{
|
||||
/**
|
||||
* @var Decoder
|
||||
*/
|
||||
private $decoder;
|
||||
|
||||
/**
|
||||
* @var Manager
|
||||
*/
|
||||
private $algorithmManager;
|
||||
|
||||
public function __construct(Manager $algorithmManager)
|
||||
{
|
||||
$this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager());
|
||||
$this->algorithmManager = $algorithmManager;
|
||||
}
|
||||
|
||||
public function name(): string
|
||||
{
|
||||
return 'packed';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $attestation
|
||||
*/
|
||||
public function load(array $attestation): AttestationStatement
|
||||
{
|
||||
Assertion::keyExists($attestation['attStmt'], 'sig', 'The attestation statement value "sig" is missing.');
|
||||
Assertion::keyExists($attestation['attStmt'], 'alg', 'The attestation statement value "alg" is missing.');
|
||||
Assertion::string($attestation['attStmt']['sig'], 'The attestation statement value "sig" is missing.');
|
||||
switch (true) {
|
||||
case array_key_exists('x5c', $attestation['attStmt']):
|
||||
return $this->loadBasicType($attestation);
|
||||
case array_key_exists('ecdaaKeyId', $attestation['attStmt']):
|
||||
return $this->loadEcdaaType($attestation['attStmt']);
|
||||
default:
|
||||
return $this->loadEmptyType($attestation);
|
||||
}
|
||||
}
|
||||
|
||||
public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
|
||||
{
|
||||
$trustPath = $attestationStatement->getTrustPath();
|
||||
switch (true) {
|
||||
case $trustPath instanceof CertificateTrustPath:
|
||||
return $this->processWithCertificate($clientDataJSONHash, $attestationStatement, $authenticatorData, $trustPath);
|
||||
case $trustPath instanceof EcdaaKeyIdTrustPath:
|
||||
return $this->processWithECDAA();
|
||||
case $trustPath instanceof EmptyTrustPath:
|
||||
return $this->processWithSelfAttestation($clientDataJSONHash, $attestationStatement, $authenticatorData);
|
||||
default:
|
||||
throw new InvalidArgumentException('Unsupported attestation statement');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $attestation
|
||||
*/
|
||||
private function loadBasicType(array $attestation): AttestationStatement
|
||||
{
|
||||
$certificates = $attestation['attStmt']['x5c'];
|
||||
Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.');
|
||||
Assertion::minCount($certificates, 1, 'The attestation statement value "x5c" must be a list with at least one certificate.');
|
||||
$certificates = CertificateToolbox::convertAllDERToPEM($certificates);
|
||||
|
||||
return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates));
|
||||
}
|
||||
|
||||
private function loadEcdaaType(array $attestation): AttestationStatement
|
||||
{
|
||||
$ecdaaKeyId = $attestation['attStmt']['ecdaaKeyId'];
|
||||
Assertion::string($ecdaaKeyId, 'The attestation statement value "ecdaaKeyId" is invalid.');
|
||||
|
||||
return AttestationStatement::createEcdaa($attestation['fmt'], $attestation['attStmt'], new EcdaaKeyIdTrustPath($attestation['ecdaaKeyId']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $attestation
|
||||
*/
|
||||
private function loadEmptyType(array $attestation): AttestationStatement
|
||||
{
|
||||
return AttestationStatement::createSelf($attestation['fmt'], $attestation['attStmt'], new EmptyTrustPath());
|
||||
}
|
||||
|
||||
private function checkCertificate(string $attestnCert, AuthenticatorData $authenticatorData): void
|
||||
{
|
||||
$parsed = openssl_x509_parse($attestnCert);
|
||||
Assertion::isArray($parsed, 'Invalid certificate');
|
||||
|
||||
//Check version
|
||||
Assertion::false(!isset($parsed['version']) || 2 !== $parsed['version'], 'Invalid certificate version');
|
||||
|
||||
//Check subject field
|
||||
Assertion::false(!isset($parsed['name']) || false === mb_strpos($parsed['name'], '/OU=Authenticator Attestation'), 'Invalid certificate name. The Subject Organization Unit must be "Authenticator Attestation"');
|
||||
|
||||
//Check extensions
|
||||
Assertion::false(!isset($parsed['extensions']) || !is_array($parsed['extensions']), 'Certificate extensions are missing');
|
||||
|
||||
//Check certificate is not a CA cert
|
||||
Assertion::false(!isset($parsed['extensions']['basicConstraints']) || 'CA:FALSE' !== $parsed['extensions']['basicConstraints'], 'The Basic Constraints extension must have the CA component set to false');
|
||||
|
||||
$attestedCredentialData = $authenticatorData->getAttestedCredentialData();
|
||||
Assertion::notNull($attestedCredentialData, 'No attested credential available');
|
||||
|
||||
// id-fido-gen-ce-aaguid OID check
|
||||
Assertion::false(in_array('1.3.6.1.4.1.45724.1.1.4', $parsed['extensions'], true) && !hash_equals($attestedCredentialData->getAaguid()->getBytes(), $parsed['extensions']['1.3.6.1.4.1.45724.1.1.4']), 'The value of the "aaguid" does not match with the certificate');
|
||||
}
|
||||
|
||||
private function processWithCertificate(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData, CertificateTrustPath $trustPath): bool
|
||||
{
|
||||
$certificates = $trustPath->getCertificates();
|
||||
|
||||
// Check leaf certificate
|
||||
$this->checkCertificate($certificates[0], $authenticatorData);
|
||||
|
||||
// Get the COSE algorithm identifier and the corresponding OpenSSL one
|
||||
$coseAlgorithmIdentifier = (int) $attestationStatement->get('alg');
|
||||
$opensslAlgorithmIdentifier = Algorithms::getOpensslAlgorithmFor($coseAlgorithmIdentifier);
|
||||
|
||||
// Verification of the signature
|
||||
$signedData = $authenticatorData->getAuthData().$clientDataJSONHash;
|
||||
$result = openssl_verify($signedData, $attestationStatement->get('sig'), $certificates[0], $opensslAlgorithmIdentifier);
|
||||
|
||||
return 1 === $result;
|
||||
}
|
||||
|
||||
private function processWithECDAA(): bool
|
||||
{
|
||||
throw new RuntimeException('ECDAA not supported');
|
||||
}
|
||||
|
||||
private function processWithSelfAttestation(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
|
||||
{
|
||||
$attestedCredentialData = $authenticatorData->getAttestedCredentialData();
|
||||
Assertion::notNull($attestedCredentialData, 'No attested credential available');
|
||||
$credentialPublicKey = $attestedCredentialData->getCredentialPublicKey();
|
||||
Assertion::notNull($credentialPublicKey, 'No credential public key available');
|
||||
$publicKeyStream = new StringStream($credentialPublicKey);
|
||||
$publicKey = $this->decoder->decode($publicKeyStream);
|
||||
Assertion::true($publicKeyStream->isEOF(), 'Invalid public key. Presence of extra bytes.');
|
||||
$publicKeyStream->close();
|
||||
Assertion::isInstanceOf($publicKey, MapObject::class, 'The attested credential data does not contain a valid public key.');
|
||||
$publicKey = $publicKey->getNormalizedData(false);
|
||||
$publicKey = new Key($publicKey);
|
||||
Assertion::eq($publicKey->alg(), (int) $attestationStatement->get('alg'), 'The algorithm of the attestation statement and the key are not identical.');
|
||||
|
||||
$dataToVerify = $authenticatorData->getAuthData().$clientDataJSONHash;
|
||||
$algorithm = $this->algorithmManager->get((int) $attestationStatement->get('alg'));
|
||||
if (!$algorithm instanceof Signature) {
|
||||
throw new RuntimeException('Invalid algorithm');
|
||||
}
|
||||
$signature = CoseSignatureFixer::fix($attestationStatement->get('sig'), $algorithm);
|
||||
|
||||
return $algorithm->verify($dataToVerify, $publicKey, $signature);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,309 @@
|
|||
<?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\AttestationStatement;
|
||||
|
||||
use Assert\Assertion;
|
||||
use Base64Url\Base64Url;
|
||||
use CBOR\Decoder;
|
||||
use CBOR\MapObject;
|
||||
use CBOR\OtherObject\OtherObjectManager;
|
||||
use CBOR\Tag\TagObjectManager;
|
||||
use Cose\Algorithms;
|
||||
use Cose\Key\Ec2Key;
|
||||
use Cose\Key\Key;
|
||||
use Cose\Key\OkpKey;
|
||||
use Cose\Key\RsaKey;
|
||||
use function count;
|
||||
use function in_array;
|
||||
use InvalidArgumentException;
|
||||
use function is_array;
|
||||
use RuntimeException;
|
||||
use Safe\DateTimeImmutable;
|
||||
use function Safe\sprintf;
|
||||
use function Safe\unpack;
|
||||
use Webauthn\AuthenticatorData;
|
||||
use Webauthn\CertificateToolbox;
|
||||
use Webauthn\StringStream;
|
||||
use Webauthn\TrustPath\CertificateTrustPath;
|
||||
use Webauthn\TrustPath\EcdaaKeyIdTrustPath;
|
||||
|
||||
final class TPMAttestationStatementSupport implements AttestationStatementSupport
|
||||
{
|
||||
public function name(): string
|
||||
{
|
||||
return 'tpm';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $attestation
|
||||
*/
|
||||
public function load(array $attestation): AttestationStatement
|
||||
{
|
||||
Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object');
|
||||
Assertion::keyNotExists($attestation['attStmt'], 'ecdaaKeyId', 'ECDAA not supported');
|
||||
foreach (['ver', 'ver', 'sig', 'alg', 'certInfo', 'pubArea'] as $key) {
|
||||
Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key));
|
||||
}
|
||||
Assertion::eq('2.0', $attestation['attStmt']['ver'], 'Invalid attestation object');
|
||||
|
||||
$certInfo = $this->checkCertInfo($attestation['attStmt']['certInfo']);
|
||||
Assertion::eq('8017', bin2hex($certInfo['type']), 'Invalid attestation object');
|
||||
|
||||
$pubArea = $this->checkPubArea($attestation['attStmt']['pubArea']);
|
||||
$pubAreaAttestedNameAlg = mb_substr($certInfo['attestedName'], 0, 2, '8bit');
|
||||
$pubAreaHash = hash($this->getTPMHash($pubAreaAttestedNameAlg), $attestation['attStmt']['pubArea'], true);
|
||||
$attestedName = $pubAreaAttestedNameAlg.$pubAreaHash;
|
||||
Assertion::eq($attestedName, $certInfo['attestedName'], 'Invalid attested name');
|
||||
|
||||
$attestation['attStmt']['parsedCertInfo'] = $certInfo;
|
||||
$attestation['attStmt']['parsedPubArea'] = $pubArea;
|
||||
|
||||
$certificates = CertificateToolbox::convertAllDERToPEM($attestation['attStmt']['x5c']);
|
||||
Assertion::minCount($certificates, 1, 'The attestation statement value "x5c" must be a list with at least one certificate.');
|
||||
|
||||
return AttestationStatement::createAttCA(
|
||||
$this->name(),
|
||||
$attestation['attStmt'],
|
||||
new CertificateTrustPath($certificates)
|
||||
);
|
||||
}
|
||||
|
||||
public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
|
||||
{
|
||||
$attToBeSigned = $authenticatorData->getAuthData().$clientDataJSONHash;
|
||||
$attToBeSignedHash = hash(Algorithms::getHashAlgorithmFor((int) $attestationStatement->get('alg')), $attToBeSigned, true);
|
||||
Assertion::eq($attestationStatement->get('parsedCertInfo')['extraData'], $attToBeSignedHash, 'Invalid attestation hash');
|
||||
$this->checkUniquePublicKey(
|
||||
$attestationStatement->get('parsedPubArea')['unique'],
|
||||
$authenticatorData->getAttestedCredentialData()->getCredentialPublicKey()
|
||||
);
|
||||
|
||||
switch (true) {
|
||||
case $attestationStatement->getTrustPath() instanceof CertificateTrustPath:
|
||||
return $this->processWithCertificate($clientDataJSONHash, $attestationStatement, $authenticatorData);
|
||||
case $attestationStatement->getTrustPath() instanceof EcdaaKeyIdTrustPath:
|
||||
return $this->processWithECDAA();
|
||||
default:
|
||||
throw new InvalidArgumentException('Unsupported attestation statement');
|
||||
}
|
||||
}
|
||||
|
||||
private function checkUniquePublicKey(string $unique, string $cborPublicKey): void
|
||||
{
|
||||
$cborDecoder = new Decoder(new TagObjectManager(), new OtherObjectManager());
|
||||
$publicKey = $cborDecoder->decode(new StringStream($cborPublicKey));
|
||||
Assertion::isInstanceOf($publicKey, MapObject::class, 'Invalid public key');
|
||||
$key = new Key($publicKey->getNormalizedData(false));
|
||||
|
||||
switch ($key->type()) {
|
||||
case Key::TYPE_OKP:
|
||||
$uniqueFromKey = (new OkpKey($key->getData()))->x();
|
||||
break;
|
||||
case Key::TYPE_EC2:
|
||||
$ec2Key = new Ec2Key($key->getData());
|
||||
$uniqueFromKey = "\x04".$ec2Key->x().$ec2Key->y();
|
||||
break;
|
||||
case Key::TYPE_RSA:
|
||||
$uniqueFromKey = (new RsaKey($key->getData()))->n();
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException('Invalid or unsupported key type.');
|
||||
}
|
||||
|
||||
Assertion::eq($unique, $uniqueFromKey, 'Invalid pubArea.unique value');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
private function checkCertInfo(string $data): array
|
||||
{
|
||||
$certInfo = new StringStream($data);
|
||||
|
||||
$magic = $certInfo->read(4);
|
||||
Assertion::eq('ff544347', bin2hex($magic), 'Invalid attestation object');
|
||||
|
||||
$type = $certInfo->read(2);
|
||||
|
||||
$qualifiedSignerLength = unpack('n', $certInfo->read(2))[1];
|
||||
$qualifiedSigner = $certInfo->read($qualifiedSignerLength); //Ignored
|
||||
|
||||
$extraDataLength = unpack('n', $certInfo->read(2))[1];
|
||||
$extraData = $certInfo->read($extraDataLength);
|
||||
|
||||
$clockInfo = $certInfo->read(17); //Ignore
|
||||
|
||||
$firmwareVersion = $certInfo->read(8);
|
||||
|
||||
$attestedNameLength = unpack('n', $certInfo->read(2))[1];
|
||||
$attestedName = $certInfo->read($attestedNameLength);
|
||||
|
||||
$attestedQualifiedNameLength = unpack('n', $certInfo->read(2))[1];
|
||||
$attestedQualifiedName = $certInfo->read($attestedQualifiedNameLength); //Ignore
|
||||
Assertion::true($certInfo->isEOF(), 'Invalid certificate information. Presence of extra bytes.');
|
||||
$certInfo->close();
|
||||
|
||||
return [
|
||||
'magic' => $magic,
|
||||
'type' => $type,
|
||||
'qualifiedSigner' => $qualifiedSigner,
|
||||
'extraData' => $extraData,
|
||||
'clockInfo' => $clockInfo,
|
||||
'firmwareVersion' => $firmwareVersion,
|
||||
'attestedName' => $attestedName,
|
||||
'attestedQualifiedName' => $attestedQualifiedName,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
private function checkPubArea(string $data): array
|
||||
{
|
||||
$pubArea = new StringStream($data);
|
||||
|
||||
$type = $pubArea->read(2);
|
||||
|
||||
$nameAlg = $pubArea->read(2);
|
||||
|
||||
$objectAttributes = $pubArea->read(4);
|
||||
|
||||
$authPolicyLength = unpack('n', $pubArea->read(2))[1];
|
||||
$authPolicy = $pubArea->read($authPolicyLength);
|
||||
|
||||
$parameters = $this->getParameters($type, $pubArea);
|
||||
|
||||
$uniqueLength = unpack('n', $pubArea->read(2))[1];
|
||||
$unique = $pubArea->read($uniqueLength);
|
||||
Assertion::true($pubArea->isEOF(), 'Invalid public area. Presence of extra bytes.');
|
||||
$pubArea->close();
|
||||
|
||||
return [
|
||||
'type' => $type,
|
||||
'nameAlg' => $nameAlg,
|
||||
'objectAttributes' => $objectAttributes,
|
||||
'authPolicy' => $authPolicy,
|
||||
'parameters' => $parameters,
|
||||
'unique' => $unique,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
private function getParameters(string $type, StringStream $stream): array
|
||||
{
|
||||
switch (bin2hex($type)) {
|
||||
case '0001':
|
||||
case '0014':
|
||||
case '0016':
|
||||
return [
|
||||
'symmetric' => $stream->read(2),
|
||||
'scheme' => $stream->read(2),
|
||||
'keyBits' => unpack('n', $stream->read(2))[1],
|
||||
'exponent' => $this->getExponent($stream->read(4)),
|
||||
];
|
||||
case '0018':
|
||||
return [
|
||||
'symmetric' => $stream->read(2),
|
||||
'scheme' => $stream->read(2),
|
||||
'curveId' => $stream->read(2),
|
||||
'kdf' => $stream->read(2),
|
||||
];
|
||||
default:
|
||||
throw new InvalidArgumentException('Unsupported type');
|
||||
}
|
||||
}
|
||||
|
||||
private function getExponent(string $exponent): string
|
||||
{
|
||||
return '00000000' === bin2hex($exponent) ? Base64Url::decode('AQAB') : $exponent;
|
||||
}
|
||||
|
||||
private function getTPMHash(string $nameAlg): string
|
||||
{
|
||||
switch (bin2hex($nameAlg)) {
|
||||
case '0004':
|
||||
return 'sha1'; //: "TPM_ALG_SHA1",
|
||||
case '000b':
|
||||
return 'sha256'; //: "TPM_ALG_SHA256",
|
||||
case '000c':
|
||||
return 'sha384'; //: "TPM_ALG_SHA384",
|
||||
case '000d':
|
||||
return 'sha512'; //: "TPM_ALG_SHA512",
|
||||
default:
|
||||
throw new InvalidArgumentException('Unsupported hash algorithm');
|
||||
}
|
||||
}
|
||||
|
||||
private function processWithCertificate(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
|
||||
{
|
||||
$trustPath = $attestationStatement->getTrustPath();
|
||||
Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path');
|
||||
|
||||
$certificates = $trustPath->getCertificates();
|
||||
|
||||
// Check certificate CA chain and returns the Attestation Certificate
|
||||
$this->checkCertificate($certificates[0], $authenticatorData);
|
||||
|
||||
// Get the COSE algorithm identifier and the corresponding OpenSSL one
|
||||
$coseAlgorithmIdentifier = (int) $attestationStatement->get('alg');
|
||||
$opensslAlgorithmIdentifier = Algorithms::getOpensslAlgorithmFor($coseAlgorithmIdentifier);
|
||||
|
||||
$result = openssl_verify($attestationStatement->get('certInfo'), $attestationStatement->get('sig'), $certificates[0], $opensslAlgorithmIdentifier);
|
||||
|
||||
return 1 === $result;
|
||||
}
|
||||
|
||||
private function checkCertificate(string $attestnCert, AuthenticatorData $authenticatorData): void
|
||||
{
|
||||
$parsed = openssl_x509_parse($attestnCert);
|
||||
Assertion::isArray($parsed, 'Invalid certificate');
|
||||
|
||||
//Check version
|
||||
Assertion::false(!isset($parsed['version']) || 2 !== $parsed['version'], 'Invalid certificate version');
|
||||
|
||||
//Check subject field is empty
|
||||
Assertion::false(!isset($parsed['subject']) || !is_array($parsed['subject']) || 0 !== count($parsed['subject']), 'Invalid certificate name. The Subject should be empty');
|
||||
|
||||
// Check period of validity
|
||||
Assertion::keyExists($parsed, 'validFrom_time_t', 'Invalid certificate start date.');
|
||||
Assertion::integer($parsed['validFrom_time_t'], 'Invalid certificate start date.');
|
||||
$startDate = (new DateTimeImmutable())->setTimestamp($parsed['validFrom_time_t']);
|
||||
Assertion::true($startDate < new DateTimeImmutable(), 'Invalid certificate start date.');
|
||||
|
||||
Assertion::keyExists($parsed, 'validTo_time_t', 'Invalid certificate end date.');
|
||||
Assertion::integer($parsed['validTo_time_t'], 'Invalid certificate end date.');
|
||||
$endDate = (new DateTimeImmutable())->setTimestamp($parsed['validTo_time_t']);
|
||||
Assertion::true($endDate > new DateTimeImmutable(), 'Invalid certificate end date.');
|
||||
|
||||
//Check extensions
|
||||
Assertion::false(!isset($parsed['extensions']) || !is_array($parsed['extensions']), 'Certificate extensions are missing');
|
||||
|
||||
//Check subjectAltName
|
||||
Assertion::false(!isset($parsed['extensions']['subjectAltName']), 'The "subjectAltName" is missing');
|
||||
|
||||
//Check extendedKeyUsage
|
||||
Assertion::false(!isset($parsed['extensions']['extendedKeyUsage']), 'The "subjectAltName" is missing');
|
||||
Assertion::eq($parsed['extensions']['extendedKeyUsage'], '2.23.133.8.3', 'The "extendedKeyUsage" is invalid');
|
||||
|
||||
// id-fido-gen-ce-aaguid OID check
|
||||
Assertion::false(in_array('1.3.6.1.4.1.45724.1.1.4', $parsed['extensions'], true) && !hash_equals($authenticatorData->getAttestedCredentialData()->getAaguid()->getBytes(), $parsed['extensions']['1.3.6.1.4.1.45724.1.1.4']), 'The value of the "aaguid" does not match with the certificate');
|
||||
}
|
||||
|
||||
private function processWithECDAA(): bool
|
||||
{
|
||||
throw new RuntimeException('ECDAA not supported');
|
||||
}
|
||||
}
|
113
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AttestedCredentialData.php
vendored
Normal file
113
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AttestedCredentialData.php
vendored
Normal file
|
@ -0,0 +1,113 @@
|
|||
<?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;
|
||||
|
||||
use Assert\Assertion;
|
||||
use JsonSerializable;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
use function Safe\base64_decode;
|
||||
|
||||
/**
|
||||
* @see https://www.w3.org/TR/webauthn/#sec-attested-credential-data
|
||||
*/
|
||||
class AttestedCredentialData implements JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var UuidInterface
|
||||
*/
|
||||
private $aaguid;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $credentialId;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $credentialPublicKey;
|
||||
|
||||
public function __construct(UuidInterface $aaguid, string $credentialId, ?string $credentialPublicKey)
|
||||
{
|
||||
$this->aaguid = $aaguid;
|
||||
$this->credentialId = $credentialId;
|
||||
$this->credentialPublicKey = $credentialPublicKey;
|
||||
}
|
||||
|
||||
public function getAaguid(): UuidInterface
|
||||
{
|
||||
return $this->aaguid;
|
||||
}
|
||||
|
||||
public function setAaguid(UuidInterface $aaguid): void
|
||||
{
|
||||
$this->aaguid = $aaguid;
|
||||
}
|
||||
|
||||
public function getCredentialId(): string
|
||||
{
|
||||
return $this->credentialId;
|
||||
}
|
||||
|
||||
public function getCredentialPublicKey(): ?string
|
||||
{
|
||||
return $this->credentialPublicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $json
|
||||
*/
|
||||
public static function createFromArray(array $json): self
|
||||
{
|
||||
Assertion::keyExists($json, 'aaguid', 'Invalid input. "aaguid" is missing.');
|
||||
Assertion::keyExists($json, 'credentialId', 'Invalid input. "credentialId" is missing.');
|
||||
switch (true) {
|
||||
case 36 === mb_strlen($json['aaguid'], '8bit'):
|
||||
$uuid = Uuid::fromString($json['aaguid']);
|
||||
break;
|
||||
default: // Kept for compatibility with old format
|
||||
$decoded = base64_decode($json['aaguid'], true);
|
||||
$uuid = Uuid::fromBytes($decoded);
|
||||
}
|
||||
$credentialId = base64_decode($json['credentialId'], true);
|
||||
|
||||
$credentialPublicKey = null;
|
||||
if (isset($json['credentialPublicKey'])) {
|
||||
$credentialPublicKey = base64_decode($json['credentialPublicKey'], true);
|
||||
}
|
||||
|
||||
return new self(
|
||||
$uuid,
|
||||
$credentialId,
|
||||
$credentialPublicKey
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$result = [
|
||||
'aaguid' => $this->aaguid->toString(),
|
||||
'credentialId' => base64_encode($this->credentialId),
|
||||
];
|
||||
if (null !== $this->credentialPublicKey) {
|
||||
$result['credentialPublicKey'] = base64_encode($this->credentialPublicKey);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<?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\AuthenticationExtensions;
|
||||
|
||||
use JsonSerializable;
|
||||
|
||||
class AuthenticationExtension implements JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __construct(string $name, $value)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function name(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function value()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
<?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\AuthenticationExtensions;
|
||||
|
||||
use function array_key_exists;
|
||||
use ArrayIterator;
|
||||
use Assert\Assertion;
|
||||
use function count;
|
||||
use Countable;
|
||||
use Iterator;
|
||||
use IteratorAggregate;
|
||||
use JsonSerializable;
|
||||
use function Safe\sprintf;
|
||||
|
||||
class AuthenticationExtensionsClientInputs implements JsonSerializable, Countable, IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* @var AuthenticationExtension[]
|
||||
*/
|
||||
private $extensions = [];
|
||||
|
||||
public function add(AuthenticationExtension $extension): void
|
||||
{
|
||||
$this->extensions[$extension->name()] = $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $json
|
||||
*/
|
||||
public static function createFromArray(array $json): self
|
||||
{
|
||||
$object = new self();
|
||||
foreach ($json as $k => $v) {
|
||||
$object->add(new AuthenticationExtension($k, $v));
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
public function has(string $key): bool
|
||||
{
|
||||
return array_key_exists($key, $this->extensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $key)
|
||||
{
|
||||
Assertion::true($this->has($key), sprintf('The extension with key "%s" is not available', $key));
|
||||
|
||||
return $this->extensions[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AuthenticationExtension[]
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return array_map(static function (AuthenticationExtension $object) {
|
||||
return $object->jsonSerialize();
|
||||
}, $this->extensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Iterator<string, AuthenticationExtension>
|
||||
*/
|
||||
public function getIterator(): Iterator
|
||||
{
|
||||
return new ArrayIterator($this->extensions);
|
||||
}
|
||||
|
||||
public function count(int $mode = COUNT_NORMAL): int
|
||||
{
|
||||
return count($this->extensions, $mode);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
<?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\AuthenticationExtensions;
|
||||
|
||||
use function array_key_exists;
|
||||
use ArrayIterator;
|
||||
use Assert\Assertion;
|
||||
use function count;
|
||||
use Countable;
|
||||
use Iterator;
|
||||
use IteratorAggregate;
|
||||
use JsonSerializable;
|
||||
use function Safe\json_decode;
|
||||
use function Safe\sprintf;
|
||||
|
||||
class AuthenticationExtensionsClientOutputs implements JsonSerializable, Countable, IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* @var AuthenticationExtension[]
|
||||
*/
|
||||
private $extensions = [];
|
||||
|
||||
public function add(AuthenticationExtension $extension): void
|
||||
{
|
||||
$this->extensions[$extension->name()] = $extension;
|
||||
}
|
||||
|
||||
public static function createFromString(string $data): self
|
||||
{
|
||||
$data = json_decode($data, true);
|
||||
Assertion::isArray($data, 'Invalid data');
|
||||
|
||||
return self::createFromArray($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $json
|
||||
*/
|
||||
public static function createFromArray(array $json): self
|
||||
{
|
||||
$object = new self();
|
||||
foreach ($json as $k => $v) {
|
||||
$object->add(new AuthenticationExtension($k, $v));
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
public function has(string $key): bool
|
||||
{
|
||||
return array_key_exists($key, $this->extensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $key)
|
||||
{
|
||||
Assertion::true($this->has($key), sprintf('The extension with key "%s" is not available', $key));
|
||||
|
||||
return $this->extensions[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AuthenticationExtension[]
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return array_map(static function (AuthenticationExtension $object) {
|
||||
return $object->jsonSerialize();
|
||||
}, $this->extensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Iterator<string, AuthenticationExtension>
|
||||
*/
|
||||
public function getIterator(): Iterator
|
||||
{
|
||||
return new ArrayIterator($this->extensions);
|
||||
}
|
||||
|
||||
public function count(int $mode = COUNT_NORMAL): int
|
||||
{
|
||||
return count($this->extensions, $mode);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?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\AuthenticationExtensions;
|
||||
|
||||
use Assert\Assertion;
|
||||
use CBOR\CBORObject;
|
||||
use CBOR\MapObject;
|
||||
|
||||
abstract class AuthenticationExtensionsClientOutputsLoader
|
||||
{
|
||||
public static function load(CBORObject $object): AuthenticationExtensionsClientOutputs
|
||||
{
|
||||
Assertion::isInstanceOf($object, MapObject::class, 'Invalid extension object');
|
||||
$data = $object->getNormalizedData();
|
||||
$extensions = new AuthenticationExtensionsClientOutputs();
|
||||
foreach ($data as $key => $value) {
|
||||
Assertion::string($key, 'Invalid extension key');
|
||||
$extensions->add(new AuthenticationExtension($key, $value));
|
||||
}
|
||||
|
||||
return $extensions;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<?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\AuthenticationExtensions;
|
||||
|
||||
interface ExtensionOutputChecker
|
||||
{
|
||||
/**
|
||||
* @throws ExtensionOutputError
|
||||
*/
|
||||
public function check(AuthenticationExtensionsClientInputs $inputs, AuthenticationExtensionsClientOutputs $outputs): void;
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?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\AuthenticationExtensions;
|
||||
|
||||
class ExtensionOutputCheckerHandler
|
||||
{
|
||||
/**
|
||||
* @var ExtensionOutputChecker[]
|
||||
*/
|
||||
private $checkers = [];
|
||||
|
||||
public function add(ExtensionOutputChecker $checker): void
|
||||
{
|
||||
$this->checkers[] = $checker;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ExtensionOutputError
|
||||
*/
|
||||
public function check(AuthenticationExtensionsClientInputs $inputs, AuthenticationExtensionsClientOutputs $outputs): void
|
||||
{
|
||||
foreach ($this->checkers as $checker) {
|
||||
$checker->check($inputs, $outputs);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?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\AuthenticationExtensions;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class ExtensionOutputError extends Exception
|
||||
{
|
||||
/**
|
||||
* @var AuthenticationExtension
|
||||
*/
|
||||
private $authenticationExtension;
|
||||
|
||||
public function __construct(AuthenticationExtension $authenticationExtension, string $message = '', int $code = 0, Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
$this->authenticationExtension = $authenticationExtension;
|
||||
}
|
||||
|
||||
public function getAuthenticationExtension(): AuthenticationExtension
|
||||
{
|
||||
return $this->authenticationExtension;
|
||||
}
|
||||
}
|
64
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AuthenticatorAssertionResponse.php
vendored
Normal file
64
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AuthenticatorAssertionResponse.php
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?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;
|
||||
|
||||
use function Safe\base64_decode;
|
||||
|
||||
/**
|
||||
* @see https://www.w3.org/TR/webauthn/#authenticatorassertionresponse
|
||||
*/
|
||||
class AuthenticatorAssertionResponse extends AuthenticatorResponse
|
||||
{
|
||||
/**
|
||||
* @var AuthenticatorData
|
||||
*/
|
||||
private $authenticatorData;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $signature;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $userHandle;
|
||||
|
||||
public function __construct(CollectedClientData $clientDataJSON, AuthenticatorData $authenticatorData, string $signature, ?string $userHandle)
|
||||
{
|
||||
parent::__construct($clientDataJSON);
|
||||
$this->authenticatorData = $authenticatorData;
|
||||
$this->signature = $signature;
|
||||
$this->userHandle = $userHandle;
|
||||
}
|
||||
|
||||
public function getAuthenticatorData(): AuthenticatorData
|
||||
{
|
||||
return $this->authenticatorData;
|
||||
}
|
||||
|
||||
public function getSignature(): string
|
||||
{
|
||||
return $this->signature;
|
||||
}
|
||||
|
||||
public function getUserHandle(): ?string
|
||||
{
|
||||
if (null === $this->userHandle || '' === $this->userHandle) {
|
||||
return $this->userHandle;
|
||||
}
|
||||
|
||||
return base64_decode($this->userHandle, true);
|
||||
}
|
||||
}
|
272
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AuthenticatorAssertionResponseValidator.php
vendored
Normal file
272
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AuthenticatorAssertionResponseValidator.php
vendored
Normal file
|
@ -0,0 +1,272 @@
|
|||
<?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;
|
||||
|
||||
use Assert\Assertion;
|
||||
use CBOR\Decoder;
|
||||
use CBOR\OtherObject\OtherObjectManager;
|
||||
use CBOR\Tag\TagObjectManager;
|
||||
use Cose\Algorithm\Manager;
|
||||
use Cose\Algorithm\Signature\Signature;
|
||||
use Cose\Key\Key;
|
||||
use function count;
|
||||
use function in_array;
|
||||
use function is_string;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use function Safe\parse_url;
|
||||
use Throwable;
|
||||
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
|
||||
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputs;
|
||||
use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler;
|
||||
use Webauthn\Counter\CounterChecker;
|
||||
use Webauthn\Counter\ThrowExceptionIfInvalid;
|
||||
use Webauthn\TokenBinding\TokenBindingHandler;
|
||||
use Webauthn\Util\CoseSignatureFixer;
|
||||
|
||||
class AuthenticatorAssertionResponseValidator
|
||||
{
|
||||
/**
|
||||
* @var PublicKeyCredentialSourceRepository
|
||||
*/
|
||||
private $publicKeyCredentialSourceRepository;
|
||||
|
||||
/**
|
||||
* @var Decoder
|
||||
*/
|
||||
private $decoder;
|
||||
|
||||
/**
|
||||
* @var TokenBindingHandler
|
||||
*/
|
||||
private $tokenBindingHandler;
|
||||
|
||||
/**
|
||||
* @var ExtensionOutputCheckerHandler
|
||||
*/
|
||||
private $extensionOutputCheckerHandler;
|
||||
|
||||
/**
|
||||
* @var Manager|null
|
||||
*/
|
||||
private $algorithmManager;
|
||||
|
||||
/**
|
||||
* @var CounterChecker
|
||||
*/
|
||||
private $counterChecker;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface|null
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
public function __construct(PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository, TokenBindingHandler $tokenBindingHandler, ExtensionOutputCheckerHandler $extensionOutputCheckerHandler, Manager $algorithmManager, ?CounterChecker $counterChecker = null, ?LoggerInterface $logger = null)
|
||||
{
|
||||
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".', E_USER_DEPRECATED);
|
||||
}
|
||||
if (null !== $counterChecker) {
|
||||
@trigger_error('The argument "counterChecker" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setCounterChecker".', E_USER_DEPRECATED);
|
||||
}
|
||||
$this->publicKeyCredentialSourceRepository = $publicKeyCredentialSourceRepository;
|
||||
$this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager());
|
||||
$this->tokenBindingHandler = $tokenBindingHandler;
|
||||
$this->extensionOutputCheckerHandler = $extensionOutputCheckerHandler;
|
||||
$this->algorithmManager = $algorithmManager;
|
||||
$this->counterChecker = $counterChecker ?? new ThrowExceptionIfInvalid();
|
||||
$this->logger = $logger ?? new NullLogger();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.w3.org/TR/webauthn/#verifying-assertion
|
||||
*/
|
||||
public function check(string $credentialId, AuthenticatorAssertionResponse $authenticatorAssertionResponse, PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, ServerRequestInterface $request, ?string $userHandle, array $securedRelyingPartyId = []): PublicKeyCredentialSource
|
||||
{
|
||||
try {
|
||||
$this->logger->info('Checking the authenticator assertion response', [
|
||||
'credentialId' => $credentialId,
|
||||
'authenticatorAssertionResponse' => $authenticatorAssertionResponse,
|
||||
'publicKeyCredentialRequestOptions' => $publicKeyCredentialRequestOptions,
|
||||
'host' => $request->getUri()->getHost(),
|
||||
'userHandle' => $userHandle,
|
||||
]);
|
||||
/** @see 7.2.1 */
|
||||
if (0 !== count($publicKeyCredentialRequestOptions->getAllowCredentials())) {
|
||||
Assertion::true($this->isCredentialIdAllowed($credentialId, $publicKeyCredentialRequestOptions->getAllowCredentials()), 'The credential ID is not allowed.');
|
||||
}
|
||||
|
||||
/** @see 7.2.2 */
|
||||
$publicKeyCredentialSource = $this->publicKeyCredentialSourceRepository->findOneByCredentialId($credentialId);
|
||||
Assertion::notNull($publicKeyCredentialSource, 'The credential ID is invalid.');
|
||||
|
||||
/** @see 7.2.3 */
|
||||
$attestedCredentialData = $publicKeyCredentialSource->getAttestedCredentialData();
|
||||
$credentialUserHandle = $publicKeyCredentialSource->getUserHandle();
|
||||
$responseUserHandle = $authenticatorAssertionResponse->getUserHandle();
|
||||
|
||||
/** @see 7.2.2 User Handle*/
|
||||
if (null !== $userHandle) { //If the user was identified before the authentication ceremony was initiated,
|
||||
Assertion::eq($credentialUserHandle, $userHandle, 'Invalid user handle');
|
||||
if (null !== $responseUserHandle && '' !== $responseUserHandle) {
|
||||
Assertion::eq($credentialUserHandle, $responseUserHandle, 'Invalid user handle');
|
||||
}
|
||||
} else {
|
||||
Assertion::notEmpty($responseUserHandle, 'User handle is mandatory');
|
||||
Assertion::eq($credentialUserHandle, $responseUserHandle, 'Invalid user handle');
|
||||
}
|
||||
|
||||
$credentialPublicKey = $attestedCredentialData->getCredentialPublicKey();
|
||||
$isU2F = U2FPublicKey::isU2FKey($credentialPublicKey);
|
||||
if ($isU2F) {
|
||||
$credentialPublicKey = U2FPublicKey::createCOSEKey($credentialPublicKey);
|
||||
}
|
||||
Assertion::notNull($credentialPublicKey, 'No public key available.');
|
||||
$stream = new StringStream($credentialPublicKey);
|
||||
$credentialPublicKeyStream = $this->decoder->decode($stream);
|
||||
Assertion::true($stream->isEOF(), 'Invalid key. Presence of extra bytes.');
|
||||
$stream->close();
|
||||
|
||||
/** @see 7.2.4 */
|
||||
/** @see 7.2.5 */
|
||||
//Nothing to do. Use of objects directly
|
||||
|
||||
/** @see 7.2.6 */
|
||||
$C = $authenticatorAssertionResponse->getClientDataJSON();
|
||||
|
||||
/** @see 7.2.7 */
|
||||
Assertion::eq('webauthn.get', $C->getType(), 'The client data type is not "webauthn.get".');
|
||||
|
||||
/** @see 7.2.8 */
|
||||
Assertion::true(hash_equals($publicKeyCredentialRequestOptions->getChallenge(), $C->getChallenge()), 'Invalid challenge.');
|
||||
|
||||
/** @see 7.2.9 */
|
||||
$rpId = $publicKeyCredentialRequestOptions->getRpId() ?? $request->getUri()->getHost();
|
||||
$facetId = $this->getFacetId($rpId, $publicKeyCredentialRequestOptions->getExtensions(), $authenticatorAssertionResponse->getAuthenticatorData()->getExtensions());
|
||||
$parsedRelyingPartyId = parse_url($C->getOrigin());
|
||||
Assertion::isArray($parsedRelyingPartyId, 'Invalid origin');
|
||||
if (!in_array($facetId, $securedRelyingPartyId, true)) {
|
||||
$scheme = $parsedRelyingPartyId['scheme'] ?? '';
|
||||
Assertion::eq('https', $scheme, 'Invalid scheme. HTTPS required.');
|
||||
}
|
||||
$clientDataRpId = $parsedRelyingPartyId['host'] ?? '';
|
||||
Assertion::notEmpty($clientDataRpId, 'Invalid origin rpId.');
|
||||
$rpIdLength = mb_strlen($facetId);
|
||||
Assertion::eq(mb_substr('.'.$clientDataRpId, -($rpIdLength + 1)), '.'.$facetId, 'rpId mismatch.');
|
||||
|
||||
/** @see 7.2.10 */
|
||||
if (null !== $C->getTokenBinding()) {
|
||||
$this->tokenBindingHandler->check($C->getTokenBinding(), $request);
|
||||
}
|
||||
|
||||
$expectedRpIdHash = $isU2F ? $C->getOrigin() : $facetId;
|
||||
// u2f response has full origin in rpIdHash
|
||||
/** @see 7.2.11 */
|
||||
$rpIdHash = hash('sha256', $expectedRpIdHash, true);
|
||||
Assertion::true(hash_equals($rpIdHash, $authenticatorAssertionResponse->getAuthenticatorData()->getRpIdHash()), 'rpId hash mismatch.');
|
||||
|
||||
/** @see 7.2.12 */
|
||||
Assertion::true($authenticatorAssertionResponse->getAuthenticatorData()->isUserPresent(), 'User was not present');
|
||||
/** @see 7.2.13 */
|
||||
if (AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED === $publicKeyCredentialRequestOptions->getUserVerification()) {
|
||||
Assertion::true($authenticatorAssertionResponse->getAuthenticatorData()->isUserVerified(), 'User authentication required.');
|
||||
}
|
||||
|
||||
/** @see 7.2.14 */
|
||||
$extensionsClientOutputs = $authenticatorAssertionResponse->getAuthenticatorData()->getExtensions();
|
||||
if (null !== $extensionsClientOutputs) {
|
||||
$this->extensionOutputCheckerHandler->check(
|
||||
$publicKeyCredentialRequestOptions->getExtensions(),
|
||||
$extensionsClientOutputs
|
||||
);
|
||||
}
|
||||
|
||||
/** @see 7.2.15 */
|
||||
$getClientDataJSONHash = hash('sha256', $authenticatorAssertionResponse->getClientDataJSON()->getRawData(), true);
|
||||
|
||||
/** @see 7.2.16 */
|
||||
$dataToVerify = $authenticatorAssertionResponse->getAuthenticatorData()->getAuthData().$getClientDataJSONHash;
|
||||
$signature = $authenticatorAssertionResponse->getSignature();
|
||||
$coseKey = new Key($credentialPublicKeyStream->getNormalizedData());
|
||||
$algorithm = $this->algorithmManager->get($coseKey->alg());
|
||||
Assertion::isInstanceOf($algorithm, Signature::class, 'Invalid algorithm identifier. Should refer to a signature algorithm');
|
||||
$signature = CoseSignatureFixer::fix($signature, $algorithm);
|
||||
Assertion::true($algorithm->verify($dataToVerify, $coseKey, $signature), 'Invalid signature.');
|
||||
|
||||
/** @see 7.2.17 */
|
||||
$storedCounter = $publicKeyCredentialSource->getCounter();
|
||||
$responseCounter = $authenticatorAssertionResponse->getAuthenticatorData()->getSignCount();
|
||||
if (0 !== $responseCounter || 0 !== $storedCounter) {
|
||||
$this->counterChecker->check($publicKeyCredentialSource, $responseCounter);
|
||||
}
|
||||
$publicKeyCredentialSource->setCounter($responseCounter);
|
||||
$this->publicKeyCredentialSourceRepository->saveCredentialSource($publicKeyCredentialSource);
|
||||
|
||||
/** @see 7.2.18 */
|
||||
//All good. We can continue.
|
||||
$this->logger->info('The assertion is valid');
|
||||
$this->logger->debug('Public Key Credential Source', ['publicKeyCredentialSource' => $publicKeyCredentialSource]);
|
||||
|
||||
return $publicKeyCredentialSource;
|
||||
} catch (Throwable $throwable) {
|
||||
$this->logger->error('An error occurred', [
|
||||
'exception' => $throwable,
|
||||
]);
|
||||
throw $throwable;
|
||||
}
|
||||
}
|
||||
|
||||
public function setLogger(LoggerInterface $logger): self
|
||||
{
|
||||
$this->logger = $logger;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCounterChecker(CounterChecker $counterChecker): self
|
||||
{
|
||||
$this->counterChecker = $counterChecker;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<PublicKeyCredentialDescriptor> $allowedCredentials
|
||||
*/
|
||||
private function isCredentialIdAllowed(string $credentialId, array $allowedCredentials): bool
|
||||
{
|
||||
foreach ($allowedCredentials as $allowedCredential) {
|
||||
if (hash_equals($allowedCredential->getId(), $credentialId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getFacetId(string $rpId, AuthenticationExtensionsClientInputs $authenticationExtensionsClientInputs, ?AuthenticationExtensionsClientOutputs $authenticationExtensionsClientOutputs): string
|
||||
{
|
||||
if (null === $authenticationExtensionsClientOutputs || !$authenticationExtensionsClientInputs->has('appid') || !$authenticationExtensionsClientOutputs->has('appid')) {
|
||||
return $rpId;
|
||||
}
|
||||
$appId = $authenticationExtensionsClientInputs->get('appid')->value();
|
||||
$wasUsed = $authenticationExtensionsClientOutputs->get('appid')->value();
|
||||
if (!is_string($appId) || true !== $wasUsed) {
|
||||
return $rpId;
|
||||
}
|
||||
|
||||
return $appId;
|
||||
}
|
||||
}
|
38
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AuthenticatorAttestationResponse.php
vendored
Normal file
38
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AuthenticatorAttestationResponse.php
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?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;
|
||||
|
||||
use Webauthn\AttestationStatement\AttestationObject;
|
||||
|
||||
/**
|
||||
* @see https://www.w3.org/TR/webauthn/#authenticatorattestationresponse
|
||||
*/
|
||||
class AuthenticatorAttestationResponse extends AuthenticatorResponse
|
||||
{
|
||||
/**
|
||||
* @var AttestationObject
|
||||
*/
|
||||
private $attestationObject;
|
||||
|
||||
public function __construct(CollectedClientData $clientDataJSON, AttestationObject $attestationObject)
|
||||
{
|
||||
parent::__construct($clientDataJSON);
|
||||
$this->attestationObject = $attestationObject;
|
||||
}
|
||||
|
||||
public function getAttestationObject(): AttestationObject
|
||||
{
|
||||
return $this->attestationObject;
|
||||
}
|
||||
}
|
384
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AuthenticatorAttestationResponseValidator.php
vendored
Normal file
384
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AuthenticatorAttestationResponseValidator.php
vendored
Normal file
|
@ -0,0 +1,384 @@
|
|||
<?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;
|
||||
|
||||
use Assert\Assertion;
|
||||
use function count;
|
||||
use function in_array;
|
||||
use InvalidArgumentException;
|
||||
use function is_string;
|
||||
use LogicException;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use function Safe\parse_url;
|
||||
use function Safe\sprintf;
|
||||
use Throwable;
|
||||
use Webauthn\AttestationStatement\AttestationObject;
|
||||
use Webauthn\AttestationStatement\AttestationStatement;
|
||||
use Webauthn\AttestationStatement\AttestationStatementSupportManager;
|
||||
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
|
||||
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputs;
|
||||
use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler;
|
||||
use Webauthn\CertificateChainChecker\CertificateChainChecker;
|
||||
use Webauthn\MetadataService\MetadataStatement;
|
||||
use Webauthn\MetadataService\MetadataStatementRepository;
|
||||
use Webauthn\MetadataService\StatusReport;
|
||||
use Webauthn\TokenBinding\TokenBindingHandler;
|
||||
use Webauthn\TrustPath\CertificateTrustPath;
|
||||
use Webauthn\TrustPath\EmptyTrustPath;
|
||||
|
||||
class AuthenticatorAttestationResponseValidator
|
||||
{
|
||||
/**
|
||||
* @var AttestationStatementSupportManager
|
||||
*/
|
||||
private $attestationStatementSupportManager;
|
||||
|
||||
/**
|
||||
* @var PublicKeyCredentialSourceRepository
|
||||
*/
|
||||
private $publicKeyCredentialSource;
|
||||
|
||||
/**
|
||||
* @var TokenBindingHandler
|
||||
*/
|
||||
private $tokenBindingHandler;
|
||||
|
||||
/**
|
||||
* @var ExtensionOutputCheckerHandler
|
||||
*/
|
||||
private $extensionOutputCheckerHandler;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* @var MetadataStatementRepository|null
|
||||
*/
|
||||
private $metadataStatementRepository;
|
||||
|
||||
/**
|
||||
* @var CertificateChainChecker|null
|
||||
*/
|
||||
private $certificateChainChecker;
|
||||
|
||||
public function __construct(AttestationStatementSupportManager $attestationStatementSupportManager, PublicKeyCredentialSourceRepository $publicKeyCredentialSource, TokenBindingHandler $tokenBindingHandler, ExtensionOutputCheckerHandler $extensionOutputCheckerHandler, ?MetadataStatementRepository $metadataStatementRepository = null, ?LoggerInterface $logger = null)
|
||||
{
|
||||
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".', E_USER_DEPRECATED);
|
||||
}
|
||||
if (null !== $metadataStatementRepository) {
|
||||
@trigger_error('The argument "metadataStatementRepository" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setMetadataStatementRepository".', E_USER_DEPRECATED);
|
||||
}
|
||||
$this->attestationStatementSupportManager = $attestationStatementSupportManager;
|
||||
$this->publicKeyCredentialSource = $publicKeyCredentialSource;
|
||||
$this->tokenBindingHandler = $tokenBindingHandler;
|
||||
$this->extensionOutputCheckerHandler = $extensionOutputCheckerHandler;
|
||||
$this->metadataStatementRepository = $metadataStatementRepository;
|
||||
$this->logger = $logger ?? new NullLogger();
|
||||
}
|
||||
|
||||
public function setLogger(LoggerInterface $logger): self
|
||||
{
|
||||
$this->logger = $logger;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCertificateChainChecker(CertificateChainChecker $certificateChainChecker): self
|
||||
{
|
||||
$this->certificateChainChecker = $certificateChainChecker;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMetadataStatementRepository(MetadataStatementRepository $metadataStatementRepository): self
|
||||
{
|
||||
$this->metadataStatementRepository = $metadataStatementRepository;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.w3.org/TR/webauthn/#registering-a-new-credential
|
||||
*/
|
||||
public function check(AuthenticatorAttestationResponse $authenticatorAttestationResponse, PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, ServerRequestInterface $request, array $securedRelyingPartyId = []): PublicKeyCredentialSource
|
||||
{
|
||||
try {
|
||||
$this->logger->info('Checking the authenticator attestation response', [
|
||||
'authenticatorAttestationResponse' => $authenticatorAttestationResponse,
|
||||
'publicKeyCredentialCreationOptions' => $publicKeyCredentialCreationOptions,
|
||||
'host' => $request->getUri()->getHost(),
|
||||
]);
|
||||
/** @see 7.1.1 */
|
||||
//Nothing to do
|
||||
|
||||
/** @see 7.1.2 */
|
||||
$C = $authenticatorAttestationResponse->getClientDataJSON();
|
||||
|
||||
/** @see 7.1.3 */
|
||||
Assertion::eq('webauthn.create', $C->getType(), 'The client data type is not "webauthn.create".');
|
||||
|
||||
/** @see 7.1.4 */
|
||||
Assertion::true(hash_equals($publicKeyCredentialCreationOptions->getChallenge(), $C->getChallenge()), 'Invalid challenge.');
|
||||
|
||||
/** @see 7.1.5 */
|
||||
$rpId = $publicKeyCredentialCreationOptions->getRp()->getId() ?? $request->getUri()->getHost();
|
||||
$facetId = $this->getFacetId($rpId, $publicKeyCredentialCreationOptions->getExtensions(), $authenticatorAttestationResponse->getAttestationObject()->getAuthData()->getExtensions());
|
||||
|
||||
$parsedRelyingPartyId = parse_url($C->getOrigin());
|
||||
Assertion::isArray($parsedRelyingPartyId, sprintf('The origin URI "%s" is not valid', $C->getOrigin()));
|
||||
Assertion::keyExists($parsedRelyingPartyId, 'scheme', 'Invalid origin rpId.');
|
||||
$clientDataRpId = $parsedRelyingPartyId['host'] ?? '';
|
||||
Assertion::notEmpty($clientDataRpId, 'Invalid origin rpId.');
|
||||
$rpIdLength = mb_strlen($facetId);
|
||||
Assertion::eq(mb_substr('.'.$clientDataRpId, -($rpIdLength + 1)), '.'.$facetId, 'rpId mismatch.');
|
||||
|
||||
if (!in_array($facetId, $securedRelyingPartyId, true)) {
|
||||
$scheme = $parsedRelyingPartyId['scheme'] ?? '';
|
||||
Assertion::eq('https', $scheme, 'Invalid scheme. HTTPS required.');
|
||||
}
|
||||
|
||||
/** @see 7.1.6 */
|
||||
if (null !== $C->getTokenBinding()) {
|
||||
$this->tokenBindingHandler->check($C->getTokenBinding(), $request);
|
||||
}
|
||||
|
||||
/** @see 7.1.7 */
|
||||
$clientDataJSONHash = hash('sha256', $authenticatorAttestationResponse->getClientDataJSON()->getRawData(), true);
|
||||
|
||||
/** @see 7.1.8 */
|
||||
$attestationObject = $authenticatorAttestationResponse->getAttestationObject();
|
||||
|
||||
/** @see 7.1.9 */
|
||||
$rpIdHash = hash('sha256', $facetId, true);
|
||||
Assertion::true(hash_equals($rpIdHash, $attestationObject->getAuthData()->getRpIdHash()), 'rpId hash mismatch.');
|
||||
|
||||
/** @see 7.1.10 */
|
||||
Assertion::true($attestationObject->getAuthData()->isUserPresent(), 'User was not present');
|
||||
/** @see 7.1.11 */
|
||||
if (AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED === $publicKeyCredentialCreationOptions->getAuthenticatorSelection()->getUserVerification()) {
|
||||
Assertion::true($attestationObject->getAuthData()->isUserVerified(), 'User authentication required.');
|
||||
}
|
||||
|
||||
/** @see 7.1.12 */
|
||||
$extensionsClientOutputs = $attestationObject->getAuthData()->getExtensions();
|
||||
if (null !== $extensionsClientOutputs) {
|
||||
$this->extensionOutputCheckerHandler->check(
|
||||
$publicKeyCredentialCreationOptions->getExtensions(),
|
||||
$extensionsClientOutputs
|
||||
);
|
||||
}
|
||||
|
||||
/** @see 7.1.13 */
|
||||
$this->checkMetadataStatement($publicKeyCredentialCreationOptions, $attestationObject);
|
||||
$fmt = $attestationObject->getAttStmt()->getFmt();
|
||||
Assertion::true($this->attestationStatementSupportManager->has($fmt), 'Unsupported attestation statement format.');
|
||||
|
||||
/** @see 7.1.14 */
|
||||
$attestationStatementSupport = $this->attestationStatementSupportManager->get($fmt);
|
||||
Assertion::true($attestationStatementSupport->isValid($clientDataJSONHash, $attestationObject->getAttStmt(), $attestationObject->getAuthData()), 'Invalid attestation statement.');
|
||||
|
||||
/** @see 7.1.15 */
|
||||
/** @see 7.1.16 */
|
||||
/** @see 7.1.17 */
|
||||
Assertion::true($attestationObject->getAuthData()->hasAttestedCredentialData(), 'There is no attested credential data.');
|
||||
$attestedCredentialData = $attestationObject->getAuthData()->getAttestedCredentialData();
|
||||
Assertion::notNull($attestedCredentialData, 'There is no attested credential data.');
|
||||
$credentialId = $attestedCredentialData->getCredentialId();
|
||||
Assertion::null($this->publicKeyCredentialSource->findOneByCredentialId($credentialId), 'The credential ID already exists.');
|
||||
|
||||
/** @see 7.1.18 */
|
||||
/** @see 7.1.19 */
|
||||
$publicKeyCredentialSource = $this->createPublicKeyCredentialSource(
|
||||
$credentialId,
|
||||
$attestedCredentialData,
|
||||
$attestationObject,
|
||||
$publicKeyCredentialCreationOptions->getUser()->getId()
|
||||
);
|
||||
$this->logger->info('The attestation is valid');
|
||||
$this->logger->debug('Public Key Credential Source', ['publicKeyCredentialSource' => $publicKeyCredentialSource]);
|
||||
|
||||
return $publicKeyCredentialSource;
|
||||
} catch (Throwable $throwable) {
|
||||
$this->logger->error('An error occurred', [
|
||||
'exception' => $throwable,
|
||||
]);
|
||||
throw $throwable;
|
||||
}
|
||||
}
|
||||
|
||||
private function checkCertificateChain(AttestationStatement $attestationStatement, ?MetadataStatement $metadataStatement): void
|
||||
{
|
||||
$trustPath = $attestationStatement->getTrustPath();
|
||||
if (!$trustPath instanceof CertificateTrustPath) {
|
||||
return;
|
||||
}
|
||||
$authenticatorCertificates = $trustPath->getCertificates();
|
||||
|
||||
if (null === $metadataStatement) {
|
||||
// @phpstan-ignore-next-line
|
||||
null === $this->certificateChainChecker ? CertificateToolbox::checkChain($authenticatorCertificates) : $this->certificateChainChecker->check($authenticatorCertificates, [], null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$metadataStatementCertificates = $metadataStatement->getAttestationRootCertificates();
|
||||
$rootStatementCertificates = $metadataStatement->getRootCertificates();
|
||||
foreach ($metadataStatementCertificates as $key => $metadataStatementCertificate) {
|
||||
$metadataStatementCertificates[$key] = CertificateToolbox::fixPEMStructure($metadataStatementCertificate);
|
||||
}
|
||||
$trustedCertificates = array_merge(
|
||||
$metadataStatementCertificates,
|
||||
$rootStatementCertificates
|
||||
);
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
null === $this->certificateChainChecker ? CertificateToolbox::checkChain($authenticatorCertificates, $trustedCertificates) : $this->certificateChainChecker->check($authenticatorCertificates, $trustedCertificates);
|
||||
}
|
||||
|
||||
private function checkMetadataStatement(PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, AttestationObject $attestationObject): void
|
||||
{
|
||||
$attestationStatement = $attestationObject->getAttStmt();
|
||||
$attestedCredentialData = $attestationObject->getAuthData()->getAttestedCredentialData();
|
||||
Assertion::notNull($attestedCredentialData, 'No attested credential data found');
|
||||
$aaguid = $attestedCredentialData->getAaguid()->toString();
|
||||
if (PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE === $publicKeyCredentialCreationOptions->getAttestation()) {
|
||||
$this->logger->debug('No attestation is asked.');
|
||||
//No attestation is asked. We shall ensure that the data is anonymous.
|
||||
if (
|
||||
'00000000-0000-0000-0000-000000000000' === $aaguid
|
||||
&& (AttestationStatement::TYPE_NONE === $attestationStatement->getType() || AttestationStatement::TYPE_SELF === $attestationStatement->getType())) {
|
||||
$this->logger->debug('The Attestation Statement is anonymous.');
|
||||
$this->checkCertificateChain($attestationStatement, null);
|
||||
|
||||
return;
|
||||
}
|
||||
$this->logger->debug('Anonymization required. AAGUID and Attestation Statement changed.', [
|
||||
'aaguid' => $aaguid,
|
||||
'AttestationStatement' => $attestationStatement,
|
||||
]);
|
||||
$attestedCredentialData->setAaguid(
|
||||
Uuid::fromString('00000000-0000-0000-0000-000000000000')
|
||||
);
|
||||
$attestationObject->setAttStmt(AttestationStatement::createNone('none', [], new EmptyTrustPath()));
|
||||
|
||||
return;
|
||||
}
|
||||
if (AttestationStatement::TYPE_NONE === $attestationStatement->getType()) {
|
||||
$this->logger->debug('No attestation returned.');
|
||||
//No attestation is returned. We shall ensure that the AAGUID is a null one.
|
||||
if ('00000000-0000-0000-0000-000000000000' !== $aaguid) {
|
||||
$this->logger->debug('Anonymization required. AAGUID and Attestation Statement changed.', [
|
||||
'aaguid' => $aaguid,
|
||||
'AttestationStatement' => $attestationStatement,
|
||||
]);
|
||||
$attestedCredentialData->setAaguid(
|
||||
Uuid::fromString('00000000-0000-0000-0000-000000000000')
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//The MDS Repository is mandatory here
|
||||
Assertion::notNull($this->metadataStatementRepository, 'The Metadata Statement Repository is mandatory when requesting attestation objects.');
|
||||
$metadataStatement = $this->metadataStatementRepository->findOneByAAGUID($aaguid);
|
||||
|
||||
// We check the last status report
|
||||
$this->checkStatusReport(null === $metadataStatement ? [] : $metadataStatement->getStatusReports());
|
||||
|
||||
// We check the certificate chain (if any)
|
||||
$this->checkCertificateChain($attestationStatement, $metadataStatement);
|
||||
|
||||
// If no Attestation Statement has been returned or if null AAGUID (=00000000-0000-0000-0000-000000000000)
|
||||
// => nothing to check
|
||||
if ('00000000-0000-0000-0000-000000000000' === $aaguid || AttestationStatement::TYPE_NONE === $attestationStatement->getType()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// At this point, the Metadata Statement is mandatory
|
||||
Assertion::notNull($metadataStatement, sprintf('The Metadata Statement for the AAGUID "%s" is missing', $aaguid));
|
||||
|
||||
// Check Attestation Type is allowed
|
||||
if (0 !== count($metadataStatement->getAttestationTypes())) {
|
||||
$type = $this->getAttestationType($attestationStatement);
|
||||
Assertion::inArray($type, $metadataStatement->getAttestationTypes(), 'Invalid attestation statement. The attestation type is not allowed for this authenticator');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param StatusReport[] $statusReports
|
||||
*/
|
||||
private function checkStatusReport(array $statusReports): void
|
||||
{
|
||||
if (0 !== count($statusReports)) {
|
||||
$lastStatusReport = end($statusReports);
|
||||
if ($lastStatusReport->isCompromised()) {
|
||||
throw new LogicException('The authenticator is compromised and cannot be used');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function createPublicKeyCredentialSource(string $credentialId, AttestedCredentialData $attestedCredentialData, AttestationObject $attestationObject, string $userHandle): PublicKeyCredentialSource
|
||||
{
|
||||
return new PublicKeyCredentialSource(
|
||||
$credentialId,
|
||||
PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY,
|
||||
[],
|
||||
$attestationObject->getAttStmt()->getType(),
|
||||
$attestationObject->getAttStmt()->getTrustPath(),
|
||||
$attestedCredentialData->getAaguid(),
|
||||
$attestedCredentialData->getCredentialPublicKey(),
|
||||
$userHandle,
|
||||
$attestationObject->getAuthData()->getSignCount()
|
||||
);
|
||||
}
|
||||
|
||||
private function getAttestationType(AttestationStatement $attestationStatement): int
|
||||
{
|
||||
switch ($attestationStatement->getType()) {
|
||||
case AttestationStatement::TYPE_BASIC:
|
||||
return MetadataStatement::ATTESTATION_BASIC_FULL;
|
||||
case AttestationStatement::TYPE_SELF:
|
||||
return MetadataStatement::ATTESTATION_BASIC_SURROGATE;
|
||||
case AttestationStatement::TYPE_ATTCA:
|
||||
return MetadataStatement::ATTESTATION_ATTCA;
|
||||
case AttestationStatement::TYPE_ECDAA:
|
||||
return MetadataStatement::ATTESTATION_ECDAA;
|
||||
default:
|
||||
throw new InvalidArgumentException('Invalid attestation type');
|
||||
}
|
||||
}
|
||||
|
||||
private function getFacetId(string $rpId, AuthenticationExtensionsClientInputs $authenticationExtensionsClientInputs, ?AuthenticationExtensionsClientOutputs $authenticationExtensionsClientOutputs): string
|
||||
{
|
||||
if (null === $authenticationExtensionsClientOutputs || !$authenticationExtensionsClientInputs->has('appid') || !$authenticationExtensionsClientOutputs->has('appid')) {
|
||||
return $rpId;
|
||||
}
|
||||
$appId = $authenticationExtensionsClientInputs->get('appid')->value();
|
||||
$wasUsed = $authenticationExtensionsClientOutputs->get('appid')->value();
|
||||
if (!is_string($appId) || true !== $wasUsed) {
|
||||
return $rpId;
|
||||
}
|
||||
|
||||
return $appId;
|
||||
}
|
||||
}
|
124
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AuthenticatorData.php
vendored
Normal file
124
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AuthenticatorData.php
vendored
Normal file
|
@ -0,0 +1,124 @@
|
|||
<?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;
|
||||
|
||||
use function ord;
|
||||
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputs;
|
||||
|
||||
/**
|
||||
* @see https://www.w3.org/TR/webauthn/#sec-authenticator-data
|
||||
*/
|
||||
class AuthenticatorData
|
||||
{
|
||||
private const FLAG_UP = 0b00000001;
|
||||
private const FLAG_RFU1 = 0b00000010;
|
||||
private const FLAG_UV = 0b00000100;
|
||||
private const FLAG_RFU2 = 0b00111000;
|
||||
private const FLAG_AT = 0b01000000;
|
||||
private const FLAG_ED = 0b10000000;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $authData;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $rpIdHash;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $flags;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $signCount;
|
||||
|
||||
/**
|
||||
* @var AttestedCredentialData|null
|
||||
*/
|
||||
protected $attestedCredentialData;
|
||||
|
||||
/**
|
||||
* @var AuthenticationExtensionsClientOutputs|null
|
||||
*/
|
||||
protected $extensions;
|
||||
|
||||
public function __construct(string $authData, string $rpIdHash, string $flags, int $signCount, ?AttestedCredentialData $attestedCredentialData, ?AuthenticationExtensionsClientOutputs $extensions)
|
||||
{
|
||||
$this->rpIdHash = $rpIdHash;
|
||||
$this->flags = $flags;
|
||||
$this->signCount = $signCount;
|
||||
$this->attestedCredentialData = $attestedCredentialData;
|
||||
$this->extensions = $extensions;
|
||||
$this->authData = $authData;
|
||||
}
|
||||
|
||||
public function getAuthData(): string
|
||||
{
|
||||
return $this->authData;
|
||||
}
|
||||
|
||||
public function getRpIdHash(): string
|
||||
{
|
||||
return $this->rpIdHash;
|
||||
}
|
||||
|
||||
public function isUserPresent(): bool
|
||||
{
|
||||
return 0 !== (ord($this->flags) & self::FLAG_UP) ? true : false;
|
||||
}
|
||||
|
||||
public function isUserVerified(): bool
|
||||
{
|
||||
return 0 !== (ord($this->flags) & self::FLAG_UV) ? true : false;
|
||||
}
|
||||
|
||||
public function hasAttestedCredentialData(): bool
|
||||
{
|
||||
return 0 !== (ord($this->flags) & self::FLAG_AT) ? true : false;
|
||||
}
|
||||
|
||||
public function hasExtensions(): bool
|
||||
{
|
||||
return 0 !== (ord($this->flags) & self::FLAG_ED) ? true : false;
|
||||
}
|
||||
|
||||
public function getReservedForFutureUse1(): int
|
||||
{
|
||||
return ord($this->flags) & self::FLAG_RFU1;
|
||||
}
|
||||
|
||||
public function getReservedForFutureUse2(): int
|
||||
{
|
||||
return ord($this->flags) & self::FLAG_RFU2;
|
||||
}
|
||||
|
||||
public function getSignCount(): int
|
||||
{
|
||||
return $this->signCount;
|
||||
}
|
||||
|
||||
public function getAttestedCredentialData(): ?AttestedCredentialData
|
||||
{
|
||||
return $this->attestedCredentialData;
|
||||
}
|
||||
|
||||
public function getExtensions(): ?AuthenticationExtensionsClientOutputs
|
||||
{
|
||||
return null !== $this->extensions && $this->hasExtensions() ? $this->extensions : null;
|
||||
}
|
||||
}
|
35
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AuthenticatorResponse.php
vendored
Normal file
35
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AuthenticatorResponse.php
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?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;
|
||||
|
||||
/**
|
||||
* @see https://www.w3.org/TR/webauthn/#authenticatorresponse
|
||||
*/
|
||||
abstract class AuthenticatorResponse
|
||||
{
|
||||
/**
|
||||
* @var CollectedClientData
|
||||
*/
|
||||
private $clientDataJSON;
|
||||
|
||||
public function __construct(CollectedClientData $clientDataJSON)
|
||||
{
|
||||
$this->clientDataJSON = $clientDataJSON;
|
||||
}
|
||||
|
||||
public function getClientDataJSON(): CollectedClientData
|
||||
{
|
||||
return $this->clientDataJSON;
|
||||
}
|
||||
}
|
167
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AuthenticatorSelectionCriteria.php
vendored
Normal file
167
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/AuthenticatorSelectionCriteria.php
vendored
Normal file
|
@ -0,0 +1,167 @@
|
|||
<?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;
|
||||
|
||||
use Assert\Assertion;
|
||||
use JsonSerializable;
|
||||
use function Safe\json_decode;
|
||||
|
||||
class AuthenticatorSelectionCriteria implements JsonSerializable
|
||||
{
|
||||
public const AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE = null;
|
||||
public const AUTHENTICATOR_ATTACHMENT_PLATFORM = 'platform';
|
||||
public const AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM = 'cross-platform';
|
||||
|
||||
public const USER_VERIFICATION_REQUIREMENT_REQUIRED = 'required';
|
||||
public const USER_VERIFICATION_REQUIREMENT_PREFERRED = 'preferred';
|
||||
public const USER_VERIFICATION_REQUIREMENT_DISCOURAGED = 'discouraged';
|
||||
|
||||
public const RESIDENT_KEY_REQUIREMENT_NONE = null;
|
||||
public const RESIDENT_KEY_REQUIREMENT_REQUIRED = 'required';
|
||||
public const RESIDENT_KEY_REQUIREMENT_PREFERRED = 'preferred';
|
||||
public const RESIDENT_KEY_REQUIREMENT_DISCOURAGED = 'discouraged';
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $authenticatorAttachment;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $requireResidentKey;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $userVerification;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $residentKey;
|
||||
|
||||
public function __construct(?string $authenticatorAttachment = null, ?bool $requireResidentKey = null, ?string $userVerification = null, ?string $residentKey = null)
|
||||
{
|
||||
if (null !== $authenticatorAttachment) {
|
||||
@trigger_error('The argument "authenticatorAttachment" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setAuthenticatorAttachment".', E_USER_DEPRECATED);
|
||||
}
|
||||
if (null !== $requireResidentKey) {
|
||||
@trigger_error('The argument "requireResidentKey" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setRequireResidentKey".', E_USER_DEPRECATED);
|
||||
}
|
||||
if (null !== $userVerification) {
|
||||
@trigger_error('The argument "userVerification" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setUserVerification".', E_USER_DEPRECATED);
|
||||
}
|
||||
if (null !== $residentKey) {
|
||||
@trigger_error('The argument "residentKey" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setResidentKey".', E_USER_DEPRECATED);
|
||||
}
|
||||
$this->authenticatorAttachment = $authenticatorAttachment;
|
||||
$this->requireResidentKey = $requireResidentKey ?? false;
|
||||
$this->userVerification = $userVerification ?? self::USER_VERIFICATION_REQUIREMENT_PREFERRED;
|
||||
$this->residentKey = $residentKey ?? self::RESIDENT_KEY_REQUIREMENT_NONE;
|
||||
}
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function setAuthenticatorAttachment(?string $authenticatorAttachment): self
|
||||
{
|
||||
$this->authenticatorAttachment = $authenticatorAttachment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setRequireResidentKey(bool $requireResidentKey): self
|
||||
{
|
||||
$this->requireResidentKey = $requireResidentKey;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setUserVerification(string $userVerification): self
|
||||
{
|
||||
$this->userVerification = $userVerification;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setResidentKey(?string $residentKey): self
|
||||
{
|
||||
$this->residentKey = $residentKey;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAuthenticatorAttachment(): ?string
|
||||
{
|
||||
return $this->authenticatorAttachment;
|
||||
}
|
||||
|
||||
public function isRequireResidentKey(): bool
|
||||
{
|
||||
return $this->requireResidentKey;
|
||||
}
|
||||
|
||||
public function getUserVerification(): string
|
||||
{
|
||||
return $this->userVerification;
|
||||
}
|
||||
|
||||
public function getResidentKey(): ?string
|
||||
{
|
||||
return $this->residentKey;
|
||||
}
|
||||
|
||||
public static function createFromString(string $data): self
|
||||
{
|
||||
$data = json_decode($data, true);
|
||||
Assertion::isArray($data, 'Invalid data');
|
||||
|
||||
return self::createFromArray($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $json
|
||||
*/
|
||||
public static function createFromArray(array $json): self
|
||||
{
|
||||
return self::create()
|
||||
->setAuthenticatorAttachment($json['authenticatorAttachment'] ?? null)
|
||||
->setRequireResidentKey($json['requireResidentKey'] ?? false)
|
||||
->setUserVerification($json['userVerification'] ?? self::USER_VERIFICATION_REQUIREMENT_PREFERRED)
|
||||
->setResidentKey($json['residentKey'] ?? self::RESIDENT_KEY_REQUIREMENT_NONE)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$json = [
|
||||
'requireResidentKey' => $this->requireResidentKey,
|
||||
'userVerification' => $this->userVerification,
|
||||
];
|
||||
if (null !== $this->authenticatorAttachment) {
|
||||
$json['authenticatorAttachment'] = $this->authenticatorAttachment;
|
||||
}
|
||||
if (null !== $this->residentKey) {
|
||||
$json['residentKey'] = $this->residentKey;
|
||||
}
|
||||
|
||||
return $json;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?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\CertificateChainChecker;
|
||||
|
||||
interface CertificateChainChecker
|
||||
{
|
||||
/**
|
||||
* @param string[] $authenticatorCertificates
|
||||
* @param string[] $trustedCertificates
|
||||
*/
|
||||
public function check(array $authenticatorCertificates, array $trustedCertificates): void;
|
||||
}
|
|
@ -0,0 +1,239 @@
|
|||
<?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\CertificateChainChecker;
|
||||
|
||||
use Assert\Assertion;
|
||||
use function count;
|
||||
use InvalidArgumentException;
|
||||
use function is_int;
|
||||
use Psr\Http\Client\ClientInterface;
|
||||
use Psr\Http\Message\RequestFactoryInterface;
|
||||
use RuntimeException;
|
||||
use Safe\Exceptions\FilesystemException;
|
||||
use function Safe\file_put_contents;
|
||||
use function Safe\ksort;
|
||||
use function Safe\mkdir;
|
||||
use function Safe\rename;
|
||||
use function Safe\sprintf;
|
||||
use function Safe\tempnam;
|
||||
use function Safe\unlink;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
final class OpenSSLCertificateChainChecker implements CertificateChainChecker
|
||||
{
|
||||
/**
|
||||
* @var ClientInterface
|
||||
*/
|
||||
private $client;
|
||||
|
||||
/**
|
||||
* @var RequestFactoryInterface
|
||||
*/
|
||||
private $requestFactory;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $rootCertificates = [];
|
||||
|
||||
public function __construct(ClientInterface $client, RequestFactoryInterface $requestFactory)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->requestFactory = $requestFactory;
|
||||
}
|
||||
|
||||
public function addRootCertificate(string $certificate): self
|
||||
{
|
||||
$this->rootCertificates[] = $certificate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $authenticatorCertificates
|
||||
* @param string[] $trustedCertificates
|
||||
*/
|
||||
public function check(array $authenticatorCertificates, array $trustedCertificates): void
|
||||
{
|
||||
if (0 === count($trustedCertificates)) {
|
||||
$this->checkCertificatesValidity($authenticatorCertificates, true);
|
||||
|
||||
return;
|
||||
}
|
||||
$this->checkCertificatesValidity($authenticatorCertificates, false);
|
||||
|
||||
$hasCrls = false;
|
||||
$processArguments = ['-no-CAfile', '-no-CApath'];
|
||||
|
||||
$caDirname = $this->createTemporaryDirectory();
|
||||
$processArguments[] = '--CApath';
|
||||
$processArguments[] = $caDirname;
|
||||
|
||||
foreach ($trustedCertificates as $certificate) {
|
||||
$this->saveToTemporaryFile($caDirname, $certificate, 'webauthn-trusted-', '.pem');
|
||||
$crl = $this->getCrls($certificate);
|
||||
if ('' !== $crl) {
|
||||
$hasCrls = true;
|
||||
$this->saveToTemporaryFile($caDirname, $crl, 'webauthn-trusted-crl-', '.crl');
|
||||
}
|
||||
}
|
||||
|
||||
$rehashProcess = new Process(['openssl', 'rehash', $caDirname]);
|
||||
$rehashProcess->run();
|
||||
while ($rehashProcess->isRunning()) {
|
||||
//Just wait
|
||||
}
|
||||
if (!$rehashProcess->isSuccessful()) {
|
||||
throw new InvalidArgumentException('Invalid certificate or certificate chain');
|
||||
}
|
||||
|
||||
$filenames = [];
|
||||
$leafCertificate = array_shift($authenticatorCertificates);
|
||||
$leafFilename = $this->saveToTemporaryFile(sys_get_temp_dir(), $leafCertificate, 'webauthn-leaf-', '.pem');
|
||||
$crl = $this->getCrls($leafCertificate);
|
||||
if ('' !== $crl) {
|
||||
$hasCrls = true;
|
||||
$this->saveToTemporaryFile($caDirname, $crl, 'webauthn-leaf-crl-', '.pem');
|
||||
}
|
||||
$filenames[] = $leafFilename;
|
||||
|
||||
foreach ($authenticatorCertificates as $certificate) {
|
||||
$untrustedFilename = $this->saveToTemporaryFile(sys_get_temp_dir(), $certificate, 'webauthn-untrusted-', '.pem');
|
||||
$crl = $this->getCrls($certificate);
|
||||
if ('' !== $crl) {
|
||||
$hasCrls = true;
|
||||
$this->saveToTemporaryFile($caDirname, $crl, 'webauthn-untrusted-crl-', '.pem');
|
||||
}
|
||||
$processArguments[] = '-untrusted';
|
||||
$processArguments[] = $untrustedFilename;
|
||||
$filenames[] = $untrustedFilename;
|
||||
}
|
||||
|
||||
$processArguments[] = $leafFilename;
|
||||
if ($hasCrls) {
|
||||
array_unshift($processArguments, '-crl_check');
|
||||
array_unshift($processArguments, '-crl_check_all');
|
||||
//array_unshift($processArguments, '-crl_download');
|
||||
array_unshift($processArguments, '-extended_crl');
|
||||
}
|
||||
array_unshift($processArguments, 'openssl', 'verify');
|
||||
|
||||
$process = new Process($processArguments);
|
||||
$process->run();
|
||||
while ($process->isRunning()) {
|
||||
//Just wait
|
||||
}
|
||||
|
||||
foreach ($filenames as $filename) {
|
||||
try {
|
||||
unlink($filename);
|
||||
} catch (FilesystemException $e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$this->deleteDirectory($caDirname);
|
||||
|
||||
if (!$process->isSuccessful()) {
|
||||
throw new InvalidArgumentException('Invalid certificate or certificate chain');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $certificates
|
||||
*/
|
||||
private function checkCertificatesValidity(array $certificates, bool $allowRootCertificate): void
|
||||
{
|
||||
foreach ($certificates as $certificate) {
|
||||
$parsed = openssl_x509_parse($certificate);
|
||||
Assertion::isArray($parsed, 'Unable to read the certificate');
|
||||
if (false === $allowRootCertificate) {
|
||||
$this->checkRootCertificate($parsed);
|
||||
}
|
||||
|
||||
Assertion::keyExists($parsed, 'validTo_time_t', 'The certificate has no validity period');
|
||||
Assertion::keyExists($parsed, 'validFrom_time_t', 'The certificate has no validity period');
|
||||
Assertion::lessOrEqualThan(time(), $parsed['validTo_time_t'], 'The certificate expired');
|
||||
Assertion::greaterOrEqualThan(time(), $parsed['validFrom_time_t'], 'The certificate is not usable yet');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $parsed
|
||||
*/
|
||||
private function checkRootCertificate(array $parsed): void
|
||||
{
|
||||
Assertion::keyExists($parsed, 'subject', 'The certificate has no subject');
|
||||
Assertion::keyExists($parsed, 'issuer', 'The certificate has no issuer');
|
||||
$subject = $parsed['subject'];
|
||||
$issuer = $parsed['issuer'];
|
||||
ksort($subject);
|
||||
ksort($issuer);
|
||||
Assertion::notEq($subject, $issuer, 'Root certificates are not allowed');
|
||||
}
|
||||
|
||||
private function createTemporaryDirectory(): string
|
||||
{
|
||||
$caDir = tempnam(sys_get_temp_dir(), 'webauthn-ca-');
|
||||
if (file_exists($caDir)) {
|
||||
unlink($caDir);
|
||||
}
|
||||
mkdir($caDir);
|
||||
if (!is_dir($caDir)) {
|
||||
throw new RuntimeException(sprintf('Directory "%s" was not created', $caDir));
|
||||
}
|
||||
|
||||
return $caDir;
|
||||
}
|
||||
|
||||
private function deleteDirectory(string $dirname): void
|
||||
{
|
||||
$rehashProcess = new Process(['rm', '-rf', $dirname]);
|
||||
$rehashProcess->run();
|
||||
while ($rehashProcess->isRunning()) {
|
||||
//Just wait
|
||||
}
|
||||
}
|
||||
|
||||
private function saveToTemporaryFile(string $folder, string $certificate, string $prefix, string $suffix): string
|
||||
{
|
||||
$filename = tempnam($folder, $prefix);
|
||||
rename($filename, $filename.$suffix);
|
||||
file_put_contents($filename.$suffix, $certificate, FILE_APPEND);
|
||||
|
||||
return $filename.$suffix;
|
||||
}
|
||||
|
||||
private function getCrls(string $certificate): string
|
||||
{
|
||||
$parsed = openssl_x509_parse($certificate);
|
||||
if (false === $parsed || !isset($parsed['extensions']['crlDistributionPoints'])) {
|
||||
return '';
|
||||
}
|
||||
$endpoint = $parsed['extensions']['crlDistributionPoints'];
|
||||
$pos = mb_strpos($endpoint, 'URI:');
|
||||
if (!is_int($pos)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$endpoint = trim(mb_substr($endpoint, $pos + 4));
|
||||
$request = $this->requestFactory->createRequest('GET', $endpoint);
|
||||
$response = $this->client->sendRequest($request);
|
||||
|
||||
if (200 !== $response->getStatusCode()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $response->getBody()->getContents();
|
||||
}
|
||||
}
|
223
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/CertificateToolbox.php
vendored
Normal file
223
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/CertificateToolbox.php
vendored
Normal file
|
@ -0,0 +1,223 @@
|
|||
<?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;
|
||||
|
||||
use Assert\Assertion;
|
||||
use function count;
|
||||
use function in_array;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Safe\Exceptions\FilesystemException;
|
||||
use function Safe\file_put_contents;
|
||||
use function Safe\ksort;
|
||||
use function Safe\mkdir;
|
||||
use function Safe\rename;
|
||||
use function Safe\sprintf;
|
||||
use function Safe\tempnam;
|
||||
use function Safe\unlink;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
class CertificateToolbox
|
||||
{
|
||||
/**
|
||||
* @deprecated "This method is deprecated since v3.3 and will be removed en v4.0. Please use Webauthn\CertificateChainChecker\CertificateChainChecker instead"
|
||||
*
|
||||
* @param string[] $authenticatorCertificates
|
||||
* @param string[] $trustedCertificates
|
||||
*/
|
||||
public static function checkChain(array $authenticatorCertificates, array $trustedCertificates = []): void
|
||||
{
|
||||
if (0 === count($trustedCertificates)) {
|
||||
self::checkCertificatesValidity($authenticatorCertificates, true);
|
||||
|
||||
return;
|
||||
}
|
||||
self::checkCertificatesValidity($authenticatorCertificates, false);
|
||||
|
||||
$processArguments = ['-no-CAfile', '-no-CApath'];
|
||||
|
||||
$caDirname = self::createTemporaryDirectory();
|
||||
$processArguments[] = '--CApath';
|
||||
$processArguments[] = $caDirname;
|
||||
|
||||
foreach ($trustedCertificates as $certificate) {
|
||||
self::prepareCertificate($caDirname, $certificate, 'webauthn-trusted-', '.pem');
|
||||
}
|
||||
|
||||
$rehashProcess = new Process(['openssl', 'rehash', $caDirname]);
|
||||
$rehashProcess->run();
|
||||
while ($rehashProcess->isRunning()) {
|
||||
//Just wait
|
||||
}
|
||||
if (!$rehashProcess->isSuccessful()) {
|
||||
throw new InvalidArgumentException('Invalid certificate or certificate chain');
|
||||
}
|
||||
|
||||
$filenames = [];
|
||||
$leafCertificate = array_shift($authenticatorCertificates);
|
||||
$leafFilename = self::prepareCertificate(sys_get_temp_dir(), $leafCertificate, 'webauthn-leaf-', '.pem');
|
||||
$filenames[] = $leafFilename;
|
||||
|
||||
foreach ($authenticatorCertificates as $certificate) {
|
||||
$untrustedFilename = self::prepareCertificate(sys_get_temp_dir(), $certificate, 'webauthn-untrusted-', '.pem');
|
||||
$processArguments[] = '-untrusted';
|
||||
$processArguments[] = $untrustedFilename;
|
||||
$filenames[] = $untrustedFilename;
|
||||
}
|
||||
|
||||
$processArguments[] = $leafFilename;
|
||||
array_unshift($processArguments, 'openssl', 'verify');
|
||||
|
||||
$process = new Process($processArguments);
|
||||
$process->run();
|
||||
while ($process->isRunning()) {
|
||||
//Just wait
|
||||
}
|
||||
|
||||
foreach ($filenames as $filename) {
|
||||
try {
|
||||
unlink($filename);
|
||||
} catch (FilesystemException $e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
self::deleteDirectory($caDirname);
|
||||
|
||||
if (!$process->isSuccessful()) {
|
||||
throw new InvalidArgumentException('Invalid certificate or certificate chain');
|
||||
}
|
||||
}
|
||||
|
||||
public static function fixPEMStructure(string $certificate, string $type = 'CERTIFICATE'): string
|
||||
{
|
||||
$pemCert = '-----BEGIN '.$type.'-----'.PHP_EOL;
|
||||
$pemCert .= chunk_split($certificate, 64, PHP_EOL);
|
||||
$pemCert .= '-----END '.$type.'-----'.PHP_EOL;
|
||||
|
||||
return $pemCert;
|
||||
}
|
||||
|
||||
public static function convertDERToPEM(string $certificate, string $type = 'CERTIFICATE'): string
|
||||
{
|
||||
$derCertificate = self::unusedBytesFix($certificate);
|
||||
|
||||
return self::fixPEMStructure(base64_encode($derCertificate), $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $certificates
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function convertAllDERToPEM(array $certificates, string $type = 'CERTIFICATE'): array
|
||||
{
|
||||
$certs = [];
|
||||
foreach ($certificates as $publicKey) {
|
||||
$certs[] = self::convertDERToPEM($publicKey, $type);
|
||||
}
|
||||
|
||||
return $certs;
|
||||
}
|
||||
|
||||
private static function unusedBytesFix(string $certificate): string
|
||||
{
|
||||
$certificateHash = hash('sha256', $certificate);
|
||||
if (in_array($certificateHash, self::getCertificateHashes(), true)) {
|
||||
$certificate[mb_strlen($certificate, '8bit') - 257] = "\0";
|
||||
}
|
||||
|
||||
return $certificate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $certificates
|
||||
*/
|
||||
private static function checkCertificatesValidity(array $certificates, bool $allowRootCertificate): void
|
||||
{
|
||||
foreach ($certificates as $certificate) {
|
||||
$parsed = openssl_x509_parse($certificate);
|
||||
Assertion::isArray($parsed, 'Unable to read the certificate');
|
||||
if (false === $allowRootCertificate) {
|
||||
self::checkRootCertificate($parsed);
|
||||
}
|
||||
|
||||
Assertion::keyExists($parsed, 'validTo_time_t', 'The certificate has no validity period');
|
||||
Assertion::keyExists($parsed, 'validFrom_time_t', 'The certificate has no validity period');
|
||||
Assertion::lessOrEqualThan(time(), $parsed['validTo_time_t'], 'The certificate expired');
|
||||
Assertion::greaterOrEqualThan(time(), $parsed['validFrom_time_t'], 'The certificate is not usable yet');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $parsed
|
||||
*/
|
||||
private static function checkRootCertificate(array $parsed): void
|
||||
{
|
||||
Assertion::keyExists($parsed, 'subject', 'The certificate has no subject');
|
||||
Assertion::keyExists($parsed, 'issuer', 'The certificate has no issuer');
|
||||
$subject = $parsed['subject'];
|
||||
$issuer = $parsed['issuer'];
|
||||
ksort($subject);
|
||||
ksort($issuer);
|
||||
Assertion::notEq($subject, $issuer, 'Root certificates are not allowed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private static function getCertificateHashes(): array
|
||||
{
|
||||
return [
|
||||
'349bca1031f8c82c4ceca38b9cebf1a69df9fb3b94eed99eb3fb9aa3822d26e8',
|
||||
'dd574527df608e47ae45fbba75a2afdd5c20fd94a02419381813cd55a2a3398f',
|
||||
'1d8764f0f7cd1352df6150045c8f638e517270e8b5dda1c63ade9c2280240cae',
|
||||
'd0edc9a91a1677435a953390865d208c55b3183c6759c9b5a7ff494c322558eb',
|
||||
'6073c436dcd064a48127ddbf6032ac1a66fd59a0c24434f070d4e564c124c897',
|
||||
'ca993121846c464d666096d35f13bf44c1b05af205f9b4a1e00cf6cc10c5e511',
|
||||
];
|
||||
}
|
||||
|
||||
private static function createTemporaryDirectory(): string
|
||||
{
|
||||
$caDir = tempnam(sys_get_temp_dir(), 'webauthn-ca-');
|
||||
if (file_exists($caDir)) {
|
||||
unlink($caDir);
|
||||
}
|
||||
mkdir($caDir);
|
||||
if (!is_dir($caDir)) {
|
||||
throw new RuntimeException(sprintf('Directory "%s" was not created', $caDir));
|
||||
}
|
||||
|
||||
return $caDir;
|
||||
}
|
||||
|
||||
private static function deleteDirectory(string $dirname): void
|
||||
{
|
||||
$rehashProcess = new Process(['rm', '-rf', $dirname]);
|
||||
$rehashProcess->run();
|
||||
while ($rehashProcess->isRunning()) {
|
||||
//Just wait
|
||||
}
|
||||
}
|
||||
|
||||
private static function prepareCertificate(string $folder, string $certificate, string $prefix, string $suffix): string
|
||||
{
|
||||
$untrustedFilename = tempnam($folder, $prefix);
|
||||
rename($untrustedFilename, $untrustedFilename.$suffix);
|
||||
file_put_contents($untrustedFilename.$suffix, $certificate, FILE_APPEND);
|
||||
file_put_contents($untrustedFilename.$suffix, PHP_EOL, FILE_APPEND);
|
||||
|
||||
return $untrustedFilename.$suffix;
|
||||
}
|
||||
}
|
145
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/CollectedClientData.php
vendored
Normal file
145
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/CollectedClientData.php
vendored
Normal file
|
@ -0,0 +1,145 @@
|
|||
<?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;
|
||||
|
||||
use function array_key_exists;
|
||||
use Assert\Assertion;
|
||||
use Base64Url\Base64Url;
|
||||
use InvalidArgumentException;
|
||||
use function Safe\json_decode;
|
||||
use function Safe\sprintf;
|
||||
use Webauthn\TokenBinding\TokenBinding;
|
||||
|
||||
class CollectedClientData
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $rawData;
|
||||
|
||||
/**
|
||||
* @var mixed[]
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $challenge;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $origin;
|
||||
|
||||
/**
|
||||
* @var mixed[]|null
|
||||
*/
|
||||
private $tokenBinding;
|
||||
|
||||
/**
|
||||
* @param mixed[] $data
|
||||
*/
|
||||
public function __construct(string $rawData, array $data)
|
||||
{
|
||||
$this->type = $this->findData($data, 'type');
|
||||
$this->challenge = $this->findData($data, 'challenge', true, true);
|
||||
$this->origin = $this->findData($data, 'origin');
|
||||
$this->tokenBinding = $this->findData($data, 'tokenBinding', false);
|
||||
$this->rawData = $rawData;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public static function createFormJson(string $data): self
|
||||
{
|
||||
$rawData = Base64Url::decode($data);
|
||||
$json = json_decode($rawData, true);
|
||||
Assertion::isArray($json, 'Invalid collected client data');
|
||||
|
||||
return new self($rawData, $json);
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function getChallenge(): string
|
||||
{
|
||||
return $this->challenge;
|
||||
}
|
||||
|
||||
public function getOrigin(): string
|
||||
{
|
||||
return $this->origin;
|
||||
}
|
||||
|
||||
public function getTokenBinding(): ?TokenBinding
|
||||
{
|
||||
return null === $this->tokenBinding ? null : TokenBinding::createFormArray($this->tokenBinding);
|
||||
}
|
||||
|
||||
public function getRawData(): string
|
||||
{
|
||||
return $this->rawData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function all(): array
|
||||
{
|
||||
return array_keys($this->data);
|
||||
}
|
||||
|
||||
public function has(string $key): bool
|
||||
{
|
||||
return array_key_exists($key, $this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $key)
|
||||
{
|
||||
if (!$this->has($key)) {
|
||||
throw new InvalidArgumentException(sprintf('The key "%s" is missing', $key));
|
||||
}
|
||||
|
||||
return $this->data[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $json
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
private function findData(array $json, string $key, bool $isRequired = true, bool $isB64 = false)
|
||||
{
|
||||
if (!array_key_exists($key, $json)) {
|
||||
if ($isRequired) {
|
||||
throw new InvalidArgumentException(sprintf('The key "%s" is missing', $key));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return $isB64 ? Base64Url::decode($json[$key]) : $json[$key];
|
||||
}
|
||||
}
|
21
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/Counter/CounterChecker.php
vendored
Normal file
21
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/Counter/CounterChecker.php
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?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\Counter;
|
||||
|
||||
use Webauthn\PublicKeyCredentialSource;
|
||||
|
||||
interface CounterChecker
|
||||
{
|
||||
public function check(PublicKeyCredentialSource $publicKeyCredentialSource, int $currentCounter): void;
|
||||
}
|
46
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/Counter/ThrowExceptionIfInvalid.php
vendored
Normal file
46
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/Counter/ThrowExceptionIfInvalid.php
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?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\Counter;
|
||||
|
||||
use Assert\Assertion;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Throwable;
|
||||
use Webauthn\PublicKeyCredentialSource;
|
||||
|
||||
final class ThrowExceptionIfInvalid implements CounterChecker
|
||||
{
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
public function __construct(?LoggerInterface $logger = null)
|
||||
{
|
||||
$this->logger = $logger ?? new NullLogger();
|
||||
}
|
||||
|
||||
public function check(PublicKeyCredentialSource $publicKeyCredentialSource, int $currentCounter): void
|
||||
{
|
||||
try {
|
||||
Assertion::greaterThan($currentCounter, $publicKeyCredentialSource->getCounter(), 'Invalid counter.');
|
||||
} catch (Throwable $throwable) {
|
||||
$this->logger->error('The counter is invalid', [
|
||||
'current' => $currentCounter,
|
||||
'new' => $publicKeyCredentialSource->getCounter(),
|
||||
]);
|
||||
throw $throwable;
|
||||
}
|
||||
}
|
||||
}
|
46
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/Credential.php
vendored
Normal file
46
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/Credential.php
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?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;
|
||||
|
||||
/**
|
||||
* @see https://w3c.github.io/webappsec-credential-management/#credential
|
||||
*/
|
||||
abstract class Credential
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
public function __construct(string $id, string $type)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
}
|
62
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredential.php
vendored
Normal file
62
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredential.php
vendored
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?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;
|
||||
|
||||
use function Safe\json_encode;
|
||||
|
||||
/**
|
||||
* @see https://www.w3.org/TR/webauthn/#iface-pkcredential
|
||||
*/
|
||||
class PublicKeyCredential extends Credential
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $rawId;
|
||||
|
||||
/**
|
||||
* @var AuthenticatorResponse
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
public function __construct(string $id, string $type, string $rawId, AuthenticatorResponse $response)
|
||||
{
|
||||
parent::__construct($id, $type);
|
||||
$this->rawId = $rawId;
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return json_encode($this);
|
||||
}
|
||||
|
||||
public function getRawId(): string
|
||||
{
|
||||
return $this->rawId;
|
||||
}
|
||||
|
||||
public function getResponse(): AuthenticatorResponse
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $transport
|
||||
*/
|
||||
public function getPublicKeyCredentialDescriptor(array $transport = []): PublicKeyCredentialDescriptor
|
||||
{
|
||||
return new PublicKeyCredentialDescriptor($this->getType(), $this->getRawId(), $transport);
|
||||
}
|
||||
}
|
261
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialCreationOptions.php
vendored
Normal file
261
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialCreationOptions.php
vendored
Normal file
|
@ -0,0 +1,261 @@
|
|||
<?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;
|
||||
|
||||
use Assert\Assertion;
|
||||
use Base64Url\Base64Url;
|
||||
use function count;
|
||||
use function Safe\json_decode;
|
||||
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
|
||||
|
||||
class PublicKeyCredentialCreationOptions extends PublicKeyCredentialOptions
|
||||
{
|
||||
public const ATTESTATION_CONVEYANCE_PREFERENCE_NONE = 'none';
|
||||
public const ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT = 'indirect';
|
||||
public const ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT = 'direct';
|
||||
public const ATTESTATION_CONVEYANCE_PREFERENCE_ENTERPRISE = 'enterprise';
|
||||
|
||||
/**
|
||||
* @var PublicKeyCredentialRpEntity
|
||||
*/
|
||||
private $rp;
|
||||
|
||||
/**
|
||||
* @var PublicKeyCredentialUserEntity
|
||||
*/
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* @var PublicKeyCredentialParameters[]
|
||||
*/
|
||||
private $pubKeyCredParams = [];
|
||||
|
||||
/**
|
||||
* @var PublicKeyCredentialDescriptor[]
|
||||
*/
|
||||
private $excludeCredentials = [];
|
||||
|
||||
/**
|
||||
* @var AuthenticatorSelectionCriteria
|
||||
*/
|
||||
private $authenticatorSelection;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $attestation;
|
||||
|
||||
/**
|
||||
* @param PublicKeyCredentialParameters[] $pubKeyCredParams
|
||||
* @param PublicKeyCredentialDescriptor[] $excludeCredentials
|
||||
*/
|
||||
public function __construct(PublicKeyCredentialRpEntity $rp, PublicKeyCredentialUserEntity $user, string $challenge, array $pubKeyCredParams, ?int $timeout = null, array $excludeCredentials = [], ?AuthenticatorSelectionCriteria $authenticatorSelection = null, ?string $attestation = null, ?AuthenticationExtensionsClientInputs $extensions = null)
|
||||
{
|
||||
if (0 !== count($excludeCredentials)) {
|
||||
@trigger_error('The argument "excludeCredentials" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "excludeCredentials" or "excludeCredential".', E_USER_DEPRECATED);
|
||||
}
|
||||
if (null !== $authenticatorSelection) {
|
||||
@trigger_error('The argument "authenticatorSelection" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setAuthenticatorSelection".', E_USER_DEPRECATED);
|
||||
}
|
||||
if (null !== $attestation) {
|
||||
@trigger_error('The argument "attestation" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setAttestation".', E_USER_DEPRECATED);
|
||||
}
|
||||
parent::__construct($challenge, $timeout, $extensions);
|
||||
$this->rp = $rp;
|
||||
$this->user = $user;
|
||||
$this->pubKeyCredParams = $pubKeyCredParams;
|
||||
$this->authenticatorSelection = $authenticatorSelection ?? new AuthenticatorSelectionCriteria();
|
||||
$this->attestation = $attestation ?? self::ATTESTATION_CONVEYANCE_PREFERENCE_NONE;
|
||||
$this->excludeCredentials($excludeCredentials)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PublicKeyCredentialParameters[] $pubKeyCredParams
|
||||
*/
|
||||
public static function create(PublicKeyCredentialRpEntity $rp, PublicKeyCredentialUserEntity $user, string $challenge, array $pubKeyCredParams): self
|
||||
{
|
||||
return new self($rp, $user, $challenge, $pubKeyCredParams);
|
||||
}
|
||||
|
||||
public function addPubKeyCredParam(PublicKeyCredentialParameters $pubKeyCredParam): self
|
||||
{
|
||||
$this->pubKeyCredParams[] = $pubKeyCredParam;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PublicKeyCredentialParameters[] $pubKeyCredParams
|
||||
*/
|
||||
public function addPubKeyCredParams(array $pubKeyCredParams): self
|
||||
{
|
||||
foreach ($pubKeyCredParams as $pubKeyCredParam) {
|
||||
$this->addPubKeyCredParam($pubKeyCredParam);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function excludeCredential(PublicKeyCredentialDescriptor $excludeCredential): self
|
||||
{
|
||||
$this->excludeCredentials[] = $excludeCredential;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PublicKeyCredentialDescriptor[] $excludeCredentials
|
||||
*/
|
||||
public function excludeCredentials(array $excludeCredentials): self
|
||||
{
|
||||
foreach ($excludeCredentials as $excludeCredential) {
|
||||
$this->excludeCredential($excludeCredential);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setAuthenticatorSelection(AuthenticatorSelectionCriteria $authenticatorSelection): self
|
||||
{
|
||||
$this->authenticatorSelection = $authenticatorSelection;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setAttestation(string $attestation): self
|
||||
{
|
||||
Assertion::inArray($attestation, [
|
||||
self::ATTESTATION_CONVEYANCE_PREFERENCE_NONE,
|
||||
self::ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT,
|
||||
self::ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT,
|
||||
self::ATTESTATION_CONVEYANCE_PREFERENCE_ENTERPRISE,
|
||||
], 'Invalid attestation conveyance mode');
|
||||
$this->attestation = $attestation;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRp(): PublicKeyCredentialRpEntity
|
||||
{
|
||||
return $this->rp;
|
||||
}
|
||||
|
||||
public function getUser(): PublicKeyCredentialUserEntity
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PublicKeyCredentialParameters[]
|
||||
*/
|
||||
public function getPubKeyCredParams(): array
|
||||
{
|
||||
return $this->pubKeyCredParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PublicKeyCredentialDescriptor[]
|
||||
*/
|
||||
public function getExcludeCredentials(): array
|
||||
{
|
||||
return $this->excludeCredentials;
|
||||
}
|
||||
|
||||
public function getAuthenticatorSelection(): AuthenticatorSelectionCriteria
|
||||
{
|
||||
return $this->authenticatorSelection;
|
||||
}
|
||||
|
||||
public function getAttestation(): string
|
||||
{
|
||||
return $this->attestation;
|
||||
}
|
||||
|
||||
public static function createFromString(string $data): PublicKeyCredentialOptions
|
||||
{
|
||||
$data = json_decode($data, true);
|
||||
Assertion::isArray($data, 'Invalid data');
|
||||
|
||||
return self::createFromArray($data);
|
||||
}
|
||||
|
||||
public static function createFromArray(array $json): PublicKeyCredentialOptions
|
||||
{
|
||||
Assertion::keyExists($json, 'rp', 'Invalid input. "rp" is missing.');
|
||||
Assertion::keyExists($json, 'pubKeyCredParams', 'Invalid input. "pubKeyCredParams" is missing.');
|
||||
Assertion::isArray($json['pubKeyCredParams'], 'Invalid input. "pubKeyCredParams" is not an array.');
|
||||
Assertion::keyExists($json, 'challenge', 'Invalid input. "challenge" is missing.');
|
||||
Assertion::keyExists($json, 'attestation', 'Invalid input. "attestation" is missing.');
|
||||
Assertion::keyExists($json, 'user', 'Invalid input. "user" is missing.');
|
||||
Assertion::keyExists($json, 'authenticatorSelection', 'Invalid input. "authenticatorSelection" is missing.');
|
||||
|
||||
$pubKeyCredParams = [];
|
||||
foreach ($json['pubKeyCredParams'] as $pubKeyCredParam) {
|
||||
$pubKeyCredParams[] = PublicKeyCredentialParameters::createFromArray($pubKeyCredParam);
|
||||
}
|
||||
$excludeCredentials = [];
|
||||
if (isset($json['excludeCredentials'])) {
|
||||
foreach ($json['excludeCredentials'] as $excludeCredential) {
|
||||
$excludeCredentials[] = PublicKeyCredentialDescriptor::createFromArray($excludeCredential);
|
||||
}
|
||||
}
|
||||
|
||||
return self::create(
|
||||
PublicKeyCredentialRpEntity::createFromArray($json['rp']),
|
||||
PublicKeyCredentialUserEntity::createFromArray($json['user']),
|
||||
Base64Url::decode($json['challenge']),
|
||||
$pubKeyCredParams
|
||||
)
|
||||
->excludeCredentials($excludeCredentials)
|
||||
->setAuthenticatorSelection(AuthenticatorSelectionCriteria::createFromArray($json['authenticatorSelection']))
|
||||
->setAttestation($json['attestation'])
|
||||
->setTimeout($json['timeout'] ?? null)
|
||||
->setExtensions(isset($json['extensions']) ? AuthenticationExtensionsClientInputs::createFromArray($json['extensions']) : new AuthenticationExtensionsClientInputs())
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$json = [
|
||||
'rp' => $this->rp->jsonSerialize(),
|
||||
'pubKeyCredParams' => array_map(static function (PublicKeyCredentialParameters $object): array {
|
||||
return $object->jsonSerialize();
|
||||
}, $this->pubKeyCredParams),
|
||||
'challenge' => Base64Url::encode($this->challenge),
|
||||
'attestation' => $this->attestation,
|
||||
'user' => $this->user->jsonSerialize(),
|
||||
'authenticatorSelection' => $this->authenticatorSelection->jsonSerialize(),
|
||||
];
|
||||
|
||||
if (0 !== count($this->excludeCredentials)) {
|
||||
$json['excludeCredentials'] = array_map(static function (PublicKeyCredentialDescriptor $object): array {
|
||||
return $object->jsonSerialize();
|
||||
}, $this->excludeCredentials);
|
||||
}
|
||||
|
||||
if (0 !== $this->extensions->count()) {
|
||||
$json['extensions'] = $this->extensions;
|
||||
}
|
||||
|
||||
if (null !== $this->timeout) {
|
||||
$json['timeout'] = $this->timeout;
|
||||
}
|
||||
|
||||
return $json;
|
||||
}
|
||||
}
|
112
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptor.php
vendored
Normal file
112
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptor.php
vendored
Normal file
|
@ -0,0 +1,112 @@
|
|||
<?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;
|
||||
|
||||
use Assert\Assertion;
|
||||
use Base64Url\Base64Url;
|
||||
use function count;
|
||||
use JsonSerializable;
|
||||
use function Safe\json_decode;
|
||||
|
||||
class PublicKeyCredentialDescriptor implements JsonSerializable
|
||||
{
|
||||
public const CREDENTIAL_TYPE_PUBLIC_KEY = 'public-key';
|
||||
|
||||
public const AUTHENTICATOR_TRANSPORT_USB = 'usb';
|
||||
public const AUTHENTICATOR_TRANSPORT_NFC = 'nfc';
|
||||
public const AUTHENTICATOR_TRANSPORT_BLE = 'ble';
|
||||
public const AUTHENTICATOR_TRANSPORT_INTERNAL = 'internal';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $transports;
|
||||
|
||||
/**
|
||||
* @param string[] $transports
|
||||
*/
|
||||
public function __construct(string $type, string $id, array $transports = [])
|
||||
{
|
||||
$this->type = $type;
|
||||
$this->id = $id;
|
||||
$this->transports = $transports;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getTransports(): array
|
||||
{
|
||||
return $this->transports;
|
||||
}
|
||||
|
||||
public static function createFromString(string $data): self
|
||||
{
|
||||
$data = json_decode($data, true);
|
||||
Assertion::isArray($data, 'Invalid data');
|
||||
|
||||
return self::createFromArray($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $json
|
||||
*/
|
||||
public static function createFromArray(array $json): self
|
||||
{
|
||||
Assertion::keyExists($json, 'type', 'Invalid input. "type" is missing.');
|
||||
Assertion::keyExists($json, 'id', 'Invalid input. "id" is missing.');
|
||||
|
||||
return new self(
|
||||
$json['type'],
|
||||
Base64Url::decode($json['id']),
|
||||
$json['transports'] ?? []
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$json = [
|
||||
'type' => $this->type,
|
||||
'id' => Base64Url::encode($this->id),
|
||||
];
|
||||
if (0 !== count($this->transports)) {
|
||||
$json['transports'] = $this->transports;
|
||||
}
|
||||
|
||||
return $json;
|
||||
}
|
||||
}
|
95
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptorCollection.php
vendored
Normal file
95
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialDescriptorCollection.php
vendored
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?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;
|
||||
|
||||
use function array_key_exists;
|
||||
use ArrayIterator;
|
||||
use Assert\Assertion;
|
||||
use function count;
|
||||
use Countable;
|
||||
use Iterator;
|
||||
use IteratorAggregate;
|
||||
use JsonSerializable;
|
||||
use function Safe\json_decode;
|
||||
|
||||
class PublicKeyCredentialDescriptorCollection implements JsonSerializable, Countable, IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* @var PublicKeyCredentialDescriptor[]
|
||||
*/
|
||||
private $publicKeyCredentialDescriptors = [];
|
||||
|
||||
public function add(PublicKeyCredentialDescriptor $publicKeyCredentialDescriptor): void
|
||||
{
|
||||
$this->publicKeyCredentialDescriptors[$publicKeyCredentialDescriptor->getId()] = $publicKeyCredentialDescriptor;
|
||||
}
|
||||
|
||||
public function has(string $id): bool
|
||||
{
|
||||
return array_key_exists($id, $this->publicKeyCredentialDescriptors);
|
||||
}
|
||||
|
||||
public function remove(string $id): void
|
||||
{
|
||||
if (!$this->has($id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
unset($this->publicKeyCredentialDescriptors[$id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Iterator<string, PublicKeyCredentialDescriptor>
|
||||
*/
|
||||
public function getIterator(): Iterator
|
||||
{
|
||||
return new ArrayIterator($this->publicKeyCredentialDescriptors);
|
||||
}
|
||||
|
||||
public function count(int $mode = COUNT_NORMAL): int
|
||||
{
|
||||
return count($this->publicKeyCredentialDescriptors, $mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return array_map(static function (PublicKeyCredentialDescriptor $object): array {
|
||||
return $object->jsonSerialize();
|
||||
}, $this->publicKeyCredentialDescriptors);
|
||||
}
|
||||
|
||||
public static function createFromString(string $data): self
|
||||
{
|
||||
$data = json_decode($data, true);
|
||||
Assertion::isArray($data, 'Invalid data');
|
||||
|
||||
return self::createFromArray($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $json
|
||||
*/
|
||||
public static function createFromArray(array $json): self
|
||||
{
|
||||
$collection = new self();
|
||||
foreach ($json as $item) {
|
||||
$collection->add(PublicKeyCredentialDescriptor::createFromArray($item));
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
}
|
60
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialEntity.php
vendored
Normal file
60
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialEntity.php
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?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;
|
||||
|
||||
use JsonSerializable;
|
||||
|
||||
abstract class PublicKeyCredentialEntity implements JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $icon;
|
||||
|
||||
public function __construct(string $name, ?string $icon)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->icon = $icon;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getIcon(): ?string
|
||||
{
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$json = [
|
||||
'name' => $this->name,
|
||||
];
|
||||
if (null !== $this->icon) {
|
||||
$json['icon'] = $this->icon;
|
||||
}
|
||||
|
||||
return $json;
|
||||
}
|
||||
}
|
181
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialLoader.php
vendored
Normal file
181
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialLoader.php
vendored
Normal file
|
@ -0,0 +1,181 @@
|
|||
<?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;
|
||||
|
||||
use function array_key_exists;
|
||||
use Assert\Assertion;
|
||||
use Base64Url\Base64Url;
|
||||
use CBOR\Decoder;
|
||||
use CBOR\MapObject;
|
||||
use CBOR\OtherObject\OtherObjectManager;
|
||||
use CBOR\Tag\TagObjectManager;
|
||||
use InvalidArgumentException;
|
||||
use function ord;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use function Safe\json_decode;
|
||||
use function Safe\sprintf;
|
||||
use function Safe\unpack;
|
||||
use Throwable;
|
||||
use Webauthn\AttestationStatement\AttestationObjectLoader;
|
||||
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputsLoader;
|
||||
|
||||
class PublicKeyCredentialLoader
|
||||
{
|
||||
private const FLAG_AT = 0b01000000;
|
||||
private const FLAG_ED = 0b10000000;
|
||||
|
||||
/**
|
||||
* @var AttestationObjectLoader
|
||||
*/
|
||||
private $attestationObjectLoader;
|
||||
|
||||
/**
|
||||
* @var Decoder
|
||||
*/
|
||||
private $decoder;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
public function __construct(AttestationObjectLoader $attestationObjectLoader, ?LoggerInterface $logger = null)
|
||||
{
|
||||
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".', E_USER_DEPRECATED);
|
||||
}
|
||||
$this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager());
|
||||
$this->attestationObjectLoader = $attestationObjectLoader;
|
||||
$this->logger = $logger ?? new NullLogger();
|
||||
}
|
||||
|
||||
public static function create(AttestationObjectLoader $attestationObjectLoader): self
|
||||
{
|
||||
return new self($attestationObjectLoader);
|
||||
}
|
||||
|
||||
public function setLogger(LoggerInterface $logger): self
|
||||
{
|
||||
$this->logger = $logger;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $json
|
||||
*/
|
||||
public function loadArray(array $json): PublicKeyCredential
|
||||
{
|
||||
$this->logger->info('Trying to load data from an array', ['data' => $json]);
|
||||
try {
|
||||
foreach (['id', 'rawId', 'type'] as $key) {
|
||||
Assertion::keyExists($json, $key, sprintf('The parameter "%s" is missing', $key));
|
||||
Assertion::string($json[$key], sprintf('The parameter "%s" shall be a string', $key));
|
||||
}
|
||||
Assertion::keyExists($json, 'response', 'The parameter "response" is missing');
|
||||
Assertion::isArray($json['response'], 'The parameter "response" shall be an array');
|
||||
Assertion::eq($json['type'], 'public-key', sprintf('Unsupported type "%s"', $json['type']));
|
||||
|
||||
$id = Base64Url::decode($json['id']);
|
||||
$rawId = Base64Url::decode($json['rawId']);
|
||||
Assertion::true(hash_equals($id, $rawId));
|
||||
|
||||
$publicKeyCredential = new PublicKeyCredential(
|
||||
$json['id'],
|
||||
$json['type'],
|
||||
$rawId,
|
||||
$this->createResponse($json['response'])
|
||||
);
|
||||
$this->logger->info('The data has been loaded');
|
||||
$this->logger->debug('Public Key Credential', ['publicKeyCredential' => $publicKeyCredential]);
|
||||
|
||||
return $publicKeyCredential;
|
||||
} catch (Throwable $throwable) {
|
||||
$this->logger->error('An error occurred', [
|
||||
'exception' => $throwable,
|
||||
]);
|
||||
throw $throwable;
|
||||
}
|
||||
}
|
||||
|
||||
public function load(string $data): PublicKeyCredential
|
||||
{
|
||||
$this->logger->info('Trying to load data from a string', ['data' => $data]);
|
||||
try {
|
||||
$json = json_decode($data, true);
|
||||
|
||||
return $this->loadArray($json);
|
||||
} catch (Throwable $throwable) {
|
||||
$this->logger->error('An error occurred', [
|
||||
'exception' => $throwable,
|
||||
]);
|
||||
throw $throwable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $response
|
||||
*/
|
||||
private function createResponse(array $response): AuthenticatorResponse
|
||||
{
|
||||
Assertion::keyExists($response, 'clientDataJSON', 'Invalid data. The parameter "clientDataJSON" is missing');
|
||||
Assertion::string($response['clientDataJSON'], 'Invalid data. The parameter "clientDataJSON" is invalid');
|
||||
switch (true) {
|
||||
case array_key_exists('attestationObject', $response):
|
||||
Assertion::string($response['attestationObject'], 'Invalid data. The parameter "attestationObject " is invalid');
|
||||
$attestationObject = $this->attestationObjectLoader->load($response['attestationObject']);
|
||||
|
||||
return new AuthenticatorAttestationResponse(CollectedClientData::createFormJson($response['clientDataJSON']), $attestationObject);
|
||||
case array_key_exists('authenticatorData', $response) && array_key_exists('signature', $response):
|
||||
$authData = Base64Url::decode($response['authenticatorData']);
|
||||
|
||||
$authDataStream = new StringStream($authData);
|
||||
$rp_id_hash = $authDataStream->read(32);
|
||||
$flags = $authDataStream->read(1);
|
||||
$signCount = $authDataStream->read(4);
|
||||
$signCount = unpack('N', $signCount)[1];
|
||||
|
||||
$attestedCredentialData = null;
|
||||
if (0 !== (ord($flags) & self::FLAG_AT)) {
|
||||
$aaguid = Uuid::fromBytes($authDataStream->read(16));
|
||||
$credentialLength = $authDataStream->read(2);
|
||||
$credentialLength = unpack('n', $credentialLength)[1];
|
||||
$credentialId = $authDataStream->read($credentialLength);
|
||||
$credentialPublicKey = $this->decoder->decode($authDataStream);
|
||||
Assertion::isInstanceOf($credentialPublicKey, MapObject::class, 'The data does not contain a valid credential public key.');
|
||||
$attestedCredentialData = new AttestedCredentialData($aaguid, $credentialId, (string) $credentialPublicKey);
|
||||
}
|
||||
|
||||
$extension = null;
|
||||
if (0 !== (ord($flags) & self::FLAG_ED)) {
|
||||
$extension = $this->decoder->decode($authDataStream);
|
||||
$extension = AuthenticationExtensionsClientOutputsLoader::load($extension);
|
||||
}
|
||||
Assertion::true($authDataStream->isEOF(), 'Invalid authentication data. Presence of extra bytes.');
|
||||
$authDataStream->close();
|
||||
$authenticatorData = new AuthenticatorData($authData, $rp_id_hash, $flags, $signCount, $attestedCredentialData, $extension);
|
||||
|
||||
return new AuthenticatorAssertionResponse(
|
||||
CollectedClientData::createFormJson($response['clientDataJSON']),
|
||||
$authenticatorData,
|
||||
Base64Url::decode($response['signature']),
|
||||
$response['userHandle'] ?? null
|
||||
);
|
||||
default:
|
||||
throw new InvalidArgumentException('Unable to create the response object');
|
||||
}
|
||||
}
|
||||
}
|
104
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialOptions.php
vendored
Normal file
104
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialOptions.php
vendored
Normal file
|
@ -0,0 +1,104 @@
|
|||
<?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;
|
||||
|
||||
use JsonSerializable;
|
||||
use Webauthn\AuthenticationExtensions\AuthenticationExtension;
|
||||
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
|
||||
|
||||
abstract class PublicKeyCredentialOptions implements JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $challenge;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
protected $timeout;
|
||||
|
||||
/**
|
||||
* @var AuthenticationExtensionsClientInputs
|
||||
*/
|
||||
protected $extensions;
|
||||
|
||||
public function __construct(string $challenge, ?int $timeout = null, ?AuthenticationExtensionsClientInputs $extensions = null)
|
||||
{
|
||||
if (null !== $timeout) {
|
||||
@trigger_error('The argument "timeout" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setTimeout".', E_USER_DEPRECATED);
|
||||
}
|
||||
if (null !== $extensions) {
|
||||
@trigger_error('The argument "extensions" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "addExtension" or "addExtensions".', E_USER_DEPRECATED);
|
||||
}
|
||||
$this->challenge = $challenge;
|
||||
$this->setTimeout($timeout);
|
||||
$this->extensions = $extensions ?? new AuthenticationExtensionsClientInputs();
|
||||
}
|
||||
|
||||
public function setTimeout(?int $timeout): self
|
||||
{
|
||||
$this->timeout = $timeout;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addExtension(AuthenticationExtension $extension): self
|
||||
{
|
||||
$this->extensions->add($extension);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AuthenticationExtension[] $extensions
|
||||
*/
|
||||
public function addExtensions(array $extensions): self
|
||||
{
|
||||
foreach ($extensions as $extension) {
|
||||
$this->addExtension($extension);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setExtensions(AuthenticationExtensionsClientInputs $extensions): self
|
||||
{
|
||||
$this->extensions = $extensions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getChallenge(): string
|
||||
{
|
||||
return $this->challenge;
|
||||
}
|
||||
|
||||
public function getTimeout(): ?int
|
||||
{
|
||||
return $this->timeout;
|
||||
}
|
||||
|
||||
public function getExtensions(): AuthenticationExtensionsClientInputs
|
||||
{
|
||||
return $this->extensions;
|
||||
}
|
||||
|
||||
abstract public static function createFromString(string $data): self;
|
||||
|
||||
/**
|
||||
* @param mixed[] $json
|
||||
*/
|
||||
abstract public static function createFromArray(array $json): self;
|
||||
}
|
82
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialParameters.php
vendored
Normal file
82
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialParameters.php
vendored
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?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;
|
||||
|
||||
use Assert\Assertion;
|
||||
use JsonSerializable;
|
||||
use function Safe\json_decode;
|
||||
|
||||
class PublicKeyCredentialParameters implements JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $alg;
|
||||
|
||||
public function __construct(string $type, int $alg)
|
||||
{
|
||||
$this->type = $type;
|
||||
$this->alg = $alg;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function getAlg(): int
|
||||
{
|
||||
return $this->alg;
|
||||
}
|
||||
|
||||
public static function createFromString(string $data): self
|
||||
{
|
||||
$data = json_decode($data, true);
|
||||
Assertion::isArray($data, 'Invalid data');
|
||||
|
||||
return self::createFromArray($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $json
|
||||
*/
|
||||
public static function createFromArray(array $json): self
|
||||
{
|
||||
Assertion::keyExists($json, 'type', 'Invalid input. "type" is missing.');
|
||||
Assertion::string($json['type'], 'Invalid input. "type" is not a string.');
|
||||
Assertion::keyExists($json, 'alg', 'Invalid input. "alg" is missing.');
|
||||
Assertion::integer($json['alg'], 'Invalid input. "alg" is not an integer.');
|
||||
|
||||
return new self(
|
||||
$json['type'],
|
||||
$json['alg']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'type' => $this->type,
|
||||
'alg' => $this->alg,
|
||||
];
|
||||
}
|
||||
}
|
194
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialRequestOptions.php
vendored
Normal file
194
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialRequestOptions.php
vendored
Normal file
|
@ -0,0 +1,194 @@
|
|||
<?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;
|
||||
|
||||
use Assert\Assertion;
|
||||
use Base64Url\Base64Url;
|
||||
use function count;
|
||||
use function Safe\json_decode;
|
||||
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
|
||||
|
||||
class PublicKeyCredentialRequestOptions extends PublicKeyCredentialOptions
|
||||
{
|
||||
public const USER_VERIFICATION_REQUIREMENT_REQUIRED = 'required';
|
||||
public const USER_VERIFICATION_REQUIREMENT_PREFERRED = 'preferred';
|
||||
public const USER_VERIFICATION_REQUIREMENT_DISCOURAGED = 'discouraged';
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $rpId;
|
||||
|
||||
/**
|
||||
* @var PublicKeyCredentialDescriptor[]
|
||||
*/
|
||||
private $allowCredentials = [];
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $userVerification;
|
||||
|
||||
/**
|
||||
* @param PublicKeyCredentialDescriptor[] $allowCredentials
|
||||
*/
|
||||
public function __construct(string $challenge, ?int $timeout = null, ?string $rpId = null, array $allowCredentials = [], ?string $userVerification = null, ?AuthenticationExtensionsClientInputs $extensions = null)
|
||||
{
|
||||
if (0 !== count($allowCredentials)) {
|
||||
@trigger_error('The argument "allowCredentials" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "addAllowedCredentials" or "addAllowedCredential".', E_USER_DEPRECATED);
|
||||
}
|
||||
if (null !== $rpId) {
|
||||
@trigger_error('The argument "rpId" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setRpId".', E_USER_DEPRECATED);
|
||||
}
|
||||
if (null !== $userVerification) {
|
||||
@trigger_error('The argument "userVerification" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setUserVerification".', E_USER_DEPRECATED);
|
||||
}
|
||||
parent::__construct($challenge, $timeout, $extensions);
|
||||
$this
|
||||
->setRpId($rpId)
|
||||
->allowCredentials($allowCredentials)
|
||||
->setUserVerification($userVerification)
|
||||
;
|
||||
}
|
||||
|
||||
public static function create(string $challenge): self
|
||||
{
|
||||
return new self($challenge);
|
||||
}
|
||||
|
||||
public function setRpId(?string $rpId): self
|
||||
{
|
||||
$this->rpId = $rpId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function allowCredential(PublicKeyCredentialDescriptor $allowCredential): self
|
||||
{
|
||||
$this->allowCredentials[] = $allowCredential;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PublicKeyCredentialDescriptor[] $allowCredentials
|
||||
*/
|
||||
public function allowCredentials(array $allowCredentials): self
|
||||
{
|
||||
foreach ($allowCredentials as $allowCredential) {
|
||||
$this->allowCredential($allowCredential);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setUserVerification(?string $userVerification): self
|
||||
{
|
||||
if (null === $userVerification) {
|
||||
$this->rpId = null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
Assertion::inArray($userVerification, [
|
||||
self::USER_VERIFICATION_REQUIREMENT_REQUIRED,
|
||||
self::USER_VERIFICATION_REQUIREMENT_PREFERRED,
|
||||
self::USER_VERIFICATION_REQUIREMENT_DISCOURAGED,
|
||||
], 'Invalid user verification requirement');
|
||||
$this->userVerification = $userVerification;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRpId(): ?string
|
||||
{
|
||||
return $this->rpId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PublicKeyCredentialDescriptor[]
|
||||
*/
|
||||
public function getAllowCredentials(): array
|
||||
{
|
||||
return $this->allowCredentials;
|
||||
}
|
||||
|
||||
public function getUserVerification(): ?string
|
||||
{
|
||||
return $this->userVerification;
|
||||
}
|
||||
|
||||
public static function createFromString(string $data): PublicKeyCredentialOptions
|
||||
{
|
||||
$data = json_decode($data, true);
|
||||
Assertion::isArray($data, 'Invalid data');
|
||||
|
||||
return self::createFromArray($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $json
|
||||
*/
|
||||
public static function createFromArray(array $json): PublicKeyCredentialOptions
|
||||
{
|
||||
Assertion::keyExists($json, 'challenge', 'Invalid input. "challenge" is missing.');
|
||||
|
||||
$allowCredentials = [];
|
||||
$allowCredentialList = $json['allowCredentials'] ?? [];
|
||||
foreach ($allowCredentialList as $allowCredential) {
|
||||
$allowCredentials[] = PublicKeyCredentialDescriptor::createFromArray($allowCredential);
|
||||
}
|
||||
|
||||
return self::create(Base64Url::decode($json['challenge']))
|
||||
->setRpId($json['rpId'] ?? null)
|
||||
->allowCredentials($allowCredentials)
|
||||
->setUserVerification($json['userVerification'] ?? null)
|
||||
->setTimeout($json['timeout'] ?? null)
|
||||
->setExtensions(isset($json['extensions']) ? AuthenticationExtensionsClientInputs::createFromArray($json['extensions']) : new AuthenticationExtensionsClientInputs())
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$json = [
|
||||
'challenge' => Base64Url::encode($this->challenge),
|
||||
];
|
||||
|
||||
if (null !== $this->rpId) {
|
||||
$json['rpId'] = $this->rpId;
|
||||
}
|
||||
|
||||
if (null !== $this->userVerification) {
|
||||
$json['userVerification'] = $this->userVerification;
|
||||
}
|
||||
|
||||
if (0 !== count($this->allowCredentials)) {
|
||||
$json['allowCredentials'] = array_map(static function (PublicKeyCredentialDescriptor $object): array {
|
||||
return $object->jsonSerialize();
|
||||
}, $this->allowCredentials);
|
||||
}
|
||||
|
||||
if (0 !== $this->extensions->count()) {
|
||||
$json['extensions'] = $this->extensions->jsonSerialize();
|
||||
}
|
||||
|
||||
if (null !== $this->timeout) {
|
||||
$json['timeout'] = $this->timeout;
|
||||
}
|
||||
|
||||
return $json;
|
||||
}
|
||||
}
|
62
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialRpEntity.php
vendored
Normal file
62
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialRpEntity.php
vendored
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?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;
|
||||
|
||||
use Assert\Assertion;
|
||||
|
||||
class PublicKeyCredentialRpEntity extends PublicKeyCredentialEntity
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
public function __construct(string $name, ?string $id = null, ?string $icon = null)
|
||||
{
|
||||
parent::__construct($name, $icon);
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getId(): ?string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $json
|
||||
*/
|
||||
public static function createFromArray(array $json): self
|
||||
{
|
||||
Assertion::keyExists($json, 'name', 'Invalid input. "name" is missing.');
|
||||
|
||||
return new self(
|
||||
$json['name'],
|
||||
$json['id'] ?? null,
|
||||
$json['icon'] ?? null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$json = parent::jsonSerialize();
|
||||
if (null !== $this->id) {
|
||||
$json['id'] = $this->id;
|
||||
}
|
||||
|
||||
return $json;
|
||||
}
|
||||
}
|
240
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialSource.php
vendored
Normal file
240
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialSource.php
vendored
Normal file
|
@ -0,0 +1,240 @@
|
|||
<?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;
|
||||
|
||||
use Assert\Assertion;
|
||||
use Base64Url\Base64Url;
|
||||
use InvalidArgumentException;
|
||||
use JsonSerializable;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
use function Safe\base64_decode;
|
||||
use function Safe\sprintf;
|
||||
use Throwable;
|
||||
use Webauthn\TrustPath\TrustPath;
|
||||
use Webauthn\TrustPath\TrustPathLoader;
|
||||
|
||||
/**
|
||||
* @see https://www.w3.org/TR/webauthn/#iface-pkcredential
|
||||
*/
|
||||
class PublicKeyCredentialSource implements JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $publicKeyCredentialId;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $transports;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $attestationType;
|
||||
|
||||
/**
|
||||
* @var TrustPath
|
||||
*/
|
||||
protected $trustPath;
|
||||
|
||||
/**
|
||||
* @var UuidInterface
|
||||
*/
|
||||
protected $aaguid;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $credentialPublicKey;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $userHandle;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $counter;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
protected $otherUI;
|
||||
|
||||
/**
|
||||
* @param string[] $transports
|
||||
*/
|
||||
public function __construct(string $publicKeyCredentialId, string $type, array $transports, string $attestationType, TrustPath $trustPath, UuidInterface $aaguid, string $credentialPublicKey, string $userHandle, int $counter, ?array $otherUI = null)
|
||||
{
|
||||
$this->publicKeyCredentialId = $publicKeyCredentialId;
|
||||
$this->type = $type;
|
||||
$this->transports = $transports;
|
||||
$this->aaguid = $aaguid;
|
||||
$this->credentialPublicKey = $credentialPublicKey;
|
||||
$this->userHandle = $userHandle;
|
||||
$this->counter = $counter;
|
||||
$this->attestationType = $attestationType;
|
||||
$this->trustPath = $trustPath;
|
||||
$this->otherUI = $otherUI;
|
||||
}
|
||||
|
||||
public function getPublicKeyCredentialId(): string
|
||||
{
|
||||
return $this->publicKeyCredentialId;
|
||||
}
|
||||
|
||||
public function getPublicKeyCredentialDescriptor(): PublicKeyCredentialDescriptor
|
||||
{
|
||||
return new PublicKeyCredentialDescriptor(
|
||||
$this->type,
|
||||
$this->publicKeyCredentialId,
|
||||
$this->transports
|
||||
);
|
||||
}
|
||||
|
||||
public function getAttestationType(): string
|
||||
{
|
||||
return $this->attestationType;
|
||||
}
|
||||
|
||||
public function getTrustPath(): TrustPath
|
||||
{
|
||||
return $this->trustPath;
|
||||
}
|
||||
|
||||
public function getAttestedCredentialData(): AttestedCredentialData
|
||||
{
|
||||
return new AttestedCredentialData(
|
||||
$this->aaguid,
|
||||
$this->publicKeyCredentialId,
|
||||
$this->credentialPublicKey
|
||||
);
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getTransports(): array
|
||||
{
|
||||
return $this->transports;
|
||||
}
|
||||
|
||||
public function getAaguid(): UuidInterface
|
||||
{
|
||||
return $this->aaguid;
|
||||
}
|
||||
|
||||
public function getCredentialPublicKey(): string
|
||||
{
|
||||
return $this->credentialPublicKey;
|
||||
}
|
||||
|
||||
public function getUserHandle(): string
|
||||
{
|
||||
return $this->userHandle;
|
||||
}
|
||||
|
||||
public function getCounter(): int
|
||||
{
|
||||
return $this->counter;
|
||||
}
|
||||
|
||||
public function setCounter(int $counter): void
|
||||
{
|
||||
$this->counter = $counter;
|
||||
}
|
||||
|
||||
public function getOtherUI(): ?array
|
||||
{
|
||||
return $this->otherUI;
|
||||
}
|
||||
|
||||
public function setOtherUI(?array $otherUI): self
|
||||
{
|
||||
$this->otherUI = $otherUI;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $data
|
||||
*/
|
||||
public static function createFromArray(array $data): self
|
||||
{
|
||||
$keys = array_keys(get_class_vars(self::class));
|
||||
foreach ($keys as $key) {
|
||||
if ('otherUI' === $key) {
|
||||
continue;
|
||||
}
|
||||
Assertion::keyExists($data, $key, sprintf('The parameter "%s" is missing', $key));
|
||||
}
|
||||
switch (true) {
|
||||
case 36 === mb_strlen($data['aaguid'], '8bit'):
|
||||
$uuid = Uuid::fromString($data['aaguid']);
|
||||
break;
|
||||
default: // Kept for compatibility with old format
|
||||
$decoded = base64_decode($data['aaguid'], true);
|
||||
$uuid = Uuid::fromBytes($decoded);
|
||||
}
|
||||
|
||||
try {
|
||||
return new self(
|
||||
Base64Url::decode($data['publicKeyCredentialId']),
|
||||
$data['type'],
|
||||
$data['transports'],
|
||||
$data['attestationType'],
|
||||
TrustPathLoader::loadTrustPath($data['trustPath']),
|
||||
$uuid,
|
||||
Base64Url::decode($data['credentialPublicKey']),
|
||||
Base64Url::decode($data['userHandle']),
|
||||
$data['counter'],
|
||||
$data['otherUI'] ?? null
|
||||
);
|
||||
} catch (Throwable $throwable) {
|
||||
throw new InvalidArgumentException('Unable to load the data', $throwable->getCode(), $throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'publicKeyCredentialId' => Base64Url::encode($this->publicKeyCredentialId),
|
||||
'type' => $this->type,
|
||||
'transports' => $this->transports,
|
||||
'attestationType' => $this->attestationType,
|
||||
'trustPath' => $this->trustPath->jsonSerialize(),
|
||||
'aaguid' => $this->aaguid->toString(),
|
||||
'credentialPublicKey' => Base64Url::encode($this->credentialPublicKey),
|
||||
'userHandle' => Base64Url::encode($this->userHandle),
|
||||
'counter' => $this->counter,
|
||||
'otherUI' => $this->otherUI,
|
||||
];
|
||||
}
|
||||
}
|
26
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialSourceRepository.php
vendored
Normal file
26
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialSourceRepository.php
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?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;
|
||||
|
||||
interface PublicKeyCredentialSourceRepository
|
||||
{
|
||||
public function findOneByCredentialId(string $publicKeyCredentialId): ?PublicKeyCredentialSource;
|
||||
|
||||
/**
|
||||
* @return PublicKeyCredentialSource[]
|
||||
*/
|
||||
public function findAllForUserEntity(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array;
|
||||
|
||||
public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource): void;
|
||||
}
|
87
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialUserEntity.php
vendored
Normal file
87
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/PublicKeyCredentialUserEntity.php
vendored
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?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;
|
||||
|
||||
use Assert\Assertion;
|
||||
use function Safe\base64_decode;
|
||||
use function Safe\json_decode;
|
||||
|
||||
class PublicKeyCredentialUserEntity extends PublicKeyCredentialEntity
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $displayName;
|
||||
|
||||
public function __construct(string $name, string $id, string $displayName, ?string $icon = null)
|
||||
{
|
||||
parent::__construct($name, $icon);
|
||||
Assertion::maxLength($id, 64, 'User ID max length is 64 bytes', 'id', '8bit');
|
||||
$this->id = $id;
|
||||
$this->displayName = $displayName;
|
||||
}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getDisplayName(): string
|
||||
{
|
||||
return $this->displayName;
|
||||
}
|
||||
|
||||
public static function createFromString(string $data): self
|
||||
{
|
||||
$data = json_decode($data, true);
|
||||
Assertion::isArray($data, 'Invalid data');
|
||||
|
||||
return self::createFromArray($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $json
|
||||
*/
|
||||
public static function createFromArray(array $json): self
|
||||
{
|
||||
Assertion::keyExists($json, 'name', 'Invalid input. "name" is missing.');
|
||||
Assertion::keyExists($json, 'id', 'Invalid input. "id" is missing.');
|
||||
Assertion::keyExists($json, 'displayName', 'Invalid input. "displayName" is missing.');
|
||||
$id = base64_decode($json['id'], true);
|
||||
|
||||
return new self(
|
||||
$json['name'],
|
||||
$id,
|
||||
$json['displayName'],
|
||||
$json['icon'] ?? null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$json = parent::jsonSerialize();
|
||||
$json['id'] = base64_encode($this->id);
|
||||
$json['displayName'] = $this->displayName;
|
||||
|
||||
return $json;
|
||||
}
|
||||
}
|
351
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/Server.php
vendored
Normal file
351
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/Server.php
vendored
Normal file
|
@ -0,0 +1,351 @@
|
|||
<?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;
|
||||
|
||||
use Assert\Assertion;
|
||||
use Cose\Algorithm\Algorithm;
|
||||
use Cose\Algorithm\ManagerFactory;
|
||||
use Cose\Algorithm\Signature\ECDSA;
|
||||
use Cose\Algorithm\Signature\EdDSA;
|
||||
use Cose\Algorithm\Signature\RSA;
|
||||
use Jose\Component\KeyManagement\JWKFactory;
|
||||
use Jose\Component\Signature\Algorithm\RS256;
|
||||
use Psr\Http\Client\ClientInterface;
|
||||
use Psr\Http\Message\RequestFactoryInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Webauthn\AttestationStatement\AndroidKeyAttestationStatementSupport;
|
||||
use Webauthn\AttestationStatement\AndroidSafetyNetAttestationStatementSupport;
|
||||
use Webauthn\AttestationStatement\AttestationObjectLoader;
|
||||
use Webauthn\AttestationStatement\AttestationStatementSupportManager;
|
||||
use Webauthn\AttestationStatement\FidoU2FAttestationStatementSupport;
|
||||
use Webauthn\AttestationStatement\NoneAttestationStatementSupport;
|
||||
use Webauthn\AttestationStatement\PackedAttestationStatementSupport;
|
||||
use Webauthn\AttestationStatement\TPMAttestationStatementSupport;
|
||||
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
|
||||
use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler;
|
||||
use Webauthn\Counter\CounterChecker;
|
||||
use Webauthn\MetadataService\MetadataStatementRepository;
|
||||
use Webauthn\TokenBinding\IgnoreTokenBindingHandler;
|
||||
use Webauthn\TokenBinding\TokenBindingHandler;
|
||||
|
||||
class Server
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $timeout = 60000;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $challengeSize = 32;
|
||||
|
||||
/**
|
||||
* @var PublicKeyCredentialRpEntity
|
||||
*/
|
||||
private $rpEntity;
|
||||
|
||||
/**
|
||||
* @var ManagerFactory
|
||||
*/
|
||||
private $coseAlgorithmManagerFactory;
|
||||
|
||||
/**
|
||||
* @var PublicKeyCredentialSourceRepository
|
||||
*/
|
||||
private $publicKeyCredentialSourceRepository;
|
||||
|
||||
/**
|
||||
* @var TokenBindingHandler
|
||||
*/
|
||||
private $tokenBindingHandler;
|
||||
|
||||
/**
|
||||
* @var ExtensionOutputCheckerHandler
|
||||
*/
|
||||
private $extensionOutputCheckerHandler;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $selectedAlgorithms;
|
||||
|
||||
/**
|
||||
* @var MetadataStatementRepository|null
|
||||
*/
|
||||
private $metadataStatementRepository;
|
||||
|
||||
/**
|
||||
* @var ClientInterface|null
|
||||
*/
|
||||
private $httpClient;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $googleApiKey;
|
||||
|
||||
/**
|
||||
* @var RequestFactoryInterface|null
|
||||
*/
|
||||
private $requestFactory;
|
||||
|
||||
/**
|
||||
* @var CounterChecker|null
|
||||
*/
|
||||
private $counterChecker;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $securedRelyingPartyId = [];
|
||||
|
||||
public function __construct(PublicKeyCredentialRpEntity $relyingParty, PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository, ?MetadataStatementRepository $metadataStatementRepository = null)
|
||||
{
|
||||
if (null !== $metadataStatementRepository) {
|
||||
@trigger_error('The argument "metadataStatementRepository" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setMetadataStatementRepository".', E_USER_DEPRECATED);
|
||||
}
|
||||
$this->rpEntity = $relyingParty;
|
||||
$this->logger = new NullLogger();
|
||||
|
||||
$this->coseAlgorithmManagerFactory = new ManagerFactory();
|
||||
$this->coseAlgorithmManagerFactory->add('RS1', new RSA\RS1());
|
||||
$this->coseAlgorithmManagerFactory->add('RS256', new RSA\RS256());
|
||||
$this->coseAlgorithmManagerFactory->add('RS384', new RSA\RS384());
|
||||
$this->coseAlgorithmManagerFactory->add('RS512', new RSA\RS512());
|
||||
$this->coseAlgorithmManagerFactory->add('PS256', new RSA\PS256());
|
||||
$this->coseAlgorithmManagerFactory->add('PS384', new RSA\PS384());
|
||||
$this->coseAlgorithmManagerFactory->add('PS512', new RSA\PS512());
|
||||
$this->coseAlgorithmManagerFactory->add('ES256', new ECDSA\ES256());
|
||||
$this->coseAlgorithmManagerFactory->add('ES256K', new ECDSA\ES256K());
|
||||
$this->coseAlgorithmManagerFactory->add('ES384', new ECDSA\ES384());
|
||||
$this->coseAlgorithmManagerFactory->add('ES512', new ECDSA\ES512());
|
||||
$this->coseAlgorithmManagerFactory->add('Ed25519', new EdDSA\Ed25519());
|
||||
|
||||
$this->selectedAlgorithms = ['RS256', 'RS512', 'PS256', 'PS512', 'ES256', 'ES512', 'Ed25519'];
|
||||
$this->publicKeyCredentialSourceRepository = $publicKeyCredentialSourceRepository;
|
||||
$this->tokenBindingHandler = new IgnoreTokenBindingHandler();
|
||||
$this->extensionOutputCheckerHandler = new ExtensionOutputCheckerHandler();
|
||||
$this->metadataStatementRepository = $metadataStatementRepository;
|
||||
}
|
||||
|
||||
public function setMetadataStatementRepository(MetadataStatementRepository $metadataStatementRepository): self
|
||||
{
|
||||
$this->metadataStatementRepository = $metadataStatementRepository;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $selectedAlgorithms
|
||||
*/
|
||||
public function setSelectedAlgorithms(array $selectedAlgorithms): self
|
||||
{
|
||||
$this->selectedAlgorithms = $selectedAlgorithms;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTokenBindingHandler(TokenBindingHandler $tokenBindingHandler): self
|
||||
{
|
||||
$this->tokenBindingHandler = $tokenBindingHandler;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addAlgorithm(string $alias, Algorithm $algorithm): self
|
||||
{
|
||||
$this->coseAlgorithmManagerFactory->add($alias, $algorithm);
|
||||
$this->selectedAlgorithms[] = $alias;
|
||||
$this->selectedAlgorithms = array_unique($this->selectedAlgorithms);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setExtensionOutputCheckerHandler(ExtensionOutputCheckerHandler $extensionOutputCheckerHandler): self
|
||||
{
|
||||
$this->extensionOutputCheckerHandler = $extensionOutputCheckerHandler;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $securedRelyingPartyId
|
||||
*/
|
||||
public function setSecuredRelyingPartyId(array $securedRelyingPartyId): self
|
||||
{
|
||||
Assertion::allString($securedRelyingPartyId, 'Invalid list. Shall be a list of strings');
|
||||
$this->securedRelyingPartyId = $securedRelyingPartyId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PublicKeyCredentialDescriptor[] $excludedPublicKeyDescriptors
|
||||
*/
|
||||
public function generatePublicKeyCredentialCreationOptions(PublicKeyCredentialUserEntity $userEntity, ?string $attestationMode = null, array $excludedPublicKeyDescriptors = [], ?AuthenticatorSelectionCriteria $criteria = null, ?AuthenticationExtensionsClientInputs $extensions = null): PublicKeyCredentialCreationOptions
|
||||
{
|
||||
$coseAlgorithmManager = $this->coseAlgorithmManagerFactory->create($this->selectedAlgorithms);
|
||||
$publicKeyCredentialParametersList = [];
|
||||
foreach ($coseAlgorithmManager->all() as $algorithm) {
|
||||
$publicKeyCredentialParametersList[] = new PublicKeyCredentialParameters(
|
||||
PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY,
|
||||
$algorithm::identifier()
|
||||
);
|
||||
}
|
||||
$criteria = $criteria ?? new AuthenticatorSelectionCriteria();
|
||||
$extensions = $extensions ?? new AuthenticationExtensionsClientInputs();
|
||||
$challenge = random_bytes($this->challengeSize);
|
||||
|
||||
return PublicKeyCredentialCreationOptions::create(
|
||||
$this->rpEntity,
|
||||
$userEntity,
|
||||
$challenge,
|
||||
$publicKeyCredentialParametersList
|
||||
)
|
||||
->excludeCredentials($excludedPublicKeyDescriptors)
|
||||
->setAuthenticatorSelection($criteria)
|
||||
->setAttestation($attestationMode ?? PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE)
|
||||
->setExtensions($extensions)
|
||||
->setTimeout($this->timeout)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PublicKeyCredentialDescriptor[] $allowedPublicKeyDescriptors
|
||||
*/
|
||||
public function generatePublicKeyCredentialRequestOptions(?string $userVerification = null, array $allowedPublicKeyDescriptors = [], ?AuthenticationExtensionsClientInputs $extensions = null): PublicKeyCredentialRequestOptions
|
||||
{
|
||||
return PublicKeyCredentialRequestOptions::create(random_bytes($this->challengeSize))
|
||||
->setRpId($this->rpEntity->getId())
|
||||
->setUserVerification($userVerification ?? PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED)
|
||||
->allowCredentials($allowedPublicKeyDescriptors)
|
||||
->setTimeout($this->timeout)
|
||||
->setExtensions($extensions ?? new AuthenticationExtensionsClientInputs())
|
||||
;
|
||||
}
|
||||
|
||||
public function loadAndCheckAttestationResponse(string $data, PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, ServerRequestInterface $serverRequest): PublicKeyCredentialSource
|
||||
{
|
||||
$attestationStatementSupportManager = $this->getAttestationStatementSupportManager();
|
||||
$attestationObjectLoader = AttestationObjectLoader::create($attestationStatementSupportManager)
|
||||
->setLogger($this->logger)
|
||||
;
|
||||
$publicKeyCredentialLoader = PublicKeyCredentialLoader::create($attestationObjectLoader)
|
||||
->setLogger($this->logger)
|
||||
;
|
||||
|
||||
$publicKeyCredential = $publicKeyCredentialLoader->load($data);
|
||||
$authenticatorResponse = $publicKeyCredential->getResponse();
|
||||
Assertion::isInstanceOf($authenticatorResponse, AuthenticatorAttestationResponse::class, 'Not an authenticator attestation response');
|
||||
|
||||
$authenticatorAttestationResponseValidator = new AuthenticatorAttestationResponseValidator(
|
||||
$attestationStatementSupportManager,
|
||||
$this->publicKeyCredentialSourceRepository,
|
||||
$this->tokenBindingHandler,
|
||||
$this->extensionOutputCheckerHandler,
|
||||
$this->metadataStatementRepository
|
||||
);
|
||||
$authenticatorAttestationResponseValidator->setLogger($this->logger);
|
||||
|
||||
return $authenticatorAttestationResponseValidator->check($authenticatorResponse, $publicKeyCredentialCreationOptions, $serverRequest, $this->securedRelyingPartyId);
|
||||
}
|
||||
|
||||
public function loadAndCheckAssertionResponse(string $data, PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, ?PublicKeyCredentialUserEntity $userEntity, ServerRequestInterface $serverRequest): PublicKeyCredentialSource
|
||||
{
|
||||
$attestationStatementSupportManager = $this->getAttestationStatementSupportManager();
|
||||
$attestationObjectLoader = AttestationObjectLoader::create($attestationStatementSupportManager)
|
||||
->setLogger($this->logger)
|
||||
;
|
||||
$publicKeyCredentialLoader = PublicKeyCredentialLoader::create($attestationObjectLoader)
|
||||
->setLogger($this->logger)
|
||||
;
|
||||
|
||||
$publicKeyCredential = $publicKeyCredentialLoader->load($data);
|
||||
$authenticatorResponse = $publicKeyCredential->getResponse();
|
||||
Assertion::isInstanceOf($authenticatorResponse, AuthenticatorAssertionResponse::class, 'Not an authenticator assertion response');
|
||||
|
||||
$authenticatorAssertionResponseValidator = new AuthenticatorAssertionResponseValidator(
|
||||
$this->publicKeyCredentialSourceRepository,
|
||||
$this->tokenBindingHandler,
|
||||
$this->extensionOutputCheckerHandler,
|
||||
$this->coseAlgorithmManagerFactory->create($this->selectedAlgorithms),
|
||||
$this->counterChecker
|
||||
);
|
||||
$authenticatorAssertionResponseValidator->setLogger($this->logger);
|
||||
|
||||
return $authenticatorAssertionResponseValidator->check(
|
||||
$publicKeyCredential->getRawId(),
|
||||
$authenticatorResponse,
|
||||
$publicKeyCredentialRequestOptions,
|
||||
$serverRequest,
|
||||
null !== $userEntity ? $userEntity->getId() : null,
|
||||
$this->securedRelyingPartyId
|
||||
);
|
||||
}
|
||||
|
||||
public function setCounterChecker(CounterChecker $counterChecker): self
|
||||
{
|
||||
$this->counterChecker = $counterChecker;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setLogger(LoggerInterface $logger): self
|
||||
{
|
||||
$this->logger = $logger;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function enforceAndroidSafetyNetVerification(ClientInterface $client, string $apiKey, RequestFactoryInterface $requestFactory): self
|
||||
{
|
||||
$this->httpClient = $client;
|
||||
$this->googleApiKey = $apiKey;
|
||||
$this->requestFactory = $requestFactory;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function getAttestationStatementSupportManager(): AttestationStatementSupportManager
|
||||
{
|
||||
$attestationStatementSupportManager = new AttestationStatementSupportManager();
|
||||
$attestationStatementSupportManager->add(new NoneAttestationStatementSupport());
|
||||
$attestationStatementSupportManager->add(new FidoU2FAttestationStatementSupport());
|
||||
if (class_exists(RS256::class) && class_exists(JWKFactory::class)) {
|
||||
$androidSafetyNetAttestationStatementSupport = new AndroidSafetyNetAttestationStatementSupport();
|
||||
if (null !== $this->httpClient && null !== $this->googleApiKey && null !== $this->requestFactory) {
|
||||
$androidSafetyNetAttestationStatementSupport
|
||||
->enableApiVerification($this->httpClient, $this->googleApiKey, $this->requestFactory)
|
||||
->setLeeway(2000)
|
||||
->setMaxAge(60000)
|
||||
;
|
||||
}
|
||||
$attestationStatementSupportManager->add($androidSafetyNetAttestationStatementSupport);
|
||||
}
|
||||
$attestationStatementSupportManager->add(new AndroidKeyAttestationStatementSupport());
|
||||
$attestationStatementSupportManager->add(new TPMAttestationStatementSupport());
|
||||
$coseAlgorithmManager = $this->coseAlgorithmManagerFactory->create($this->selectedAlgorithms);
|
||||
$attestationStatementSupportManager->add(new PackedAttestationStatementSupport($coseAlgorithmManager));
|
||||
|
||||
return $attestationStatementSupportManager;
|
||||
}
|
||||
}
|
73
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/StringStream.php
vendored
Normal file
73
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/StringStream.php
vendored
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?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;
|
||||
|
||||
use Assert\Assertion;
|
||||
use CBOR\Stream;
|
||||
use function Safe\fclose;
|
||||
use function Safe\fopen;
|
||||
use function Safe\fread;
|
||||
use function Safe\fwrite;
|
||||
use function Safe\rewind;
|
||||
use function Safe\sprintf;
|
||||
|
||||
final class StringStream implements Stream
|
||||
{
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $length;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $totalRead = 0;
|
||||
|
||||
public function __construct(string $data)
|
||||
{
|
||||
$this->length = mb_strlen($data, '8bit');
|
||||
$resource = fopen('php://memory', 'rb+');
|
||||
fwrite($resource, $data);
|
||||
rewind($resource);
|
||||
$this->data = $resource;
|
||||
}
|
||||
|
||||
public function read(int $length): string
|
||||
{
|
||||
if (0 === $length) {
|
||||
return '';
|
||||
}
|
||||
$read = fread($this->data, $length);
|
||||
$bytesRead = mb_strlen($read, '8bit');
|
||||
Assertion::length($read, $length, sprintf('Out of range. Expected: %d, read: %d.', $length, $bytesRead), null, '8bit');
|
||||
$this->totalRead += $bytesRead;
|
||||
|
||||
return $read;
|
||||
}
|
||||
|
||||
public function close(): void
|
||||
{
|
||||
fclose($this->data);
|
||||
}
|
||||
|
||||
public function isEOF(): bool
|
||||
{
|
||||
return $this->totalRead === $this->length;
|
||||
}
|
||||
}
|
24
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/TokenBinding/IgnoreTokenBindingHandler.php
vendored
Normal file
24
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/TokenBinding/IgnoreTokenBindingHandler.php
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?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\TokenBinding;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
final class IgnoreTokenBindingHandler implements TokenBindingHandler
|
||||
{
|
||||
public function check(TokenBinding $tokenBinding, ServerRequestInterface $request): void
|
||||
{
|
||||
//Does nothing
|
||||
}
|
||||
}
|
33
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/TokenBinding/SecTokenBindingHandler.php
vendored
Normal file
33
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/TokenBinding/SecTokenBindingHandler.php
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?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\TokenBinding;
|
||||
|
||||
use Assert\Assertion;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
final class SecTokenBindingHandler implements TokenBindingHandler
|
||||
{
|
||||
public function check(TokenBinding $tokenBinding, ServerRequestInterface $request): void
|
||||
{
|
||||
if (TokenBinding::TOKEN_BINDING_STATUS_PRESENT !== $tokenBinding->getStatus()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Assertion::true($request->hasHeader('Sec-Token-Binding'), 'The header parameter "Sec-Token-Binding" is missing.');
|
||||
$tokenBindingIds = $request->getHeader('Sec-Token-Binding');
|
||||
Assertion::count($tokenBindingIds, 1, 'The header parameter "Sec-Token-Binding" is invalid.');
|
||||
$tokenBindingId = reset($tokenBindingIds);
|
||||
Assertion::eq($tokenBindingId, $tokenBinding->getId(), 'The header parameter "Sec-Token-Binding" is invalid.');
|
||||
}
|
||||
}
|
82
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/TokenBinding/TokenBinding.php
vendored
Normal file
82
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/TokenBinding/TokenBinding.php
vendored
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?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\TokenBinding;
|
||||
|
||||
use function array_key_exists;
|
||||
use Assert\Assertion;
|
||||
use Base64Url\Base64Url;
|
||||
use function Safe\sprintf;
|
||||
|
||||
class TokenBinding
|
||||
{
|
||||
public const TOKEN_BINDING_STATUS_PRESENT = 'present';
|
||||
public const TOKEN_BINDING_STATUS_SUPPORTED = 'supported';
|
||||
public const TOKEN_BINDING_STATUS_NOT_SUPPORTED = 'not-supported';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $status;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $id;
|
||||
|
||||
public function __construct(string $status, ?string $id)
|
||||
{
|
||||
Assertion::false(self::TOKEN_BINDING_STATUS_PRESENT === $status && null === $id, 'The member "id" is required when status is "present"');
|
||||
$this->status = $status;
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $json
|
||||
*/
|
||||
public static function createFormArray(array $json): self
|
||||
{
|
||||
Assertion::keyExists($json, 'status', 'The member "status" is required');
|
||||
$status = $json['status'];
|
||||
Assertion::inArray(
|
||||
$status,
|
||||
self::getSupportedStatus(),
|
||||
sprintf('The member "status" is invalid. Supported values are: %s', implode(', ', self::getSupportedStatus()))
|
||||
);
|
||||
$id = array_key_exists('id', $json) ? Base64Url::decode($json['id']) : null;
|
||||
|
||||
return new self($status, $id);
|
||||
}
|
||||
|
||||
public function getStatus(): string
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
public function getId(): ?string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private static function getSupportedStatus(): array
|
||||
{
|
||||
return [
|
||||
self::TOKEN_BINDING_STATUS_PRESENT,
|
||||
self::TOKEN_BINDING_STATUS_SUPPORTED,
|
||||
self::TOKEN_BINDING_STATUS_NOT_SUPPORTED,
|
||||
];
|
||||
}
|
||||
}
|
21
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/TokenBinding/TokenBindingHandler.php
vendored
Normal file
21
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/TokenBinding/TokenBindingHandler.php
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?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\TokenBinding;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
interface TokenBindingHandler
|
||||
{
|
||||
public function check(TokenBinding $tokenBinding, ServerRequestInterface $request): void;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?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\TokenBinding;
|
||||
|
||||
use Assert\Assertion;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
final class TokenBindingNotSupportedHandler implements TokenBindingHandler
|
||||
{
|
||||
public function check(TokenBinding $tokenBinding, ServerRequestInterface $request): void
|
||||
{
|
||||
Assertion::true(TokenBinding::TOKEN_BINDING_STATUS_PRESENT !== $tokenBinding->getStatus(), 'Token binding not supported.');
|
||||
}
|
||||
}
|
61
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/TrustPath/CertificateTrustPath.php
vendored
Normal file
61
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/TrustPath/CertificateTrustPath.php
vendored
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?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\TrustPath;
|
||||
|
||||
use Assert\Assertion;
|
||||
|
||||
final class CertificateTrustPath implements TrustPath
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $certificates;
|
||||
|
||||
/**
|
||||
* @param string[] $certificates
|
||||
*/
|
||||
public function __construct(array $certificates)
|
||||
{
|
||||
$this->certificates = $certificates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getCertificates(): array
|
||||
{
|
||||
return $this->certificates;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createFromArray(array $data): TrustPath
|
||||
{
|
||||
Assertion::keyExists($data, 'x5c', 'The trust path type is invalid');
|
||||
|
||||
return new CertificateTrustPath($data['x5c']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'type' => self::class,
|
||||
'x5c' => $this->certificates,
|
||||
];
|
||||
}
|
||||
}
|
55
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/TrustPath/EcdaaKeyIdTrustPath.php
vendored
Normal file
55
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/TrustPath/EcdaaKeyIdTrustPath.php
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?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\TrustPath;
|
||||
|
||||
use Assert\Assertion;
|
||||
|
||||
final class EcdaaKeyIdTrustPath implements TrustPath
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $ecdaaKeyId;
|
||||
|
||||
public function __construct(string $ecdaaKeyId)
|
||||
{
|
||||
$this->ecdaaKeyId = $ecdaaKeyId;
|
||||
}
|
||||
|
||||
public function getEcdaaKeyId(): string
|
||||
{
|
||||
return $this->ecdaaKeyId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'type' => self::class,
|
||||
'ecdaaKeyId' => $this->ecdaaKeyId,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createFromArray(array $data): TrustPath
|
||||
{
|
||||
Assertion::keyExists($data, 'ecdaaKeyId', 'The trust path type is invalid');
|
||||
|
||||
return new EcdaaKeyIdTrustPath($data['ecdaaKeyId']);
|
||||
}
|
||||
}
|
35
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/TrustPath/EmptyTrustPath.php
vendored
Normal file
35
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/TrustPath/EmptyTrustPath.php
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?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\TrustPath;
|
||||
|
||||
final class EmptyTrustPath implements TrustPath
|
||||
{
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'type' => self::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createFromArray(array $data): TrustPath
|
||||
{
|
||||
return new EmptyTrustPath();
|
||||
}
|
||||
}
|
24
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/TrustPath/TrustPath.php
vendored
Normal file
24
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/TrustPath/TrustPath.php
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?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\TrustPath;
|
||||
|
||||
use JsonSerializable;
|
||||
|
||||
interface TrustPath extends JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @param mixed[] $data
|
||||
*/
|
||||
public static function createFromArray(array $data): self;
|
||||
}
|
58
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/TrustPath/TrustPathLoader.php
vendored
Normal file
58
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/TrustPath/TrustPathLoader.php
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?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\TrustPath;
|
||||
|
||||
use function array_key_exists;
|
||||
use Assert\Assertion;
|
||||
use function in_array;
|
||||
use InvalidArgumentException;
|
||||
use function Safe\class_implements;
|
||||
use function Safe\sprintf;
|
||||
|
||||
abstract class TrustPathLoader
|
||||
{
|
||||
/**
|
||||
* @param mixed[] $data
|
||||
*/
|
||||
public static function loadTrustPath(array $data): TrustPath
|
||||
{
|
||||
Assertion::keyExists($data, 'type', 'The trust path type is missing');
|
||||
$type = $data['type'];
|
||||
$oldTypes = self::oldTrustPathTypes();
|
||||
switch (true) {
|
||||
case array_key_exists($type, $oldTypes):
|
||||
return $oldTypes[$type]::createFromArray($data);
|
||||
case class_exists($type):
|
||||
$implements = class_implements($type);
|
||||
if (in_array(TrustPath::class, $implements, true)) {
|
||||
return $type::createFromArray($data);
|
||||
}
|
||||
// no break
|
||||
default:
|
||||
throw new InvalidArgumentException(sprintf('The trust path type "%s" is not supported', $data['type']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private static function oldTrustPathTypes(): array
|
||||
{
|
||||
return [
|
||||
'empty' => EmptyTrustPath::class,
|
||||
'ecdaa_key_id' => EcdaaKeyIdTrustPath::class,
|
||||
'x5c' => CertificateTrustPath::class,
|
||||
];
|
||||
}
|
||||
}
|
46
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/U2FPublicKey.php
vendored
Normal file
46
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/U2FPublicKey.php
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace Webauthn;
|
||||
|
||||
use CBOR\ByteStringObject;
|
||||
use CBOR\MapItem;
|
||||
use CBOR\MapObject;
|
||||
use CBOR\NegativeIntegerObject;
|
||||
use CBOR\UnsignedIntegerObject;
|
||||
|
||||
class U2FPublicKey
|
||||
{
|
||||
public static function isU2FKey($publicKey): bool
|
||||
{
|
||||
return $publicKey[0] === "\x04";
|
||||
}
|
||||
|
||||
public static function createCOSEKey($publicKey): string
|
||||
{
|
||||
|
||||
$mapObject = new MapObject([
|
||||
1 => MapItem::create(
|
||||
new UnsignedIntegerObject(1, null),
|
||||
new UnsignedIntegerObject(2, null)
|
||||
),
|
||||
3 => MapItem::create(
|
||||
new UnsignedIntegerObject(3, null),
|
||||
new NegativeIntegerObject(6, null)
|
||||
),
|
||||
-1 => MapItem::create(
|
||||
new NegativeIntegerObject(0, null),
|
||||
new UnsignedIntegerObject(1, null)
|
||||
),
|
||||
-2 => MapItem::create(
|
||||
new NegativeIntegerObject(1, null),
|
||||
new ByteStringObject(substr($publicKey, 1, 32))
|
||||
),
|
||||
-3 => MapItem::create(
|
||||
new NegativeIntegerObject(2, null),
|
||||
new ByteStringObject(substr($publicKey, 33))
|
||||
),
|
||||
]);
|
||||
|
||||
return $mapObject->__toString();
|
||||
}
|
||||
}
|
54
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/Util/CoseSignatureFixer.php
vendored
Normal file
54
admin/phpMyAdmin/vendor/web-auth/webauthn-lib/src/Util/CoseSignatureFixer.php
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?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\Util;
|
||||
|
||||
use Cose\Algorithm\Signature\ECDSA;
|
||||
use Cose\Algorithm\Signature\Signature;
|
||||
|
||||
/**
|
||||
* This class fixes the signature of the ECDSA based algorithms.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @see https://www.w3.org/TR/webauthn/#signature-attestation-types
|
||||
*/
|
||||
abstract class CoseSignatureFixer
|
||||
{
|
||||
public static function fix(string $signature, Signature $algorithm): string
|
||||
{
|
||||
switch ($algorithm::identifier()) {
|
||||
case ECDSA\ES256K::ID:
|
||||
case ECDSA\ES256::ID:
|
||||
if (64 === mb_strlen($signature, '8bit')) {
|
||||
return $signature;
|
||||
}
|
||||
|
||||
return ECDSA\ECSignature::fromAsn1($signature, 64); //TODO: fix this hardcoded value by adding a dedicated method for the algorithms
|
||||
case ECDSA\ES384::ID:
|
||||
if (96 === mb_strlen($signature, '8bit')) {
|
||||
return $signature;
|
||||
}
|
||||
|
||||
return ECDSA\ECSignature::fromAsn1($signature, 96);
|
||||
case ECDSA\ES512::ID:
|
||||
if (132 === mb_strlen($signature, '8bit')) {
|
||||
return $signature;
|
||||
}
|
||||
|
||||
return ECDSA\ECSignature::fromAsn1($signature, 132);
|
||||
}
|
||||
|
||||
return $signature;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue