mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
* Use Laravel for authentication Support legacy auth methods Always create DB entry for users (segregate by auth method) Port api auth to Laravel restrict poller errors to devices the user has access to Run checks on every page load. But set a 5 minute (configurable) timer. Only run some checks if the user is an admin Move toastr down a few pixels so it isn't as annoying. Fix menu not loaded on laravel pages when twofactor is enabled for the system, but disabled for the user. Add two missing menu entries in the laravel menu Rewrite 2FA code Simplify some and verify code before applying Get http-auth working Handle legacy $_SESSION differently. Allows Auth::once(), etc to work. * Fix tests and mysqli extension check * remove duplicate Toastr messages * Fix new items * Rename 266.sql to 267.sql
199 lines
5.5 KiB
PHP
199 lines
5.5 KiB
PHP
<?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\Config;
|
|
use LibreNMS\Exceptions\AuthenticationException;
|
|
use Session;
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
}
|