seeddms-code/views/bootstrap/class.SetupWebauthn.php
2020-04-23 21:03:46 +02:00

415 lines
12 KiB
PHP

<?php
/**
* Implementation of SetupWebauthn view
*
* @category DMS
* @package SeedDMS
* @license GPL 2
* @version @version@
* @author Uwe Steinmann <uwe@steinmann.cx>
* @copyright Copyright (C) 2016 Uwe Steinmann
* @version Release: @package_version@
*/
/**
* Include parent class
*/
require_once("class.Bootstrap.php");
/**
* Include classes for 2-factor authentication
*/
require "vendor/autoload.php";
/**
* Class which outputs the html page for ForcePasswordChange view
*
* @category DMS
* @package SeedDMS
* @author Markus Westphal, Malcolm Cowe, Uwe Steinmann <uwe@steinmann.cx>
* @copyright Copyright (C) 2016 Uwe Steinmann
* @version Release: @package_version@
*/
class SeedDMS_View_SetupWebauthn extends SeedDMS_Bootstrap_Style {
function css() { /* {{{ */
header('Content-Type: text/css; charset=UTF-8');
?>
.cdokey {
display: none;
background-color: orange;
color: white;
font-weight: bold;
margin: 10px 0;
padding: 10px;
}
<?php
} /* }}} */
function js() { /* {{{ */
header('Content-Type: application/javascript');
?>
function webauthnRegister(key, callback){
key = JSON.parse(key);
key.publicKey.attestation = undefined;
key.publicKey.challenge = new Uint8Array(key.publicKey.challenge); // convert type for use by key
key.publicKey.user.id = new Uint8Array(key.publicKey.user.id);
navigator.credentials.create({publicKey: key.publicKey})
.then(function (aNewCredentialInfo) {
console.log("Credentials.Create response: ", aNewCredentialInfo);
var cd = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(aNewCredentialInfo.response.clientDataJSON)));
if (key.b64challenge != cd.challenge) {
callback(false, 'key returned something unexpected (1)');
}
if ('http://'+key.publicKey.rp.name != cd.origin) {
console.log(key.publicKey.rp.name);
console.log(cd.origin);
return callback(false, 'key returned something unexpected (2)');
}
if (! ('type' in cd)) {
return callback(false, 'key returned something unexpected (3)');
}
if (cd.type != 'webauthn.create') {
return callback(false, 'key returned something unexpected (4)');
}
var ao = [];
(new Uint8Array(aNewCredentialInfo.response.attestationObject)).forEach(function(v){
ao.push(v);
});
var rawId = [];
(new Uint8Array(aNewCredentialInfo.rawId)).forEach(function(v){
rawId.push(v);
});
var info = {
rawId: rawId,
id: aNewCredentialInfo.id,
type: aNewCredentialInfo.type,
response: {
attestationObject: ao,
clientDataJSON:
JSON.parse(String.fromCharCode.apply(null, new Uint8Array(aNewCredentialInfo.response.clientDataJSON)))
}
};
console.log("Info: ", info);
callback(true, JSON.stringify(info));
})
.catch(function (aErr) {
if (
("name" in aErr) && (aErr.name == "AbortError" || aErr.name == "NS_ERROR_ABORT")
|| aErr.name == 'NotAllowedError'
) {
callback(false, 'abort');
} else {
callback(false, aErr.toString());
}
});
}
function webauthnAuthenticate(key, cb){
var pk = JSON.parse(key);
var originalChallenge = pk.challenge;
pk.challenge = new Uint8Array(pk.challenge);
pk.allowCredentials.forEach(function(k, idx){
pk.allowCredentials[idx].id = new Uint8Array(k.id);
});
/* ask the browser to prompt the user */
navigator.credentials.get({publicKey: pk})
.then(function(aAssertion) {
// console.log("Credentials.Get response: ", aAssertion);
var ida = [];
(new Uint8Array(aAssertion.rawId)).forEach(function(v){ ida.push(v); });
var cd = JSON.parse(String.fromCharCode.apply(null,
new Uint8Array(aAssertion.response.clientDataJSON)));
var cda = [];
(new Uint8Array(aAssertion.response.clientDataJSON)).forEach(function(v){ cda.push(v); });
var ad = [];
(new Uint8Array(aAssertion.response.authenticatorData)).forEach(function(v){ ad.push(v); });
var sig = [];
(new Uint8Array(aAssertion.response.signature)).forEach(function(v){ sig.push(v); });
var info = {
type: aAssertion.type,
originalChallenge: originalChallenge,
rawId: ida,
response: {
authenticatorData: ad,
clientData: cd,
clientDataJSONarray: cda,
signature: sig
}
};
cb(true, JSON.stringify(info));
})
.catch(function (aErr) {
if (("name" in aErr) && (aErr.name == "AbortError" || aErr.name == "NS_ERROR_ABORT" ||
aErr.name == "NotAllowedError")) {
cb(false, 'abort');
} else {
cb(false, aErr.toString());
}
});
}
$(function(){
$('#iregisterform').submit(function(ev){
var self = $(this);
ev.preventDefault();
var cp = $('select[name=cp]').val();
if (cp == "") {
$('.cerror').show().text("Please choose cross-platform setting - see note below about what this means");
return;
}
$('.cerror').empty().hide();
$.ajax({url: '../out/out.SetupWebauthn.php',
method: 'POST',
data: {action: 'registeruser', registerusername: self.find('[name=registerusername]').val(), crossplatform: cp},
dataType: 'json',
success: function(j){
/* activate the key and get the response */
webauthnRegister(j.challenge, function(success, info){
if (success) {
$.ajax({url: '../out/out.SetupWebauthn.php',
method: 'POST',
data: {action: 'register', register: info},
dataType: 'json',
success: function(j){
noty({
text: 'registration completed successfully',
type: 'success',
dismissQueue: true,
layout: 'topRight',
theme: 'defaultTheme',
timeout: 1500,
});
},
error: function(xhr, status, error){
noty({
text: 'registration failed: '+error+": "+xhr.responseText,
type: 'error',
dismissQueue: true,
layout: 'topRight',
theme: 'defaultTheme',
timeout: 1500,
});
// $('.cerror').text("registration failed: "+error+": "+xhr.responseText).show();
}
});
} else {
noty({
text: info,
type: 'error',
dismissQueue: true,
layout: 'topRight',
theme: 'defaultTheme',
timeout: 1500,
});
// $('.cerror').text(info).show();
}
});
},
error: function(xhr, status, error){
noty({
text: "couldn't initiate registration: "+error+": "+xhr.responseText,
type: 'error',
dismissQueue: true,
layout: 'topRight',
theme: 'defaultTheme',
timeout: 1500,
});
}
});
});
$('#iloginform').submit(function(ev){
var self = $(this);
ev.preventDefault();
$('.cerror').empty().hide();
$.ajax({url: '../out/out.SetupWebauthn.php',
method: 'POST',
data: {action: 'preparelogin', loginusername: self.find('[name=loginusername]').val()},
dataType: 'json',
success: function(j){
/* activate the key and get the response */
webauthnAuthenticate(j.challenge, function(success, info){
if (success) {
$.ajax({url: '../out/out.SetupWebauthn.php',
method: 'POST',
data: {action: 'login', login: info},
dataType: 'json',
success: function(j){
noty({
text: 'login completed successfully',
type: 'success',
dismissQueue: true,
layout: 'topRight',
theme: 'defaultTheme',
timeout: 1500,
});
},
error: function(xhr, status, error){
noty({
text: 'login failed: '+error+": "+xhr.responseText,
type: 'error',
dismissQueue: true,
layout: 'topRight',
theme: 'defaultTheme',
timeout: 1500,
});
// $('.cerror').text("login failed: "+error+": "+xhr.responseText).show();
}
});
} else {
noty({
text: info,
type: 'error',
dismissQueue: true,
layout: 'topRight',
theme: 'defaultTheme',
timeout: 1500,
});
// $('.cerror').text(info).show();
}
});
},
error: function(xhr, status, error){
noty({
text: "couldn't initiate login: "+error+": "+xhr.responseText,
type: 'error',
dismissQueue: true,
layout: 'topRight',
theme: 'defaultTheme',
timeout: 1500,
});
}
});
});
});
<?php
} /* }}} */
function registeruser() { /* {{{ */
$dms = $this->params['dms'];
$user = $this->params['user'];
$webauthn = new \Davidearl\WebAuthn\WebAuthn($_SERVER['HTTP_HOST']);
/* initiate the registration */
$username = $user->getLogin();
$userid = $user->getId();
$crossplatform = ! empty($_POST['crossplatform']) && $_POST['crossplatform'] == 'Yes';
// $user->setWebauthn($webauthn->cancel());
$j = ['challenge' => $webauthn->prepareChallengeForRegistration($username, $userid, $crossplatform)];
header('Content-type: application/json');
echo json_encode($j);
} /* }}} */
function register() { /* {{{ */
$dms = $this->params['dms'];
$user = $this->params['user'];
$webauthn = new \Davidearl\WebAuthn\WebAuthn($_SERVER['HTTP_HOST']);
/* The heart of the matter */
$user->setWebauthn($webauthn->register($_POST['register'], $user->getWebauthn()));
$j = 'ok';
header('Content-type: application/json');
echo json_encode($j);
} /* }}} */
function preparelogin() { /* {{{ */
$dms = $this->params['dms'];
$user = $this->params['user'];
$webauthn = new \Davidearl\WebAuthn\WebAuthn($_SERVER['HTTP_HOST']);
$j['challenge'] = $webauthn->prepareForLogin($user->getWebauthn());
header('Content-type: application/json');
echo json_encode($j);
} /* }}} */
function login() { /* {{{ */
$dms = $this->params['dms'];
$user = $this->params['user'];
$webauthn = new \Davidearl\WebAuthn\WebAuthn($_SERVER['HTTP_HOST']);
if (! $webauthn->authenticate($_POST['login'], $user->getWebauthn())) {
http_response_code(401);
echo 'failed to authenticate with that key';
exit;
}
$j = 'ok';
header('Content-type: application/json');
echo json_encode($j);
} /* }}} */
function show() { /* {{{ */
$dms = $this->params['dms'];
$user = $this->params['user'];
$sitename = $this->params['sitename'];
$this->htmlStartPage(getMLText("webauthn"), "forcepasswordchange");
$this->globalNavigation();
$this->contentStart();
$this->pageNavigation(getMLText("my_account"), "my_account");
$this->contentHeading(getMLText('webauthn_auth'));
echo "<div class=\"alert\">".getMLText('webauthn_info')."</div>";
echo '<div class="row-fluid">';
echo '<div class="span6">';
$this->contentHeading('webauthn_registration');
$this->contentContainerStart();
?>
<div class='cerror'></div>
<div class='cdone'></div>
<div class='ccontent'></div>
<form class="form-horizontal" id="iregisterform" action="/" method="post" name="form1">
<input type="hidden" name="registerusername" value="<?= $user->getLogin() ?>" />
<div class="control-group"><label class="control-label"><?php printMLText('webauth_crossplatform'); ?></label><div class="controls">
<select name="crossplatform" id="crossplatform">
<option value="yes">Yes</option>
<option value="no">No</option>
</select>
</div></div>
<?php
$this->formSubmit(getMLText('submit_webauthn_register'));
?>
</form>
<?php
$this->contentContainerEnd();
echo $this->infoMsg(getMLText('webauthn_crossplatform_info'));
echo "</div>";
if($user->getWebauthn()) {
echo '<div class="span6">';
$this->contentHeading('webauthn_login_test');
$this->contentContainerStart();
?>
<form class="form-horizontal" id="iloginform" action="/" method="post" name="form1">
<input type="hidden" name="loginusername" value="<?= $user->getLogin() ?>" />
<?php
$this->formSubmit(getMLText('submit_webauthn_login'));
?>
</form>
<?php
$this->contentContainerEnd();
}
echo '</div>';
echo '</div>';
$this->contentEnd();
$this->htmlEndPage();
} /* }}} */
}