mirror of
https://github.com/sorenpeter/timeline.git
synced 2025-12-15 10:57:01 +00:00
Merge pull request #2 from sorenpeter/implement-totp-login
Implement support for TOTP
This commit is contained in:
commit
37a9d77d7b
4 changed files with 193 additions and 17 deletions
166
libs/TOTP.php
Normal file
166
libs/TOTP.php
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
<?php
|
||||
function verifyTOTP($secret, $enteredCode, $digits = 10, $window = 1) {
|
||||
$windowTime = 30;
|
||||
$timeSlice = floor(time() / $windowTime);
|
||||
|
||||
$enteredCode = trim($enteredCode);
|
||||
$enteredCode = str_replace(' ', '', trim($enteredCode));
|
||||
|
||||
$enteredCodeInt = intval($enteredCode);
|
||||
#var_dump($enteredCodeInt);
|
||||
#echo "<br>\n";
|
||||
|
||||
for ($currentWindow = -$window; $currentWindow <= $window; $currentWindow++) {
|
||||
$time = $timeSlice + $currentWindow;
|
||||
$generatedCode = generateTOTP($secret, $digits, $time);
|
||||
#echo "$time $generatedCode<br>\n";
|
||||
|
||||
if ($generatedCode === $enteredCodeInt) {
|
||||
return true; // Code is valid within the window
|
||||
}
|
||||
}
|
||||
|
||||
return false; // Code is not valid
|
||||
}
|
||||
|
||||
function generateTOTP($secret, $digits = 6, $timeSlice = null) {
|
||||
if ($timeSlice === null) {
|
||||
$timeSlice = floor(time() / 30);
|
||||
}
|
||||
|
||||
$secret = base32Decode($secret);
|
||||
$timeSlice = pack('N*', 0) . pack('N*', $timeSlice);
|
||||
|
||||
$hash = hash_hmac('sha1', $timeSlice, $secret, true);
|
||||
$offset = ord($hash[19]) & 0xf;
|
||||
|
||||
$otp = (
|
||||
(ord($hash[$offset + 0]) & 0x7f) << 24 |
|
||||
(ord($hash[$offset + 1]) & 0xff) << 16 |
|
||||
(ord($hash[$offset + 2]) & 0xff) << 8 |
|
||||
(ord($hash[$offset + 3]) & 0xff)
|
||||
);
|
||||
|
||||
#var_dump($otp);
|
||||
#echo "<br>\n";
|
||||
|
||||
// When digits is 10, the padding was giving a wrong OTP
|
||||
// Just return the calculated value
|
||||
if ($digits === 10) {
|
||||
return $otp;
|
||||
}
|
||||
|
||||
$otp = $otp % pow(10, $digits);
|
||||
return str_pad($otp, $digits, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
function base32Decode($base32) {
|
||||
$base32chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
||||
$base32charsFlipped = array_flip(str_split($base32chars));
|
||||
$paddingChar = '=';
|
||||
$paddingCharCount = 0;
|
||||
|
||||
if (strpos($base32, $paddingChar) !== false) {
|
||||
$paddingCharCount = substr_count($base32, $paddingChar);
|
||||
}
|
||||
|
||||
$allowedValues = array(6, 4, 3, 1, 0);
|
||||
|
||||
if (!in_array($paddingCharCount, $allowedValues)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < 4; ++$i) {
|
||||
if ($paddingCharCount == $allowedValues[$i] &&
|
||||
substr($base32, -$allowedValues[$i]) != str_repeat($paddingChar, $allowedValues[$i])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$base32 = str_replace($paddingChar, '', $base32);
|
||||
$base32 = str_split($base32);
|
||||
$binaryString = '';
|
||||
|
||||
foreach ($base32 as $char) {
|
||||
if (!isset($base32charsFlipped[$char])) {
|
||||
return false; // Invalid character found
|
||||
}
|
||||
$binaryString .= sprintf('%05b', $base32charsFlipped[$char]);
|
||||
}
|
||||
|
||||
$length = strlen($binaryString);
|
||||
$offset = 0;
|
||||
$binaryData = '';
|
||||
|
||||
while ($offset < $length) {
|
||||
$binaryChunk = substr($binaryString, $offset, 8);
|
||||
|
||||
if (strlen($binaryChunk) < 8) {
|
||||
$binaryChunk = str_pad($binaryChunk, 8, '0', STR_PAD_RIGHT);
|
||||
}
|
||||
|
||||
$decimalChunk = bindec($binaryChunk);
|
||||
$binaryData .= pack('C', $decimalChunk);
|
||||
$offset += 8;
|
||||
}
|
||||
|
||||
return $binaryData;
|
||||
}
|
||||
|
||||
function base32Encode($input) {
|
||||
$base32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
||||
$base32String = '';
|
||||
$position = 0;
|
||||
$carry = 0;
|
||||
foreach (str_split($input) as $byte) {
|
||||
$carry |= ord($byte) << $position;
|
||||
$position += 8;
|
||||
while ($position >= 5) {
|
||||
$base32String .= $base32Chars[$carry & 31];
|
||||
$carry >>= 5;
|
||||
$position -= 5;
|
||||
}
|
||||
}
|
||||
if ($position > 0) {
|
||||
if ($carry & 31) {
|
||||
$base32String .= $base32Chars[$carry & 31];
|
||||
}
|
||||
}
|
||||
return $base32String;
|
||||
}
|
||||
|
||||
function generateRandomSecret($length = 32) {
|
||||
// https://medium.com/@nicola88/two-factor-authentication-with-totp-ccc5f828b6df
|
||||
$bytes = random_bytes($length);
|
||||
$base32 = base32Encode($bytes);
|
||||
|
||||
return substr($base32, 0, $length);
|
||||
}
|
||||
|
||||
# Examples of usage (To move somewhere else)
|
||||
/*
|
||||
$randomSecret = generateRandomSecret();
|
||||
echo "Random Secret Key: $randomSecret<br>\n";
|
||||
|
||||
$secret = 'K3OBZ7XPR6T4PTNXSNCQ';
|
||||
$enteredCode = '123456';
|
||||
$digits = 6;
|
||||
if (isset($_GET['c']) && isset($_GET['s'])) {
|
||||
$enteredCode = $_GET['c'];
|
||||
$secret = $_GET['s'];
|
||||
$isCodeValid = verifyTOTP($secret, $digits, $enteredCode);
|
||||
|
||||
if ($isCodeValid) {
|
||||
echo "Code $enteredCode is valid!<br>";
|
||||
} else {
|
||||
echo "Code $enteredCode is invalid!<br>";
|
||||
}
|
||||
}
|
||||
|
||||
$code = generateTOTP($secret);
|
||||
echo "TOTP code: $code<br>\n";
|
||||
|
||||
$code = generateTOTP($secret, 8);
|
||||
echo "TOTP code: $code<br>\n";
|
||||
*/
|
||||
|
|
@ -1,18 +1,30 @@
|
|||
<?php
|
||||
require_once('libs/TOTP.php');
|
||||
$config = parse_ini_file('private/config.ini');
|
||||
$password = $config['password'];
|
||||
|
||||
session_start();
|
||||
|
||||
if(isset($_POST['submit_pass']) && $_POST['pass'])
|
||||
if (isset($_POST['submit_pass']) && $_POST['pass'])
|
||||
{
|
||||
$pass=$_POST['pass'];
|
||||
if($pass=="$password")
|
||||
$pass = $_POST['pass'];
|
||||
|
||||
// @eapl.me 2023-11-23 - I'm trying to add support to passwords
|
||||
// and TOTP (passwordless). So, in the Pwd field you can enter
|
||||
// the password, or the current TOTP
|
||||
if ($pass == $password)
|
||||
{
|
||||
$_SESSION['password']=$pass;
|
||||
$_SESSION['password'] = $pass;
|
||||
}
|
||||
elseif ($isCodeValid = verifyTOTP(
|
||||
$config['totp_secret'], $pass, intval($config['totp_digits']))
|
||||
)
|
||||
{
|
||||
// If TOTP is valid, assume that we entered the Password
|
||||
$_SESSION['password'] = $password;
|
||||
}
|
||||
else
|
||||
{
|
||||
$error="Incorrect Password";
|
||||
$error = "Incorrect Password";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ declare(strict_types=1);
|
|||
# hash(string) =
|
||||
#
|
||||
|
||||
require_once("libs/session.php");
|
||||
require_once('libs/session.php');
|
||||
require_once('libs/twtxt.php');
|
||||
require_once('libs/hash.php');
|
||||
require_once('libs/Slimdown.php');
|
||||
|
|
@ -32,7 +32,7 @@ $config = parse_ini_file('private/config.ini');
|
|||
// TODO: Take the title from the config.ini
|
||||
$title = "Timeline"; // Fallback, should be set in all views
|
||||
|
||||
// HACKED by sp@darch.dk
|
||||
// HACKED by sp@darch.dk
|
||||
if(!empty($_GET['list'])) {
|
||||
$url = "https://darch.dk/twtxt-lists/".$_GET['list'];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,23 +5,21 @@ $title = "Login - ".$title;
|
|||
|
||||
include 'partials/header.php';
|
||||
?>
|
||||
|
||||
|
||||
<?php
|
||||
//$config = parse_ini_file('private/config.ini');
|
||||
//$password = $config['password'];
|
||||
|
||||
if( isset($_SESSION['password'])) {
|
||||
if($_SESSION['password']=="$password") {
|
||||
header("Location: .");
|
||||
die();
|
||||
// Password comes from libs/session.php
|
||||
if (isset($_SESSION['password'])) {
|
||||
if ($_SESSION['password'] == $password) {
|
||||
header("Location: .");
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
||||
else { ?>
|
||||
<center>
|
||||
<h2>Enter password:</h2>
|
||||
<h2>Enter password or TOTP</h2>
|
||||
<form method="post" action="" id="login_form">
|
||||
<input type="password" name="pass"><br>
|
||||
<input type="password" name="pass" placeholder="Password" autofocus><br>
|
||||
<input type="submit" name="submit_pass" value="Login">
|
||||
<p><font style="color:red;"><?php if(isset($error)) {echo $error;}?></font></p>
|
||||
</form>
|
||||
|
|
|
|||
Loading…
Reference in a new issue