[Web] add verify selected tfa

This commit is contained in:
FreddleSpl0it 2022-02-21 14:10:12 +01:00
parent 3ef2b6cfa2
commit 5fcccbc97d
No known key found for this signature in database
GPG Key ID: F1B3BE8A3BBA3451
3 changed files with 52 additions and 38 deletions

View File

@ -1493,7 +1493,7 @@ function unset_tfa_key($_data) {
return false; return false;
} }
} }
function get_tfa($username = null, $key_id = null) { function get_tfa($username = null, $id = null) {
global $pdo; global $pdo;
if (isset($_SESSION['mailcow_cc_username'])) { if (isset($_SESSION['mailcow_cc_username'])) {
$username = $_SESSION['mailcow_cc_username']; $username = $_SESSION['mailcow_cc_username'];
@ -1502,7 +1502,7 @@ function get_tfa($username = null, $key_id = null) {
return false; return false;
} }
if (!isset($key_id)){ if (!isset($id)){
// fetch all tfa methods - just get information about possible authenticators // fetch all tfa methods - just get information about possible authenticators
$stmt = $pdo->prepare("SELECT `id`, `key_id`, `authmech` FROM `tfa` $stmt = $pdo->prepare("SELECT `id`, `key_id`, `authmech` FROM `tfa`
WHERE `username` = :username AND `active` = '1'"); WHERE `username` = :username AND `active` = '1'");
@ -1520,10 +1520,10 @@ function get_tfa($username = null, $key_id = null) {
$data['additional'] = $results; $data['additional'] = $results;
return $data; return $data;
} else { } else {
// fetch specific authenticator details by key_id // fetch specific authenticator details by id
$stmt = $pdo->prepare("SELECT * FROM `tfa` $stmt = $pdo->prepare("SELECT * FROM `tfa`
WHERE `username` = :username AND `active` = '1'"); WHERE `username` = :username AND `id` = :id AND `active` = '1'");
$stmt->execute(array(':username' => $username)); $stmt->execute(array(':username' => $username, ':id' => $id));
$row = $stmt->fetch(PDO::FETCH_ASSOC); $row = $stmt->fetch(PDO::FETCH_ASSOC);
if (isset($row["authmech"])) { if (isset($row["authmech"])) {
@ -1531,9 +1531,10 @@ function get_tfa($username = null, $key_id = null) {
case "yubi_otp": case "yubi_otp":
$data['name'] = "yubi_otp"; $data['name'] = "yubi_otp";
$data['pretty'] = "Yubico OTP"; $data['pretty'] = "Yubico OTP";
$stmt = $pdo->prepare("SELECT `id`, `key_id`, RIGHT(`secret`, 12) AS 'modhex' FROM `tfa` WHERE `authmech` = 'yubi_otp' AND `username` = :username"); $stmt = $pdo->prepare("SELECT `id`, `key_id`, RIGHT(`secret`, 12) AS 'modhex' FROM `tfa` WHERE `authmech` = 'yubi_otp' AND `username` = :username AND `id` = :id");
$stmt->execute(array( $stmt->execute(array(
':username' => $username, ':username' => $username,
':id' => $id
)); ));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) { while($row = array_shift($rows)) {
@ -1545,9 +1546,10 @@ function get_tfa($username = null, $key_id = null) {
case "u2f": case "u2f":
$data['name'] = "u2f"; $data['name'] = "u2f";
$data['pretty'] = "Fido U2F"; $data['pretty'] = "Fido U2F";
$stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username"); $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username AND `id` = :id");
$stmt->execute(array( $stmt->execute(array(
':username' => $username, ':username' => $username,
':id' => $id
)); ));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) { while($row = array_shift($rows)) {
@ -1563,9 +1565,10 @@ function get_tfa($username = null, $key_id = null) {
case "totp": case "totp":
$data['name'] = "totp"; $data['name'] = "totp";
$data['pretty'] = "Time-based OTP"; $data['pretty'] = "Time-based OTP";
$stmt = $pdo->prepare("SELECT `id`, `key_id`, `secret` FROM `tfa` WHERE `authmech` = 'totp' AND `username` = :username"); $stmt = $pdo->prepare("SELECT `id`, `key_id`, `secret` FROM `tfa` WHERE `authmech` = 'totp' AND `username` = :username AND `id` = :id");
$stmt->execute(array( $stmt->execute(array(
':username' => $username, ':username' => $username,
':id' => $id
)); ));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) { while($row = array_shift($rows)) {
@ -1576,9 +1579,10 @@ function get_tfa($username = null, $key_id = null) {
case "webauthn": case "webauthn":
$data['name'] = "webauthn"; $data['name'] = "webauthn";
$data['pretty'] = "WebAuthn"; $data['pretty'] = "WebAuthn";
$stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'webauthn' AND `username` = :username"); $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'webauthn' AND `username` = :username AND `id` = :id");
$stmt->execute(array( $stmt->execute(array(
':username' => $username, ':username' => $username,
':id' => $id
)); ));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) { while($row = array_shift($rows)) {
@ -1609,8 +1613,8 @@ function verify_tfa_login($username, $_data) {
if ($_data['tfa_method'] != 'u2f'){ if ($_data['tfa_method'] != 'u2f'){
$stmt = $pdo->prepare("SELECT `authmech` FROM `tfa` $stmt = $pdo->prepare("SELECT `authmech` FROM `tfa`
WHERE `username` = :username AND `key_id` = :key_id AND `active` = '1'"); WHERE `username` = :username AND `id` = :id AND `active` = '1'");
$stmt->execute(array(':username' => $username, ':key_id' => $_data['key_id'])); $stmt->execute(array(':username' => $username, ':id' => $_data['id']));
$row = $stmt->fetch(PDO::FETCH_ASSOC); $row = $stmt->fetch(PDO::FETCH_ASSOC);
switch ($row["authmech"]) { switch ($row["authmech"]) {
@ -1627,10 +1631,10 @@ function verify_tfa_login($username, $_data) {
$stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa` $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa`
WHERE `username` = :username WHERE `username` = :username
AND `authmech` = 'yubi_otp' AND `authmech` = 'yubi_otp'
AND `key_id` = ':key_id' AND `id` = ':id'
AND `active`='1' AND `active`='1'
AND `secret` LIKE :modhex"); AND `secret` LIKE :modhex");
$stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id, ':key_id' => $_data['key_id'])); $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id, ':id' => $_data['id']));
$row = $stmt->fetch(PDO::FETCH_ASSOC); $row = $stmt->fetch(PDO::FETCH_ASSOC);
$yubico_auth = explode(':', $row['secret']); $yubico_auth = explode(':', $row['secret']);
$yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]); $yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]);
@ -1663,16 +1667,16 @@ function verify_tfa_login($username, $_data) {
return false; return false;
break; break;
case "totp": case "totp":
try { try {
$stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa` $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa`
WHERE `username` = :username WHERE `username` = :username
AND `authmech` = 'totp' AND `authmech` = 'totp'
AND `key_id` = :key_id AND `id` = :id
AND `active`='1'"); AND `active`='1'");
$stmt->execute(array(':username' => $username, ':key_id' => $_data['key_id'])); $stmt->execute(array(':username' => $username, ':id' => $_data['id']));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) { foreach ($rows as $row) {
if ($tfa->verifyCode($row['secret'], $_data['token']) === true) { if ($tfa->verifyCode($row['secret'], $_data['token']) === true) {
$_SESSION['tfa_id'] = $row['id']; $_SESSION['tfa_id'] = $row['id'];
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
@ -1680,7 +1684,7 @@ function verify_tfa_login($username, $_data) {
'msg' => 'verified_totp_login' 'msg' => 'verified_totp_login'
); );
return true; return true;
} }
} }
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
@ -1688,15 +1692,15 @@ function verify_tfa_login($username, $_data) {
'msg' => 'totp_verification_failed' 'msg' => 'totp_verification_failed'
); );
return false; return false;
} }
catch (PDOException $e) { catch (PDOException $e) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'), 'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('mysql_error', $e) 'msg' => array('mysql_error', $e)
); );
return false; return false;
} }
break; break;
case "webauthn": case "webauthn":
$tokenData = json_decode($_data['token']); $tokenData = json_decode($_data['token']);
@ -1706,13 +1710,20 @@ function verify_tfa_login($username, $_data) {
$id = base64_decode($tokenData->id); $id = base64_decode($tokenData->id);
$challenge = $_SESSION['challenge']; $challenge = $_SESSION['challenge'];
$stmt = $pdo->prepare("SELECT `id`, `key_id`, `keyHandle`, `username`, `publicKey` FROM `tfa` WHERE `keyHandle` = :tokenId"); $stmt = $pdo->prepare("SELECT `id`, `key_id`, `keyHandle`, `username`, `publicKey` FROM `tfa` WHERE `id` = :id");
$stmt->execute(array(':tokenId' => $tokenData->id)); $stmt->execute(array(':id' => $_data['id']));
$process_webauthn = $stmt->fetch(PDO::FETCH_ASSOC); $process_webauthn = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($process_webauthn) || empty($process_webauthn['publicKey']) || empty($process_webauthn['username'])) return false; if (empty($process_webauthn)){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('webauthn_verification_failed', 'authenticator not found')
);
return false;
}
if ($process_webauthn['publicKey'] === false) { if (empty($process_webauthn['publicKey']) || $process_webauthn['publicKey'] === false) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'), 'log' => array(__FUNCTION__, $username, '*'),

View File

@ -194,8 +194,8 @@ function recursiveBase64StrToArrayBuffer(obj) {
$(".yubi-authenticator-selection").removeClass("active"); $(".yubi-authenticator-selection").removeClass("active");
$(this).addClass("active"); $(this).addClass("active");
var key_id = $(this).children('span').first().text(); var id = $(this).children('input').first().val();
$("#yubi_selected_key_id").val(key_id); $("#yubi_selected_id").val(id);
$("#collapseYubiTFA").collapse('show'); $("#collapseYubiTFA").collapse('show');
}); });
@ -211,8 +211,8 @@ function recursiveBase64StrToArrayBuffer(obj) {
$(".totp-authenticator-selection").removeClass("active"); $(".totp-authenticator-selection").removeClass("active");
$(this).addClass("active"); $(this).addClass("active");
var key_id = $(this).children('span').first().text(); var id = $(this).children('input').first().val();
$("#totp_selected_key_id").val(key_id); $("#totp_selected_id").val(id);
$("#collapseTotpTFA").collapse('show'); $("#collapseTotpTFA").collapse('show');
}); });
@ -228,8 +228,8 @@ function recursiveBase64StrToArrayBuffer(obj) {
$(".webauthn-authenticator-selection").removeClass("active"); $(".webauthn-authenticator-selection").removeClass("active");
$(this).addClass("active"); $(this).addClass("active");
var key_id = $(this).children('span').first().text(); var id = $(this).children('input').first().val();
$("#webauthn_selected_key_id").val(key_id); $("#webauthn_selected_id").val(id);
$("#collapseWebAuthnTFA").collapse('show'); $("#collapseWebAuthnTFA").collapse('show');

View File

@ -139,7 +139,7 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span></button> <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span></button>
<h3 class="modal-title">2-Factor-Authentication</h3> <h3 class="modal-title">{{ lang.tfa.tfa }}</h3>
</div> </div>
<ul class="nav nav-tabs" id="tabContent"> <ul class="nav nav-tabs" id="tabContent">
@ -173,7 +173,7 @@
<form role="form" method="post" id="webauthn_auth_form"> <form role="form" method="post" id="webauthn_auth_form">
<legend> <legend>
<i class="bi bi-shield-fill-check"></i> <i class="bi bi-shield-fill-check"></i>
Available Authenticators Authenticators
</legend> </legend>
<div class="list-group"> <div class="list-group">
{% for authenticator in pending_tfa_methods %} {% for authenticator in pending_tfa_methods %}
@ -181,6 +181,7 @@
<a href="#" class="list-group-item webauthn-authenticator-selection"> <a href="#" class="list-group-item webauthn-authenticator-selection">
<i class="bi bi-key-fill" style="margin-right: 5px"></i> <i class="bi bi-key-fill" style="margin-right: 5px"></i>
<span>{{ authenticator["key_id"] }}</span> <span>{{ authenticator["key_id"] }}</span>
<input type="hidden" value="{{ authenticator["id"] }}" /><br/>
</a> </a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
@ -192,7 +193,7 @@
<input type="hidden" name="token" id="webauthn_auth_data"/> <input type="hidden" name="token" id="webauthn_auth_data"/>
<input type="hidden" name="tfa_method" value="webauthn"> <input type="hidden" name="tfa_method" value="webauthn">
<input type="hidden" name="verify_tfa_login"/><br/> <input type="hidden" name="verify_tfa_login"/><br/>
<input type="hidden" name="key_id" id="webauthn_selected_key_id" /><br/> <input type="hidden" name="id" id="webauthn_selected_id" /><br/>
</form> </form>
</div> </div>
</div> </div>
@ -205,7 +206,7 @@
<form role="form" method="post"> <form role="form" method="post">
<legend> <legend>
<i class="bi bi-shield-fill-check"></i> <i class="bi bi-shield-fill-check"></i>
Available Authenticators Authenticators
</legend> </legend>
<div class="list-group"> <div class="list-group">
{% for authenticator in pending_tfa_methods %} {% for authenticator in pending_tfa_methods %}
@ -213,6 +214,7 @@
<a href="#" class="list-group-item yubi-authenticator-selection"> <a href="#" class="list-group-item yubi-authenticator-selection">
<i class="bi bi-key-fill" style="margin-right: 5px"></i> <i class="bi bi-key-fill" style="margin-right: 5px"></i>
<span>{{ authenticator["key_id"] }}</span> <span>{{ authenticator["key_id"] }}</span>
<input type="hidden" value="{{ authenticator["id"] }}" />
</a> </a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
@ -223,7 +225,7 @@
<span class="input-group-addon" id="yubi-addon"><img alt="Yubicon Icon" src="/img/yubi.ico"></span> <span class="input-group-addon" id="yubi-addon"><img alt="Yubicon Icon" src="/img/yubi.ico"></span>
<input type="text" name="token" class="form-control" autocomplete="off" placeholder="Touch Yubikey" aria-describedby="yubi-addon"> <input type="text" name="token" class="form-control" autocomplete="off" placeholder="Touch Yubikey" aria-describedby="yubi-addon">
<input type="hidden" name="tfa_method" value="yubi_otp"> <input type="hidden" name="tfa_method" value="yubi_otp">
<input type="hidden" name="key_id" id="yubi_selected_key_id" /> <input type="hidden" name="id" id="yubi_selected_id" />
</div> </div>
</div> </div>
<button class="btn btn-sm visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline btn-sm btn-default" type="submit" name="verify_tfa_login">{{ lang.login.login }}</button> <button class="btn btn-sm visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline btn-sm btn-default" type="submit" name="verify_tfa_login">{{ lang.login.login }}</button>
@ -240,7 +242,7 @@
<form role="form" method="post"> <form role="form" method="post">
<legend> <legend>
<i class="bi bi-shield-fill-check"></i> <i class="bi bi-shield-fill-check"></i>
Available Authenticators Authenticators
</legend> </legend>
<div class="list-group"> <div class="list-group">
{% for authenticator in pending_tfa_methods %} {% for authenticator in pending_tfa_methods %}
@ -248,6 +250,7 @@
<a href="#" class="list-group-item totp-authenticator-selection"> <a href="#" class="list-group-item totp-authenticator-selection">
<i class="bi bi-key-fill" style="margin-right: 5px"></i> <i class="bi bi-key-fill" style="margin-right: 5px"></i>
<span>{{ authenticator["key_id"] }}</span> <span>{{ authenticator["key_id"] }}</span>
<input type="hidden" value="{{ authenticator["id"] }}" />
</a> </a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
@ -258,7 +261,7 @@
<span class="input-group-addon" id="tfa-addon"><i class="bi bi-shield-lock-fill"></i></span> <span class="input-group-addon" id="tfa-addon"><i class="bi bi-shield-lock-fill"></i></span>
<input type="number" min="000000" max="999999" name="token" class="form-control" placeholder="123456" autocomplete="one-time-code" aria-describedby="tfa-addon"> <input type="number" min="000000" max="999999" name="token" class="form-control" placeholder="123456" autocomplete="one-time-code" aria-describedby="tfa-addon">
<input type="hidden" name="tfa_method" value="totp"> <input type="hidden" name="tfa_method" value="totp">
<input type="hidden" name="key_id" id="totp_selected_key_id" /><br/> <input type="hidden" name="id" id="totp_selected_id" /><br/>
</div> </div>
</div> </div>
<button class="btn btn-sm visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline btn-default" type="submit" name="verify_tfa_login">{{ lang.login.login }}</button> <button class="btn btn-sm visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline btn-default" type="submit" name="verify_tfa_login">{{ lang.login.login }}</button>