can add new key and delete existing one
This commit is contained in:
@ -15,8 +15,11 @@
|
|||||||
|
|
||||||
namespace D3\Webauthn\Application\Controller\Admin;
|
namespace D3\Webauthn\Application\Controller\Admin;
|
||||||
|
|
||||||
|
use D3\Webauthn\Application\Model\Credential\PublicKeyCredential;
|
||||||
use D3\Webauthn\Application\Model\Credential\PublicKeyCredentialList;
|
use D3\Webauthn\Application\Model\Credential\PublicKeyCredentialList;
|
||||||
use D3\Webauthn\Application\Model\d3webauthn;
|
use D3\Webauthn\Application\Model\d3webauthn;
|
||||||
|
use D3\Webauthn\Application\Model\Webauthn;
|
||||||
|
use D3\Webauthn\Application\Model\WebauthnErrors;
|
||||||
use D3\Webauthn\Modules\Application\Model\d3_User_Webauthn;
|
use D3\Webauthn\Modules\Application\Model\d3_User_Webauthn;
|
||||||
use Exception;
|
use Exception;
|
||||||
use OxidEsales\Eshop\Application\Controller\Admin\AdminDetailsController;
|
use OxidEsales\Eshop\Application\Controller\Admin\AdminDetailsController;
|
||||||
@ -58,22 +61,53 @@ class d3user_webauthn extends AdminDetailsController
|
|||||||
$this->addTplParam("sSaveError", $this->_sSaveError);
|
$this->addTplParam("sSaveError", $this->_sSaveError);
|
||||||
}
|
}
|
||||||
|
|
||||||
// $this->setAuthnRegister();
|
|
||||||
|
|
||||||
return $this->_sThisTemplate;
|
return $this->_sThisTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function requestNewCredential()
|
||||||
* @throws DatabaseConnectionException
|
{
|
||||||
* @throws DatabaseErrorException
|
$this->setPageType('requestnew');
|
||||||
*/
|
$this->setAuthnRegister();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveAuthn()
|
||||||
|
{
|
||||||
|
if (strlen(Registry::getRequest()->getRequestEscapedParameter('error'))) {
|
||||||
|
$errors = oxNew(WebauthnErrors::class);
|
||||||
|
Registry::getUtilsView()->addErrorToDisplay(
|
||||||
|
$errors->translateError(Registry::getRequest()->getRequestEscapedParameter('error'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(Registry::getRequest()->getRequestEscapedParameter('credential'))) {
|
||||||
|
/** @var Webauthn $webauthn */
|
||||||
|
$webauthn = oxNew(Webauthn::class);
|
||||||
|
$webauthn->saveAuthn(
|
||||||
|
Registry::getRequest()->getRequestEscapedParameter('credential'),
|
||||||
|
Registry::getRequest()->getRequestEscapedParameter('keyname')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPageType($pageType)
|
||||||
|
{
|
||||||
|
$this->addTplParam('pageType', $pageType);
|
||||||
|
}
|
||||||
|
|
||||||
public function setAuthnRegister()
|
public function setAuthnRegister()
|
||||||
{
|
{
|
||||||
$publicKeyCredentialCreationOptions = $this->getWebauthnObject()->setAuthnRegister($this->getEditObjectId());
|
$authn = oxNew(Webauthn::class);
|
||||||
|
|
||||||
|
$user = $this->getUserObject();
|
||||||
|
$user->load($this->getEditObjectId());
|
||||||
|
$publicKeyCredentialCreationOptions = $authn->getCreationOptions($user);
|
||||||
|
|
||||||
$this->addTplParam(
|
$this->addTplParam(
|
||||||
'webauthn_publickey_register',
|
'webauthn_publickey_create',
|
||||||
json_encode($publicKeyCredentialCreationOptions, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
|
$publicKeyCredentialCreationOptions
|
||||||
);
|
);
|
||||||
|
$this->addTplParam('isAdmin', isAdmin());
|
||||||
|
$this->addTplParam('keyname', Registry::getRequest()->getRequestEscapedParameter('credenialname'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -105,6 +139,13 @@ class d3user_webauthn extends AdminDetailsController
|
|||||||
return oxNew(d3webauthn::class);
|
return oxNew(d3webauthn::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function deleteKey()
|
||||||
|
{
|
||||||
|
/** @var PublicKeyCredential $credential */
|
||||||
|
$credential = oxNew(PublicKeyCredential::class);
|
||||||
|
$credential->delete(Registry::getRequest()->getRequestEscapedParameter('deleteoxid'));
|
||||||
|
}
|
||||||
|
|
||||||
public function registerNewKey()
|
public function registerNewKey()
|
||||||
{
|
{
|
||||||
$this->getWebauthnObject()->registerNewKey(Registry::getRequest()->getRequestParameter('authn'));
|
$this->getWebauthnObject()->registerNewKey(Registry::getRequest()->getRequestParameter('authn'));
|
||||||
|
@ -35,7 +35,6 @@ class PublicKeyCredential extends BaseModel
|
|||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
public function setName($name)
|
public function setName($name)
|
||||||
{
|
{
|
||||||
$this->assign(['name' => $name]);
|
$this->assign(['name' => $name]);
|
||||||
@ -45,7 +44,7 @@ class PublicKeyCredential extends BaseModel
|
|||||||
{
|
{
|
||||||
return $this->getFieldData('name');
|
return $this->getFieldData('name');
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
public function setCredentialId($credentialId)
|
public function setCredentialId($credentialId)
|
||||||
{
|
{
|
||||||
$this->assign([
|
$this->assign([
|
||||||
@ -97,7 +96,7 @@ class PublicKeyCredential extends BaseModel
|
|||||||
* @return void
|
* @return void
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource): void
|
public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource, string $keyName = null): void
|
||||||
{
|
{
|
||||||
// will save on every successfully assertion, set id to prevent duplicated database entries
|
// will save on every successfully assertion, set id to prevent duplicated database entries
|
||||||
$id = $this->getIdByCredentialId($publicKeyCredentialSource->getPublicKeyCredentialId());
|
$id = $this->getIdByCredentialId($publicKeyCredentialSource->getPublicKeyCredentialId());
|
||||||
@ -107,6 +106,7 @@ class PublicKeyCredential extends BaseModel
|
|||||||
$this->setUserId($publicKeyCredentialSource->getUserHandle());
|
$this->setUserId($publicKeyCredentialSource->getUserHandle());
|
||||||
$this->setCredentialId($publicKeyCredentialSource->getPublicKeyCredentialId());
|
$this->setCredentialId($publicKeyCredentialSource->getPublicKeyCredentialId());
|
||||||
$this->setCredential($publicKeyCredentialSource);
|
$this->setCredential($publicKeyCredentialSource);
|
||||||
|
$this->setName($keyName ?: $this->getName());
|
||||||
|
|
||||||
// ToDo: required??
|
// ToDo: required??
|
||||||
$this->assign([
|
$this->assign([
|
||||||
@ -124,7 +124,7 @@ class PublicKeyCredential extends BaseModel
|
|||||||
->where(
|
->where(
|
||||||
$qb->expr()->and(
|
$qb->expr()->and(
|
||||||
$qb->expr()->eq(
|
$qb->expr()->eq(
|
||||||
'credid_hex',
|
'credentialid',
|
||||||
$qb->createNamedParameter(bin2hex($publicKeyCredentialId))
|
$qb->createNamedParameter(bin2hex($publicKeyCredentialId))
|
||||||
),
|
),
|
||||||
$qb->expr()->eq(
|
$qb->expr()->eq(
|
||||||
|
@ -46,7 +46,7 @@ class PublicKeyCredentialList extends ListModel implements PublicKeyCredentialSo
|
|||||||
->where(
|
->where(
|
||||||
$qb->expr()->and(
|
$qb->expr()->and(
|
||||||
$qb->expr()->eq(
|
$qb->expr()->eq(
|
||||||
'credid_hex',
|
'credentialid',
|
||||||
$qb->createNamedParameter(bin2hex($publicKeyCredentialId))
|
$qb->createNamedParameter(bin2hex($publicKeyCredentialId))
|
||||||
),
|
),
|
||||||
$qb->expr()->eq(
|
$qb->expr()->eq(
|
||||||
|
163
src/Application/Model/Webauthn.php
Normal file
163
src/Application/Model/Webauthn.php
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace D3\Webauthn\Application\Model;
|
||||||
|
|
||||||
|
use D3\Totp\Modules\Application\Model\d3_totp_user;
|
||||||
|
use D3\Webauthn\Application\Model\Credential\PublicKeyCredential;
|
||||||
|
use D3\Webauthn\Application\Model\Credential\PublicKeyCredentialList;
|
||||||
|
use Nyholm\Psr7\Factory\Psr17Factory;
|
||||||
|
use Nyholm\Psr7Server\ServerRequestCreator;
|
||||||
|
use OxidEsales\Eshop\Application\Model\User;
|
||||||
|
use OxidEsales\Eshop\Core\Registry;
|
||||||
|
use Webauthn\PublicKeyCredentialCreationOptions;
|
||||||
|
use Webauthn\PublicKeyCredentialRequestOptions;
|
||||||
|
use Webauthn\PublicKeyCredentialRpEntity;
|
||||||
|
use Webauthn\PublicKeyCredentialSource;
|
||||||
|
use Webauthn\Server;
|
||||||
|
|
||||||
|
class Webauthn
|
||||||
|
{
|
||||||
|
public const SESSION_CREATIONS_OPTIONS = 'd3WebAuthnCreationOptions';
|
||||||
|
public const SESSION_ASSERTION_OPTIONS = 'd3WebAuthnAssertionOptions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return false|string
|
||||||
|
*/
|
||||||
|
public function getCreationOptions(User $user)
|
||||||
|
{
|
||||||
|
/** @var d3_totp_user $user */
|
||||||
|
$userEntity = $user->d3GetWebauthnUserEntity();
|
||||||
|
|
||||||
|
/** @var PublicKeyCredentialList $credentialSourceRepository */
|
||||||
|
$credentialSourceRepository = oxNew(PublicKeyCredentialList::class);
|
||||||
|
$credentialSources = $credentialSourceRepository->findAllForUserEntity($userEntity);
|
||||||
|
$excludeCredentials = array_map(function (PublicKeyCredentialSource $credential) {
|
||||||
|
return $credential->getPublicKeyCredentialDescriptor();
|
||||||
|
}, $credentialSources);
|
||||||
|
|
||||||
|
$server = $this->getServer();
|
||||||
|
$publicKeyCredentialCreationOptions = $server->generatePublicKeyCredentialCreationOptions(
|
||||||
|
$userEntity,
|
||||||
|
PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE,
|
||||||
|
$excludeCredentials
|
||||||
|
);
|
||||||
|
|
||||||
|
Registry::getSession()->setVariable(self::SESSION_CREATIONS_OPTIONS, $publicKeyCredentialCreationOptions);
|
||||||
|
|
||||||
|
return json_encode($publicKeyCredentialCreationOptions,JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequestOptions()
|
||||||
|
{
|
||||||
|
/** @var d3_totp_user $user */
|
||||||
|
$user = oxNew(User::class);
|
||||||
|
$user->load('oxdefaultadmin');
|
||||||
|
$userEntity = $user->d3GetWebauthnUserEntity();
|
||||||
|
|
||||||
|
// Get the list of authenticators associated to the user
|
||||||
|
$credentialSourceRepository = oxNew(PublicKeyCredentials::class);
|
||||||
|
$credentialSources = $credentialSourceRepository->findAllForUserEntity($userEntity);
|
||||||
|
|
||||||
|
// Convert the Credential Sources into Public Key Credential Descriptors
|
||||||
|
$allowedCredentials = array_map(function (PublicKeyCredentialSource $credential) {
|
||||||
|
return $credential->getPublicKeyCredentialDescriptor();
|
||||||
|
}, $credentialSources);
|
||||||
|
|
||||||
|
$server = $this->getServer();
|
||||||
|
|
||||||
|
// We generate the set of options.
|
||||||
|
$publicKeyCredentialRequestOptions = $server->generatePublicKeyCredentialRequestOptions(
|
||||||
|
PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED, // Default value
|
||||||
|
$allowedCredentials
|
||||||
|
);
|
||||||
|
|
||||||
|
Registry::getSession()->setVariable(self::SESSION_ASSERTION_OPTIONS, $publicKeyCredentialRequestOptions);
|
||||||
|
|
||||||
|
return json_encode($publicKeyCredentialRequestOptions, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Server
|
||||||
|
*/
|
||||||
|
public function getServer()
|
||||||
|
{
|
||||||
|
$rpEntity = new PublicKeyCredentialRpEntity(
|
||||||
|
Registry::getConfig()->getActiveShop()->getFieldData('oxname'),
|
||||||
|
preg_replace('/(^www\.)(.*)/mi', '$2', $_SERVER['HTTP_HOST'])
|
||||||
|
);
|
||||||
|
|
||||||
|
return new Server($rpEntity, oxNew(PublicKeyCredentialList::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveAuthn(string $credential, string $keyName = null)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$psr17Factory = new Psr17Factory();
|
||||||
|
$creator = new ServerRequestCreator(
|
||||||
|
$psr17Factory,
|
||||||
|
$psr17Factory,
|
||||||
|
$psr17Factory,
|
||||||
|
$psr17Factory
|
||||||
|
);
|
||||||
|
$serverRequest = $creator->fromGlobals();
|
||||||
|
|
||||||
|
$publicKeyCredentialSource = $this->getServer()->loadAndCheckAttestationResponse(
|
||||||
|
html_entity_decode($credential),
|
||||||
|
Registry::getSession()->getVariable(self::SESSION_CREATIONS_OPTIONS),
|
||||||
|
$serverRequest
|
||||||
|
);
|
||||||
|
|
||||||
|
$pkCredential = oxNew(PublicKeyCredential::class);
|
||||||
|
$pkCredential->saveCredentialSource($publicKeyCredentialSource, $keyName);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
dumpvar($e->getMessage());
|
||||||
|
dumpvar($e);
|
||||||
|
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function assertAuthn(string $response)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$psr17Factory = new Psr17Factory();
|
||||||
|
$creator = new ServerRequestCreator(
|
||||||
|
$psr17Factory,
|
||||||
|
$psr17Factory,
|
||||||
|
$psr17Factory,
|
||||||
|
$psr17Factory
|
||||||
|
);
|
||||||
|
$serverRequest = $creator->fromGlobals();
|
||||||
|
|
||||||
|
/** @var d3_totp_user $user */
|
||||||
|
$user = oxNew(User::class);
|
||||||
|
$user->load('oxdefaultadmin');
|
||||||
|
$userEntity = $user->d3GetWebauthnUserEntity();
|
||||||
|
|
||||||
|
$publicKeySource = $this->getServer()->loadAndCheckAssertionResponse(
|
||||||
|
html_entity_decode($response),
|
||||||
|
Registry::getSession()->getVariable(self::SESSION_ASSERTION_OPTIONS),
|
||||||
|
$userEntity,
|
||||||
|
$serverRequest
|
||||||
|
);
|
||||||
|
/*
|
||||||
|
dumpvar($publicKeySource);
|
||||||
|
dumpvar(serialize($publicKeySource));
|
||||||
|
dumpvar(unserialize(serialize($publicKeySource)));
|
||||||
|
echo "<hr>";
|
||||||
|
dumpvar(bin2hex(serialize($publicKeySource)));
|
||||||
|
dumpvar(unserialize(hex2bin(bin2hex(serialize($publicKeySource)))));
|
||||||
|
*/
|
||||||
|
|
||||||
|
dumpvar('successfully');
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
dumpvar($e->getMessage());
|
||||||
|
dumpvar($e);
|
||||||
|
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,10 @@
|
|||||||
* @link http://www.oxidmodule.com
|
* @link http://www.oxidmodule.com
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace D3\Webauthn\Application\Model\Webauthn;
|
namespace D3\Webauthn\Application\Model\Webauthn;
|
||||||
|
16
src/Application/Model/WebauthnErrors.php
Normal file
16
src/Application/Model/WebauthnErrors.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace D3\Webauthn\Application\Model;
|
||||||
|
|
||||||
|
class WebauthnErrors
|
||||||
|
{
|
||||||
|
public function translateError($msg)
|
||||||
|
{
|
||||||
|
switch ($msg) {
|
||||||
|
case 'InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable':
|
||||||
|
return 'A key from this token is already saved';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $msg;
|
||||||
|
}
|
||||||
|
}
|
@ -35,6 +35,10 @@ use Webauthn\PublicKeyCredentialCreationOptions;
|
|||||||
use Webauthn\PublicKeyCredentialRequestOptions;
|
use Webauthn\PublicKeyCredentialRequestOptions;
|
||||||
use Webauthn\Server;
|
use Webauthn\Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
|
||||||
class d3webauthn extends BaseModel
|
class d3webauthn extends BaseModel
|
||||||
{
|
{
|
||||||
public $tableName = 'd3PublicKeyCredential';
|
public $tableName = 'd3PublicKeyCredential';
|
||||||
|
@ -15,6 +15,10 @@
|
|||||||
|
|
||||||
namespace D3\Webauthn\Application\Model;
|
namespace D3\Webauthn\Application\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
|
||||||
class d3webauthn_conf
|
class d3webauthn_conf
|
||||||
{
|
{
|
||||||
const WEBAUTHN_SESSION_AUTH = 'webauthn_auth';
|
const WEBAUTHN_SESSION_AUTH = 'webauthn_auth';
|
||||||
|
@ -14,6 +14,9 @@
|
|||||||
td.edittext {
|
td.edittext {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
.hidden-delete {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<form name="transfer" id="transfer" action="[{$oViewConf->getSelfLink()}]" method="post">
|
<form name="transfer" id="transfer" action="[{$oViewConf->getSelfLink()}]" method="post">
|
||||||
@ -22,6 +25,33 @@
|
|||||||
<input type="hidden" name="cl" value="[{$oViewConf->getActiveClassName()}]">
|
<input type="hidden" name="cl" value="[{$oViewConf->getActiveClassName()}]">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
[{capture name="javascripts"}]
|
||||||
|
function deleteItem(id) {
|
||||||
|
if (confirm('wirklich loeschen?') === true) {
|
||||||
|
document.getElementById('fncname').value = 'deleteKey';
|
||||||
|
document.getElementById('oxidvalue').value = id;
|
||||||
|
document.getElementById('myedit').submit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle(elementId) {
|
||||||
|
document.getElementById(elementId).classList.toggle("hidden-delete");
|
||||||
|
}
|
||||||
|
[{/capture}]
|
||||||
|
[{oxscript add=$smarty.capture.javascripts}]
|
||||||
|
|
||||||
|
|
||||||
|
[{if $oxid && $oxid != '-1'}]
|
||||||
|
[{if $pageType === 'requestnew'}]
|
||||||
|
[{include file="js_create.tpl"}]
|
||||||
|
|
||||||
|
<div>
|
||||||
|
Bitte die Anfrage Ihres Browsers bestätigen.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button onclick="document.getElementById('webauthn').submit();">Abbrechen</button>
|
||||||
|
[{else}]
|
||||||
|
|
||||||
<form name="myedit" id="myedit" action="[{$oViewConf->getSelfLink()}]" method="post" style="padding: 0;margin: 0;height:0;">
|
<form name="myedit" id="myedit" action="[{$oViewConf->getSelfLink()}]" method="post" style="padding: 0;margin: 0;height:0;">
|
||||||
[{$oViewConf->getHiddenSid()}]
|
[{$oViewConf->getHiddenSid()}]
|
||||||
<input type="hidden" name="cl" value="[{$oViewConf->getActiveClassName()}]">
|
<input type="hidden" name="cl" value="[{$oViewConf->getActiveClassName()}]">
|
||||||
@ -29,10 +59,12 @@
|
|||||||
<input type="hidden" id="authnvalue" name="authnvalue" value="">
|
<input type="hidden" id="authnvalue" name="authnvalue" value="">
|
||||||
<input type="hidden" id="errorvalue" name="errorvalue" value="">
|
<input type="hidden" id="errorvalue" name="errorvalue" value="">
|
||||||
<input type="hidden" name="oxid" value="[{$oxid}]">
|
<input type="hidden" name="oxid" value="[{$oxid}]">
|
||||||
|
<input type="hidden" name="deleteoxid" id="oxidvalue" value="">
|
||||||
<button type="submit" style="display: none;"></button>
|
<button type="submit" style="display: none;"></button>
|
||||||
[{* <input type="hidden" name="editval[d3totp__oxid]" value="[{$webauthn->getId()}]">
|
[{* <input type="hidden" name="editval[d3totp__oxid]" value="[{$webauthn->getId()}]">
|
||||||
<input type="hidden" name="editval[d3totp__oxuserid]" value="[{$oxid}]">
|
<input type="hidden" name="editval[d3totp__oxuserid]" value="[{$oxid}]">
|
||||||
*}]
|
*}]
|
||||||
|
</form>
|
||||||
|
|
||||||
[{if $sSaveError}]
|
[{if $sSaveError}]
|
||||||
<table style="padding:0; border:0; width:98%;">
|
<table style="padding:0; border:0; width:98%;">
|
||||||
@ -43,90 +75,14 @@
|
|||||||
</table>
|
</table>
|
||||||
[{/if}]
|
[{/if}]
|
||||||
|
|
||||||
[{capture name="javascripts"}]
|
|
||||||
function arrayToBase64String(a) {
|
|
||||||
return btoa(String.fromCharCode(...a));
|
|
||||||
}
|
|
||||||
|
|
||||||
function base64url2base64(input) {
|
|
||||||
input = input
|
|
||||||
.replace(/=/g, "")
|
|
||||||
.replace(/-/g, '+')
|
|
||||||
.replace(/_/g, '/');
|
|
||||||
|
|
||||||
const pad = input.length % 4;
|
|
||||||
if(pad) {
|
|
||||||
if(pad === 1) {
|
|
||||||
throw new Error('InvalidLengthError: Input base64url string is the wrong length to determine padding');
|
|
||||||
}
|
|
||||||
input += new Array(5-pad).join('=');
|
|
||||||
}
|
|
||||||
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
function authnregister() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
let publicKey = [{$webauthn_publickey_register}];
|
|
||||||
console.log('71');
|
|
||||||
publicKey.challenge = Uint8Array.from(window.atob(base64url2base64(publicKey.challenge)), function(c){return c.charCodeAt(0);});
|
|
||||||
publicKey.user.id = Uint8Array.from(window.atob(publicKey.user.id), function(c){return c.charCodeAt(0);});
|
|
||||||
console.log('74');
|
|
||||||
if (publicKey.excludeCredentials) {
|
|
||||||
publicKey.excludeCredentials = publicKey.excludeCredentials.map(function(data) {
|
|
||||||
data.id = Uint8Array.from(window.atob(base64url2base64(data.id)), function(c){return c.charCodeAt(0);});
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('81');
|
|
||||||
navigator.credentials.create({ 'publicKey': publicKey }).then(function(data){
|
|
||||||
console.log('83');
|
|
||||||
let publicKeyCredential = {
|
|
||||||
id: data.id,
|
|
||||||
type: data.type,
|
|
||||||
rawId: arrayToBase64String(new Uint8Array(data.rawId)),
|
|
||||||
response: {
|
|
||||||
clientDataJSON: arrayToBase64String(new Uint8Array(data.response.clientDataJSON)),
|
|
||||||
attestationObject: arrayToBase64String(new Uint8Array(data.response.attestationObject))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
console.log('92');
|
|
||||||
document.getElementById('fncname').value = 'registerNewKey';
|
|
||||||
console.log('94');
|
|
||||||
document.getElementById('authnvalue').value = btoa(JSON.stringify(publicKeyCredential));
|
|
||||||
console.log('96');
|
|
||||||
}).catch(function(error){
|
|
||||||
console.log(error);
|
|
||||||
// document.getElementById('errorvalue').value = btoa(JSON.stringify(error));
|
|
||||||
// document.getElementById('myedit').submit();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteItem(id) {
|
|
||||||
document.getElementById('fncname').value = 'deleteKey';
|
|
||||||
document.getElementById('oxidvalue').value = id;
|
|
||||||
document.getElementById('actionform').submit();
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggle(elementId) {
|
|
||||||
$("#" + elementId).toggle();
|
|
||||||
}
|
|
||||||
[{/capture}]
|
|
||||||
[{oxscript add=$smarty.capture.javascripts}]
|
|
||||||
|
|
||||||
|
|
||||||
[{if $oxid && $oxid != '-1'}]
|
|
||||||
<table style="padding:0; border:0; width:98%;">
|
<table style="padding:0; border:0; width:98%;">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="edittext" style="vertical-align: top; padding-top:10px;padding-left:10px; width: 50%;">
|
<td class="edittext" style="vertical-align: top; padding-top:10px;padding-left:10px; width: 50%;">
|
||||||
|
<form name="newcred" id="newcred" action="[{$oViewConf->getSelfLink()}]" method="post">
|
||||||
|
[{$oViewConf->getHiddenSid()}]
|
||||||
|
<input type="hidden" name="cl" value="[{$oView->getClassName()}]">
|
||||||
|
<input type="hidden" name="fnc" value="requestNewCredential">
|
||||||
|
<input type="hidden" name="oxid" value="[{$oxid}]">
|
||||||
<table style="padding:0; border:0">
|
<table style="padding:0; border:0">
|
||||||
[{block name="user_d3user_totp_form1"}]
|
[{block name="user_d3user_totp_form1"}]
|
||||||
<tr>
|
<tr>
|
||||||
@ -136,11 +92,18 @@ console.log('96');
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="edittext">
|
<td class="edittext">
|
||||||
<button onclick="authnregister();">[{oxmultilang ident="D3_WEBAUTHN_ADDKEY"}]</button>
|
<label for="credentialname">Name des Schlüssels</label>
|
||||||
|
<input id="credentialname" type="text" name="credenialname">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="edittext">
|
||||||
|
<button type="submit">[{oxmultilang ident="D3_WEBAUTHN_ADDKEY"}]</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
[{/block}]
|
[{/block}]
|
||||||
</table>
|
</table>
|
||||||
|
</form>
|
||||||
</td>
|
</td>
|
||||||
<!-- Anfang rechte Seite -->
|
<!-- Anfang rechte Seite -->
|
||||||
<td class="edittext" style="text-align: left; vertical-align: top; height:99%;padding-left:5px;padding-bottom:30px;padding-top:10px; width: 50%;">
|
<td class="edittext" style="text-align: left; vertical-align: top; height:99%;padding-left:5px;padding-bottom:30px;padding-top:10px; width: 50%;">
|
||||||
@ -160,10 +123,9 @@ console.log('96');
|
|||||||
***}]
|
***}]
|
||||||
<td class="edittext">
|
<td class="edittext">
|
||||||
<a href="#" onclick="toggle('keydetails_[{$credential->getId()}]'); return false;" class="list-group-item">
|
<a href="#" onclick="toggle('keydetails_[{$credential->getId()}]'); return false;" class="list-group-item">
|
||||||
[{** [{$credential->d3GetName()}] (last used: XX) **}]
|
[{$credential->getName()}]
|
||||||
[{$credential->getId()}]
|
|
||||||
</a>
|
</a>
|
||||||
<div class="list-group-item" id="keydetails_[{$credential->getId()}]" style="display: none">
|
<div class="list-group-item hidden-delete" id="keydetails_[{$credential->getId()}]">
|
||||||
<a onclick="deleteItem('[{$credential->getId()}]'); return false;"><span class="glyphicon glyphicon-pencil">delete</span></a>
|
<a onclick="deleteItem('[{$credential->getId()}]'); return false;"><span class="glyphicon glyphicon-pencil">delete</span></a>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@ -176,7 +138,8 @@ console.log('96');
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
[{/if}]
|
[{/if}]
|
||||||
</form>
|
[{/if}]
|
||||||
|
|
||||||
|
|
||||||
[{include file="bottomnaviitem.tpl"}]
|
[{include file="bottomnaviitem.tpl"}]
|
||||||
[{include file="bottomitem.tpl"}]
|
[{include file="bottomitem.tpl"}]
|
28
src/Application/views/tpl/inc/js_create.tpl
Normal file
28
src/Application/views/tpl/inc/js_create.tpl
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
[{*** require creationOptions variable containing ... ***}]
|
||||||
|
|
||||||
|
[{oxscript include=$oViewConf->getModuleUrl('d3webauthn', 'out/src/js/webauthn.js')}]
|
||||||
|
|
||||||
|
[{capture name="d3script"}]
|
||||||
|
var creationOptions = [{$webauthn_publickey_create}];
|
||||||
|
createCredentials(creationOptions);
|
||||||
|
[{/capture}]
|
||||||
|
[{oxscript add=$smarty.capture.d3script}]
|
||||||
|
|
||||||
|
[{if $isAdmin}]
|
||||||
|
[{assign var="action" value=$oViewConf->getSelfLink()}]
|
||||||
|
[{assign var="formNavParams" value=""}]
|
||||||
|
[{else}]
|
||||||
|
[{assign var="action" value=$oViewConf->getSelfActionLink()}]
|
||||||
|
[{assign var="formNavParams" value=""}]
|
||||||
|
[{/if}]
|
||||||
|
|
||||||
|
<form id="webauthn" action="[{$action}]" method="post">
|
||||||
|
[{$oViewConf->getHiddenSid()}]
|
||||||
|
[{$formNavParams}]
|
||||||
|
<input type="hidden" name="fnc" value="saveAuthn">
|
||||||
|
<input type="hidden" name="cl" value="[{$oViewConf->getActiveClassName()}]">
|
||||||
|
<input type="hidden" name="credential" value=''>
|
||||||
|
<input type="hidden" name="error" value=''>
|
||||||
|
<input type="hidden" name="keyname" value='[{$keyname}]'>
|
||||||
|
<input type="hidden" name="oxid" value="[{$oxid}]">
|
||||||
|
</form>
|
@ -17,7 +17,9 @@ namespace D3\Webauthn\Modules\Application\Model;
|
|||||||
|
|
||||||
use D3\Webauthn\Application\Model\d3webauthn;
|
use D3\Webauthn\Application\Model\d3webauthn;
|
||||||
use D3\Webauthn\Application\Model\d3webauthn_conf;
|
use D3\Webauthn\Application\Model\d3webauthn_conf;
|
||||||
|
use OxidEsales\Eshop\Core\Exception\StandardException;
|
||||||
use OxidEsales\Eshop\Core\Registry;
|
use OxidEsales\Eshop\Core\Registry;
|
||||||
|
use Webauthn\PublicKeyCredentialUserEntity;
|
||||||
|
|
||||||
class d3_User_Webauthn extends d3_User_Webauthn_parent
|
class d3_User_Webauthn extends d3_User_Webauthn_parent
|
||||||
{
|
{
|
||||||
@ -60,4 +62,20 @@ class d3_User_Webauthn extends d3_User_Webauthn_parent
|
|||||||
{
|
{
|
||||||
return oxNew(d3webauthn::class);
|
return oxNew(d3webauthn::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return PublicKeyCredentialUserEntity
|
||||||
|
*/
|
||||||
|
public function d3GetWebauthnUserEntity(): PublicKeyCredentialUserEntity
|
||||||
|
{
|
||||||
|
if ($this->isLoaded()) {
|
||||||
|
return oxNew(PublicKeyCredentialUserEntity::class,
|
||||||
|
$this->getFieldData('oxusername'),
|
||||||
|
$this->getId(),
|
||||||
|
$this->getFieldData('oxfname') . ' ' . $this->getFieldData('oxlname')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw oxNew(StandardException::class, 'can not create webauthn user entity from not loaded user');
|
||||||
|
}
|
||||||
}
|
}
|
@ -82,6 +82,8 @@ $aModule = array(
|
|||||||
'd3user_webauthn.tpl' => 'd3/oxwebauthn/Application/views/admin/tpl/d3user_webauthn.tpl',
|
'd3user_webauthn.tpl' => 'd3/oxwebauthn/Application/views/admin/tpl/d3user_webauthn.tpl',
|
||||||
'd3webauthnlogin.tpl' => 'd3/oxwebauthn/Application/views/tpl/d3webauthnlogin.tpl',
|
'd3webauthnlogin.tpl' => 'd3/oxwebauthn/Application/views/tpl/d3webauthnlogin.tpl',
|
||||||
'd3_account_webauthn.tpl' => 'd3/oxwebauthn/Application/views/tpl/d3_account_webauthn.tpl',
|
'd3_account_webauthn.tpl' => 'd3/oxwebauthn/Application/views/tpl/d3_account_webauthn.tpl',
|
||||||
|
|
||||||
|
'js_create.tpl' => 'd3/oxwebauthn/Application/views/tpl/inc/js_create.tpl',
|
||||||
],
|
],
|
||||||
'events' => [
|
'events' => [
|
||||||
'onActivate' => '\D3\Webauthn\Setup\Events::onActivate',
|
'onActivate' => '\D3\Webauthn\Setup\Events::onActivate',
|
||||||
|
184
src/out/src/js/webauthn.js
Normal file
184
src/out/src/js/webauthn.js
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
if (!window.PublicKeyCredential) {
|
||||||
|
console.error('no window pubkeycred available');
|
||||||
|
}
|
||||||
|
|
||||||
|
const base64UrlDecode = (input) => {
|
||||||
|
"use strict";
|
||||||
|
input = input
|
||||||
|
.replace(/-/g, '+')
|
||||||
|
.replace(/_/g, '/');
|
||||||
|
|
||||||
|
const pad = input.length % 4;
|
||||||
|
if (pad) {
|
||||||
|
if (pad === 1) {
|
||||||
|
throw new Error('InvalidLengthError: Input base64url string is the wrong length to determine padding');
|
||||||
|
}
|
||||||
|
input += new Array(5-pad).join('=');
|
||||||
|
}
|
||||||
|
|
||||||
|
return window.atob(input);
|
||||||
|
};
|
||||||
|
|
||||||
|
const prepareOptions = (publicKey) => {
|
||||||
|
"use strict";
|
||||||
|
//Convert challenge from Base64Url string to Uint8Array
|
||||||
|
publicKey.challenge = Uint8Array.from(
|
||||||
|
base64UrlDecode(publicKey.challenge),
|
||||||
|
c => c.charCodeAt(0)
|
||||||
|
);
|
||||||
|
|
||||||
|
//Convert the user ID from Base64 string to Uint8Array
|
||||||
|
if (publicKey.user !== undefined) {
|
||||||
|
publicKey.user = {
|
||||||
|
...publicKey.user,
|
||||||
|
id: Uint8Array.from(
|
||||||
|
window.atob(publicKey.user.id),
|
||||||
|
c => c.charCodeAt(0)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//If excludeCredentials is defined, we convert all IDs to Uint8Array
|
||||||
|
if (publicKey.excludeCredentials !== undefined) {
|
||||||
|
publicKey.excludeCredentials = publicKey.excludeCredentials.map(
|
||||||
|
data => {
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
id: Uint8Array.from(
|
||||||
|
base64UrlDecode(data.id),
|
||||||
|
c => c.charCodeAt(0)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (publicKey.allowCredentials !== undefined) {
|
||||||
|
publicKey.allowCredentials = publicKey.allowCredentials.map(
|
||||||
|
data => {
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
id: Uint8Array.from(
|
||||||
|
base64UrlDecode(data.id),
|
||||||
|
c => c.charCodeAt(0)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return publicKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** https://gist.github.com/jonleighton/958841 **/
|
||||||
|
function base64ArrayBuffer(arrayBuffer) {
|
||||||
|
"use strict";
|
||||||
|
var base64 = ''
|
||||||
|
var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||||
|
|
||||||
|
var bytes = new Uint8Array(arrayBuffer)
|
||||||
|
var byteLength = bytes.byteLength
|
||||||
|
var byteRemainder = byteLength % 3
|
||||||
|
var mainLength = byteLength - byteRemainder
|
||||||
|
|
||||||
|
var a, b, c, d
|
||||||
|
var chunk
|
||||||
|
|
||||||
|
// Main loop deals with bytes in chunks of 3
|
||||||
|
for (var i = 0; i < mainLength; i = i + 3) {
|
||||||
|
// Combine the three bytes into a single integer
|
||||||
|
chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]
|
||||||
|
|
||||||
|
// Use bitmasks to extract 6-bit segments from the triplet
|
||||||
|
a = (chunk & 16515072) >> 18 // 16515072 = (2^6 - 1) << 18
|
||||||
|
b = (chunk & 258048) >> 12 // 258048 = (2^6 - 1) << 12
|
||||||
|
c = (chunk & 4032) >> 6 // 4032 = (2^6 - 1) << 6
|
||||||
|
d = chunk & 63 // 63 = 2^6 - 1
|
||||||
|
|
||||||
|
// Convert the raw binary segments to the appropriate ASCII encoding
|
||||||
|
base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deal with the remaining bytes and padding
|
||||||
|
if (byteRemainder === 1) {
|
||||||
|
chunk = bytes[mainLength]
|
||||||
|
|
||||||
|
a = (chunk & 252) >> 2 // 252 = (2^6 - 1) << 2
|
||||||
|
|
||||||
|
// Set the 4 least significant bits to zero
|
||||||
|
b = (chunk & 3) << 4 // 3 = 2^2 - 1
|
||||||
|
|
||||||
|
base64 += encodings[a] + encodings[b] + '=='
|
||||||
|
} else if (byteRemainder === 2) {
|
||||||
|
chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1]
|
||||||
|
|
||||||
|
a = (chunk & 64512) >> 10 // 64512 = (2^6 - 1) << 10
|
||||||
|
b = (chunk & 1008) >> 4 // 1008 = (2^6 - 1) << 4
|
||||||
|
|
||||||
|
// Set the 2 least significant bits to zero
|
||||||
|
c = (chunk & 15) << 2 // 15 = 2^4 - 1
|
||||||
|
|
||||||
|
base64 += encodings[a] + encodings[b] + encodings[c] + '='
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64
|
||||||
|
}
|
||||||
|
|
||||||
|
const createCredentials = (publicKey) => {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
prepareOptions(publicKey);
|
||||||
|
|
||||||
|
navigator.credentials.create({publicKey: publicKey})
|
||||||
|
.then(function (newCredentialInfo) {
|
||||||
|
// Send new credential info to server for verification and registration.
|
||||||
|
var cred = {
|
||||||
|
id: newCredentialInfo.id,
|
||||||
|
rawId: base64ArrayBuffer(newCredentialInfo.rawId),
|
||||||
|
response: {
|
||||||
|
clientDataJSON: base64ArrayBuffer(newCredentialInfo.response.clientDataJSON),
|
||||||
|
attestationObject: base64ArrayBuffer(newCredentialInfo.response.attestationObject)
|
||||||
|
},
|
||||||
|
type: newCredentialInfo.type
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById('webauthn').credential.value = JSON.stringify(cred);
|
||||||
|
document.getElementById('webauthn').submit();
|
||||||
|
}).catch(function (err) {
|
||||||
|
document.getElementById('webauthn').error.value = err;
|
||||||
|
document.getElementById('webauthn').submit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestCredentials = (publicKey) => {
|
||||||
|
"use strict";
|
||||||
|
console.log('--AB--');
|
||||||
|
|
||||||
|
prepareOptions(publicKey);
|
||||||
|
|
||||||
|
navigator.credentials.get({publicKey: publicKey})
|
||||||
|
.then(function (authenticateInfo) {
|
||||||
|
console.log(authenticateInfo);
|
||||||
|
// Send authenticate info to server for verification.
|
||||||
|
var cred = {
|
||||||
|
id: authenticateInfo.id,
|
||||||
|
rawId: base64ArrayBuffer(authenticateInfo.rawId),
|
||||||
|
response: {
|
||||||
|
authenticatorData: base64ArrayBuffer(authenticateInfo.response.authenticatorData),
|
||||||
|
signature: base64ArrayBuffer(authenticateInfo.response.signature),
|
||||||
|
userHandle: authenticateInfo.response.userHandle,
|
||||||
|
clientDataJSON: base64ArrayBuffer(authenticateInfo.response.clientDataJSON)
|
||||||
|
},
|
||||||
|
type: authenticateInfo.type
|
||||||
|
};
|
||||||
|
console.log(cred);
|
||||||
|
document.getElementById('webauthn').credential.value = JSON.stringify(cred);
|
||||||
|
document.getElementById('webauthn').submit();
|
||||||
|
}).catch(function (err) {
|
||||||
|
console.log('--2--');
|
||||||
|
console.log('WebAuthn auth: ' + err);
|
||||||
|
// No acceptable authenticator or user refused consent. Handle appropriately.
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Reference in New Issue
Block a user