Skip to content

Commit

Permalink
revision to prior commit, take 2
Browse files Browse the repository at this point in the history
  • Loading branch information
bradymiller committed Mar 9, 2019
1 parent 56ebe5c commit d4ca70f
Show file tree
Hide file tree
Showing 9 changed files with 406 additions and 354 deletions.
192 changes: 94 additions & 98 deletions composer.lock

Large diffs are not rendered by default.

320 changes: 186 additions & 134 deletions interface/main/main_screen.php

Large diffs are not rendered by default.

37 changes: 30 additions & 7 deletions interface/usergroup/mfa_registrations.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @author Rod Roark <[email protected]>
* @author Brady Miller <[email protected]>
* @copyright Copyright (c) 2018 Rod Roark <[email protected]>
* @copyright Copyright (c) 2018 Brady Miller <[email protected]>
* @copyright Copyright (c) 2018-2019 Brady Miller <[email protected]>
* @license https://github.com/openemr/openemr/blob/master/LICENSE CNU General Public License 3
*/

Expand All @@ -16,15 +16,18 @@

use OpenEMR\Core\Header;

function writeRow($method, $name)
function writeRow($method, $name, $allowEdit = false)
{
echo " <tr><td>&nbsp;";
echo text($method);
echo "&nbsp;</td><td>&nbsp;";
echo text($name);
echo "&nbsp;</td><td>";
echo "<input type='button' onclick='delclick(" . attr_js($method) . ", " .
attr_js($name) . ")' value='" . xla('Delete') . "' />";
if ($allowEdit) {
echo "<button type='button' class='btn btn-default btn-search' onclick='editclick(" . attr_js($method) . ")'>" . xlt('View') . "</button>";
}
echo "<button type='button' class='btn btn-default btn-delete' onclick='delclick(" . attr_js($method) . ", " .
attr_js($name) . ")'>" . xlt('Delete') . "</button>";
echo "</td></tr>\n";
}

Expand Down Expand Up @@ -57,6 +60,16 @@ function delclick(mfamethod, mfaname) {
f.submit();
}

function editclick(method) {
top.restoreSession();
if (method == 'TOTP') {
window.location.href = 'mfa_totp.php?action=reg1';
}
else {
alert(<?php echo xlj('Not yet implemented.'); ?>);
}
}

function addclick(sel) {
top.restoreSession();
if (sel.value) {
Expand Down Expand Up @@ -102,8 +115,14 @@ function addclick(sel) {
<?php
$res = sqlStatement("SELECT name, method FROM login_mfa_registrations WHERE " .
"user_id = ? ORDER BY method, name", array($userid));
$disableNewTotp = false;
while ($row = sqlFetchArray($res)) {
writeRow($row['method'], $row['name']);
if ($row['method'] == "TOTP") {
$disableNewTotp = true;
writeRow($row['method'], $row['name'], true);
} else {
writeRow($row['method'], $row['name']);
}
}
?>
</table>
Expand All @@ -114,8 +133,12 @@ function addclick(sel) {
&nbsp;<br />
<select name='form_add' onchange='addclick(this)'>
<option value=''><?php echo xlt('Add New...'); ?></option>
<option value='U2F' ><?php echo xlt('U2F USB Device'); ?></option>
<option value='TOTP'><?php echo xlt('TOTP Key'); ?></option>
<option value='U2F'><?php echo xlt('U2F USB Device'); ?></option>
<option value='TOTP'
<?php echo ($disableNewTotp) ? 'title="' . xla('Only one TOTP Key can be set up per user') . '"' : ''; ?>
<?php echo ($disableNewTotp) ? 'disabled' : ''; ?>>
<?php echo xlt('TOTP Key'); ?>
</option>
</select>
<input type='hidden' name='form_delete_method' value='' />
<input type='hidden' name='form_delete_name' value='' />
Expand Down
87 changes: 40 additions & 47 deletions interface/usergroup/mfa_totp.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
* @copyright Copyright (c) 2018 Brady Miller <[email protected]>
* @license https://github.com/openemr/openemr/blob/master/LICENSE CNU General Public License 3
*/


require_once('../globals.php');
require_once("$srcdir/classes/Totp.class.php");

Expand Down Expand Up @@ -43,11 +45,6 @@ function docancel() {
window.location.href = 'mfa_registrations.php';
}

function dodelete() {
var f = document.forms[0];
f.action = 'mfa_registrations.php';
doregister('delete');
}
</script>
</head>
<body class="body_top">
Expand Down Expand Up @@ -77,31 +74,18 @@ function dodelete() {
<div class="col-xs-12">
<?php if ($error == "auth") { ?>
<div class="alert alert-danger login-failure m-1">
Invalid password
<?php echo xlt('Invalid password'); ?>
</div>
<?php } ?>
<p><?php echo xlt('In order to register your device, please provide your password'); ?></p>
<table cellspacing="5">
<tr>
<td>
<label for="clearPass"><?php echo xlt('Password:'); ?>
<label for="clearPass"><?php echo xlt('Password'); ?>:
</td>
<td>
<input type="password" class="form-control" id="clearPass" name="clearPass" placeholder="<?php echo xlt('Password:'); ?>" >

<?php
// collect groups
$res = sqlStatement("select distinct name from `groups`");
for ($iter = 0; $row = sqlFetchArray($res); $iter++) {
$result[$iter] = $row;
}

if (count($result) == 1) {
$resvalue = $result[0]{"name"};
echo "<input type='hidden' name='authProvider' value='" . attr($resvalue) . "' />\n";
} ?>

</td>
<input type="password" class="form-control" id="clearPass" name="clearPass" placeholder="<?php echo xla('Password'); ?>:" >
</td>
</tr>
<tr>
<td></td>
Expand All @@ -117,8 +101,12 @@ function dodelete() {
<?php
// step 2 is to validate password and display qr code
} elseif ($action == 'reg2') {
if (!verifyCsrfToken($_POST["csrf_token_form"])) {
csrfNotVerified();
}

// Redirect back to step 1 if user password is incorrect
if (!validate_user_password($_SESSION["pc_username"], $_POST['clearPass'], $_POST['authProvider'])) {
if (!validate_user_password($_SESSION['authUser'], $_POST['clearPass'], $_SESSION['authProvider'])) {
header("Location: mfa_totp.php?action=reg1&error=auth");
exit();
}
Expand All @@ -138,7 +126,7 @@ function dodelete() {
}

// Generate a new QR code or existing QR code
$googleAuth = new Totp($secret, $_SESSION["pc_username"]);
$googleAuth = new Totp($secret, $_SESSION['authUser']);
$qr = $googleAuth->generateQrCode();


Expand All @@ -149,36 +137,41 @@ function dodelete() {
?>
<div class="row">
<div class="col-xs-12">
<p>
<?php echo xlt('This will register a new TOTP key.'); ?>
<?php echo xlt('Scan the following QR code with your preferred authenticator app.'); ?>
</p>
<img src="<?php echo attr($qr); ?>" height="150" />
<p>
<?php echo xlt('Example authenticator apps include:'); ?>
<?php if (!$doesExist) { ?>
<p>
<?php echo xlt('This will register a new TOTP key.'); ?>
<?php echo xlt('Scan the following QR code with your preferred authenticator app.'); ?>
</p>
<?php } else { // $doesExist ?>
<p>
<?php echo xlt('Your current TOTP key QR code is displayed below.'); ?>
</p>
<?php } ?>
<img src="<?php echo attr($qr); ?>" height="150" />
<p>
<?php echo xlt('Example authenticator apps include:'); ?>
<ul>
<li><?php echo xla('Google Auth'); ?>
<li><?php echo xlt('Google Auth'); ?>
(<a href="https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8">
<?php echo xla('ios'); ?>
<?php echo xlt('ios'); ?>
</a>,
<a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en">
<?php echo xla('android'); ?>
<?php echo xlt('android'); ?>
</a>)</li>
<li><?php echo xla('Authy'); ?>
(<a href="https://itunes.apple.com/us/app/authy/id494168017?mt=8"><?php echo xla('ios'); ?></a>, <a href="https://play.google.com/store/apps/details?id=com.authy.authy&hl=en"><?php echo xla('android'); ?></a>)</li>
<li><?php echo xlt('Authy'); ?>
(<a href="https://itunes.apple.com/us/app/authy/id494168017?mt=8"><?php echo xlt('ios'); ?></a>, <a href="https://play.google.com/store/apps/details?id=com.authy.authy&hl=en"><?php echo xlt('android'); ?></a>)</li>
</ul>
</p>
<p>
<?php if ($doesExist) { ?>
<input type='hidden' name='form_delete_method' value='TOTP' />
<input type='hidden' name='form_delete_name' value='App Based 2FA' />
<input type='button' value='<?php echo xla('Disable'); ?>' onclick='dodelete();' />
<?php } else { ?>
</p>
<?php if (!$doesExist) { ?>
<p>
<input type='button' value='<?php echo xla('Register'); ?>' onclick='doregister("reg3")' />
<?php } ?>

<input type='button' value='<?php echo xla('Cancel'); ?>' onclick='docancel()' />
</p>
<input type='button' value='<?php echo xla('Cancel'); ?>' onclick='docancel()' />
</p>
<?php } else { // $doesExist ?>
<p>
<input type='button' value='<?php echo xla('Back'); ?>' onclick='docancel()' />
</p>
<?php } ?>
</div>
</div>
</div>
Expand Down
5 changes: 2 additions & 3 deletions library/classes/Installer.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ public function add_initial_user()
}

// Create new 2fa if enabled
if (($this->i2faEnable) && (class_exists('Totp'))) {
if (($this->i2faEnable) && (!empty($this->i2faSecret)) && (class_exists('Totp'))) {
// Encrypt the new secret with the hashed password
$secret = encryptStandard($this->i2faSecret, $hash);
if ($this->execute_sql("INSERT INTO login_mfa_registrations (user_id, name, method, var1, var2) VALUES (1, 'App Based 2FA', 'TOTP', '".$this->escapeSql($secret)."', '')") == false) {
Expand All @@ -397,11 +397,10 @@ public function add_initial_user()
*/
public function get_initial_user_2fa_qr()
{
if (($this->i2faEnable) && (class_exists('Totp'))) {
if (($this->i2faEnable) && (!empty($this->i2faSecret)) && (class_exists('Totp'))) {
$adminTotp = new Totp($this->i2faSecret, $this->iuser);
$qr = $adminTotp->generateQrCode();
return $qr;

}
return false;
}
Expand Down
52 changes: 13 additions & 39 deletions library/classes/Totp.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@
* @copyright Copyright (c) 2019 Brady Miller <[email protected]>
* @license https://github.com/openemr/openemr/blob/master/LICENSE CNU General Public License 3
*/

use ParagonIE\MultiFactor\Vendor\GoogleAuth;
use ParagonIE\Halite\Symmetric\Crypto as SymmetricCrypto;
use Defuse\Crypto\Crypto;

/**
* Class Totp
Expand All @@ -22,10 +21,6 @@ class Totp

/** @var bool|GoogleAuth */
private $_googleAuth = false;
/** @var bool|string */
private $_qrFileName = false;
/** @var bool|string - user's hashed password */
private $_hashedPass = false;
/** @var bool|string - totp hashed secret */
private $_secret = false;
/** @var string - issuer mentioned in the QR App */
Expand All @@ -44,7 +39,11 @@ public function __construct($secret = false, $username = '')
if ($secret) {
$this->_secret = $secret;
} else {
$this->_secret = $this->_createRandString(16);
// Shared key (per rfc6238 and rfc4226) should be 20 bytes (160 bits) and encoded in base32, which should
// be 32 characters in base32
// Would be nice to use the produceRandomBytes() function and then encode to base32, but does not appear
// to be a standard way to encode binary to base32 in php.
$this->_secret = produceRandomString(32, "234567ABCDEFGHIJKLMNOPQRSTUVWXYZ");
}
}

Expand All @@ -55,21 +54,22 @@ public function __construct($secret = false, $username = '')
public function generateQrCode()
{
if (class_exists('ParagonIE\MultiFactor\Vendor\GoogleAuth')) {

// Generates a file with a PNG of the qr code
$tempFilePath = $this->_getQrFilePath();
if (!empty($GLOBALS['temporary_files_dir'])) {
$tempFilePath = tempnam($GLOBALS['temporary_files_dir'], "oer");
} else {
$tempFilePath = tempnam(sys_get_temp_dir(), 'oer');
}
$this->_getGoogleAuth()->makeQRCode(null, $tempFilePath, $this->_username, $this->_issuer);

// Gets the image file data to return
$imageInfo = getimagesize($tempFilePath);
$data = base64_encode(file_get_contents($tempFilePath));
$image = sprintf('data:%s;base64,%s', $imageInfo['mime'], $data);
$image = sprintf('data:%s;base64,%s', 'image/png', $data);

// Delete image file before returning
unlink($tempFilePath);

return $image;

}
return false;
}
Expand All @@ -81,10 +81,8 @@ public function generateQrCode()
*/
public function validateCode($totp)
{
if (class_exists('ParagonIE\MultiFactor\Vendor\GoogleAuth')) {

if (class_exists('ParagonIE\MultiFactor\Vendor\GoogleAuth') && (!empty($this->_secret))) {
return $this->_getGoogleAuth()->validateCode($totp, strtotime("now"));

}
return false;
}
Expand All @@ -98,18 +96,6 @@ public function getSecret()
return $this->_secret;
}

/**
* Gets the file name of the string as a png
* @return string
*/
private function _getQrFilePath()
{
if (!$this->_qrFileName) {
$this->_qrFileName = md5($this->getSecret());
}
return $this->_qrFileName.".png";
}

/**
* Gets the GoogleAuth object related this Totp
* @return bool|GoogleAuth
Expand All @@ -121,16 +107,4 @@ private function _getGoogleAuth()
}
return $this->_googleAuth;
}

/**
* Creates a random string of given length
* @param $len - length of string
* @return string
*/
private function _createRandString($len)
{
return substr(str_shuffle(str_repeat("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", $len)), 0, $len);
}

}
?>
19 changes: 19 additions & 0 deletions library/crypto.php
Original file line number Diff line number Diff line change
Expand Up @@ -408,3 +408,22 @@ function produceRandomBytes($length)

return $randomBytes;
}

// Produce random string (uses random_int with error checking)
function produceRandomString($length = 26, $alphabet = 'abcdefghijklmnopqrstuvwxyz234567')
{
$str = '';
$alphamax = strlen($alphabet) - 1;
for ($i = 0; $i < $length; ++$i) {
try {
$str .= $alphabet[random_int(0, $alphamax)];
} catch (Error $e) {
error_log('OpenEMR Error : Encryption is not working because of random_int() Error: ' . $e->getMessage());
return false;
} catch (Exception $e) {
error_log('OpenEMR Error : Encryption is not working because of random_int() Exception: ' . $e->getMessage());
return false;
}
}
return $str;
}
Loading

0 comments on commit d4ca70f

Please sign in to comment.