can add new key and delete existing one
This commit is contained in:
parent
b16f55c80d
commit
7752fcf3b0
|
@ -15,8 +15,11 @@
|
|||
|
||||
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\d3webauthn;
|
||||
use D3\Webauthn\Application\Model\Webauthn;
|
||||
use D3\Webauthn\Application\Model\WebauthnErrors;
|
||||
use D3\Webauthn\Modules\Application\Model\d3_User_Webauthn;
|
||||
use Exception;
|
||||
use OxidEsales\Eshop\Application\Controller\Admin\AdminDetailsController;
|
||||
|
@ -58,22 +61,53 @@ class d3user_webauthn extends AdminDetailsController
|
|||
$this->addTplParam("sSaveError", $this->_sSaveError);
|
||||
}
|
||||
|
||||
// $this->setAuthnRegister();
|
||||
|
||||
return $this->_sThisTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws DatabaseErrorException
|
||||
*/
|
||||
public function requestNewCredential()
|
||||
{
|
||||
$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()
|
||||
{
|
||||
$publicKeyCredentialCreationOptions = $this->getWebauthnObject()->setAuthnRegister($this->getEditObjectId());
|
||||
$authn = oxNew(Webauthn::class);
|
||||
|
||||
$user = $this->getUserObject();
|
||||
$user->load($this->getEditObjectId());
|
||||
$publicKeyCredentialCreationOptions = $authn->getCreationOptions($user);
|
||||
|
||||
$this->addTplParam(
|
||||
'webauthn_publickey_register',
|
||||
json_encode($publicKeyCredentialCreationOptions, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
|
||||
'webauthn_publickey_create',
|
||||
$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);
|
||||
}
|
||||
|
||||
public function deleteKey()
|
||||
{
|
||||
/** @var PublicKeyCredential $credential */
|
||||
$credential = oxNew(PublicKeyCredential::class);
|
||||
$credential->delete(Registry::getRequest()->getRequestEscapedParameter('deleteoxid'));
|
||||
}
|
||||
|
||||
public function registerNewKey()
|
||||
{
|
||||
$this->getWebauthnObject()->registerNewKey(Registry::getRequest()->getRequestParameter('authn'));
|
||||
|
|
|
@ -35,7 +35,6 @@ class PublicKeyCredential extends BaseModel
|
|||
parent::__construct();
|
||||
}
|
||||
|
||||
/*
|
||||
public function setName($name)
|
||||
{
|
||||
$this->assign(['name' => $name]);
|
||||
|
@ -45,7 +44,7 @@ class PublicKeyCredential extends BaseModel
|
|||
{
|
||||
return $this->getFieldData('name');
|
||||
}
|
||||
*/
|
||||
|
||||
public function setCredentialId($credentialId)
|
||||
{
|
||||
$this->assign([
|
||||
|
@ -97,7 +96,7 @@ class PublicKeyCredential extends BaseModel
|
|||
* @return void
|
||||
* @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
|
||||
$id = $this->getIdByCredentialId($publicKeyCredentialSource->getPublicKeyCredentialId());
|
||||
|
@ -107,6 +106,7 @@ class PublicKeyCredential extends BaseModel
|
|||
$this->setUserId($publicKeyCredentialSource->getUserHandle());
|
||||
$this->setCredentialId($publicKeyCredentialSource->getPublicKeyCredentialId());
|
||||
$this->setCredential($publicKeyCredentialSource);
|
||||
$this->setName($keyName ?: $this->getName());
|
||||
|
||||
// ToDo: required??
|
||||
$this->assign([
|
||||
|
@ -124,7 +124,7 @@ class PublicKeyCredential extends BaseModel
|
|||
->where(
|
||||
$qb->expr()->and(
|
||||
$qb->expr()->eq(
|
||||
'credid_hex',
|
||||
'credentialid',
|
||||
$qb->createNamedParameter(bin2hex($publicKeyCredentialId))
|
||||
),
|
||||
$qb->expr()->eq(
|
||||
|
|
|
@ -46,7 +46,7 @@ class PublicKeyCredentialList extends ListModel implements PublicKeyCredentialSo
|
|||
->where(
|
||||
$qb->expr()->and(
|
||||
$qb->expr()->eq(
|
||||
'credid_hex',
|
||||
'credentialid',
|
||||
$qb->createNamedParameter(bin2hex($publicKeyCredentialId))
|
||||
),
|
||||
$qb->expr()->eq(
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Webauthn\Application\Model\Webauthn;
|
||||
|
|
|
@ -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\Server;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
class d3webauthn extends BaseModel
|
||||
{
|
||||
public $tableName = 'd3PublicKeyCredential';
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
|
||||
namespace D3\Webauthn\Application\Model;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
class d3webauthn_conf
|
||||
{
|
||||
const WEBAUTHN_SESSION_AUTH = 'webauthn_auth';
|
||||
|
|
|
@ -14,6 +14,9 @@
|
|||
td.edittext {
|
||||
white-space: normal;
|
||||
}
|
||||
.hidden-delete {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<form name="transfer" id="transfer" action="[{$oViewConf->getSelfLink()}]" method="post">
|
||||
|
@ -22,125 +25,85 @@
|
|||
<input type="hidden" name="cl" value="[{$oViewConf->getActiveClassName()}]">
|
||||
</form>
|
||||
|
||||
<form name="myedit" id="myedit" action="[{$oViewConf->getSelfLink()}]" method="post" style="padding: 0;margin: 0;height:0;">
|
||||
[{$oViewConf->getHiddenSid()}]
|
||||
<input type="hidden" name="cl" value="[{$oViewConf->getActiveClassName()}]">
|
||||
<input type="hidden" id="fncname" name="fnc" value="">
|
||||
<input type="hidden" id="authnvalue" name="authnvalue" value="">
|
||||
<input type="hidden" id="errorvalue" name="errorvalue" value="">
|
||||
<input type="hidden" name="oxid" value="[{$oxid}]">
|
||||
<button type="submit" style="display: none;"></button>
|
||||
[{* <input type="hidden" name="editval[d3totp__oxid]" value="[{$webauthn->getId()}]">
|
||||
<input type="hidden" name="editval[d3totp__oxuserid]" value="[{$oxid}]">
|
||||
*}]
|
||||
|
||||
[{if $sSaveError}]
|
||||
<table style="padding:0; border:0; width:98%;">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td class="errorbox">[{oxmultilang ident=$sSaveError}]</td>
|
||||
</tr>
|
||||
</table>
|
||||
[{/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) {
|
||||
[{capture name="javascripts"}]
|
||||
function deleteItem(id) {
|
||||
if (confirm('wirklich loeschen?') === true) {
|
||||
document.getElementById('fncname').value = 'deleteKey';
|
||||
document.getElementById('oxidvalue').value = id;
|
||||
document.getElementById('actionform').submit();
|
||||
document.getElementById('myedit').submit();
|
||||
}
|
||||
}
|
||||
|
||||
function toggle(elementId) {
|
||||
$("#" + elementId).toggle();
|
||||
}
|
||||
[{/capture}]
|
||||
[{oxscript add=$smarty.capture.javascripts}]
|
||||
function toggle(elementId) {
|
||||
document.getElementById(elementId).classList.toggle("hidden-delete");
|
||||
}
|
||||
[{/capture}]
|
||||
[{oxscript add=$smarty.capture.javascripts}]
|
||||
|
||||
|
||||
[{if $oxid && $oxid != '-1'}]
|
||||
[{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;">
|
||||
[{$oViewConf->getHiddenSid()}]
|
||||
<input type="hidden" name="cl" value="[{$oViewConf->getActiveClassName()}]">
|
||||
<input type="hidden" id="fncname" name="fnc" value="">
|
||||
<input type="hidden" id="authnvalue" name="authnvalue" value="">
|
||||
<input type="hidden" id="errorvalue" name="errorvalue" value="">
|
||||
<input type="hidden" name="oxid" value="[{$oxid}]">
|
||||
<input type="hidden" name="deleteoxid" id="oxidvalue" value="">
|
||||
<button type="submit" style="display: none;"></button>
|
||||
[{* <input type="hidden" name="editval[d3totp__oxid]" value="[{$webauthn->getId()}]">
|
||||
<input type="hidden" name="editval[d3totp__oxuserid]" value="[{$oxid}]">
|
||||
*}]
|
||||
</form>
|
||||
|
||||
[{if $sSaveError}]
|
||||
<table style="padding:0; border:0; width:98%;">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td class="errorbox">[{oxmultilang ident=$sSaveError}]</td>
|
||||
</tr>
|
||||
</table>
|
||||
[{/if}]
|
||||
|
||||
<table style="padding:0; border:0; width:98%;">
|
||||
<tr>
|
||||
<td class="edittext" style="vertical-align: top; padding-top:10px;padding-left:10px; width: 50%;">
|
||||
<table style="padding:0; border:0">
|
||||
[{block name="user_d3user_totp_form1"}]
|
||||
<tr>
|
||||
<td class="edittext">
|
||||
<h4>[{oxmultilang ident="D3_WEBAUTHN_REGISTERNEW"}]</h4>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="edittext">
|
||||
<button onclick="authnregister();">[{oxmultilang ident="D3_WEBAUTHN_ADDKEY"}]</button>
|
||||
</td>
|
||||
</tr>
|
||||
[{/block}]
|
||||
</table>
|
||||
<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">
|
||||
[{block name="user_d3user_totp_form1"}]
|
||||
<tr>
|
||||
<td class="edittext">
|
||||
<h4>[{oxmultilang ident="D3_WEBAUTHN_REGISTERNEW"}]</h4>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="edittext">
|
||||
<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>
|
||||
</tr>
|
||||
[{/block}]
|
||||
</table>
|
||||
</form>
|
||||
</td>
|
||||
<!-- 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%;">
|
||||
|
@ -160,10 +123,9 @@ console.log('96');
|
|||
***}]
|
||||
<td class="edittext">
|
||||
<a href="#" onclick="toggle('keydetails_[{$credential->getId()}]'); return false;" class="list-group-item">
|
||||
[{** [{$credential->d3GetName()}] (last used: XX) **}]
|
||||
[{$credential->getId()}]
|
||||
[{$credential->getName()}]
|
||||
</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>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -176,7 +138,8 @@ console.log('96');
|
|||
</tr>
|
||||
</table>
|
||||
[{/if}]
|
||||
</form>
|
||||
[{/if}]
|
||||
|
||||
|
||||
[{include file="bottomnaviitem.tpl"}]
|
||||
[{include file="bottomitem.tpl"}]
|
|
@ -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_conf;
|
||||
use OxidEsales\Eshop\Core\Exception\StandardException;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use Webauthn\PublicKeyCredentialUserEntity;
|
||||
|
||||
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 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',
|
||||
'd3webauthnlogin.tpl' => 'd3/oxwebauthn/Application/views/tpl/d3webauthnlogin.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' => [
|
||||
'onActivate' => '\D3\Webauthn\Setup\Events::onActivate',
|
||||
|
|
|
@ -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.
|
||||
});
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue