Merge pull request #18752 from MauricioFauth/webauthn-lib-v4
Upgrade web-auth/webauthn-lib to version 4
This commit is contained in:
commit
ca2fb1bd4c
@ -295,7 +295,7 @@ security_checkup() {
|
||||
echo 'TCPDF should be installed, detection failed !'
|
||||
exit 1;
|
||||
fi
|
||||
if [ ! -f vendor/web-auth/webauthn-lib/src/Server.php ]; then
|
||||
if [ ! -f vendor/web-auth/webauthn-lib/src/PublicKeyCredential.php ]; then
|
||||
echo 'Webauthn-lib should be installed, detection failed !'
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
@ -119,7 +119,7 @@
|
||||
"symfony/console": "^6.2",
|
||||
"tecnickcom/tcpdf": "^6.4.4",
|
||||
"vimeo/psalm": "^5.14",
|
||||
"web-auth/webauthn-lib": "^3.3.12"
|
||||
"web-auth/webauthn-lib": "^4.9"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
|
||||
1293
composer.lock
generated
1293
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -14050,6 +14050,46 @@ parameters:
|
||||
count: 1
|
||||
path: src/WebAuthn/CBORDecoder.php
|
||||
|
||||
-
|
||||
message: "#^Call to static method Webmozart\\\\Assert\\\\Assert\\:\\:isInstanceOf\\(\\) with Webauthn\\\\PublicKeyCredentialCreationOptions and 'Webauthn\\\\\\\\PublicKeyCredentialCreationOptions' will always evaluate to true\\.$#"
|
||||
count: 1
|
||||
path: src/WebAuthn/WebauthnLibServer.php
|
||||
|
||||
-
|
||||
message: "#^Call to static method Webmozart\\\\Assert\\\\Assert\\:\\:isInstanceOf\\(\\) with Webauthn\\\\PublicKeyCredentialRequestOptions and 'Webauthn\\\\\\\\PublicKeyCredentialRequestOptions' will always evaluate to true\\.$#"
|
||||
count: 1
|
||||
path: src/WebAuthn/WebauthnLibServer.php
|
||||
|
||||
-
|
||||
message: "#^Cannot access offset 'authenticatorData' on mixed\\.$#"
|
||||
count: 2
|
||||
path: src/WebAuthn/WebauthnLibServer.php
|
||||
|
||||
-
|
||||
message: "#^Cannot access offset 'clientDataJSON' on mixed\\.$#"
|
||||
count: 4
|
||||
path: src/WebAuthn/WebauthnLibServer.php
|
||||
|
||||
-
|
||||
message: "#^Cannot access offset 'response' on mixed\\.$#"
|
||||
count: 6
|
||||
path: src/WebAuthn/WebauthnLibServer.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$haystack of function str_ends_with expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/WebAuthn/WebauthnLibServer.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$string of function rtrim expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/WebAuthn/WebauthnLibServer.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$string of function sodium_base642bin expects string, mixed given\\.$#"
|
||||
count: 3
|
||||
path: src/WebAuthn/WebauthnLibServer.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpMyAdmin\\\\ZipExtension\\:\\:getContents\\(\\) should return array\\{error\\: string, data\\: string\\} but returns array\\{error\\: '', data\\: string\\|false\\}\\.$#"
|
||||
count: 1
|
||||
|
||||
@ -11989,6 +11989,84 @@
|
||||
* }]]></code>
|
||||
</MoreSpecificReturnType>
|
||||
</file>
|
||||
<file src="src/WebAuthn/WebauthnLibServer.php">
|
||||
<DeprecatedClass>
|
||||
<code><![CDATA[PublicKeyCredentialLoader::create($attestationObjectLoader)]]></code>
|
||||
<code><![CDATA[PublicKeyCredentialLoader::create($attestationObjectLoader)]]></code>
|
||||
<code><![CDATA[PublicKeyCredentialLoader::create($attestationObjectLoader)]]></code>
|
||||
<code><![CDATA[new AuthenticationExtensionsClientInputs()]]></code>
|
||||
<code><![CDATA[new AuthenticationExtensionsClientInputs()]]></code>
|
||||
<code><![CDATA[new IgnoreTokenBindingHandler()]]></code>
|
||||
<code><![CDATA[new IgnoreTokenBindingHandler()]]></code>
|
||||
</DeprecatedClass>
|
||||
<DeprecatedInterface>
|
||||
<code><![CDATA[PublicKeyCredentialSourceRepository]]></code>
|
||||
<code><![CDATA[class ($this->twofactor) implements PublicKeyCredentialSourceRepository {]]></code>
|
||||
</DeprecatedInterface>
|
||||
<DeprecatedMethod>
|
||||
<code><![CDATA[AuthenticatorSelectionCriteria::createFromArray([
|
||||
'authenticatorAttachment' => 'cross-platform',
|
||||
'userVerification' => 'discouraged',
|
||||
])]]></code>
|
||||
<code><![CDATA[PublicKeyCredentialCreationOptions::createFromArray($creationOptionsArray)]]></code>
|
||||
<code><![CDATA[PublicKeyCredentialRequestOptions::createFromArray([
|
||||
'challenge' => $challenge,
|
||||
'allowCredentials' => $allowedCredentials,
|
||||
'rpId' => $host,
|
||||
'timeout' => 60000,
|
||||
])]]></code>
|
||||
<code><![CDATA[PublicKeyCredentialSource::createFromArray($data)]]></code>
|
||||
<code><![CDATA[PublicKeyCredentialSource::createFromArray($data[base64_encode($publicKeyCredentialId)])]]></code>
|
||||
<code><![CDATA[allowCredentials]]></code>
|
||||
<code><![CDATA[getId]]></code>
|
||||
<code><![CDATA[getId]]></code>
|
||||
<code><![CDATA[getId]]></code>
|
||||
<code><![CDATA[getPublicKeyCredentialId]]></code>
|
||||
<code><![CDATA[getRawId]]></code>
|
||||
<code><![CDATA[getResponse]]></code>
|
||||
<code><![CDATA[getResponse]]></code>
|
||||
<code><![CDATA[getUserHandle]]></code>
|
||||
<code><![CDATA[setAttestation]]></code>
|
||||
<code><![CDATA[setAuthenticatorSelection]]></code>
|
||||
<code><![CDATA[setExtensions]]></code>
|
||||
<code><![CDATA[setExtensions]]></code>
|
||||
<code><![CDATA[setRpId]]></code>
|
||||
<code><![CDATA[setTimeout]]></code>
|
||||
<code><![CDATA[setTimeout]]></code>
|
||||
<code><![CDATA[setUserVerification]]></code>
|
||||
</DeprecatedMethod>
|
||||
<MixedArgument>
|
||||
<code><![CDATA[$assertionResponseDecoded['response']['authenticatorData']]]></code>
|
||||
<code><![CDATA[$assertionResponseDecoded['response']['clientDataJSON']]]></code>
|
||||
<code><![CDATA[$attestationResponseDecoded['response']['clientDataJSON']]]></code>
|
||||
<code><![CDATA[$credential['userHandle']]]></code>
|
||||
</MixedArgument>
|
||||
<MixedArrayAccess>
|
||||
<code><![CDATA[$assertionResponseDecoded['response']]]></code>
|
||||
<code><![CDATA[$assertionResponseDecoded['response']]]></code>
|
||||
<code><![CDATA[$assertionResponseDecoded['response']['authenticatorData']]]></code>
|
||||
<code><![CDATA[$assertionResponseDecoded['response']['clientDataJSON']]]></code>
|
||||
<code><![CDATA[$attestationResponseDecoded['response']]]></code>
|
||||
<code><![CDATA[$attestationResponseDecoded['response']]]></code>
|
||||
<code><![CDATA[$attestationResponseDecoded['response']['clientDataJSON']]]></code>
|
||||
</MixedArrayAccess>
|
||||
<MixedArrayAssignment>
|
||||
<code><![CDATA[$assertionResponseDecoded['response']]]></code>
|
||||
<code><![CDATA[$assertionResponseDecoded['response']]]></code>
|
||||
<code><![CDATA[$assertionResponseDecoded['response']['authenticatorData']]]></code>
|
||||
<code><![CDATA[$assertionResponseDecoded['response']['clientDataJSON']]]></code>
|
||||
<code><![CDATA[$attestationResponseDecoded['response']]]></code>
|
||||
<code><![CDATA[$attestationResponseDecoded['response']['clientDataJSON']]]></code>
|
||||
</MixedArrayAssignment>
|
||||
<MixedAssignment>
|
||||
<code><![CDATA[$assertionResponseDecoded]]></code>
|
||||
<code><![CDATA[$attestationResponseDecoded]]></code>
|
||||
</MixedAssignment>
|
||||
<RedundantCondition>
|
||||
<code><![CDATA[isInstanceOf]]></code>
|
||||
<code><![CDATA[isInstanceOf]]></code>
|
||||
</RedundantCondition>
|
||||
</file>
|
||||
<file src="src/ZipExtension.php">
|
||||
<ArgumentTypeCoercion>
|
||||
<code><![CDATA[$regex]]></code>
|
||||
|
||||
@ -13,7 +13,7 @@ use PhpMyAdmin\WebAuthn\Server;
|
||||
use PhpMyAdmin\WebAuthn\WebauthnLibServer;
|
||||
use SodiumException;
|
||||
use Throwable;
|
||||
use Webauthn\Server as WebauthnServer;
|
||||
use Webauthn\PublicKeyCredential;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
use function __;
|
||||
@ -61,7 +61,7 @@ class WebAuthn extends TwoFactorPlugin
|
||||
|
||||
private function createServer(): Server
|
||||
{
|
||||
return class_exists(WebauthnServer::class) ? new WebauthnLibServer($this->twofactor) : new CustomServer();
|
||||
return class_exists(PublicKeyCredential::class) ? new WebauthnLibServer($this->twofactor) : new CustomServer();
|
||||
}
|
||||
|
||||
public function setServer(Server $server): void
|
||||
|
||||
@ -4,33 +4,78 @@ declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\WebAuthn;
|
||||
|
||||
use Cose\Algorithm\ManagerFactory;
|
||||
use Cose\Algorithm\Signature\ECDSA;
|
||||
use Cose\Algorithm\Signature\EdDSA;
|
||||
use Cose\Algorithm\Signature\RSA;
|
||||
use PhpMyAdmin\TwoFactor;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Webauthn\AttestationStatement\AttestationObjectLoader;
|
||||
use Webauthn\AttestationStatement\AttestationStatementSupportManager;
|
||||
use Webauthn\AttestationStatement\NoneAttestationStatementSupport;
|
||||
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
|
||||
use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler;
|
||||
use Webauthn\AuthenticatorAssertionResponse;
|
||||
use Webauthn\AuthenticatorAssertionResponseValidator;
|
||||
use Webauthn\AuthenticatorAttestationResponse;
|
||||
use Webauthn\AuthenticatorAttestationResponseValidator;
|
||||
use Webauthn\AuthenticatorSelectionCriteria;
|
||||
use Webauthn\PublicKeyCredentialCreationOptions;
|
||||
use Webauthn\PublicKeyCredentialDescriptor;
|
||||
use Webauthn\PublicKeyCredentialLoader;
|
||||
use Webauthn\PublicKeyCredentialParameters;
|
||||
use Webauthn\PublicKeyCredentialRequestOptions;
|
||||
use Webauthn\PublicKeyCredentialRpEntity;
|
||||
use Webauthn\PublicKeyCredentialSource;
|
||||
use Webauthn\PublicKeyCredentialSourceRepository;
|
||||
use Webauthn\PublicKeyCredentialUserEntity;
|
||||
use Webauthn\Server as WebauthnServer;
|
||||
use Webauthn\TokenBinding\IgnoreTokenBindingHandler;
|
||||
use Webauthn\TrustPath\EmptyTrustPath;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
use function array_map;
|
||||
use function base64_encode;
|
||||
use function json_decode;
|
||||
use function json_encode;
|
||||
use function random_bytes;
|
||||
use function rtrim;
|
||||
use function sodium_base642bin;
|
||||
use function sodium_bin2base64;
|
||||
use function str_ends_with;
|
||||
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
use const SODIUM_BASE64_VARIANT_ORIGINAL;
|
||||
use const SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING;
|
||||
|
||||
final class WebauthnLibServer implements Server
|
||||
{
|
||||
private int $timeout = 60000;
|
||||
|
||||
/** @phpstan-var int<1, max> */
|
||||
private int $challengeSize = 32;
|
||||
|
||||
private ManagerFactory $coseAlgorithmManagerFactory;
|
||||
|
||||
/** @var string[] */
|
||||
private array $selectedAlgorithms;
|
||||
|
||||
public function __construct(private TwoFactor $twofactor)
|
||||
{
|
||||
$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'];
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
@ -38,17 +83,31 @@ final class WebauthnLibServer implements Server
|
||||
{
|
||||
$userEntity = new PublicKeyCredentialUserEntity($userName, $userId, $userName);
|
||||
$relyingPartyEntity = new PublicKeyCredentialRpEntity('phpMyAdmin (' . $relyingPartyId . ')', $relyingPartyId);
|
||||
$publicKeyCredentialSourceRepository = $this->createPublicKeyCredentialSourceRepository();
|
||||
$server = new WebauthnServer($relyingPartyEntity, $publicKeyCredentialSourceRepository);
|
||||
$publicKeyCredentialCreationOptions = $server->generatePublicKeyCredentialCreationOptions(
|
||||
|
||||
$coseAlgorithmManager = $this->coseAlgorithmManagerFactory->generate(...$this->selectedAlgorithms);
|
||||
$publicKeyCredentialParametersList = [];
|
||||
foreach ($coseAlgorithmManager->all() as $algorithm) {
|
||||
$publicKeyCredentialParametersList[] = new PublicKeyCredentialParameters(
|
||||
PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY,
|
||||
$algorithm::identifier(),
|
||||
);
|
||||
}
|
||||
|
||||
$criteria = AuthenticatorSelectionCriteria::createFromArray([
|
||||
'authenticatorAttachment' => 'cross-platform',
|
||||
'userVerification' => 'discouraged',
|
||||
]);
|
||||
$publicKeyCredentialCreationOptions = PublicKeyCredentialCreationOptions::create(
|
||||
$relyingPartyEntity,
|
||||
$userEntity,
|
||||
PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE,
|
||||
[],
|
||||
AuthenticatorSelectionCriteria::createFromArray([
|
||||
'authenticatorAttachment' => 'cross-platform',
|
||||
'userVerification' => 'discouraged',
|
||||
]),
|
||||
);
|
||||
random_bytes($this->challengeSize),
|
||||
$publicKeyCredentialParametersList,
|
||||
)
|
||||
->setAuthenticatorSelection($criteria)
|
||||
->setAttestation(PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE)
|
||||
->setExtensions(new AuthenticationExtensionsClientInputs())
|
||||
->setTimeout($this->timeout);
|
||||
|
||||
/** @psalm-var array{
|
||||
* challenge: non-empty-string,
|
||||
* rp: array{name: non-empty-string, id: non-empty-string},
|
||||
@ -58,7 +117,7 @@ final class WebauthnLibServer implements Server
|
||||
* timeout: positive-int,
|
||||
* attestation: non-empty-string
|
||||
* } $creationOptions */
|
||||
$creationOptions = $publicKeyCredentialCreationOptions->jsonSerialize();
|
||||
$creationOptions = $this->normalize($publicKeyCredentialCreationOptions);
|
||||
$creationOptions['challenge'] = sodium_bin2base64(
|
||||
sodium_base642bin($creationOptions['challenge'], SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING),
|
||||
SODIUM_BASE64_VARIANT_ORIGINAL,
|
||||
@ -78,7 +137,6 @@ final class WebauthnLibServer implements Server
|
||||
$userEntity = new PublicKeyCredentialUserEntity($userName, $userId, $userName);
|
||||
$relyingPartyEntity = new PublicKeyCredentialRpEntity('phpMyAdmin (' . $relyingPartyId . ')', $relyingPartyId);
|
||||
$publicKeyCredentialSourceRepository = $this->createPublicKeyCredentialSourceRepository();
|
||||
$server = new WebauthnServer($relyingPartyEntity, $publicKeyCredentialSourceRepository);
|
||||
$credentialSources = $publicKeyCredentialSourceRepository->findAllForUserEntity($userEntity);
|
||||
$allowedCredentials = array_map(
|
||||
static fn (
|
||||
@ -86,17 +144,22 @@ final class WebauthnLibServer implements Server
|
||||
): PublicKeyCredentialDescriptor => $credential->getPublicKeyCredentialDescriptor(),
|
||||
$credentialSources,
|
||||
);
|
||||
$publicKeyCredentialRequestOptions = $server->generatePublicKeyCredentialRequestOptions(
|
||||
'discouraged',
|
||||
$allowedCredentials,
|
||||
);
|
||||
|
||||
$challenge = random_bytes($this->challengeSize);
|
||||
$publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::create($challenge)
|
||||
->setRpId($relyingPartyEntity->getId())
|
||||
->setUserVerification(PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_DISCOURAGED)
|
||||
->allowCredentials(...$allowedCredentials)
|
||||
->setTimeout($this->timeout)
|
||||
->setExtensions(new AuthenticationExtensionsClientInputs());
|
||||
|
||||
/**
|
||||
* @psalm-var array{
|
||||
* challenge: string,
|
||||
* allowCredentials?: list<array{id: non-empty-string, type: non-empty-string}>
|
||||
* } $requestOptions
|
||||
*/
|
||||
$requestOptions = $publicKeyCredentialRequestOptions->jsonSerialize();
|
||||
$requestOptions = $this->normalize($publicKeyCredentialRequestOptions);
|
||||
$requestOptions['challenge'] = sodium_bin2base64(
|
||||
sodium_base642bin($requestOptions['challenge'], SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING),
|
||||
SODIUM_BASE64_VARIANT_ORIGINAL,
|
||||
@ -127,9 +190,7 @@ final class WebauthnLibServer implements Server
|
||||
);
|
||||
$userEntity = new PublicKeyCredentialUserEntity($this->twofactor->user, $userHandle, $this->twofactor->user);
|
||||
$host = $request->getUri()->getHost();
|
||||
$relyingPartyEntity = new PublicKeyCredentialRpEntity('phpMyAdmin (' . $host . ')', $host);
|
||||
$publicKeyCredentialSourceRepository = $this->createPublicKeyCredentialSourceRepository();
|
||||
$server = new WebauthnServer($relyingPartyEntity, $publicKeyCredentialSourceRepository);
|
||||
$requestOptions = PublicKeyCredentialRequestOptions::createFromArray([
|
||||
'challenge' => $challenge,
|
||||
'allowCredentials' => $allowedCredentials,
|
||||
@ -137,7 +198,52 @@ final class WebauthnLibServer implements Server
|
||||
'timeout' => 60000,
|
||||
]);
|
||||
Assert::isInstanceOf($requestOptions, PublicKeyCredentialRequestOptions::class);
|
||||
$server->loadAndCheckAssertionResponse($assertionResponseJson, $requestOptions, $userEntity, $request);
|
||||
|
||||
$attestationStatementSupportManager = new AttestationStatementSupportManager();
|
||||
$attestationStatementSupportManager->add(new NoneAttestationStatementSupport());
|
||||
$attestationObjectLoader = AttestationObjectLoader::create($attestationStatementSupportManager);
|
||||
$publicKeyCredentialLoader = PublicKeyCredentialLoader::create($attestationObjectLoader);
|
||||
|
||||
$assertionResponseDecoded = json_decode($assertionResponseJson, true, flags: JSON_THROW_ON_ERROR);
|
||||
$assertionResponseDecoded['response']['authenticatorData'] = sodium_bin2base64(
|
||||
sodium_base642bin(
|
||||
$assertionResponseDecoded['response']['authenticatorData'],
|
||||
SODIUM_BASE64_VARIANT_ORIGINAL,
|
||||
),
|
||||
SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING,
|
||||
);
|
||||
$assertionResponseDecoded['response']['clientDataJSON'] = sodium_bin2base64(
|
||||
sodium_base642bin(
|
||||
$assertionResponseDecoded['response']['clientDataJSON'],
|
||||
SODIUM_BASE64_VARIANT_ORIGINAL,
|
||||
),
|
||||
SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING,
|
||||
);
|
||||
|
||||
$publicKeyCredential = $publicKeyCredentialLoader->load(
|
||||
json_encode($assertionResponseDecoded, JSON_THROW_ON_ERROR),
|
||||
);
|
||||
$authenticatorResponse = $publicKeyCredential->getResponse();
|
||||
Assert::isInstanceOf(
|
||||
$authenticatorResponse,
|
||||
AuthenticatorAssertionResponse::class,
|
||||
'Not an authenticator assertion response',
|
||||
);
|
||||
|
||||
$authenticatorAssertionResponseValidator = new AuthenticatorAssertionResponseValidator(
|
||||
$publicKeyCredentialSourceRepository,
|
||||
new IgnoreTokenBindingHandler(),
|
||||
new ExtensionOutputCheckerHandler(),
|
||||
$this->coseAlgorithmManagerFactory->generate(...$this->selectedAlgorithms),
|
||||
);
|
||||
|
||||
$authenticatorAssertionResponseValidator->check(
|
||||
$publicKeyCredential->getRawId(),
|
||||
$authenticatorResponse,
|
||||
$requestOptions,
|
||||
$request,
|
||||
$userEntity->getId(),
|
||||
);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
@ -146,16 +252,14 @@ final class WebauthnLibServer implements Server
|
||||
string $credentialCreationOptions,
|
||||
ServerRequestInterface $request,
|
||||
): array {
|
||||
$creationOptions = json_decode($credentialCreationOptions, true);
|
||||
$creationOptions = json_decode($credentialCreationOptions, true, flags: JSON_THROW_ON_ERROR);
|
||||
Assert::isArray($creationOptions);
|
||||
Assert::keyExists($creationOptions, 'challenge');
|
||||
Assert::keyExists($creationOptions, 'user');
|
||||
Assert::isArray($creationOptions['user']);
|
||||
Assert::keyExists($creationOptions['user'], 'id');
|
||||
$host = $request->getUri()->getHost();
|
||||
$relyingPartyEntity = new PublicKeyCredentialRpEntity('phpMyAdmin (' . $host . ')', $host);
|
||||
$publicKeyCredentialSourceRepository = $this->createPublicKeyCredentialSourceRepository();
|
||||
$server = new WebauthnServer($relyingPartyEntity, $publicKeyCredentialSourceRepository);
|
||||
$creationOptionsArray = [
|
||||
'rp' => ['name' => 'phpMyAdmin (' . $host . ')', 'id' => $host],
|
||||
'pubKeyCredParams' => [
|
||||
@ -182,13 +286,45 @@ final class WebauthnLibServer implements Server
|
||||
];
|
||||
$credentialCreationOptions = PublicKeyCredentialCreationOptions::createFromArray($creationOptionsArray);
|
||||
Assert::isInstanceOf($credentialCreationOptions, PublicKeyCredentialCreationOptions::class);
|
||||
$publicKeyCredentialSource = $server->loadAndCheckAttestationResponse(
|
||||
$attestationResponse,
|
||||
|
||||
$attestationStatementSupportManager = new AttestationStatementSupportManager();
|
||||
$attestationStatementSupportManager->add(new NoneAttestationStatementSupport());
|
||||
$attestationObjectLoader = AttestationObjectLoader::create($attestationStatementSupportManager);
|
||||
$publicKeyCredentialLoader = PublicKeyCredentialLoader::create($attestationObjectLoader);
|
||||
|
||||
$attestationResponseDecoded = json_decode($attestationResponse, true, flags: JSON_THROW_ON_ERROR);
|
||||
$attestationResponseDecoded['response']['clientDataJSON'] = sodium_bin2base64(
|
||||
sodium_base642bin(
|
||||
$attestationResponseDecoded['response']['clientDataJSON'],
|
||||
SODIUM_BASE64_VARIANT_ORIGINAL,
|
||||
),
|
||||
SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING,
|
||||
);
|
||||
|
||||
$publicKeyCredential = $publicKeyCredentialLoader->load(
|
||||
json_encode($attestationResponseDecoded, JSON_THROW_ON_ERROR),
|
||||
);
|
||||
$authenticatorResponse = $publicKeyCredential->getResponse();
|
||||
Assert::isInstanceOf(
|
||||
$authenticatorResponse,
|
||||
AuthenticatorAttestationResponse::class,
|
||||
'Not an authenticator attestation response',
|
||||
);
|
||||
|
||||
$authenticatorAttestationResponseValidator = new AuthenticatorAttestationResponseValidator(
|
||||
$attestationStatementSupportManager,
|
||||
$publicKeyCredentialSourceRepository,
|
||||
new IgnoreTokenBindingHandler(),
|
||||
new ExtensionOutputCheckerHandler(),
|
||||
);
|
||||
|
||||
$publicKeyCredentialSource = $authenticatorAttestationResponseValidator->check(
|
||||
$authenticatorResponse,
|
||||
$credentialCreationOptions,
|
||||
$request,
|
||||
);
|
||||
|
||||
return $publicKeyCredentialSource->jsonSerialize();
|
||||
return $this->normalize($publicKeyCredentialSource);
|
||||
}
|
||||
|
||||
/** @infection-ignore-all */
|
||||
@ -229,7 +365,10 @@ final class WebauthnLibServer implements Server
|
||||
{
|
||||
$data = $this->read();
|
||||
$id = $publicKeyCredentialSource->getPublicKeyCredentialId();
|
||||
$data[base64_encode($id)] = $publicKeyCredentialSource->jsonSerialize();
|
||||
$encoded = json_encode($publicKeyCredentialSource, JSON_THROW_ON_ERROR);
|
||||
$normalized = json_decode($encoded, true, flags: JSON_THROW_ON_ERROR);
|
||||
Assert::isArray($normalized);
|
||||
$data[base64_encode($id)] = $normalized;
|
||||
$this->write($data);
|
||||
}
|
||||
|
||||
@ -239,6 +378,10 @@ final class WebauthnLibServer implements Server
|
||||
/** @psalm-var list<mixed[]> $credentials */
|
||||
$credentials = $this->twoFactor->config['settings']['credentials'];
|
||||
foreach ($credentials as &$credential) {
|
||||
if (str_ends_with($credential['userHandle'], '=')) {
|
||||
$credential['userHandle'] = rtrim($credential['userHandle'], '=');
|
||||
}
|
||||
|
||||
if (isset($credential['trustPath'])) {
|
||||
continue;
|
||||
}
|
||||
@ -256,4 +399,14 @@ final class WebauthnLibServer implements Server
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** @return mixed[] */
|
||||
private function normalize(object $object): array
|
||||
{
|
||||
$encoded = json_encode($object, JSON_THROW_ON_ERROR);
|
||||
$normalized = json_decode($encoded, true, flags: JSON_THROW_ON_ERROR);
|
||||
Assert::isArray($normalized);
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,10 +11,9 @@ use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\RequiresPhpExtension;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Webauthn\Server as WebauthnServer;
|
||||
use Webauthn\PublicKeyCredential;
|
||||
use Webauthn\TrustPath\EmptyTrustPath;
|
||||
|
||||
use function base64_encode;
|
||||
use function class_exists;
|
||||
|
||||
#[CoversClass(WebauthnLibServer::class)]
|
||||
@ -24,7 +23,7 @@ final class WebauthnLibServerTest extends TestCase
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
if (class_exists(WebauthnServer::class)) {
|
||||
if (class_exists(PublicKeyCredential::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -45,7 +44,7 @@ final class WebauthnLibServerTest extends TestCase
|
||||
self::assertSame('test.localhost', $options['rp']['id']);
|
||||
self::assertSame('user_name', $options['user']['name']);
|
||||
self::assertSame('user_name', $options['user']['displayName']);
|
||||
self::assertSame(base64_encode('user_id'), $options['user']['id']);
|
||||
self::assertSame('dXNlcl9pZA', $options['user']['id']);
|
||||
self::assertArrayHasKey('authenticatorAttachment', $options['authenticatorSelection']);
|
||||
self::assertSame('cross-platform', $options['authenticatorSelection']['authenticatorAttachment']);
|
||||
}
|
||||
@ -156,7 +155,8 @@ final class WebauthnLibServerTest extends TestCase
|
||||
'credentialPublicKey' => 'pQECAyYgASFYIJV56vRrFusoDf9hm3iDmllcxxXzzKyO9WruKw4kWx7zIlgg_nq63l8IMJcIdKDJcXRh9hoz0L-nVwP1Oxil3_oNQYs',
|
||||
'userHandle' => 'Zm9v',
|
||||
'counter' => 123,
|
||||
'otherUI' => null,
|
||||
'backupEligible' => false,
|
||||
'backupStatus' => false,
|
||||
],
|
||||
],
|
||||
$twoFactor->config['settings']['credentials'],
|
||||
@ -196,7 +196,9 @@ final class WebauthnLibServerTest extends TestCase
|
||||
'credentialPublicKey' => 'pQECAyYgASFYIBw_HArIcANWNOBOxq3hH8lrHo9a17nQDxlqwybjDpHEIlggu3QUKIbALqsGuHfJI3LTKJSNmk0YCFb5oz1hjJidRMk',
|
||||
'userHandle' => 'MJr5sD0WitVwZM0eoSO6kWhyseT67vc3oQdk_k1VdZQ',
|
||||
'counter' => 0,
|
||||
'otherUI' => null,
|
||||
'backupEligible' => false,
|
||||
'backupStatus' => false,
|
||||
'uvInitialized' => false,
|
||||
],
|
||||
$credential,
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user