mirror of
https://github.com/prasathmani/tinyfilemanager
synced 2024-05-03 14:23:14 +02:00
Implement 2FA authentication
This commit is contained in:
parent
f380478197
commit
ded07b0f76
164
2fa.lib.php
Normal file
164
2fa.lib.php
Normal file
|
@ -0,0 +1,164 @@
|
|||
<?php
|
||||
/**
|
||||
* 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/>.
|
||||
*
|
||||
* PHP Google two-factor authentication module.
|
||||
*
|
||||
* See https://www.idontplaydarts.com/2011/07/google-totp-two-factor-authentication-for-php/
|
||||
* for more details
|
||||
*
|
||||
* @author Phil
|
||||
**/
|
||||
|
||||
class Google2FA {
|
||||
|
||||
const keyRegeneration = 30; // Interval between key regeneration
|
||||
const otpLength = 6; // Length of the Token generated
|
||||
|
||||
private static $lut = array( // Lookup needed for Base32 encoding
|
||||
"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
|
||||
);
|
||||
|
||||
/**
|
||||
* Generates a 16 digit secret key in base32 format
|
||||
* @return string
|
||||
**/
|
||||
public static function generate_secret_key($length = 16) {
|
||||
$b32 = "234567QWERTYUIOPASDFGHJKLZXCVBNM";
|
||||
$s = "";
|
||||
|
||||
for ($i = 0; $i < $length; $i++)
|
||||
$s .= $b32[rand(0,31)];
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current Unix Timestamp divided by the keyRegeneration
|
||||
* period.
|
||||
* @return integer
|
||||
**/
|
||||
public static function get_timestamp() {
|
||||
return floor(microtime(true)/self::keyRegeneration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a base32 string into a binary string.
|
||||
**/
|
||||
public static function base32_decode($b32) {
|
||||
|
||||
$b32 = strtoupper($b32);
|
||||
|
||||
if (!preg_match('/^[ABCDEFGHIJKLMNOPQRSTUVWXYZ234567]+$/', $b32, $match))
|
||||
throw new Exception('Invalid characters in the base32 string.');
|
||||
|
||||
$l = strlen($b32);
|
||||
$n = 0;
|
||||
$j = 0;
|
||||
$binary = "";
|
||||
|
||||
for ($i = 0; $i < $l; $i++) {
|
||||
|
||||
$n = $n << 5; // Move buffer left by 5 to make room
|
||||
$n = $n + self::$lut[$b32[$i]]; // Add value into buffer
|
||||
$j = $j + 5; // Keep track of number of bits in buffer
|
||||
|
||||
if ($j >= 8) {
|
||||
$j = $j - 8;
|
||||
$binary .= chr(($n & (0xFF << $j)) >> $j);
|
||||
}
|
||||
}
|
||||
|
||||
return $binary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the secret key and the timestamp and returns the one time
|
||||
* password.
|
||||
*
|
||||
* @param binary $key - Secret key in binary form.
|
||||
* @param integer $counter - Timestamp as returned by get_timestamp.
|
||||
* @return string
|
||||
**/
|
||||
public static function oath_hotp($key, $counter)
|
||||
{
|
||||
if (strlen($key) < 8)
|
||||
throw new Exception('Secret key is too short. Must be at least 16 base 32 characters');
|
||||
|
||||
$bin_counter = pack('N*', 0) . pack('N*', $counter); // Counter must be 64-bit int
|
||||
$hash = hash_hmac ('sha1', $bin_counter, $key, true);
|
||||
|
||||
return str_pad(self::oath_truncate($hash), self::otpLength, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifys a user inputted key against the current timestamp. Checks $window
|
||||
* keys either side of the timestamp.
|
||||
*
|
||||
* @param string $b32seed
|
||||
* @param string $key - User specified key
|
||||
* @param integer $window
|
||||
* @param boolean $useTimeStamp
|
||||
* @return boolean
|
||||
**/
|
||||
public static function verify_key($b32seed, $key, $window = 0, $useTimeStamp = true) {
|
||||
|
||||
$timeStamp = self::get_timestamp();
|
||||
|
||||
if ($useTimeStamp !== true) $timeStamp = (int)$useTimeStamp;
|
||||
|
||||
$binarySeed = self::base32_decode($b32seed);
|
||||
|
||||
for ($ts = $timeStamp - $window; $ts <= $timeStamp + $window; $ts++)
|
||||
if (self::oath_hotp($binarySeed, $ts) == $key)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the OTP from the SHA1 hash.
|
||||
* @param binary $hash
|
||||
* @return integer
|
||||
**/
|
||||
public static function oath_truncate($hash)
|
||||
{
|
||||
$offset = ord($hash[19]) & 0xf;
|
||||
|
||||
return (
|
||||
((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::otpLength);
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
//Default Configuration
|
||||
$CONFIG = '{"lang":"en","error_reporting":false,"show_hidden":false,"hide_Cols":false,"theme":"light"}';
|
||||
$CONFIG = '{"lang":"en","error_reporting":false,"show_hidden":false,"hide_Cols":false,"use_2FA":false,"theme":"light"}';
|
||||
|
||||
/**
|
||||
* H3K | Tiny File Manager V2.5.3
|
||||
|
@ -31,6 +31,10 @@ $auth_users = array(
|
|||
'user' => '$2y$10$Fg6Dz8oH9fPoZ2jJan5tZuv6Z4Kp7avtQ9bDfrdRntXtPeiMAZyGO' //12345
|
||||
);
|
||||
|
||||
// Login 2FA / OTP secrets (login with random code to generate)
|
||||
$otp_secrets = array();
|
||||
$tfa_lib = '2fa.lib.php';
|
||||
|
||||
// Readonly users
|
||||
// e.g. array('users', 'guest', ...)
|
||||
$readonly_users = array(
|
||||
|
@ -193,6 +197,8 @@ $report_errors = isset($cfg->data['error_reporting']) ? $cfg->data['error_report
|
|||
// Hide Permissions and Owner cols in file-listing
|
||||
$hide_Cols = isset($cfg->data['hide_Cols']) ? $cfg->data['hide_Cols'] : true;
|
||||
|
||||
// Use 2FA authentication for all users
|
||||
$use_2FA = isset($cfg->data['use_2FA']) ? $cfg->data['use_2FA'] : true;
|
||||
// Theme
|
||||
$theme = isset($cfg->data['theme']) ? $cfg->data['theme'] : 'light';
|
||||
|
||||
|
@ -326,6 +332,58 @@ if ($use_auth) {
|
|||
sleep(1);
|
||||
if(function_exists('password_verify')) {
|
||||
if (isset($auth_users[$_POST['fm_usr']]) && isset($_POST['fm_pwd']) && password_verify($_POST['fm_pwd'], $auth_users[$_POST['fm_usr']]) && verifyToken($_POST['token'])) {
|
||||
// Login with 2FA TOTP
|
||||
if ($use_2FA) {
|
||||
if (!file_exists($tfa_lib)) {
|
||||
unset($_SESSION[FM_SESSION_ID]['logged']);
|
||||
die("<b>Fatal error:</b> Missing 2FA Authentication library: $tfa_lib");
|
||||
}
|
||||
require_once($tfa_lib);
|
||||
|
||||
// Generate random OTP secret, manually add entry inside '$otp_secrets' array
|
||||
if (!isset($otp_secrets[$_POST['fm_usr']])) {
|
||||
$QR_onlineAPI = 0;
|
||||
$random_Base32_InitKey = Google2FA::generate_secret_key(56);
|
||||
$otp_uri = urlencode("otpauth://totp/TFM:$_POST[fm_usr]@$_SERVER[SERVER_NAME]?secret=$random_Base32_InitKey&issuer=TFM&algorithm=SHA1&digits=6&period=30");
|
||||
//$qr_gen_api = "https://api.qrserver.com/v1/create-qr-code/?size=200x200&ecc=L&data=";
|
||||
$qr_gen_api = "https://chart.googleapis.com/chart?cht=qr&chs=200x200&chld=L|0&chl=";
|
||||
echo '<h1>New OTP secret generated!</h1>Add the secret below to the <code>$otp_secrets</code> array and scan the QR code to add it to your personal 2FA vault.<br><br>';
|
||||
echo "<code>'$_POST[fm_usr]' => '$random_Base32_InitKey'</code><br><br><br>";
|
||||
if ($QR_onlineAPI != 0) echo '<img src="'.$qr_gen_api.urlencode($otp_uri).'" alt="QR Code" width="200px" height="200px" style="box-shadow:0 0 10px 0 rgba(0, 0, 0, 0.4);background-color:rgba(0, 0, 0, 0.1);">';
|
||||
if ($QR_onlineAPI == 0) {
|
||||
echo '<script src="https://cdn.jsdelivr.net/npm/davidshimjs-qrcodejs@0.0.2/qrcode.min.js" integrity="sha256-xUHvBjJ4hahBW8qN9gceFBibSFUzbe9PNttUvehITzY=" crossorigin="anonymous"></script>';
|
||||
echo '<div id="qrcode" style="width:180px;padding:10px;box-shadow:0 0 10px 0 rgba(0, 0, 0, 0.4);"></div>';
|
||||
echo '<script type="text/javascript">
|
||||
var qrcode = new QRCode(document.getElementById("qrcode"), {
|
||||
text: "'.$otp_uri.'",
|
||||
width: 180,
|
||||
height: 180,
|
||||
colorDark : "#000000",
|
||||
colorLight : "#ffffff",
|
||||
correctLevel : QRCode.CorrectLevel.L
|
||||
});
|
||||
</script>';
|
||||
}
|
||||
unset($_SESSION[FM_SESSION_ID]['logged']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Retrieve secret for user that successfully logged in
|
||||
$InitalizationKey = $otp_secrets[$_POST['fm_usr']];
|
||||
|
||||
// Validate OTP
|
||||
if (isset($_POST['otp'])) {
|
||||
if (!Google2FA::verify_key($InitalizationKey, $_POST['otp'])) {
|
||||
unset($_SESSION[FM_SESSION_ID]['logged']);
|
||||
fm_set_msg(lng('Login failed. Invalid username or password'), 'error');
|
||||
fm_redirect(FM_SELF_URL);
|
||||
}
|
||||
} else {
|
||||
unset($_SESSION[FM_SESSION_ID]['logged']);
|
||||
fm_set_msg(lng('Login failed. Invalid username or password'), 'error');
|
||||
fm_redirect(FM_SELF_URL);
|
||||
}
|
||||
}
|
||||
$_SESSION[FM_SESSION_ID]['logged'] = $_POST['fm_usr'];
|
||||
fm_set_msg(lng('You are logged in'));
|
||||
fm_redirect(FM_SELF_URL);
|
||||
|
@ -373,6 +431,13 @@ if ($use_auth) {
|
|||
<input type="password" class="form-control" id="fm_pwd" name="fm_pwd" required>
|
||||
</div>
|
||||
|
||||
<?php if ($use_2FA) { ?>
|
||||
<div class="mb-3">
|
||||
<label for="otp" class="pb-2"><?php echo lng('2FA'); ?></label>
|
||||
<input type="text" class="form-control" id="otp" name="otp" inputmode="numeric" maxlength="6" pattern="\d{6}" autocomplete="off">
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<div class="mb-3">
|
||||
<?php fm_show_message(); ?>
|
||||
</div>
|
||||
|
@ -525,7 +590,7 @@ if ((isset($_SESSION[FM_SESSION_ID]['logged'], $auth_users[$_SESSION[FM_SESSION_
|
|||
|
||||
// Save Config
|
||||
if (isset($_POST['type']) && $_POST['type'] == "settings") {
|
||||
global $cfg, $lang, $report_errors, $show_hidden_files, $lang_list, $hide_Cols, $theme;
|
||||
global $cfg, $lang, $report_errors, $show_hidden_files, $lang_list, $hide_Cols, $use_2FA, $theme;
|
||||
$newLng = $_POST['js-language'];
|
||||
fm_get_translations([]);
|
||||
if (!array_key_exists($newLng, $lang_list)) {
|
||||
|
@ -535,6 +600,7 @@ if ((isset($_SESSION[FM_SESSION_ID]['logged'], $auth_users[$_SESSION[FM_SESSION_
|
|||
$erp = isset($_POST['js-error-report']) && $_POST['js-error-report'] == "true" ? true : false;
|
||||
$shf = isset($_POST['js-show-hidden']) && $_POST['js-show-hidden'] == "true" ? true : false;
|
||||
$hco = isset($_POST['js-hide-cols']) && $_POST['js-hide-cols'] == "true" ? true : false;
|
||||
$tfa = isset($_POST['js-use-2FA']) && $_POST['js-use-2FA'] == "true" ? true : false;
|
||||
$te3 = $_POST['js-theme-3'];
|
||||
|
||||
if ($cfg->data['lang'] != $newLng) {
|
||||
|
@ -557,6 +623,10 @@ if ((isset($_SESSION[FM_SESSION_ID]['logged'], $auth_users[$_SESSION[FM_SESSION_
|
|||
$cfg->data['hide_Cols'] = $hco;
|
||||
$hide_Cols = $hco;
|
||||
}
|
||||
if ($cfg->data['use_2FA'] != $tfa) {
|
||||
$cfg->data['use_2FA'] = $tfa;
|
||||
$use_2FA = $tfa;
|
||||
}
|
||||
if ($cfg->data['theme'] != $te3) {
|
||||
$cfg->data['theme'] = $te3;
|
||||
$theme = $te3;
|
||||
|
@ -1558,6 +1628,17 @@ if (isset($_GET['settings']) && !FM_READONLY) {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (file_exists($tfa_lib)) { ?>
|
||||
<div class="mb-3 row">
|
||||
<label for="js-hide-cols" class="col-sm-3 col-form-label"><?php echo lng('Use 2FA authentication') ?></label>
|
||||
<div class="col-sm-9">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="js-use-2FA" name="js-use-2FA" value="true" <?php echo $use_2FA ? 'checked' : ''; ?> />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<div class="mb-3 row">
|
||||
<label for="js-3-1" class="col-sm-3 col-form-label"><?php echo lng('Theme') ?></label>
|
||||
<div class="col-sm-5">
|
||||
|
|
Loading…
Reference in a new issue