* @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 * @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; } 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, }); } }); }); }); 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 "
".getMLText('webauthn_info')."
"; echo '
'; echo '
'; $this->contentHeading('webauthn_registration'); $this->contentContainerStart(); ?>
formSubmit(getMLText('submit_webauthn_register')); ?>
contentContainerEnd(); echo $this->infoMsg(getMLText('webauthn_crossplatform_info')); echo "
"; if($user->getWebauthn()) { echo '
'; $this->contentHeading('webauthn_login_test'); $this->contentContainerStart(); ?>
formSubmit(getMLText('submit_webauthn_login')); ?>
contentContainerEnd(); } echo '
'; echo '
'; $this->contentEnd(); $this->htmlEndPage(); } /* }}} */ }