mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
fix: Two-Factor Authentication (#6672)
* fix: Two-Factor Auth Moved library to a class to take advantage of namespacing and auto loading. Update the two factor code to use the AuthenticationException for error messages. Fix remember me to work with 2fa. * missing change
This commit is contained in:
committed by
Neil Lathwood
parent
f723f0ba12
commit
4d0c75343e
319
LibreNMS/Authentication/TwoFactor.php
Normal file
319
LibreNMS/Authentication/TwoFactor.php
Normal file
@@ -0,0 +1,319 @@
|
||||
<?php
|
||||
/**
|
||||
* TwoFactor.php
|
||||
*
|
||||
* Two-Factor Authentication Library
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @license GPL
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @subpackage Authentication
|
||||
* @author f0o <f0o@devilcode.org>
|
||||
* @copyright 2014 f0o, LibreNMS
|
||||
* @copyright 2017 Tony Murray
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Authentication;
|
||||
|
||||
use LibreNMS\Exceptions\AuthenticationException;
|
||||
|
||||
class TwoFactor
|
||||
{
|
||||
/**
|
||||
* Key Interval in seconds.
|
||||
* Set to 30s due to Google-Authenticator limitation.
|
||||
* Sadly Google-Auth is the most used Non-Physical OTP app.
|
||||
*/
|
||||
const KEY_INTERVAL = 30;
|
||||
|
||||
/**
|
||||
* Size of the OTP.
|
||||
* Set to 6 for the same reasons as above.
|
||||
*/
|
||||
const OTP_SIZE = 6;
|
||||
|
||||
/**
|
||||
* Window to honour whilest verifying OTP.
|
||||
*/
|
||||
const OTP_WINDOW = 4;
|
||||
|
||||
/**
|
||||
* Base32 Decoding dictionary
|
||||
*/
|
||||
private static $base32 = array(
|
||||
"A" => 0,
|
||||
"B" => 1,
|
||||
"C" => 2,
|
||||
"D" => 3,
|
||||
"E" => 4,
|
||||
"F" => 5,
|
||||
"G" => 6,
|
||||
"H" => 7,
|
||||
"I" => 8,
|
||||
"J" => 9,
|
||||
"K" => 10,
|
||||
"L" => 11,
|
||||
"M" => 12,
|
||||
"N" => 13,
|
||||
"O" => 14,
|
||||
"P" => 15,
|
||||
"Q" => 16,
|
||||
"R" => 17,
|
||||
"S" => 18,
|
||||
"T" => 19,
|
||||
"U" => 20,
|
||||
"V" => 21,
|
||||
"W" => 22,
|
||||
"X" => 23,
|
||||
"Y" => 24,
|
||||
"Z" => 25,
|
||||
"2" => 26,
|
||||
"3" => 27,
|
||||
"4" => 28,
|
||||
"5" => 29,
|
||||
"6" => 30,
|
||||
"7" => 31
|
||||
);
|
||||
|
||||
/**
|
||||
* Base32 Encoding dictionary
|
||||
*/
|
||||
private static $base32_enc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
|
||||
/**
|
||||
* Generate Secret Key
|
||||
* @return string
|
||||
*/
|
||||
public static function genKey()
|
||||
{
|
||||
// RFC 4226 recommends 160bits Secret Keys, that's 20 Bytes for the lazy ones.
|
||||
$crypto = false;
|
||||
$raw = "";
|
||||
$x = -1;
|
||||
while ($crypto == false || ++$x < 10) {
|
||||
$raw = openssl_random_pseudo_bytes(20, $crypto);
|
||||
}
|
||||
// RFC 4648 Base32 Encoding without padding
|
||||
$len = strlen($raw);
|
||||
$bin = "";
|
||||
$x = -1;
|
||||
while (++$x < $len) {
|
||||
$bin .= str_pad(base_convert(ord($raw[$x]), 10, 2), 8, '0', STR_PAD_LEFT);
|
||||
}
|
||||
$bin = str_split($bin, 5);
|
||||
$ret = "";
|
||||
$x = -1;
|
||||
while (++$x < sizeof($bin)) {
|
||||
$ret .= self::$base32_enc[base_convert(str_pad($bin[$x], 5, '0'), 2, 10)];
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the HTML for the TwoFactor Input-Form
|
||||
* @param boolean $form_tags Include FORM-tags
|
||||
* @return string
|
||||
*/
|
||||
public static function getForm($form_tags = true)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$ret = '';
|
||||
|
||||
if ($form_tags) {
|
||||
$ret .= '
|
||||
<div class="row">
|
||||
<div class="col-md-offset-4 col-md-4">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
<img src="' . $config['title_image'] . '">
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="container-fluid">
|
||||
<form class="form-horizontal" role="form" action="" method="post" name="twofactorform">';
|
||||
}
|
||||
|
||||
$ret .= '
|
||||
<div class="form-group">
|
||||
<div class="col-md-12">
|
||||
<input type="text" name="twofactor" id="twofactor" class="form-control" autocomplete="off" placeholder="Please enter auth token" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-md-12">
|
||||
<button type="submit" class="btn btn-default btn-block" name="submit">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
$ret .= '<script>document.twofactorform.twofactor.focus();</script>';
|
||||
|
||||
if ($form_tags) {
|
||||
$ret .= '</form>';
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate with two factor
|
||||
* Will set $twofactorform if the token hasn't been requested yet (page will redirect to the logon page)
|
||||
*
|
||||
* @return bool returns false if the form is not needed
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
public static function showForm()
|
||||
{
|
||||
global $twofactorform, $config;
|
||||
|
||||
$twofactor = get_user_pref('twofactor');
|
||||
|
||||
// no need to show the form, user doesn't have a token
|
||||
if (empty($twofactor)) {
|
||||
$_SESSION['twofactor'] = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// lockout the user if there are too many failures
|
||||
if ($twofactor['fails'] >= 3) {
|
||||
if (!$config['twofactor_lock']) {
|
||||
throw new AuthenticationException('Too many two-factor failures, please contact administrator.');
|
||||
} elseif ((time() - $twofactor['last']) < $config['twofactor_lock']) {
|
||||
$msg = "Too many two-factor failures, please wait " . $config['twofactor_lock'] . " seconds";
|
||||
throw new AuthenticationException($msg);
|
||||
}
|
||||
}
|
||||
|
||||
// set $twofactorform to show the form in logon.inc.php
|
||||
$twofactorform = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a 2fa token this will be stored in $_POST['twofactor'] by the form
|
||||
* If valid, $_SESSION['twofactor'] = true will be set and this will return true
|
||||
*
|
||||
* @param string $token The 2fa token, stored in $_POST['twofactor'] by the form
|
||||
* @return bool If the token was valid
|
||||
* @throws AuthenticationException Thrown if the token was invalid
|
||||
*/
|
||||
public static function authenticate($token)
|
||||
{
|
||||
if (!$token) {
|
||||
throw new AuthenticationException("No Two-Factor Token entered.");
|
||||
}
|
||||
|
||||
$twofactor = get_user_pref('twofactor');
|
||||
|
||||
if (empty($twofactor)) {
|
||||
throw new AuthenticationException('No Two-Factor settings, how did you get here?');
|
||||
}
|
||||
|
||||
if (($server_c = self::verifyHOTP($twofactor['key'], $_POST['twofactor'], $twofactor['counter'])) === false) {
|
||||
$twofactor['fails']++;
|
||||
$twofactor['last'] = time();
|
||||
set_user_pref('twofactor', $twofactor);
|
||||
throw new AuthenticationException("Wrong Two-Factor Token.");
|
||||
}
|
||||
|
||||
if ($twofactor['counter'] !== false) {
|
||||
if ($server_c !== true && $server_c !== $twofactor['counter']) {
|
||||
$twofactor['counter'] = $server_c + 1;
|
||||
} else {
|
||||
$twofactor['counter']++;
|
||||
}
|
||||
}
|
||||
$twofactor['fails'] = 0;
|
||||
set_user_pref('twofactor', $twofactor);
|
||||
|
||||
$_SESSION['twofactor'] = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify HOTP token honouring window
|
||||
*
|
||||
* @param string $key Secret Key
|
||||
* @param int $otp OTP supplied by user
|
||||
* @param int|boolean $counter Counter, if false timestamp is used
|
||||
* @return boolean|int
|
||||
*/
|
||||
public static function verifyHOTP($key, $otp, $counter = false)
|
||||
{
|
||||
if (self::oathHOTP($key, $counter) == $otp) {
|
||||
return true;
|
||||
} else {
|
||||
if ($counter === false) {
|
||||
//TimeBased HOTP requires lookbehind and lookahead.
|
||||
$counter = floor(microtime(true) / self::KEY_INTERVAL);
|
||||
$initcount = $counter - ((self::OTP_WINDOW + 1) * self::KEY_INTERVAL);
|
||||
$endcount = $counter + (self::OTP_WINDOW * self::KEY_INTERVAL);
|
||||
$totp = true;
|
||||
} else {
|
||||
//Counter based HOTP only has lookahead, not lookbehind.
|
||||
$initcount = $counter - 1;
|
||||
$endcount = $counter + self::OTP_WINDOW;
|
||||
$totp = false;
|
||||
}
|
||||
while (++$initcount <= $endcount) {
|
||||
if (self::oathHOTP($key, $initcount) == $otp) {
|
||||
if (!$totp) {
|
||||
return $initcount;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate HOTP (RFC 4226)
|
||||
* @param string $key Secret Key
|
||||
* @param int|boolean $counter Optional Counter, Defaults to Timestamp
|
||||
* @return int
|
||||
*/
|
||||
private static function oathHOTP($key, $counter = false)
|
||||
{
|
||||
if ($counter === false) {
|
||||
$counter = floor(microtime(true) / self::KEY_INTERVAL);
|
||||
}
|
||||
|
||||
$length = strlen($key);
|
||||
$x = -1;
|
||||
$y = $z = 0;
|
||||
$kbin = "";
|
||||
while (++$x < $length) {
|
||||
$y <<= 5;
|
||||
$y += self::$base32[$key[$x]];
|
||||
$z += 5;
|
||||
if ($z >= 8) {
|
||||
$z -= 8;
|
||||
$kbin .= chr(($y & (0xFF << $z)) >> $z);
|
||||
}
|
||||
}
|
||||
$hash = hash_hmac('sha1', pack('N*', 0) . pack('N*', $counter), $kbin, true);
|
||||
$offset = ord($hash[19]) & 0xf;
|
||||
$truncated = (((ord($hash[$offset + 0]) & 0x7f) << 24) |
|
||||
((ord($hash[$offset + 1]) & 0xff) << 16) |
|
||||
((ord($hash[$offset + 2]) & 0xff) << 8) |
|
||||
(ord($hash[$offset + 3]) & 0xff)) % pow(10, self::OTP_SIZE);
|
||||
return str_pad($truncated, self::OTP_SIZE, '0', STR_PAD_LEFT);
|
||||
}
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use LibreNMS\Authentication\TwoFactor;
|
||||
use LibreNMS\Exceptions\AuthenticationException;
|
||||
|
||||
ini_set('session.use_only_cookies', 1);
|
||||
@@ -25,27 +26,29 @@ dbDelete('session', '`session_expiry` < ?', array(time()));
|
||||
|
||||
session_start();
|
||||
|
||||
if ($vars['page'] == 'logout' && $_SESSION['authenticated']) {
|
||||
if ($vars['page'] == 'logout' && session_authenticated()) {
|
||||
log_out_user();
|
||||
header('Location: ' . $config['base_url']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
if (isset($_SESSION['authenticated']) && $_SESSION['authenticated']) {
|
||||
if (session_authenticated()) {
|
||||
// session authenticated already
|
||||
log_in_user();
|
||||
} else {
|
||||
// try authentication methods
|
||||
|
||||
// cookie authentication
|
||||
if (isset($_COOKIE['sess_id'], $_COOKIE['token']) &&
|
||||
if (isset($_POST['twofactor']) && TwoFactor::authenticate($_POST['twofactor'])) {
|
||||
// process two-factor auth tokens
|
||||
log_in_user();
|
||||
} elseif (isset($_COOKIE['sess_id'], $_COOKIE['token']) &&
|
||||
reauthenticate(clean($_COOKIE['sess_id']), clean($_COOKIE['token']))
|
||||
) {
|
||||
$_SESSION['remember'] = true;
|
||||
$_SESSION['twofactor'] = true; // trust cookie
|
||||
// cookie authentication
|
||||
log_in_user();
|
||||
|
||||
// update cookie expiry times
|
||||
set_remember_me();
|
||||
} else {
|
||||
// collect username and password
|
||||
$password = null;
|
||||
@@ -61,15 +64,15 @@ try {
|
||||
// form authentication
|
||||
if (isset($username) && authenticate($username, $password)) {
|
||||
$_SESSION['username'] = $username;
|
||||
log_in_user();
|
||||
|
||||
// set cookie if requested
|
||||
if (isset($_POST['remember'])) {
|
||||
set_remember_me();
|
||||
$_SESSION['remember'] = $_POST['remember'];
|
||||
}
|
||||
|
||||
// redirect to original uri or home page.
|
||||
header('Location: '.rtrim($config['base_url'], '/').$_SERVER['REQUEST_URI'], true, 303);
|
||||
if (log_in_user()) {
|
||||
// redirect to original uri or home page.
|
||||
header('Location: '.rtrim($config['base_url'], '/').$_SERVER['REQUEST_URI'], true, 303);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -23,6 +23,7 @@
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
use LibreNMS\Authentication\TwoFactor;
|
||||
use LibreNMS\Exceptions\AuthenticationException;
|
||||
use Phpass\PasswordHash;
|
||||
|
||||
@@ -72,32 +73,51 @@ function log_in_user()
|
||||
throw new AuthenticationException('Invalid Credentials');
|
||||
}
|
||||
|
||||
if (!(isset($_SESSION['authenticated']) && $_SESSION['authenticated'])) {
|
||||
if (!session_authenticated()) {
|
||||
// check twofactor
|
||||
if ($config['twofactor'] === true && !isset($_SESSION['twofactor'])) {
|
||||
include_once $config['install_dir'].'/html/includes/authentication/twofactor.lib.php';
|
||||
twofactor_auth();
|
||||
if (TwoFactor::showForm()) {
|
||||
return false; // not done yet, one more cycle to show the 2fa form
|
||||
}
|
||||
}
|
||||
|
||||
// if two factor isn't enabled or it has passed already ware are logged in
|
||||
if (!$config['twofactor'] || $_SESSION['twofactor']) {
|
||||
$_SESSION['authenticated'] = true;
|
||||
dbInsert(array('user' => $_SESSION['username'], 'address' => get_client_ip(), 'result' => 'Logged In'), 'authlog');
|
||||
} else {
|
||||
throw new AuthenticationException('Two-Factor Auth Failed');
|
||||
}
|
||||
}
|
||||
|
||||
if (session_authenticated()) {
|
||||
set_remember_me();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set or update the remember me cookie
|
||||
* Check if the session is authenticated
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function session_authenticated()
|
||||
{
|
||||
return isset($_SESSION['authenticated']) && $_SESSION['authenticated'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set or update the remember me cookie if $_SESSION['remember'] is set
|
||||
* If setting a new cookie, $_SESSION['username'] must be set
|
||||
*/
|
||||
function set_remember_me()
|
||||
{
|
||||
global $config;
|
||||
|
||||
if (!isset($_SESSION['remember'])) {
|
||||
return;
|
||||
}
|
||||
unset($_SESSION['remember']);
|
||||
|
||||
$sess_id = session_id();
|
||||
$expiration = time() + 60 * 60 * 24 * $config['auth_remember'];
|
||||
|
||||
|
@@ -1,240 +0,0 @@
|
||||
<?php
|
||||
/* Copyright (C) 2014 Daniel Preussker <f0o@devilcode.org>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Two-Factor Authentication Library
|
||||
* @author f0o <f0o@devilcode.org>
|
||||
* @copyright 2014 f0o, LibreNMS
|
||||
* @license GPL
|
||||
* @package LibreNMS
|
||||
* @subpackage Authentication
|
||||
*/
|
||||
|
||||
/**
|
||||
* Key Interval in seconds.
|
||||
* Set to 30s due to Google-Authenticator limitation.
|
||||
* Sadly Google-Auth is the most used Non-Physical OTP app.
|
||||
*/
|
||||
const KEY_INTERVAL = 30;
|
||||
|
||||
/**
|
||||
* Size of the OTP.
|
||||
* Set to 6 for the same reasons as above.
|
||||
*/
|
||||
const OTP_SIZE = 6;
|
||||
|
||||
/**
|
||||
* Window to honour whilest verifying OTP.
|
||||
*/
|
||||
const OTP_WINDOW = 4;
|
||||
|
||||
/**
|
||||
* Base32 Decoding dictionary
|
||||
*/
|
||||
$base32 = array(
|
||||
"A" => 0, "B" => 1, "C" => 2, "D" => 3,
|
||||
"E" => 4, "F" => 5, "G" => 6, "H" => 7,
|
||||
"I" => 8, "J" => 9, "K" => 10, "L" => 11,
|
||||
"M" => 12, "N" => 13, "O" => 14, "P" => 15,
|
||||
"Q" => 16, "R" => 17, "S" => 18, "T" => 19,
|
||||
"U" => 20, "V" => 21, "W" => 22, "X" => 23,
|
||||
"Y" => 24, "Z" => 25, "2" => 26, "3" => 27,
|
||||
"4" => 28, "5" => 29, "6" => 30, "7" => 31
|
||||
);
|
||||
|
||||
/**
|
||||
* Base32 Encoding dictionary
|
||||
*/
|
||||
$base32_enc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
|
||||
/**
|
||||
* Generate Secret Key
|
||||
* @return string
|
||||
*/
|
||||
function twofactor_genkey()
|
||||
{
|
||||
global $base32_enc;
|
||||
// RFC 4226 recommends 160bits Secret Keys, that's 20 Bytes for the lazy ones.
|
||||
$crypto = false;
|
||||
$raw = "";
|
||||
$x = -1;
|
||||
while ($crypto == false || ++$x < 10) {
|
||||
$raw = openssl_random_pseudo_bytes(20, $crypto);
|
||||
}
|
||||
// RFC 4648 Base32 Encoding without padding
|
||||
$len = strlen($raw);
|
||||
$bin = "";
|
||||
$x = -1;
|
||||
while (++$x < $len) {
|
||||
$bin .= str_pad(base_convert(ord($raw[$x]), 10, 2), 8, '0', STR_PAD_LEFT);
|
||||
}
|
||||
$bin = str_split($bin, 5);
|
||||
$ret = "";
|
||||
$x = -1;
|
||||
while (++$x < sizeof($bin)) {
|
||||
$ret .= $base32_enc[base_convert(str_pad($bin[$x], 5, '0'), 2, 10)];
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate HOTP (RFC 4226)
|
||||
* @param string $key Secret Key
|
||||
* @param int|boolean $counter Optional Counter, Defaults to Timestamp
|
||||
* @return int
|
||||
*/
|
||||
function oath_hotp($key, $counter = false)
|
||||
{
|
||||
global $base32;
|
||||
if ($counter === false) {
|
||||
$counter = floor(microtime(true)/KEY_INTERVAL);
|
||||
}
|
||||
$length = strlen($key);
|
||||
$x = -1;
|
||||
$y = $z = 0;
|
||||
$kbin = "";
|
||||
while (++$x < $length) {
|
||||
$y <<= 5;
|
||||
$y += $base32[$key[$x]];
|
||||
$z += 5;
|
||||
if ($z >= 8) {
|
||||
$z -= 8;
|
||||
$kbin .= chr(($y & (0xFF << $z)) >> $z);
|
||||
}
|
||||
}
|
||||
$hash = hash_hmac('sha1', pack('N*', 0).pack('N*', $counter), $kbin, true);
|
||||
$offset = ord($hash[19]) & 0xf;
|
||||
$truncated = (((ord($hash[$offset+0]) & 0x7f) << 24 ) | ((ord($hash[$offset+1]) & 0xff) << 16 ) | ((ord($hash[$offset+2]) & 0xff) << 8 ) | (ord($hash[$offset+3]) & 0xff)) % pow(10, OTP_SIZE);
|
||||
return str_pad($truncated, OTP_SIZE, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify HOTP token honouring window
|
||||
* @param string $key Secret Key
|
||||
* @param int $otp OTP supplied by user
|
||||
* @param int|boolean $counter Counter, if false timestamp is used
|
||||
* @return boolean|int
|
||||
*/
|
||||
function verify_hotp($key, $otp, $counter = false)
|
||||
{
|
||||
if (oath_hotp($key, $counter) == $otp) {
|
||||
return true;
|
||||
} else {
|
||||
if ($counter === false) {
|
||||
//TimeBased HOTP requires lookbehind and lookahead.
|
||||
$counter = floor(microtime(true)/KEY_INTERVAL);
|
||||
$initcount = $counter-((OTP_WINDOW+1)*KEY_INTERVAL);
|
||||
$endcount = $counter+(OTP_WINDOW*KEY_INTERVAL);
|
||||
$totp = true;
|
||||
} else {
|
||||
//Counter based HOTP only has lookahead, not lookbehind.
|
||||
$initcount = $counter-1;
|
||||
$endcount = $counter+OTP_WINDOW;
|
||||
$totp = false;
|
||||
}
|
||||
while (++$initcount <= $endcount) {
|
||||
if (oath_hotp($key, $initcount) == $otp) {
|
||||
if (!$totp) {
|
||||
return $initcount;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print TwoFactor Input-Form
|
||||
* @param boolean $form Include FORM-tags
|
||||
* @return string
|
||||
*/
|
||||
function twofactor_form($form = true)
|
||||
{
|
||||
global $config;
|
||||
$ret = "";
|
||||
if ($form) {
|
||||
$ret .= '
|
||||
<div class="row">
|
||||
<div class="col-md-offset-4 col-md-4">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
<img src="' . $config['title_image'] . '">
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="container-fluid">
|
||||
<form class="form-horizontal" role="form" action="" method="post" name="twofactorform">';
|
||||
}
|
||||
$ret .= '
|
||||
<div class="form-group">
|
||||
<div class="col-md-12">
|
||||
<input type="text" name="twofactor" id="twofactor" class="form-control" autocomplete="off" placeholder="Please enter auth token" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-md-12">
|
||||
<button type="submit" class="btn btn-default btn-block" name="submit">Login</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
$ret .= '<script>document.twofactorform.twofactor.focus();</script>';
|
||||
if ($form) {
|
||||
$ret .= '
|
||||
</form>';
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication logic
|
||||
* @return void
|
||||
*/
|
||||
function twofactor_auth()
|
||||
{
|
||||
global $auth_message, $twofactorform, $config;
|
||||
$twofactor = get_user_pref('twofactor');
|
||||
if (empty($twofactor)) {
|
||||
$_SESSION['twofactor'] = true;
|
||||
} else {
|
||||
if ($twofactor['fails'] >= 3 && (!$config['twofactor_lock'] || (time()-$twofactor['last']) < $config['twofactor_lock'])) {
|
||||
$auth_message = "Too many failures, please ".($config['twofactor_lock'] ? "wait ".$config['twofactor_lock']." seconds" : "contact administrator").".";
|
||||
} else {
|
||||
if (!$_POST['twofactor']) {
|
||||
$twofactorform = true;
|
||||
} else {
|
||||
if (($server_c = verify_hotp($twofactor['key'], $_POST['twofactor'], $twofactor['counter'])) === false) {
|
||||
$twofactor['fails']++;
|
||||
$twofactor['last'] = time();
|
||||
$auth_message = "Wrong Two-Factor Token.";
|
||||
} else {
|
||||
if ($twofactor['counter'] !== false) {
|
||||
if ($server_c !== true && $server_c !== $twofactor['counter']) {
|
||||
$twofactor['counter'] = $server_c+1;
|
||||
} else {
|
||||
$twofactor['counter']++;
|
||||
}
|
||||
}
|
||||
$twofactor['fails'] = 0;
|
||||
$_SESSION['twofactor'] = true;
|
||||
}
|
||||
set_user_pref('twofactor', $twofactor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
if ($config['twofactor'] && isset($twofactorform)) {
|
||||
echo twofactor_form();
|
||||
echo \LibreNMS\Authentication\TwoFactor::getForm();
|
||||
} else {
|
||||
?>
|
||||
<div class="row">
|
||||
|
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use LibreNMS\Authentication\TwoFactor;
|
||||
|
||||
$no_refresh = true;
|
||||
|
||||
$pagetitle[] = 'Preferences';
|
||||
@@ -73,11 +75,10 @@ if ($_SESSION['userlevel'] == 11) {
|
||||
|
||||
if ($config['twofactor'] === true) {
|
||||
if ($_POST['twofactorremove'] == 1) {
|
||||
include_once $config['install_dir'].'/html/includes/authentication/twofactor.lib.php';
|
||||
if (!isset($_POST['twofactor'])) {
|
||||
echo '<div class="well"><form class="form-horizontal" role="form" action="" method="post" name="twofactorform">';
|
||||
echo '<input type="hidden" name="twofactorremove" value="1" />';
|
||||
echo twofactor_form(false);
|
||||
echo TwoFactor::getForm(false);
|
||||
echo '</form></div>';
|
||||
} else {
|
||||
$twofactor = get_user_pref('twofactor');
|
||||
@@ -85,7 +86,7 @@ if ($_SESSION['userlevel'] == 11) {
|
||||
echo '<div class="alert alert-danger">Error: How did you even get here?!</div><script>window.location = "preferences/";</script>';
|
||||
}
|
||||
|
||||
if (verify_hotp($twofactor['key'], $_POST['twofactor'], $twofactor['counter'])) {
|
||||
if (TwoFactor::verifyHOTP($twofactor['key'], $_POST['twofactor'], $twofactor['counter'])) {
|
||||
if (!set_user_pref('twofactor', array())) {
|
||||
echo '<div class="alert alert-danger">Error while disabling TwoFactor.</div>';
|
||||
} else {
|
||||
@@ -134,10 +135,9 @@ if ($_SESSION['userlevel'] == 11) {
|
||||
</form>';
|
||||
} else {
|
||||
if (isset($_POST['gentwofactorkey']) && isset($_POST['twofactortype'])) {
|
||||
include_once $config['install_dir'].'/html/includes/authentication/twofactor.lib.php';
|
||||
$chk = get_user_pref('twofactor');
|
||||
if (empty($chk)) {
|
||||
$twofactor = array('key' => twofactor_genkey());
|
||||
$twofactor = array('key' => TwoFactor::genKey());
|
||||
if ($_POST['twofactortype'] == 'counter') {
|
||||
$twofactor['counter'] = 1;
|
||||
} else {
|
||||
|
Reference in New Issue
Block a user