You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
115 lines
3.6 KiB
115 lines
3.6 KiB
<?php |
|
|
|
/** |
|
* Totp class used to generated MultiFactor App Based 2FA |
|
* |
|
* @package OpenEMR |
|
* @link http://www.open-emr.org |
|
* @author Anthony Zullo <anthonykzullo@gmail.com> |
|
* @author Brady Miller <brady.g.miller@gmail.com> |
|
* @copyright Copyright (c) 2019 Anthony Zullo <anthonykzullo@gmail.com> |
|
* @copyright Copyright (c) 2019 Brady Miller <brady.g.miller@gmail.com> |
|
* @license https://github.com/openemr/openemr/blob/master/LICENSE CNU General Public License 3 |
|
*/ |
|
|
|
use OpenEMR\Common\Utils\RandomGenUtils; |
|
use ParagonIE\MultiFactor\Vendor\GoogleAuth; |
|
|
|
/** |
|
* Class Totp |
|
*/ |
|
class Totp |
|
{ |
|
/** @var bool|GoogleAuth */ |
|
private $_googleAuth = false; |
|
/** @var bool|string - totp hashed secret */ |
|
private $_secret = false; |
|
/** @var string - issuer mentioned in the QR App */ |
|
private $_issuer = "OpenEMR"; |
|
/** @var string - user name of user stored in QR App */ |
|
private $_username; |
|
|
|
/** |
|
* @param bool $secret - user secret or false to generate |
|
* @param string $username - username to store in QR App |
|
*/ |
|
public function __construct($secret = false, $username = '') |
|
{ |
|
$this->_username = $username; |
|
|
|
if ($secret) { |
|
$this->_secret = $secret; |
|
} else { |
|
// 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 OpenEMR\Common\Utils\RandomGenUtils\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 = RandomGenUtils::produceRandomString(32, "234567ABCDEFGHIJKLMNOPQRSTUVWXYZ"); |
|
if (empty($this->_secret)) { |
|
error_log('OpenEMR Error : Random String error - exiting'); |
|
die(); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Generates a QR code code |
|
* @return bool|string|void |
|
*/ |
|
public function generateQrCode() |
|
{ |
|
if (class_exists('ParagonIE\MultiFactor\Vendor\GoogleAuth')) { |
|
// Generates a file with a PNG of the qr code |
|
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 |
|
$data = base64_encode(file_get_contents($tempFilePath)); |
|
$image = sprintf('data:%s;base64,%s', 'image/png', $data); |
|
|
|
// Delete image file before returning |
|
unlink($tempFilePath); |
|
|
|
return $image; |
|
} |
|
return false; |
|
} |
|
|
|
/** |
|
* Validates a TOTP |
|
* @param $totp : unencrypted |
|
* @return bool |
|
*/ |
|
public function validateCode($totp) |
|
{ |
|
if (class_exists('ParagonIE\MultiFactor\Vendor\GoogleAuth') && (!empty($this->_secret))) { |
|
return $this->_getGoogleAuth()->validateCode($totp, strtotime("now")); |
|
} |
|
return false; |
|
} |
|
|
|
/** |
|
* Gets the encrypted value of the secret |
|
* @return string |
|
*/ |
|
public function getSecret() |
|
{ |
|
return $this->_secret; |
|
} |
|
|
|
/** |
|
* Gets the GoogleAuth object related this Totp |
|
* @return bool|GoogleAuth |
|
*/ |
|
private function _getGoogleAuth() |
|
{ |
|
if (!$this->_googleAuth) { |
|
$this->_googleAuth = new GoogleAuth($this->getSecret()); |
|
} |
|
return $this->_googleAuth; |
|
} |
|
}
|
|
|