| Linux hosting5.siteguarding.com 3.10.0-962.3.2.lve1.5.88.el7.x86_64 #1 SMP Fri Sep 26 14:06:42 UTC 2025 x86_64 Path : /home/devsafetybis/j524.dev.safetybis.com/libraries/src/WebAuthn/ |
| Current File : /home/devsafetybis/j524.dev.safetybis.com/libraries/src/WebAuthn/Server.php |
<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\WebAuthn;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
use Cose\Algorithm\Algorithm;
use Cose\Algorithm\ManagerFactory;
use Cose\Algorithm\Signature\ECDSA;
use Cose\Algorithm\Signature\EdDSA;
use Cose\Algorithm\Signature\RSA;
use ParagonIE\ConstantTime\Base64;
use ParagonIE\ConstantTime\Base64UrlSafe;
use Psr\Http\Message\ServerRequestInterface;
use Webauthn\AttestationStatement\AndroidKeyAttestationStatementSupport;
use Webauthn\AttestationStatement\AppleAttestationStatementSupport;
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\AuthenticatorAssertionResponse;
use Webauthn\AuthenticatorAssertionResponseValidator;
use Webauthn\AuthenticatorAttestationResponse;
use Webauthn\AuthenticatorAttestationResponseValidator;
use Webauthn\AuthenticatorSelectionCriteria;
use Webauthn\Exception\InvalidDataException;
use Webauthn\MetadataService\MetadataStatementRepository;
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\TokenBinding\IgnoreTokenBindingHandler;
use Webauthn\TokenBinding\TokenBindingHandler;
/**
* WebAuthn server abstraction Class.
*
* @since 5.0.0
* @internal
*/
final class Server
{
/**
* Default WebAuthn timeout in milliseconds
*
* @var int
* @since 5.0.0
*/
public int $timeout = 60000;
/**
* Random challenge size in bytes
*
* @var int
* @since 5.0.0
*/
public int $challengeSize = 32;
/**
* The relaying party entity
*
* @var PublicKeyCredentialRpEntity
* @since 5.0.0
*/
private PublicKeyCredentialRpEntity $rpEntity;
/**
* COSE algorithm manager factory instance
*
* @var ManagerFactory
* @since 5.0.0
*/
private ManagerFactory $coseAlgorithmManagerFactory;
/**
* Public Key credential source respoitory instance
*
* @var PublicKeyCredentialSourceRepository
* @since 5.0.0
*/
private PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository;
/**
* Token binding handler
*
* @var TokenBindingHandler
* @since 5.0.0
* @deprecated 6.0 Will be removed when we upgrade to WebAuthn library 5.0 or later
*/
private TokenBindingHandler $tokenBindingHandler;
/**
* Authentication extension output checker
*
* @var ExtensionOutputCheckerHandler
* @since 5.0.0
*/
private ExtensionOutputCheckerHandler $extensionOutputCheckerHandler;
/**
* COSE algorithms supported
*
* @var string[]
* @since 5.0.0
*/
private array $selectedAlgorithms;
/**
* Metadata statement repository service
*
* @var MetadataStatementRepository|null
* @since 5.0.0
*/
private ?MetadataStatementRepository $metadataStatementRepository;
/**
* Constructor
*
* @param PublicKeyCredentialRpEntity $relayingParty The relaying party entity (server information)
* @param PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository Public Key repository service
* @param MetadataStatementRepository|null $metadataStatementRepository Metadata Statement (MDS) service (optional)
*
* @since 5.0.0
*/
public function __construct(PublicKeyCredentialRpEntity $relayingParty, PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository, ?MetadataStatementRepository $metadataStatementRepository)
{
$this->rpEntity = $relayingParty;
$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;
}
/**
* Set the allowed COSE algorithms
*
* @param string[] $selectedAlgorithms
*
* @return void
* @since 5.0.0
*/
public function setSelectedAlgorithms(array $selectedAlgorithms): void
{
$this->selectedAlgorithms = $selectedAlgorithms;
}
/**
* Add an allowed COSE algorithm
*
* @param string $alias Alias for the algorithm, e.g. RS256
* @param Algorithm $algorithm The algorithm object instance
*
* @return void
* @since 5.0.0
*/
public function addAlgorithm(string $alias, Algorithm $algorithm): void
{
$this->coseAlgorithmManagerFactory->add($alias, $algorithm);
$this->selectedAlgorithms[] = $alias;
$this->selectedAlgorithms = array_unique($this->selectedAlgorithms);
}
/**
* Set the authentication extension output checker
*
* @param ExtensionOutputCheckerHandler $extensionOutputCheckerHandler
*
* @return void
* @since 5.0.0
*/
public function setExtensionOutputCheckerHandler(ExtensionOutputCheckerHandler $extensionOutputCheckerHandler): void
{
$this->extensionOutputCheckerHandler = $extensionOutputCheckerHandler;
}
/**
* Generate the Public Key credentials creation options.
*
* This is used when registering an authenticator.
*
* @param PublicKeyCredentialUserEntity $userEntity The user entity which will be bound to the authenticator.
* @param string|null $attestationMode Attestation conveyance mode. Default: not conveyed.
* @param array $excludedPublicKeyDescriptors List of PKs of authenticators already registered
* @param AuthenticatorSelectionCriteria|null $criteria Criteria for selecting an authenticator
* @param AuthenticationExtensionsClientInputs|null $extensions Allowed client inputs
*
* @return PublicKeyCredentialCreationOptions
* @since 5.0.0
*
* @throws InvalidDataException
*/
public function generatePublicKeyCredentialCreationOptions(PublicKeyCredentialUserEntity $userEntity, ?string $attestationMode = PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE, array $excludedPublicKeyDescriptors = [], ?AuthenticatorSelectionCriteria $criteria = null, ?AuthenticationExtensionsClientInputs $extensions = null): PublicKeyCredentialCreationOptions
{
$coseAlgorithmManager = $this->coseAlgorithmManagerFactory
->generate(...$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 (new PublicKeyCredentialCreationOptions(
$this->rpEntity,
$userEntity,
$challenge,
$publicKeyCredentialParametersList
))
->setTimeout($this->timeout)
->excludeCredentials(...$excludedPublicKeyDescriptors)
->setAuthenticatorSelection($criteria)
->setAttestation($attestationMode)
->setExtensions($extensions);
}
/**
* Generate Public Key credential request options
*
* @param string|null $userVerification User verification mode. Default: no preference.
* @param array $allowedPublicKeyDescriptors PKs of already registered keys the user is allowed to use.
* @param AuthenticationExtensionsClientInputs|null $extensions Allowed client inputs.
*
* @return PublicKeyCredentialRequestOptions
* @since 5.0.0
*
* @throws InvalidDataException
*/
public function generatePublicKeyCredentialRequestOptions(?string $userVerification = PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED, array $allowedPublicKeyDescriptors = [], ?AuthenticationExtensionsClientInputs $extensions = null): PublicKeyCredentialRequestOptions
{
return (new PublicKeyCredentialRequestOptions(random_bytes($this->challengeSize)))
->setTimeout($this->timeout)
->setRpId($this->rpEntity->getId())
->allowCredentials(...$allowedPublicKeyDescriptors)
->setUserVerification($userVerification)
->setExtensions($extensions ?? new AuthenticationExtensionsClientInputs());
}
/**
* Check the attestation (authenticator registration) response data and determine if it's a valid key.
*
* @param string $data The data received from the browser
* @param PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions The PK creation options used to request attestation.
* @param ServerRequestInterface $serverRequest Abstraction of the request data
*
* @return PublicKeyCredentialSource
* @since 5.0.0
*
* @throws \JsonException
* @throws \Throwable
*/
public function loadAndCheckAttestationResponse(string $data, PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, ServerRequestInterface $serverRequest): PublicKeyCredentialSource
{
// Remove padding from the response data
$temp = json_decode($data);
$temp->response = $temp?->response ?? new \stdClass();
$temp->response->clientDataJSON = rtrim($temp?->response?->clientDataJSON ?? '', '=');
$temp->response->attestationObject = rtrim($temp?->response?->attestationObject ?? '', '=');
$data = json_encode($temp);
$attestationStatementSupportManager = $this->getAttestationStatementSupportManager();
$attestationObjectLoader = new AttestationObjectLoader($attestationStatementSupportManager);
$publicKeyCredentialLoader = new PublicKeyCredentialLoader($attestationObjectLoader);
$publicKeyCredential = $publicKeyCredentialLoader->load($data);
$authenticatorResponse = $publicKeyCredential->getResponse();
if (!$authenticatorResponse instanceof AuthenticatorAttestationResponse) {
throw new \RuntimeException('Not an authenticator attestation response');
}
$authenticatorAttestationResponseValidator = new AuthenticatorAttestationResponseValidator(
$attestationStatementSupportManager,
$this->publicKeyCredentialSourceRepository,
$this->tokenBindingHandler,
$this->extensionOutputCheckerHandler
);
/**
* For our limited scope we only need an MDS repository without a statusReportRepository or a
* certificateChainValidator. For this reason we can't call the enableMetadataStatementSupport method. Instead,
* we use reflection to only set the metadataStatementRepository for partial support of MDS in the authenticator
* attestation response validator.
*
* BTW, the documentation of the library is wrong...
*/
if (!empty($this->metadataStatementRepository)) {
$refObj = new \ReflectionObject($authenticatorAttestationResponseValidator);
$refProp = $refObj->getProperty('metadataStatementRepository');
$refProp->setAccessible(true);
$refProp->setValue($authenticatorAttestationResponseValidator, $this->metadataStatementRepository);
}
return $authenticatorAttestationResponseValidator
->check($authenticatorResponse, $publicKeyCredentialCreationOptions, $serverRequest);
}
/**
* Check the assertion (authentication) response data and determine if it's valid for the user.
*
* @param string $data The data received from the browser
* @param PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions THE PK request options used during authentication
* @param PublicKeyCredentialUserEntity|null $userEntity The user we are checking against
* @param ServerRequestInterface $serverRequest Abstraction of the request data
*
* @return PublicKeyCredentialSource
* @since 5.0.0
*
* @throws \JsonException
* @throws \Throwable
*/
public function loadAndCheckAssertionResponse(string $data, PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, ?PublicKeyCredentialUserEntity $userEntity, ServerRequestInterface $serverRequest): PublicKeyCredentialSource
{
/**
* The library expects $data to be a JSON-encoded array with a 'response' key which is an array of Base64Url-
* encoded values WITHOUT padding. However, all browsers return padded values for Base64 encoding. Therefore,
* we need to manipulate $data to remove the padding which breaks the library.
*/
try {
$data = @json_decode($data, true) ?? [];
} catch (\Exception $e) {
$data = [];
}
$data['response'] = $data['response'] ?? [];
foreach (['authenticatorData', 'clientDataJSON', 'signature', 'userHandle'] as $key) {
try {
$value = Base64::decode($data['response'][$key] ?? '');
} catch (\Exception $e) {
$value = '';
}
$data['response'][$key] = Base64UrlSafe::encodeUnpadded($value);
}
$data = json_encode($data);
// Now, we can proceed with checking the assertion response.
$attestationStatementSupportManager = $this->getAttestationStatementSupportManager();
$attestationObjectLoader = new AttestationObjectLoader($attestationStatementSupportManager);
$publicKeyCredentialLoader = new PublicKeyCredentialLoader($attestationObjectLoader);
$publicKeyCredential = $publicKeyCredentialLoader->load($data);
$authenticatorResponse = $publicKeyCredential->getResponse();
if (!$authenticatorResponse instanceof AuthenticatorAssertionResponse) {
throw new \RuntimeException('Not an authenticator assertion response');
}
$authenticatorAssertionResponseValidator = new AuthenticatorAssertionResponseValidator(
$this->publicKeyCredentialSourceRepository,
null,
$this->extensionOutputCheckerHandler,
$this->coseAlgorithmManagerFactory->generate(...$this->selectedAlgorithms)
);
return $authenticatorAssertionResponseValidator->check(
$publicKeyCredential->getRawId(),
$authenticatorResponse,
$publicKeyCredentialRequestOptions,
$serverRequest,
$userEntity?->getId()
);
}
/**
* Get the attestation statement support manager object.
*
* @return AttestationStatementSupportManager
* @since 5.0.0
*/
private function getAttestationStatementSupportManager(): AttestationStatementSupportManager
{
$coseAlgorithmManager = $this->coseAlgorithmManagerFactory->generate(...$this->selectedAlgorithms);
$attestationStatementSupportManager = new AttestationStatementSupportManager();
$attestationStatementSupportManager->add(new NoneAttestationStatementSupport());
$attestationStatementSupportManager->add(new FidoU2FAttestationStatementSupport());
$attestationStatementSupportManager->add(new AppleAttestationStatementSupport());
$attestationStatementSupportManager->add(new AndroidKeyAttestationStatementSupport());
$attestationStatementSupportManager->add(new TPMAttestationStatementSupport());
$attestationStatementSupportManager->add(new PackedAttestationStatementSupport($coseAlgorithmManager));
return $attestationStatementSupportManager;
}
}