can add new key and delete existing one

This commit is contained in:
Daniel Seifert 2022-10-26 00:02:55 +02:00
parent b16f55c80d
commit 7752fcf3b0
Signed by: DanielS
GPG Key ID: 6A513E13AEE66170
13 changed files with 555 additions and 128 deletions

View File

@ -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'));

View File

@ -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(

View File

@ -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(

View 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();
}
}
}

View File

@ -15,6 +15,10 @@
* @link http://www.oxidmodule.com
*/
/**
* @deprecated
*/
declare(strict_types=1);
namespace D3\Webauthn\Application\Model\Webauthn;

View 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;
}
}

View File

@ -35,6 +35,10 @@ use Webauthn\PublicKeyCredentialCreationOptions;
use Webauthn\PublicKeyCredentialRequestOptions;
use Webauthn\Server;
/**
* @deprecated
*/
class d3webauthn extends BaseModel
{
public $tableName = 'd3PublicKeyCredential';

View File

@ -15,6 +15,10 @@
namespace D3\Webauthn\Application\Model;
/**
* @deprecated
*/
class d3webauthn_conf
{
const WEBAUTHN_SESSION_AUTH = 'webauthn_auth';

View File

@ -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"}]

View 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>

View File

@ -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');
}
}

View File

@ -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',

184
src/out/src/js/webauthn.js Normal file
View 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.
});
}