misc_pterodactyl-panel/app/Repositories/SecurityKeys/WebauthnServerRepository.php
2022-10-31 12:18:25 -06:00

161 lines
6.7 KiB
PHP

<?php
namespace Pterodactyl\Repositories\SecurityKeys;
use Cose\Algorithms;
use Illuminate\Support\Str;
use Pterodactyl\Models\User;
use Pterodactyl\Models\SecurityKey;
use Webauthn\PublicKeyCredentialLoader;
use Webauthn\PublicKeyCredentialSource;
use Webauthn\PublicKeyCredentialRpEntity;
use Webauthn\PublicKeyCredentialParameters;
use Psr\Http\Message\ServerRequestInterface;
use Webauthn\AuthenticatorAssertionResponse;
use Webauthn\AuthenticatorSelectionCriteria;
use Webauthn\AuthenticatorAttestationResponse;
use Cose\Algorithm\Manager as AlgorithmManager;
use Webauthn\PublicKeyCredentialRequestOptions;
use Webauthn\PublicKeyCredentialCreationOptions;
use Webauthn\AuthenticatorAssertionResponseValidator;
use Webauthn\AuthenticatorAttestationResponseValidator;
use Webauthn\AttestationStatement\AttestationObjectLoader;
use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler;
use Webauthn\AttestationStatement\AttestationStatementSupportManager;
final class WebauthnServerRepository
{
private PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository;
private PublicKeyCredentialRpEntity $rpEntity;
private PublicKeyCredentialLoader $credentialLoader;
private AuthenticatorAssertionResponseValidator $assertionValidator;
private AuthenticatorAttestationResponseValidator $attestationValidator;
public function __construct(PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository)
{
$url = str_replace(['http://', 'https://'], '', config('app.url'));
$this->publicKeyCredentialSourceRepository = $publicKeyCredentialSourceRepository;
$this->rpEntity = new PublicKeyCredentialRpEntity(config('app.name'), trim($url, '/'));
$this->credentialLoader = new PublicKeyCredentialLoader(new AttestationObjectLoader(new AttestationStatementSupportManager()));
$this->assertionValidator = new AuthenticatorAssertionResponseValidator(
$this->publicKeyCredentialSourceRepository,
null,
ExtensionOutputCheckerHandler::create(),
AlgorithmManager::create(),
);
$this->attestationValidator = new AuthenticatorAttestationResponseValidator(
new AttestationStatementSupportManager(),
$this->publicKeyCredentialSourceRepository,
null,
new ExtensionOutputCheckerHandler(),
);
}
/**
* @throws \Webauthn\Exception\InvalidDataException
*/
public function getPublicKeyCredentialCreationOptions(User $user): PublicKeyCredentialCreationOptions
{
$excluded = $user->securityKeys->map(function (SecurityKey $key) {
return $key->getPublicKeyCredentialDescriptor();
})->values()->toArray();
$challenge = Str::random(16);
return (new PublicKeyCredentialCreationOptions(
$this->rpEntity,
$user->toPublicKeyCredentialEntity(),
$challenge,
[
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ES256),
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ES256K),
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ES384),
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ES512),
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_RS256),
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_RS384),
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_RS512),
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_PS256),
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_PS384),
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_PS512),
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ED256),
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ED512),
],
))
->setTimeout(30_000)
->excludeCredentials(...$excluded)
->setAttestation(PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE)
->setAuthenticatorSelection(AuthenticatorSelectionCriteria::create());
}
/**
* @throws \Webauthn\Exception\InvalidDataException
*/
public function generatePublicKeyCredentialRequestOptions(User $user): PublicKeyCredentialRequestOptions
{
$allowedCredentials = $user->securityKeys->map(function (SecurityKey $key) {
return $key->getPublicKeyCredentialDescriptor();
})->values()->toArray();
return (new PublicKeyCredentialRequestOptions(Str::random(32)))
->allowCredentials(...$allowedCredentials)
->setUserVerification(PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED);
}
/**
* @throws \Throwable
* @throws \JsonException
*/
public function loadAndCheckAssertionResponse(
User $user,
array $data,
PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions,
ServerRequestInterface $request
): PublicKeyCredentialSource {
$credential = $this->credentialLoader->loadArray($data);
$authenticatorAssertionResponse = $credential->getResponse();
if (!$authenticatorAssertionResponse instanceof AuthenticatorAssertionResponse) {
// TODO
throw new \Exception('');
}
return $this->assertionValidator->check(
$credential->getRawId(),
$authenticatorAssertionResponse,
$publicKeyCredentialRequestOptions,
$request,
null, // TODO: use handle?
// $user->toPublicKeyCredentialEntity()
);
}
/**
* Register a new security key for a user.
*
* @throws \Throwable
* @throws \JsonException
*/
public function loadAndCheckAttestationResponse(
User $user,
array $data,
PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions,
ServerRequestInterface $request
): PublicKeyCredentialSource {
$credential = $this->credentialLoader->loadArray($data);
$authenticatorAttestationResponse = $credential->getResponse();
if (!$authenticatorAttestationResponse instanceof AuthenticatorAttestationResponse) {
// TODO
throw new \Exception('');
}
return $this->attestationValidator->check(
$authenticatorAttestationResponse,
$publicKeyCredentialCreationOptions,
$request,
);
}
}