Merge pull request #4428 from FreddleSpl0it/master

Migrating from U2F to WebAuthn for 2FA
master
Niklas Meyer 2022-01-21 12:19:25 +01:00 committed by GitHub
commit 355ea71877
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 854 additions and 528 deletions

View File

@ -17,7 +17,8 @@ if (is_array($alertbox_log_parser)) {
}
$alert = array_filter(array_unique($alerts));
foreach($alert as $alert_type => $alert_msg) {
$alerts[$alert_type] = implode('<hr class="alert-hr">', $alert_msg);
// html breaks from mysql alerts, replace ` with '
$alerts[$alert_type] = implode('<hr class="alert-hr">', str_replace("`", "'", $alert_msg));
}
unset($_SESSION['return']);
}

View File

@ -1140,7 +1140,6 @@ function is_valid_domain_name($domain_name) {
function set_tfa($_data) {
global $pdo;
global $yubi;
global $u2f;
global $tfa;
$_data_log = $_data;
!isset($_data_log['confirm_password']) ?: $_data_log['confirm_password'] = '*';
@ -1183,6 +1182,8 @@ function set_tfa($_data) {
return false;
}
}
switch ($_data["tfa_method"]) {
case "yubi_otp":
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
@ -1240,31 +1241,6 @@ function set_tfa($_data) {
'msg' => array('object_modified', htmlspecialchars($username))
);
break;
case "u2f":
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
try {
$reg = $u2f->doRegister(json_decode($_SESSION['regReq']), json_decode($_data['token']));
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `authmech` != 'u2f'");
$stmt->execute(array(':username' => $username));
$stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`) VALUES (?, ?, 'u2f', ?, ?, ?, ?, '1')");
$stmt->execute(array($username, $key_id, $reg->keyHandle, $reg->publicKey, $reg->certificate, $reg->counter));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('object_modified', $username)
);
$_SESSION['regReq'] = null;
}
catch (Exception $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('u2f_verification_failed', $e->getMessage())
);
$_SESSION['regReq'] = null;
return false;
}
break;
case "totp":
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
if ($tfa->verifyCode($_POST['totp_secret'], $_POST['totp_confirm_token']) === true) {
@ -1286,6 +1262,29 @@ function set_tfa($_data) {
);
}
break;
case "webauthn":
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `authmech` != 'webauthn'");
$stmt->execute(array(':username' => $username));
$stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`)
VALUES (?, ?, 'webauthn', ?, ?, ?, ?, '1')");
$stmt->execute(array(
$username,
$key_id,
base64_encode($_data['registration']->credentialId),
$_data['registration']->credentialPublicKey,
$_data['registration']->certificate,
0
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('object_modified', $username)
);
break;
case "none":
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
@ -1360,8 +1359,8 @@ function fido2($_data) {
if (!isset($_data['cid']) || empty($_data['cid'])) {
return false;
}
$stmt = $pdo->prepare("SELECT `certificateSubject`, `username`, `credentialPublicKey`, SHA2(`credentialId`, 256) AS `cid` FROM `fido2` WHERE TO_BASE64(`credentialId`) = :cid");
$stmt->execute(array(':cid' => $_data['cid']));
$stmt = $pdo->prepare("SELECT `certificateSubject`, `username`, `credentialPublicKey`, SHA2(`credentialId`, 256) AS `cid` FROM `fido2` WHERE `credentialId` = :cid");
$stmt->execute(array(':cid' => base64_decode($_data['cid'])));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row) || empty($row['credentialPublicKey']) || empty($row['username'])) {
return false;
@ -1516,6 +1515,7 @@ function get_tfa($username = null) {
}
return $data;
break;
// u2f - deprecated, should be removed
case "u2f":
$data['name'] = "u2f";
$data['pretty'] = "Fido U2F";
@ -1534,7 +1534,7 @@ function get_tfa($username = null) {
$data['pretty'] = "HMAC-based OTP";
return $data;
break;
case "totp":
case "totp":
$data['name'] = "totp";
$data['pretty'] = "Time-based OTP";
$stmt = $pdo->prepare("SELECT `id`, `key_id`, `secret` FROM `tfa` WHERE `authmech` = 'totp' AND `username` = :username");
@ -1546,7 +1546,20 @@ function get_tfa($username = null) {
$data['additional'][] = $row;
}
return $data;
break;
break;
case "webauthn":
$data['name'] = "webauthn";
$data['pretty'] = "WebAuthn";
$stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'webauthn' AND `username` = :username");
$stmt->execute(array(
':username' => $username,
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$data['additional'][] = $row;
}
return $data;
break;
default:
$data['name'] = 'none';
$data['pretty'] = "-";
@ -1560,140 +1573,192 @@ function get_tfa($username = null) {
return $data;
}
}
function verify_tfa_login($username, $token) {
global $pdo;
global $yubi;
global $u2f;
global $tfa;
$stmt = $pdo->prepare("SELECT `authmech` FROM `tfa`
WHERE `username` = :username AND `active` = '1'");
$stmt->execute(array(':username' => $username));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
function verify_tfa_login($username, $_data, $WebAuthn) {
global $pdo;
global $yubi;
global $u2f;
global $tfa;
$stmt = $pdo->prepare("SELECT `authmech` FROM `tfa`
WHERE `username` = :username AND `active` = '1'");
$stmt->execute(array(':username' => $username));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
switch ($row["authmech"]) {
case "yubi_otp":
if (!ctype_alnum($token) || strlen($token) != 44) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('yotp_verification_failed', 'token length error')
);
return false;
}
$yubico_modhex_id = substr($token, 0, 12);
$stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa`
WHERE `username` = :username
AND `authmech` = 'yubi_otp'
AND `active`='1'
AND `secret` LIKE :modhex");
$stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$yubico_auth = explode(':', $row['secret']);
$yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]);
$yauth = $yubi->verify($token);
if (PEAR::isError($yauth)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('yotp_verification_failed', $yauth->getMessage())
);
return false;
}
else {
$_SESSION['tfa_id'] = $row['id'];
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => 'verified_yotp_login'
);
return true;
}
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('yotp_verification_failed', 'unknown')
);
return false;
break;
case "u2f":
try {
$reg = $u2f->doAuthenticate(json_decode($_SESSION['authReq']), get_u2f_registrations($username), json_decode($token));
$stmt = $pdo->prepare("SELECT `id` FROM `tfa` WHERE `keyHandle` = ?");
$stmt->execute(array($reg->keyHandle));
$row_key_id = $stmt->fetch(PDO::FETCH_ASSOC);
$_SESSION['tfa_id'] = $row_key_id['id'];
$_SESSION['authReq'] = null;
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => 'verified_u2f_login'
);
return true;
}
catch (Exception $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('u2f_verification_failed', $e->getMessage())
);
$_SESSION['regReq'] = null;
return false;
}
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('u2f_verification_failed', 'unknown')
);
return false;
break;
case "hotp":
return false;
break;
case "totp":
try {
$stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa`
WHERE `username` = :username
AND `authmech` = 'totp'
AND `active`='1'");
$stmt->execute(array(':username' => $username));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
if ($tfa->verifyCode($row['secret'], $_POST['token']) === true) {
$_SESSION['tfa_id'] = $row['id'];
$_SESSION['return'][] = array(
'type' => 'success',
switch ($row["authmech"]) {
case "yubi_otp":
if (!ctype_alnum($_data['token']) || strlen($_data['token']) != 44) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('yotp_verification_failed', 'token length error')
);
return false;
}
$yubico_modhex_id = substr($_data['token'], 0, 12);
$stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa`
WHERE `username` = :username
AND `authmech` = 'yubi_otp'
AND `active`='1'
AND `secret` LIKE :modhex");
$stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$yubico_auth = explode(':', $row['secret']);
$yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]);
$yauth = $yubi->verify($_data['token']);
if (PEAR::isError($yauth)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('yotp_verification_failed', $yauth->getMessage())
);
return false;
}
else {
$_SESSION['tfa_id'] = $row['id'];
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => 'verified_yotp_login'
);
return true;
}
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('yotp_verification_failed', 'unknown')
);
return false;
break;
case "hotp":
return false;
break;
case "totp":
try {
$stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa`
WHERE `username` = :username
AND `authmech` = 'totp'
AND `active`='1'");
$stmt->execute(array(':username' => $username));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
if ($tfa->verifyCode($row['secret'], $_data['token']) === true) {
$_SESSION['tfa_id'] = $row['id'];
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => 'verified_totp_login'
);
return true;
}
}
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => 'totp_verification_failed'
);
return false;
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('mysql_error', $e)
);
return false;
}
break;
// u2f - deprecated, should be removed
case "u2f":
// delete old keys that used u2f
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `authmech` = :authmech AND `username` = :username");
$stmt->execute(array(':authmech' => 'u2f', ':username' => $username));
return true;
case "webauthn":
$tokenData = json_decode($_data['token']);
$clientDataJSON = base64_decode($tokenData->clientDataJSON);
$authenticatorData = base64_decode($tokenData->authenticatorData);
$signature = base64_decode($tokenData->signature);
$id = base64_decode($tokenData->id);
$challenge = $_SESSION['challenge'];
$stmt = $pdo->prepare("SELECT `key_id`, `keyHandle`, `username`, `publicKey` FROM `tfa` WHERE `keyHandle` = :tokenId");
$stmt->execute(array(':tokenId' => $tokenData->id));
$process_webauthn = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($process_webauthn) || empty($process_webauthn['publicKey']) || empty($process_webauthn['username'])) return false;
if ($process_webauthn['publicKey'] === false) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('webauthn_verification_failed', 'publicKey not found')
);
return false;
}
try {
$WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $process_webauthn['publicKey'], $challenge, null, $GLOBALS['WEBAUTHN_UV_FLAG_LOGIN'], $GLOBALS['WEBAUTHN_USER_PRESENT_FLAG']);
}
catch (Throwable $ex) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('webauthn_verification_failed', $ex->getMessage())
);
return false;
}
$stmt = $pdo->prepare("SELECT `superadmin` FROM `admin` WHERE `username` = :username");
$stmt->execute(array(':username' => $process_webauthn['username']));
$obj_props = $stmt->fetch(PDO::FETCH_ASSOC);
if ($obj_props['superadmin'] === 1) {
$_SESSION["mailcow_cc_role"] = "admin";
}
elseif ($obj_props['superadmin'] === 0) {
$_SESSION["mailcow_cc_role"] = "domainadmin";
}
else {
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :username");
$stmt->execute(array(':username' => $process_webauthn['username']));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row['username'] == $process_webauthn['username']) {
$_SESSION["mailcow_cc_role"] = "user";
}
}
if ($process_webauthn['username'] != $_SESSION['pending_mailcow_cc_username']){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('webauthn_verification_failed', 'user who requests does not match with sql entry')
);
return false;
}
$_SESSION["mailcow_cc_username"] = $process_webauthn['username'];
$_SESSION['tfa_id'] = $process_webauthn['key_id'];
$_SESSION['authReq'] = null;
unset($_SESSION["challenge"]);
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array("webauthn_login"),
'msg' => array('logged_in_as', $process_webauthn['username'])
);
return true;
break;
default:
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => 'verified_totp_login'
);
return true;
}
}
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => 'totp_verification_failed'
);
return false;
'msg' => 'unknown_tfa_method'
);
return false;
break;
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('mysql_error', $e)
);
return false;
}
break;
default:
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => 'unknown_tfa_method'
);
return false;
break;
}
return false;
}
function admin_api($access, $action, $data = null) {
global $pdo;
@ -1955,12 +2020,7 @@ function rspamd_ui($action, $data = null) {
break;
}
}
function get_u2f_registrations($username) {
global $pdo;
$sel = $pdo->prepare("SELECT * FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = ? AND `active` = '1'");
$sel->execute(array($username));
return $sel->fetchAll(PDO::FETCH_OBJ);
}
function get_logs($application, $lines = false) {
if ($lines === false) {
$lines = $GLOBALS['LOG_LINES'] - 1;

View File

@ -3,7 +3,7 @@ function init_db_schema() {
try {
global $pdo;
$db_version = "31102021_0620";
$db_version = "18012022_1020";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@ -696,7 +696,7 @@ function init_db_schema() {
"id" => "INT NOT NULL AUTO_INCREMENT",
"key_id" => "VARCHAR(255) NOT NULL",
"username" => "VARCHAR(255) NOT NULL",
"authmech" => "ENUM('yubi_otp', 'u2f', 'hotp', 'totp')",
"authmech" => "ENUM('yubi_otp', 'u2f', 'hotp', 'totp', 'webauthn')",
"secret" => "VARCHAR(255) DEFAULT NULL",
"keyHandle" => "VARCHAR(255) DEFAULT NULL",
"publicKey" => "VARCHAR(255) DEFAULT NULL",
@ -1189,6 +1189,9 @@ function init_db_schema() {
// Mitigate imapsync pipemess issue
$pdo->query("UPDATE `imapsync` SET `custom_params` = '' WHERE `custom_params` LIKE '%pipemess%';");
// Migrate webauthn tfa
$stmt = $pdo->query("ALTER TABLE `tfa` MODIFY COLUMN `authmech` ENUM('yubi_otp', 'u2f', 'hotp', 'totp', 'webauthn')");
// Inject admin if not exists
$stmt = $pdo->query("SELECT NULL FROM `admin`");

View File

@ -1,9 +1,9 @@
<?php
namespace WebAuthn\Attestation;
use WebAuthn\WebAuthnException;
use WebAuthn\CBOR\CborDecoder;
use WebAuthn\Binary\ByteBuffer;
namespace lbuchs\WebAuthn\Attestation;
use lbuchs\WebAuthn\WebAuthnException;
use lbuchs\WebAuthn\CBOR\CborDecoder;
use lbuchs\WebAuthn\Binary\ByteBuffer;
/**
* @author Lukas Buchs
@ -12,6 +12,7 @@ use WebAuthn\Binary\ByteBuffer;
class AttestationObject {
private $_authenticatorData;
private $_attestationFormat;
private $_attestationFormatName;
public function __construct($binary , $allowedFormats) {
$enc = CborDecoder::decode($binary);
@ -29,13 +30,15 @@ class AttestationObject {
}
$this->_authenticatorData = new AuthenticatorData($enc['authData']->getBinaryString());
$this->_attestationFormatName = $enc['fmt'];
// Format ok?
if (!in_array($enc['fmt'], $allowedFormats)) {
throw new WebAuthnException('invalid atttestation format: ' . $enc['fmt'], WebAuthnException::INVALID_DATA);
if (!in_array($this->_attestationFormatName, $allowedFormats)) {
throw new WebAuthnException('invalid atttestation format: ' . $this->_attestationFormatName, WebAuthnException::INVALID_DATA);
}
switch ($enc['fmt']) {
switch ($this->_attestationFormatName) {
case 'android-key': $this->_attestationFormat = new Format\AndroidKey($enc, $this->_authenticatorData); break;
case 'android-safetynet': $this->_attestationFormat = new Format\AndroidSafetyNet($enc, $this->_authenticatorData); break;
case 'apple': $this->_attestationFormat = new Format\Apple($enc, $this->_authenticatorData); break;
@ -47,6 +50,14 @@ class AttestationObject {
}
}
/**
* returns the attestation format name
* @return string
*/
public function getAttestationFormatName() {
return $this->_attestationFormatName;
}
/**
* returns the attestation public key in PEM format
* @return AuthenticatorData
@ -72,16 +83,19 @@ class AttestationObject {
$issuer = '';
if ($pem) {
$certInfo = \openssl_x509_parse($pem);
if (\is_array($certInfo) && \is_array($certInfo['issuer'])) {
if ($certInfo['issuer']['CN']) {
$issuer .= \trim($certInfo['issuer']['CN']);
if (\is_array($certInfo) && \array_key_exists('issuer', $certInfo) && \is_array($certInfo['issuer'])) {
$cn = $certInfo['issuer']['CN'] ?? '';
$o = $certInfo['issuer']['O'] ?? '';
$ou = $certInfo['issuer']['OU'] ?? '';
if ($cn) {
$issuer .= $cn;
}
if ($certInfo['issuer']['O'] || $certInfo['issuer']['OU']) {
if ($issuer) {
$issuer .= ' (' . \trim($certInfo['issuer']['O'] . ' ' . $certInfo['issuer']['OU']) . ')';
} else {
$issuer .= \trim($certInfo['issuer']['O'] . ' ' . $certInfo['issuer']['OU']);
}
if ($issuer && ($o || $ou)) {
$issuer .= ' (' . trim($o . ' ' . $ou) . ')';
} else {
$issuer .= trim($o . ' ' . $ou);
}
}
}
@ -98,16 +112,19 @@ class AttestationObject {
$subject = '';
if ($pem) {
$certInfo = \openssl_x509_parse($pem);
if (\is_array($certInfo) && \is_array($certInfo['subject'])) {
if ($certInfo['subject']['CN']) {
$subject .= \trim($certInfo['subject']['CN']);
if (\is_array($certInfo) && \array_key_exists('subject', $certInfo) && \is_array($certInfo['subject'])) {
$cn = $certInfo['subject']['CN'] ?? '';
$o = $certInfo['subject']['O'] ?? '';
$ou = $certInfo['subject']['OU'] ?? '';
if ($cn) {
$subject .= $cn;
}
if ($certInfo['subject']['O'] || $certInfo['subject']['OU']) {
if ($subject) {
$subject .= ' (' . \trim($certInfo['subject']['O'] . ' ' . $certInfo['subject']['OU']) . ')';
} else {
$subject .= \trim($certInfo['subject']['O'] . ' ' . $certInfo['subject']['OU']);
}
if ($subject && ($o || $ou)) {
$subject .= ' (' . trim($o . ' ' . $ou) . ')';
} else {
$subject .= trim($o . ' ' . $ou);
}
}
}

View File

@ -1,9 +1,9 @@
<?php
namespace WebAuthn\Attestation;
use WebAuthn\WebAuthnException;
use WebAuthn\CBOR\CborDecoder;
use WebAuthn\Binary\ByteBuffer;
namespace lbuchs\WebAuthn\Attestation;
use lbuchs\WebAuthn\WebAuthnException;
use lbuchs\WebAuthn\CBOR\CborDecoder;
use lbuchs\WebAuthn\Binary\ByteBuffer;
/**
* @author Lukas Buchs

View File

@ -1,15 +1,16 @@
<?php
namespace WebAuthn\Attestation\Format;
use WebAuthn\WebAuthnException;
use WebAuthn\Binary\ByteBuffer;
namespace lbuchs\WebAuthn\Attestation\Format;
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
use lbuchs\WebAuthn\WebAuthnException;
use lbuchs\WebAuthn\Binary\ByteBuffer;
class AndroidKey extends FormatBase {
private $_alg;
private $_signature;
private $_x5c;
public function __construct($AttestionObject, \WebAuthn\Attestation\AuthenticatorData $authenticatorData) {
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
parent::__construct($AttestionObject, $authenticatorData);
// check u2f data

View File

@ -1,9 +1,10 @@
<?php
namespace WebAuthn\Attestation\Format;
use WebAuthn\WebAuthnException;
use WebAuthn\Binary\ByteBuffer;
namespace lbuchs\WebAuthn\Attestation\Format;
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
use lbuchs\WebAuthn\WebAuthnException;
use lbuchs\WebAuthn\Binary\ByteBuffer;
class AndroidSafetyNet extends FormatBase {
private $_signature;
@ -11,7 +12,7 @@ class AndroidSafetyNet extends FormatBase {
private $_x5c;
private $_payload;
public function __construct($AttestionObject, \WebAuthn\Attestation\AuthenticatorData $authenticatorData) {
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
parent::__construct($AttestionObject, $authenticatorData);
// check data

View File

@ -1,14 +1,15 @@
<?php
namespace WebAuthn\Attestation\Format;
use WebAuthn\WebAuthnException;
use WebAuthn\Binary\ByteBuffer;
namespace lbuchs\WebAuthn\Attestation\Format;
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
use lbuchs\WebAuthn\WebAuthnException;
use lbuchs\WebAuthn\Binary\ByteBuffer;
class Apple extends FormatBase {
private $_x5c;
public function __construct($AttestionObject, \WebAuthn\Attestation\AuthenticatorData $authenticatorData) {
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
parent::__construct($AttestionObject, $authenticatorData);
// check packed data

View File

@ -1,8 +1,9 @@
<?php
namespace WebAuthn\Attestation\Format;
use WebAuthn\WebAuthnException;
namespace lbuchs\WebAuthn\Attestation\Format;
use lbuchs\WebAuthn\WebAuthnException;
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
abstract class FormatBase {
@ -14,9 +15,9 @@ abstract class FormatBase {
/**
*
* @param Array $AttestionObject
* @param \WebAuthn\Attestation\AuthenticatorData $authenticatorData
* @param AuthenticatorData $authenticatorData
*/
public function __construct($AttestionObject, \WebAuthn\Attestation\AuthenticatorData $authenticatorData) {
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
$this->_attestationObject = $AttestionObject;
$this->_authenticatorData = $authenticatorData;
}
@ -26,7 +27,7 @@ abstract class FormatBase {
*/
public function __destruct() {
// delete X.509 chain certificate file after use
if (\is_file($this->_x5c_tempFile)) {
if ($this->_x5c_tempFile && \is_file($this->_x5c_tempFile)) {
\unlink($this->_x5c_tempFile);
}
}
@ -36,7 +37,7 @@ abstract class FormatBase {
* @return string|null
*/
public function getCertificateChain() {
if (\is_file($this->_x5c_tempFile)) {
if ($this->_x5c_tempFile && \is_file($this->_x5c_tempFile)) {
return \file_get_contents($this->_x5c_tempFile);
}
return null;

View File

@ -1,13 +1,14 @@
<?php
namespace WebAuthn\Attestation\Format;
use WebAuthn\WebAuthnException;
namespace lbuchs\WebAuthn\Attestation\Format;
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
use lbuchs\WebAuthn\WebAuthnException;
class None extends FormatBase {
public function __construct($AttestionObject, \WebAuthn\Attestation\AuthenticatorData $authenticatorData) {
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
parent::__construct($AttestionObject, $authenticatorData);
}
@ -28,12 +29,13 @@ class None extends FormatBase {
}
/**
* validates the certificate against root certificates
* validates the certificate against root certificates.
* Format 'none' does not contain any ca, so always false.
* @param array $rootCas
* @return boolean
* @throws WebAuthnException
*/
public function validateRootCertificate($rootCas) {
return true;
return false;
}
}

View File

@ -1,16 +1,17 @@
<?php
namespace WebAuthn\Attestation\Format;
use WebAuthn\WebAuthnException;
use WebAuthn\Binary\ByteBuffer;
namespace lbuchs\WebAuthn\Attestation\Format;
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
use lbuchs\WebAuthn\WebAuthnException;
use lbuchs\WebAuthn\Binary\ByteBuffer;
class Packed extends FormatBase {
private $_alg;
private $_signature;
private $_x5c;
public function __construct($AttestionObject, \WebAuthn\Attestation\AuthenticatorData $authenticatorData) {
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
parent::__construct($AttestionObject, $authenticatorData);
// check packed data

View File

@ -1,9 +1,10 @@
<?php
namespace WebAuthn\Attestation\Format;
use WebAuthn\WebAuthnException;
use WebAuthn\Binary\ByteBuffer;
namespace lbuchs\WebAuthn\Attestation\Format;
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
use lbuchs\WebAuthn\WebAuthnException;
use lbuchs\WebAuthn\Binary\ByteBuffer;
class Tpm extends FormatBase {
private $_TPM_GENERATED_VALUE = "\xFF\x54\x43\x47";
@ -19,7 +20,7 @@ class Tpm extends FormatBase {
private $_certInfo;
public function __construct($AttestionObject, \WebAuthn\Attestation\AuthenticatorData $authenticatorData) {
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
parent::__construct($AttestionObject, $authenticatorData);
// check packed data

View File

@ -1,9 +1,10 @@
<?php
namespace WebAuthn\Attestation\Format;
use WebAuthn\WebAuthnException;
use WebAuthn\Binary\ByteBuffer;
namespace lbuchs\WebAuthn\Attestation\Format;
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
use lbuchs\WebAuthn\WebAuthnException;
use lbuchs\WebAuthn\Binary\ByteBuffer;
class U2f extends FormatBase {
private $_alg = -7;

View File

@ -1,8 +1,8 @@
<?php
namespace WebAuthn\Binary;
use WebAuthn\WebAuthnException;
namespace lbuchs\WebAuthn\Binary;
use lbuchs\WebAuthn\WebAuthnException;
/**
* Modified version of https://github.com/madwizard-thomas/webauthn-server/blob/master/src/Format/ByteBuffer.php
@ -39,7 +39,7 @@ class ByteBuffer implements \JsonSerializable, \Serializable {
/**
* create a ByteBuffer from a base64 url encoded string
* @param string $base64url
* @return \WebAuthn\Binary\ByteBuffer
* @return ByteBuffer
*/
public static function fromBase64Url($base64url) {
$bin = self::_base64url_decode($base64url);
@ -52,7 +52,7 @@ class ByteBuffer implements \JsonSerializable, \Serializable {
/**
* create a ByteBuffer from a base64 url encoded string
* @param string $hex
* @return \WebAuthn\Binary\ByteBuffer
* @return ByteBuffer
*/
public static function fromHex($hex) {
$bin = \hex2bin($hex);
@ -65,7 +65,7 @@ class ByteBuffer implements \JsonSerializable, \Serializable {
/**
* create a random ByteBuffer
* @param string $length
* @return \WebAuthn\Binary\ByteBuffer
* @return ByteBuffer
*/
public static function randomBuffer($length) {
if (\function_exists('random_bytes')) { // >PHP 7.0
@ -97,6 +97,14 @@ class ByteBuffer implements \JsonSerializable, \Serializable {
return \ord(\substr($this->_data, $offset, 1));
}
public function getJson($jsonFlags=0) {
$data = \json_decode($this->getBinaryString(), null, 512, $jsonFlags);
if (\json_last_error() !== JSON_ERROR_NONE) {
throw new WebAuthnException(\json_last_error_msg(), WebAuthnException::BYTEBUFFER);
}
return $data;
}
public function getLength() {
return $this->_length;
}
@ -203,7 +211,7 @@ class ByteBuffer implements \JsonSerializable, \Serializable {
/**
* jsonSerialize interface
* return binary data in RFC 1342-Like serialized string
* @return \stdClass
* @return string
*/
public function jsonSerialize() {
if (ByteBuffer::$useBase64UrlEncoding) {
@ -231,6 +239,36 @@ class ByteBuffer implements \JsonSerializable, \Serializable {
$this->_length = \strlen($this->_data);
}
/**
* (PHP 8 deprecates Serializable-Interface)
* @return array
*/
public function __serialize() {
return [
'data' => \serialize($this->_data)
];
}
/**
* object to string
* @return string
*/
public function __toString() {
return $this->getHex();
}
/**
* (PHP 8 deprecates Serializable-Interface)
* @param array $data
* @return void
*/
public function __unserialize($data) {
if ($data && isset($data['data'])) {
$this->_data = \unserialize($data['data']);
$this->_length = \strlen($this->_data);
}
}
// -----------------------
// PROTECTED STATIC
// -----------------------

View File

@ -1,9 +1,9 @@
<?php
namespace WebAuthn\CBOR;
use WebAuthn\WebAuthnException;
use WebAuthn\Binary\ByteBuffer;
namespace lbuchs\WebAuthn\CBOR;
use lbuchs\WebAuthn\WebAuthnException;
use lbuchs\WebAuthn\Binary\ByteBuffer;
/**
* Modified version of https://github.com/madwizard-thomas/webauthn-server/blob/master/src/Format/CborDecoder.php

View File

@ -1,22 +0,0 @@
MIT License
Copyright © 2019 Lukas Buchs
Copyright © 2018 Thomas Bleeker (CBOR & ByteBuffer part)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,7 +1,7 @@
<?php
namespace WebAuthn;
use WebAuthn\Binary\ByteBuffer;
namespace lbuchs\WebAuthn;
use lbuchs\WebAuthn\Binary\ByteBuffer;
require_once 'WebAuthnException.php';
require_once 'Binary/ByteBuffer.php';
require_once 'Attestation/AttestationObject.php';
@ -69,16 +69,20 @@ class WebAuthn {
/**
* add a root certificate to verify new registrations
* @param string $path file path of / directory with root certificates
* @param array|null $certFileExtensions if adding a direction, all files with provided extension are added. default: pem, crt, cer, der
*/
public function addRootCertificates($path) {
public function addRootCertificates($path, $certFileExtensions=null) {
if (!\is_array($this->_caFiles)) {
$this->_caFiles = array();
}
if ($certFileExtensions === null) {
$certFileExtensions = array('pem', 'crt', 'cer', 'der');
}
$path = \rtrim(\trim($path), '\\/');
if (\is_dir($path)) {
foreach (\scandir($path) as $ca) {
if (\is_file($path . '/' . $ca)) {
$this->addRootCertificates($path . '/' . $ca);
if (\is_file($path . DIRECTORY_SEPARATOR . $ca) && \in_array(\strtolower(\pathinfo($ca, PATHINFO_EXTENSION)), $certFileExtensions)) {
$this->addRootCertificates($path . DIRECTORY_SEPARATOR . $ca);
}
}
} else if (\is_file($path) && !\in_array(\realpath($path), $this->_caFiles)) {
@ -273,10 +277,11 @@ class WebAuthn {
* @param string|ByteBuffer $challenge binary used challange
* @param bool $requireUserVerification true, if the device must verify user (e.g. by biometric data or pin)
* @param bool $requireUserPresent false, if the device must NOT check user presence (e.g. by pressing a button)
* @param bool $failIfRootMismatch false, if there should be no error thrown if root certificate doesn't match
* @return \stdClass
* @throws WebAuthnException
*/
public function processCreate($clientDataJSON, $attestationObject, $challenge, $requireUserVerification=false, $requireUserPresent=true) {
public function processCreate($clientDataJSON, $attestationObject, $challenge, $requireUserVerification=false, $requireUserPresent=true, $failIfRootMismatch=true) {
$clientDataHash = \hash('sha256', $clientDataJSON, true);
$clientData = \json_decode($clientDataJSON);
$challenge = $challenge instanceof ByteBuffer ? $challenge : new ByteBuffer($challenge);
@ -318,18 +323,21 @@ class WebAuthn {
}
// 15. If validation is successful, obtain a list of acceptable trust anchors
if (is_array($this->_caFiles) && !$attestationObject->validateRootCertificate($this->_caFiles)) {
$rootValid = is_array($this->_caFiles) ? $attestationObject->validateRootCertificate($this->_caFiles) : null;
if ($failIfRootMismatch && is_array($this->_caFiles) && !$rootValid) {
throw new WebAuthnException('invalid root certificate', WebAuthnException::CERTIFICATE_NOT_TRUSTED);
}
// 10. Verify that the User Present bit of the flags in authData is set.
if ($requireUserPresent && !$attestationObject->getAuthenticatorData()->getUserPresent()) {
$userPresent = $attestationObject->getAuthenticatorData()->getUserPresent();
if ($requireUserPresent && !$userPresent) {
throw new WebAuthnException('user not present during authentication', WebAuthnException::USER_PRESENT);
}
// 11. If user verification is required for this registration, verify that the User Verified bit of the flags in authData is set.
if ($requireUserVerification && !$attestationObject->getAuthenticatorData()->getUserVerified()) {
throw new WebAuthnException('user not verificated during authentication', WebAuthnException::USER_VERIFICATED);
$userVerified = $attestationObject->getAuthenticatorData()->getUserVerified();
if ($requireUserVerification && !$userVerified) {
throw new WebAuthnException('user not verified during authentication', WebAuthnException::USER_VERIFICATED);
}
$signCount = $attestationObject->getAuthenticatorData()->getSignCount();
@ -340,6 +348,7 @@ class WebAuthn {
// prepare data to store for future logins
$data = new \stdClass();
$data->rpId = $this->_rpId;
$data->attestationFormat = $attestationObject->getAttestationFormatName();
$data->credentialId = $attestationObject->getAuthenticatorData()->getCredentialId();
$data->credentialPublicKey = $attestationObject->getAuthenticatorData()->getPublicKeyPem();
$data->certificateChain = $attestationObject->getCertificateChain();
@ -348,6 +357,9 @@ class WebAuthn {
$data->certificateSubject = $attestationObject->getCertificateSubject();
$data->signatureCounter = $this->_signatureCounter;
$data->AAGUID = $attestationObject->getAuthenticatorData()->getAAGUID();
$data->rootValid = $rootValid;
$data->userPresent = $userPresent;
$data->userVerified = $userVerified;
return $data;
}
@ -453,6 +465,92 @@ class WebAuthn {
return true;
}
/**
* Downloads root certificates from FIDO Alliance Metadata Service (MDS) to a specific folder
* https://fidoalliance.org/metadata/
* @param string $certFolder Folder path to save the certificates in PEM format.
* @param bool $deleteCerts=true
* @return int number of cetificates
* @throws WebAuthnException
*/
public function queryFidoMetaDataService($certFolder, $deleteCerts=true) {
$url = 'https://mds.fidoalliance.org/';
$raw = null;
if (\function_exists('curl_init')) {
$ch = \curl_init($url);
\curl_setopt($ch, CURLOPT_HEADER, false);
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
\curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
\curl_setopt($ch, CURLOPT_USERAGENT, 'github.com/lbuchs/WebAuthn - A simple PHP WebAuthn server library');
$raw = \curl_exec($ch);
\curl_close($ch);
} else {
$raw = \file_get_contents($url);
}
$certFolder = \rtrim(\realpath($certFolder), '\\/');
if (!is_dir($certFolder)) {
throw new WebAuthnException('Invalid folder path for query FIDO Alliance Metadata Service');
}
if (!\is_string($raw)) {
throw new WebAuthnException('Unable to query FIDO Alliance Metadata Service');
}
$jwt = \explode('.', $raw);
if (\count($jwt) !== 3) {
throw new WebAuthnException('Invalid JWT from FIDO Alliance Metadata Service');
}
if ($deleteCerts) {
foreach (\scandir($certFolder) as $ca) {
if (\substr($ca, -4) === '.pem') {
if (\unlink($certFolder . DIRECTORY_SEPARATOR . $ca) === false) {
throw new WebAuthnException('Cannot delete certs in folder for FIDO Alliance Metadata Service');
}
}
}
}
list($header, $payload, $hash) = $jwt;
$payload = Binary\ByteBuffer::fromBase64Url($payload)->getJson();
$count = 0;
if (\is_object($payload) && \property_exists($payload, 'entries') && \is_array($payload->entries)) {
foreach ($payload->entries as $entry) {
if (\is_object($entry) && \property_exists($entry, 'metadataStatement') && \is_object($entry->metadataStatement)) {
$description = $entry->metadataStatement->description ?? null;
$attestationRootCertificates = $entry->metadataStatement->attestationRootCertificates ?? null;
if ($description && $attestationRootCertificates) {
// create filename
$certFilename = \preg_replace('/[^a-z0-9]/i', '_', $description);
$certFilename = \trim(\preg_replace('/\_{2,}/i', '_', $certFilename),'_') . '.pem';
$certFilename = \strtolower($certFilename);
// add certificate
$certContent = $description . "\n";
$certContent .= \str_repeat('-', \mb_strlen($description)) . "\n";
foreach ($attestationRootCertificates as $attestationRootCertificate) {
$count++;
$certContent .= "\n-----BEGIN CERTIFICATE-----\n";
$certContent .= \chunk_split(\trim($attestationRootCertificate), 64, "\n");
$certContent .= "-----END CERTIFICATE-----\n";
}
if (\file_put_contents($certFolder . DIRECTORY_SEPARATOR . $certFilename, $certContent) === false) {
throw new WebAuthnException('unable to save certificate from FIDO Alliance Metadata Service');
}
}
}
}
}
return $count;
}
// -----------------------------------------------
// PRIVATE
// -----------------------------------------------

View File

@ -1,5 +1,5 @@
<?php
namespace WebAuthn;
namespace lbuchs\WebAuthn;
/**
* @author Lukas Buchs

View File

@ -54,24 +54,16 @@ foreach ($css_dir as $css_file) {
}
// U2F API + T/HOTP API
// u2f - deprecated, should be removed
$u2f = new u2flib_server\U2F('https://' . $_SERVER['HTTP_HOST']);
$qrprovider = new RobThree\Auth\Providers\Qr\QRServerProvider();
$tfa = new RobThree\Auth\TwoFactorAuth($OTP_LABEL, 6, 30, 'sha1', $qrprovider);
// FIDO2
$formats = $GLOBALS['FIDO2_FORMATS'];
$WebAuthn = new \WebAuthn\WebAuthn('WebAuthn Library', $_SERVER['HTTP_HOST'], $formats);
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/solo.pem');
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/apple.pem');
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/nitro.pem');
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/yubico.pem');
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/hypersecu.pem');
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/globalSign.pem');
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/googleHardware.pem');
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/microsoftTpmCollection.pem');
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/huawei.pem');
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/trustkey.pem');
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/bsi.pem');
$WebAuthn = new lbuchs\WebAuthn\WebAuthn('WebAuthn Library', $_SERVER['HTTP_HOST'], $formats);
// only include root ca's when needed
if (getenv('WEBAUTHN_ONLY_TRUSTED_VENDORS') == 'y') $WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates');
// Redis
$redis = new Redis();

View File

@ -1,15 +1,28 @@
<?php
if (isset($_POST["verify_tfa_login"])) {
if (verify_tfa_login($_SESSION['pending_mailcow_cc_username'], $_POST["token"])) {
if (verify_tfa_login($_SESSION['pending_mailcow_cc_username'], $_POST, $WebAuthn)) {
$_SESSION['mailcow_cc_username'] = $_SESSION['pending_mailcow_cc_username'];
$_SESSION['mailcow_cc_role'] = $_SESSION['pending_mailcow_cc_role'];
unset($_SESSION['pending_mailcow_cc_username']);
unset($_SESSION['pending_mailcow_cc_role']);
unset($_SESSION['pending_tfa_method']);
header("Location: /user");
header("Location: /user");
} else {
unset($_SESSION['pending_mailcow_cc_username']);
unset($_SESSION['pending_mailcow_cc_role']);
unset($_SESSION['pending_tfa_method']);
}
}
if (isset($_GET["cancel_tfa_login"])) {
unset($_SESSION['pending_mailcow_cc_username']);
unset($_SESSION['pending_mailcow_cc_role']);
unset($_SESSION['pending_tfa_method']);
header("Location: /");
}
if (isset($_POST["quick_release"])) {
quarantine('quick_release', $_POST["quick_release"]);
}

View File

@ -195,11 +195,17 @@ $SHOW_LAST_LOGIN = true;
// true = required
// false = preferred
// string 'required' 'preferred' 'discouraged'
$WEBAUTHN_UV_FLAG_REGISTER = false;
$WEBAUTHN_UV_FLAG_LOGIN = false;
$WEBAUTHN_USER_PRESENT_FLAG = true;
$FIDO2_UV_FLAG_REGISTER = 'preferred';
$FIDO2_UV_FLAG_LOGIN = 'preferred'; // iOS ignores the key via NFC if required - known issue
$FIDO2_USER_PRESENT_FLAG = true;
$FIDO2_FORMATS = array('apple', 'android-key', 'android-safetynet', 'fido-u2f', 'none', 'packed', 'tpm');
// Set visible Rspamd maps in mailcow UI, do not change unless you know what you are doing
$RSPAMD_MAPS = array(
'regex' => array(

View File

@ -117,12 +117,12 @@ if (isset($_GET['query'])) {
echo isset($_SESSION['return']) ? json_encode($_SESSION['return']) : $generic_success;
}
}
if (!isset($_POST['attr']) && $category != "fido2-registration") {
if (!isset($_POST['attr']) && $category != "fido2-registration" && $category != "webauthn-tfa-registration") {
echo $request_incomplete;
exit;
}
else {
if ($category != "fido2-registration") {
if ($category != "fido2-registration" && $category != "webauthn-tfa-registration") {
$attr = (array)json_decode($_POST['attr'], true);
}
unset($attr['csrf_token']);
@ -170,6 +170,48 @@ if (isset($_GET['query'])) {
exit;
}
break;
case "webauthn-tfa-registration":
if (isset($_SESSION["mailcow_cc_role"])) {
// parse post data
$post = trim(file_get_contents('php://input'));
if ($post) $post = json_decode($post);
// decode base64 strings
$clientDataJSON = base64_decode($post->clientDataJSON);
$attestationObject = base64_decode($post->attestationObject);
// process registration data from authenticator
try {
// processCreate($clientDataJSON, $attestationObject, $challenge, $requireUserVerification=false, $requireUserPresent=true, $failIfRootMismatch=true)
$data = $WebAuthn->processCreate($clientDataJSON, $attestationObject, $_SESSION['challenge'], false, true);
}
catch (Throwable $ex) {
// err
$return = new stdClass();
$return->success = false;
$return->msg = $ex->getMessage();
echo json_encode($return);
exit;
}
// safe authenticator in mysql `tfa` table
$_data['tfa_method'] = $post->tfa_method;
$_data['key_id'] = $post->key_id;
$_data['registration'] = $data;
set_tfa($_data);
// send response
$return = new stdClass();
$return->success = true;
echo json_encode($return);
exit;
}
else {
// err - request incomplete
echo $request_incomplete;
exit;
}
break;
case "time_limited_alias":
process_add_return(mailbox('add', 'time_limited_alias', $attr));
break;
@ -350,29 +392,13 @@ if (isset($_GET['query'])) {
exit();
}
switch ($category) {
case "u2f-registration":
header('Content-Type: application/javascript');
if (isset($_SESSION["mailcow_cc_role"]) && $_SESSION["mailcow_cc_username"] == $object) {
list($req, $sigs) = $u2f->getRegisterData(get_u2f_registrations($object));
$_SESSION['regReq'] = json_encode($req);
$_SESSION['regSigs'] = json_encode($sigs);
echo 'var req = ' . json_encode($req) . ';';
echo 'var registeredKeys = ' . json_encode($sigs) . ';';
echo 'var appId = req.appId;';
echo 'var registerRequests = [{version: req.version, challenge: req.challenge}];';
return;
}
else {
return;
}
break;
// fido2-registration via GET
// fido2
case "fido2-registration":
header('Content-Type: application/json');
if (isset($_SESSION["mailcow_cc_role"])) {
// Exclude existing CredentialIds, if any
$excludeCredentialIds = fido2(array("action" => "get_user_cids"));
$createArgs = $WebAuthn->getCreateArgs($_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], 30, true, $GLOBALS['FIDO2_UV_FLAG_REGISTER'], $excludeCredentialIds);
$createArgs = $WebAuthn->getCreateArgs($_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], 30, true, $GLOBALS['FIDO2_UV_FLAG_REGISTER'], null, $excludeCredentialIds);
print(json_encode($createArgs));
$_SESSION['challenge'] = $WebAuthn->getChallenge();
return;
@ -381,37 +407,63 @@ if (isset($_GET['query'])) {
return;
}
break;
case "u2f-authentication":
header('Content-Type: application/javascript');
if (isset($_SESSION['pending_mailcow_cc_username']) && $_SESSION['pending_mailcow_cc_username'] == $object) {
$auth_data = $u2f->getAuthenticateData(get_u2f_registrations($object));
$challenge = $auth_data[0]->challenge;
$appId = $auth_data[0]->appId;
foreach ($auth_data as $each) {
$key = array(); // Empty array
$key['version'] = $each->version;
$key['keyHandle'] = $each->keyHandle;
$registeredKey[] = $key;
}
$_SESSION['authReq'] = json_encode($auth_data);
echo 'var appId = "' . $appId . '";';
echo 'var challenge = ' . json_encode($challenge) . ';';
echo 'var registeredKeys = ' . json_encode($registeredKey) . ';';
return;
case "fido2-get-args":
header('Content-Type: application/json');
// fetch allowed credentialIds
$cids = fido2(array("action" => "get_all_cids"));
if (count($cids) == 0) {
print(json_encode(array(
'type' => 'error',
'msg' => 'Cannot find matching credentialIds'
)));
}
$getArgs = $WebAuthn->getGetArgs($cids, 30, true, true, true, true, $GLOBALS['FIDO2_UV_FLAG_LOGIN']);
print(json_encode($getArgs));
$_SESSION['challenge'] = $WebAuthn->getChallenge();
return;
break;
// webauthn two factor authentication
case "webauthn-tfa-registration":
if (isset($_SESSION["mailcow_cc_role"])) {
// Exclude existing CredentialIds, if any
$stmt = $pdo->prepare("SELECT `keyHandle` FROM `tfa` WHERE username = :username");
$stmt->execute(array(':username' => $_SESSION['mailcow_cc_username']));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$excludeCredentialIds[] = base64_decode($row['keyHandle']);
}
// getCreateArgs($userId, $userName, $userDisplayName, $timeout=20, $requireResidentKey=false, $requireUserVerification=false, $crossPlatformAttachment=null, $excludeCredentialIds=array())
// cross-platform: true, if type internal is not allowed
// false, if only internal is allowed
// null, if internal and cross-platform is allowed
$createArgs = $WebAuthn->getCreateArgs($_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], 30, false, $GLOBALS['WEBAUTHN_UV_FLAG_REGISTER'], null, $excludeCredentialIds);
print(json_encode($createArgs));
$_SESSION['challenge'] = $WebAuthn->getChallenge();
return;
}
else {
return;
}
break;
case "fido2-get-args":
header('Content-Type: application/json');
// Login without username, no ids!
// $ids = fido2(array("action" => "get_all_cids"));
// if (count($ids) == 0) {
// return;
// }
$ids = NULL;
$getArgs = $WebAuthn->getGetArgs($ids, 30, true, true, true, true, $GLOBALS['FIDO2_UV_FLAG_LOGIN']);
case "webauthn-tfa-get-args":
$stmt = $pdo->prepare("SELECT `keyHandle` FROM `tfa` WHERE username = :username");
$stmt->execute(array(':username' => $_SESSION['pending_mailcow_cc_username']));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$cids[] = base64_decode($row['keyHandle']);
}
if (count($cids) == 0) {
print(json_encode(array(
'type' => 'error',
'msg' => 'Cannot find matching credentialIds'
)));
}
$getArgs = $WebAuthn->getGetArgs($cids, 30, true, true, true, true, $GLOBALS['WEBAUTHN_UV_FLAG_LOGIN']);
$getArgs->publicKey->extensions = array('appid' => "https://".$getArgs->publicKey->rpId);
print(json_encode($getArgs));
$_SESSION['challenge'] = $WebAuthn->getChallenge();
return;

View File

@ -443,9 +443,9 @@
"set_tfa": "Definir el mètode d'autenticació de dos factors",
"tfa": "Autenticació de dos factors",
"totp": "OTP basat en temps (Google Authenticator etc.)",
"u2f": "Autenticació U2F",
"waiting_usb_auth": "<i>Esperant el dispositiu USB...</i><br><br>Apreta el botó del teu dispositiu USB U2F ara.",
"waiting_usb_register": "<i>Esperant el dispositiu USB...</i><br><br>Posa el teu password i confirma el registre del teu U2F apretant el botó del teu dispositiiu USB U2F.",
"webauthn": "Autenticació WebAuthn",
"waiting_usb_auth": "<i>Esperant el dispositiu USB...</i><br><br>Apreta el botó del teu dispositiu USB WebAuthn ara.",
"waiting_usb_register": "<i>Esperant el dispositiu USB...</i><br><br>Posa el teu password i confirma el registre del teu WebAuthn apretant el botó del teu dispositiiu USB WebAuthn.",
"yubi_otp": "Autenticació OTP de Yubico"
},
"user": {

View File

@ -454,7 +454,7 @@
"tls_policy_map_parameter_invalid": "Parametr pravidel TLS je neplatný",
"totp_verification_failed": "TOTP ověření selhalo",
"transport_dest_exists": "Transportní cíl \"%s\" již existuje",
"u2f_verification_failed": "U2F ověření selhalo: %s",
"webauthn_verification_failed": "WebAuthn ověření selhalo: %s",
"unknown": "Došlo k neznámé chybě",
"unknown_tfa_method": "Neznámá 2FA metoda",
"unlimited_quota_acl": "Neomeznou kvótu nepovoluje seznam oprávnění ACL",
@ -972,7 +972,7 @@
"upload_success": "Soubor úspěšně nahrán",
"verified_fido2_login": "Ověřené FIDO2 přihlášení",
"verified_totp_login": "TOTP přihlášení ověřeno",
"verified_u2f_login": "U2F přihlášení ověřeno",
"verified_webauthn_login": "WebAuthn přihlášení ověřeno",
"verified_yotp_login": "Yubico OTP přihlášení ověřeno"
},
"tfa": {
@ -983,7 +983,7 @@
"disable_tfa": "Zakázat 2FA do příštího úspěšného přihlášení",
"enter_qr_code": "Kód TOTP, pokud zařízení neumí číst QR kódy",
"error_code": "Kód chyby",
"init_u2f": "Probíhá inicializace, čekejte...",
"init_webauthn": "Probíhá inicializace, čekejte...",
"key_id": "Identifikátor YubiKey",
"key_id_totp": "Identifikátor klíče",
"none": "Deaktivovat",
@ -991,13 +991,13 @@
"scan_qr_code": "Prosím načtěte následující kód svou aplikací na ověření nebo zadejte kód ručně.",
"select": "Prosím vyberte...",
"set_tfa": "Nastavení způsobu dvoufaktorového ověření",
"start_u2f_validation": "Zahájit inicializaci",
"start_webauthn_validation": "Zahájit inicializaci",
"tfa": "Dvoufaktorové ověření (TFA)",
"tfa_token_invalid": "Neplatný TFA token",
"totp": "Časově založené OTP (Google Authenticator, Authy apod.)",
"u2f": "U2F ověření",
"waiting_usb_auth": "<i>Čeká se na USB zařízení...</i><br><br>Prosím stiskněte tlačítko na svém U2F USB zařízení.",
"waiting_usb_register": "<i>Čeká se na USB zařízení...</i><br><br>Prosím zadejte své heslo výše a potvrďte U2F registraci stiskem tlačítka na svém U2F USB zařízení.",
"webauthn": "WebAuthn ověření",
"waiting_usb_auth": "<i>Čeká se na USB zařízení...</i><br><br>Prosím stiskněte tlačítko na svém WebAuthn USB zařízení.",
"waiting_usb_register": "<i>Čeká se na USB zařízení...</i><br><br>Prosím zadejte své heslo výše a potvrďte WebAuthn registraci stiskem tlačítka na svém WebAuthn USB zařízení.",
"yubi_otp": "Yubico OTP ověření"
},
"user": {

View File

@ -426,7 +426,7 @@
"tls_policy_map_parameter_invalid": "Politikparameter er ugyldig",
"totp_verification_failed": "Bekræftelse af TOTP mislykkedes",
"transport_dest_exists": "Transport destination \"%s\" eksisterer",
"u2f_verification_failed": "U2F-bekræftelse mislykkedes: %s",
"webauthn_verification_failed": "WebAuthn-bekræftelse mislykkedes: %s",
"fido2_verification_failed": "Bekræftelse af FIDO2 mislykkedes: %s",
"unknown": "Der opstod en ukendt fejl",
"unknown_tfa_method": "Ukendt TFA-metode",
@ -881,7 +881,7 @@
"ui_texts": "Gemte ændringer til UI-tekster",
"upload_success": "Filen blev uploadet",
"verified_totp_login": "Bekræftet TOTP-login",
"verified_u2f_login": "Bekræftet U2F-login",
"verified_webauthn_login": "Bekræftet WebAuthn-login",
"verified_fido2_login": "Bekræftet FIDO2-login",
"verified_yotp_login": "Bekræftet Yubico OTP-login"
},
@ -893,7 +893,7 @@
"disable_tfa": "Deaktiver TFA indtil næste vellykkede login",
"enter_qr_code": "Din TOTP kode hvis din enhed ikke kan scanne QR-koder",
"error_code": "Fejl kode",
"init_u2f": "Initialiserer, vent venligst...",
"init_webauthn": "Initialiserer, vent venligst...",
"key_id": "En identifikator til din YubiKey",
"key_id_totp": "En identifikator for din nøgle",
"none": "Deaktivere",
@ -901,11 +901,11 @@
"scan_qr_code": "Scan venligst følgende kode med din godkendelsesapp, eller indtast koden manuelt.",
"select": "Vælg venligst",
"set_tfa": "Set 2-faktor godkendelses metoden",
"start_u2f_validation": "Start validering",
"start_webauthn_validation": "Start validering",
"tfa": "2-faktor godkendelse",
"tfa_token_invalid": "TFA nøgle ugyldig",
"totp": "Tids-baseret OTP (Google Authenticator, Authy, etc.)",
"u2f": "U2F godkendelse",
"webauthn": "WebAuthn godkendelse",
"waiting_usb_auth": "<i>Venter på USB-enhed...</i><br><br>Tryk let på knappen på din USB-enhed nu.",
"waiting_usb_register": "<i>Venter på USB-enhed...</i><br><br>Indtast din adgangskode ovenfor, og bekræft din registrering ved at trykke på knappen på din USB-enhed.",
"yubi_otp": "Yubico OTP godkendelse"

View File

@ -455,7 +455,7 @@
"tls_policy_map_parameter_invalid": "Parameter ist ungültig",
"totp_verification_failed": "TOTP-Verifizierung fehlgeschlagen",
"transport_dest_exists": "Transport-Maps-Ziel \"%s\" existiert bereits",
"u2f_verification_failed": "U2F-Verifizierung fehlgeschlagen: %s",
"webauthn_verification_failed": "WebAuthn-Verifizierung fehlgeschlagen: %s",
"unknown": "Ein unbekannter Fehler trat auf",
"unknown_tfa_method": "Unbekannte TFA-Methode",
"unlimited_quota_acl": "Unendliche Quota untersagt durch ACL",
@ -971,7 +971,7 @@
"upload_success": "Datei wurde erfolgreich hochgeladen",
"verified_fido2_login": "FIDO2-Anmeldung verifiziert",
"verified_totp_login": "TOTP-Anmeldung verifiziert",
"verified_u2f_login": "U2F-Anmeldung verifiziert",
"verified_webauthn_login": "WebAuthn-Anmeldung verifiziert",
"verified_yotp_login": "Yubico-OTP-Anmeldung verifiziert"
},
"tfa": {
@ -982,19 +982,21 @@
"disable_tfa": "Deaktiviere 2FA bis zur nächsten erfolgreichen Anmeldung",
"enter_qr_code": "Falls Sie den angezeigten QR-Code nicht scannen können, verwenden Sie bitte nachstehenden Sicherheitsschlüssel",
"error_code": "Fehlercode",
"init_u2f": "Initialisiere, bitte warten...",
"key_id": "Ein Namen für diesen YubiKey",
"init_webauthn": "Initialisiere, bitte warten...",
"key_id": "Ein Namen für dieses Gerät",
"key_id_totp": "Ein eindeutiger Name",
"none": "Deaktiviert",
"reload_retry": "- (bei persistierendem Fehler, bitte Browserfenster neu laden)",
"scan_qr_code": "Bitte scannen Sie jetzt den angezeigten QR-Code:",
"select": "Bitte auswählen",
"set_tfa": "Konfiguriere Zwei-Faktor-Authentifizierungsmethode",
"start_u2f_validation": "Starte Validierung",
"start_webauthn_validation": "Starte Validierung",
"tfa": "Zwei-Faktor-Authentifizierung",
"tfa_token_invalid": "TFA-Token ungültig!",
"totp": "Time-based-OTP (Google Authenticator etc.)",
"u2f": "U2F-Authentifizierung",
"u2f_deprecated": "Es sieht so aus als wurde der Schlüssel mit der alten U2F Methode registriert. Wir werden die Zwei-Faktor-Authentifizierung deaktivieren und deinen Schlüssel löschen.",
"u2f_deprecated_important": "Bitte registriere den Schlüssel im Adminbereich mit der neuen WebAuthn Methode.",
"webauthn": "WebAuthn-Authentifizierung",
"waiting_usb_auth": "<i>Warte auf USB-Gerät...</i><br><br>Bitte jetzt den vorgesehenen Taster des USB-Gerätes berühren.",
"waiting_usb_register": "<i>Warte auf USB-Gerät...</i><br><br>Bitte zuerst das obere Passwortfeld ausfüllen und erst dann den vorgesehenen Taster des USB-Gerätes berühren.",
"yubi_otp": "Yubico OTP-Authentifizierung"

View File

@ -455,7 +455,7 @@
"tls_policy_map_parameter_invalid": "Policy parameter is invalid",
"totp_verification_failed": "TOTP verification failed",
"transport_dest_exists": "Transport destination \"%s\" exists",
"u2f_verification_failed": "U2F verification failed: %s",
"webauthn_verification_failed": "WebAuthn verification failed: %s",
"unknown": "An unknown error occurred",
"unknown_tfa_method": "Unknown TFA method",
"unlimited_quota_acl": "Unlimited quota prohibited by ACL",
@ -978,7 +978,7 @@
"upload_success": "File uploaded successfully",
"verified_fido2_login": "Verified FIDO2 login",
"verified_totp_login": "Verified TOTP login",
"verified_u2f_login": "Verified U2F login",
"verified_webauthn_login": "Verified WebAuthn login",
"verified_yotp_login": "Verified Yubico OTP login"
},
"tfa": {
@ -989,19 +989,21 @@
"disable_tfa": "Disable TFA until next successful login",
"enter_qr_code": "Your TOTP code if your device cannot scan QR codes",
"error_code": "Error code",
"init_u2f": "Initializing, please wait...",
"key_id": "An identifier for your YubiKey",
"init_webauthn": "Initializing, please wait...",
"key_id": "An identifier for your Device",
"key_id_totp": "An identifier for your key",
"none": "Deactivate",
"reload_retry": "- (reload browser if the error persists)",
"scan_qr_code": "Please scan the following code with your authenticator app or enter the code manually.",
"select": "Please select",
"set_tfa": "Set two-factor authentication method",
"start_u2f_validation": "Start validation",
"start_webauthn_validation": "Start validation",
"tfa": "Two-factor authentication",
"tfa_token_invalid": "TFA token invalid",
"totp": "Time-based OTP (Google Authenticator, Authy, etc.)",
"u2f": "U2F authentication",
"u2f_deprecated": "It seems that your Key was registered using the deprecated U2F method. We will deactivate Two-Factor-Authenticaiton for you and delete your Key.",
"u2f_deprecated_important": "Please register your Key in the admin panel with the new WebAuthn method.",
"webauthn": "WebAuthn authentication",
"waiting_usb_auth": "<i>Waiting for USB device...</i><br><br>Please tap the button on your USB device now.",
"waiting_usb_register": "<i>Waiting for USB device...</i><br><br>Please enter your password above and confirm your registration by tapping the button on your USB device.",
"yubi_otp": "Yubico OTP authentication"

View File

@ -323,7 +323,7 @@
"tls_policy_map_parameter_invalid": "El parámetro de póliza no es válido.",
"totp_verification_failed": "Verificación TOTP fallida",
"transport_dest_exists": "Destino de la regla de transporte ya existe",
"u2f_verification_failed": "Verificación U2F fallida: %s",
"webauthn_verification_failed": "Verificación WebAuthn fallida: %s",
"unknown": "Se produjo un error desconocido",
"unknown_tfa_method": "Método TFA desconocido",
"unlimited_quota_acl": "Cuota ilimitada restringida por controles administrativos",
@ -649,7 +649,7 @@
"tls_policy_map_entry_deleted": "Regla de póliza de TLS con ID %s ha sido elimindada",
"tls_policy_map_entry_saved": "Regla de póliza de TLS \"%s\" ha sido guardada",
"verified_totp_login": "Inicio de sesión TOTP verificado",
"verified_u2f_login": "Inicio de sesión U2F verificado",
"verified_webauthn_login": "Inicio de sesión WebAuthn verificado",
"verified_yotp_login": "Inicio de sesión Yubico OTP verificado"
},
"tfa": {
@ -667,9 +667,9 @@
"set_tfa": "Establecer el método de autenticación de dos factores",
"tfa": "Autenticación de dos factores",
"totp": "OTP basado en tiempo (Google Authenticator, Authy, etc.)",
"u2f": "Autenticación U2F",
"waiting_usb_auth": "<i>Esperando al dispositivo USB...</i><br><br>Toque el botón en su dispositivo USB U2F ahora.",
"waiting_usb_register": "<i>Esperando al dispositivo USB....</i><br><br>Ingrese su contraseña arriba y confirme su registro U2F tocando el botón en su dispositivo USB U2F.",
"webauthn": "Autenticación WebAuthn",
"waiting_usb_auth": "<i>Esperando al dispositivo USB...</i><br><br>Toque el botón en su dispositivo USB WebAuthn ahora.",
"waiting_usb_register": "<i>Esperando al dispositivo USB....</i><br><br>Ingrese su contraseña arriba y confirme su registro WebAuthn tocando el botón en su dispositivo USB WebAuthn.",
"yubi_otp": "Yubico OTP"
},
"user": {

View File

@ -370,7 +370,7 @@
"tls_policy_map_parameter_invalid": "Käytäntö parametri ei kelpaa",
"totp_verification_failed": "TOTP-vahvistus epäonnistui",
"transport_dest_exists": "Kuljetuksen määränpää \"%s\" olemassa",
"u2f_verification_failed": "U2F vahvistaminen epäonnistui: %s",
"webauthn_verification_failed": "WebAuthn vahvistaminen epäonnistui: %s",
"unknown": "Ilmeni tuntematon virhe",
"unknown_tfa_method": "Tuntematon TFA-menetelmä",
"unlimited_quota_acl": "Rajoittamaton kiintiö kielletty ACL",
@ -754,7 +754,7 @@
"ui_texts": "Tallennettu käyttöliittymätekstien muutokset",
"upload_success": "Tiedosto ladattu onnistuneesti",
"verified_totp_login": "Vahvistettu TOTP-kirjautuminen",
"verified_u2f_login": "Vahvistettu U2F kirjautuminen",
"verified_webauthn_login": "Vahvistettu WebAuthn kirjautuminen",
"verified_yotp_login": "Vahvistettu Yubico OTP kirjautuminen"
},
"tfa": {
@ -765,7 +765,7 @@
"disable_tfa": "Poista TFA käytöstä seuraavaan onnistuneen kirjautumisen jälkeen",
"enter_qr_code": "TOTP-koodisi, jos laitteesi ei pysty tarkistamaan QR-koodeja",
"error_code": "Virhekoodi",
"init_u2f": "Alustetaan, odota...",
"init_webauthn": "Alustetaan, odota...",
"key_id": "Tunniste YubiKey",
"key_id_totp": "Avaimen tunnus",
"none": "Poista",
@ -773,12 +773,12 @@
"scan_qr_code": "Tarkista seuraava koodi Authenticator-sovelluksella tai Syötä koodi manuaalisesti.",
"select": "Valitse",
"set_tfa": "Määritä kaksiosainen todennus menetelmä",
"start_u2f_validation": "Aloita oikeellisuus tarkistus",
"start_webauthn_validation": "Aloita oikeellisuus tarkistus",
"tfa": "Kaksiosainen todennus",
"totp": "Aikapohjainen OTP (Google Authenticator, Authy jne.)",
"u2f": "U2F todennus",
"waiting_usb_auth": "<i>Odotetaan USB-laitetta...</i><br><br>Napauta painiketta U2F USB-laitteessa nyt",
"waiting_usb_register": "<i>Odotetaan USB-laitetta...</i><br><br>Anna salasanasi yltä ja vahvista U2F-rekisteröinti napauttamalla painiketta U2F USB-laitteessa.",
"webauthn": "WebAuthn todennus",
"waiting_usb_auth": "<i>Odotetaan USB-laitetta...</i><br><br>Napauta painiketta WebAuthn USB-laitteessa nyt",
"waiting_usb_register": "<i>Odotetaan USB-laitetta...</i><br><br>Anna salasanasi yltä ja vahvista WebAuthn-rekisteröinti napauttamalla painiketta WebAuthn USB-laitteessa.",
"yubi_otp": "Yubico OTP-todennus"
},
"user": {

View File

@ -430,7 +430,7 @@
"tls_policy_map_parameter_invalid": "Le paramètre Policy est invalide",
"totp_verification_failed": "Echec de la vérification TOTP",
"transport_dest_exists": "La destination de transport \"%s\" existe",
"u2f_verification_failed": "Echec de la vérification U2F: %s",
"webauthn_verification_failed": "Echec de la vérification WebAuthn: %s",
"fido2_verification_failed": "La vérification FIDO2 a échoué: %s",
"unknown": "Une erreur inconnue est survenue",
"unknown_tfa_method": "Methode TFA inconnue",
@ -895,7 +895,7 @@
"ui_texts": "Enregistrement des modifications apportées aux textes de linterface utilisateur",
"upload_success": "Fichier téléchargé avec succès",
"verified_totp_login": "Authentification TOTP vérifiée",
"verified_u2f_login": "Authentification U2F vérifiée",
"verified_webauthn_login": "Authentification WebAuthn vérifiée",
"verified_fido2_login": "Authentification FIDO2 vérifiée",
"verified_yotp_login": "Authentification Yubico OTP vérifiée"
},
@ -907,7 +907,7 @@
"disable_tfa": "Désactiver TFA jusquà la prochaine ouverture de session réussie",
"enter_qr_code": "Votre code TOTP si votre appareil ne peut pas scanner les codes QR",
"error_code": "Code d'erreur",
"init_u2f": "Initialisation, veuillez patienter...",
"init_webauthn": "Initialisation, veuillez patienter...",
"key_id": "Un identifiant pour votre Yubikey",
"key_id_totp": "Un identifiant pour votre clé",
"none": "Désactiver",
@ -915,13 +915,13 @@
"scan_qr_code": "Veuillez scanner le code suivant avec votre application dauthentification ou entrer le code manuellement.",
"select": "Veuillez sélectionner",
"set_tfa": "Définir une méthode dauthentification à deux facteurs",
"start_u2f_validation": "Début de la validation",
"start_webauthn_validation": "Début de la validation",
"tfa": "Authentification à deux facteurs",
"tfa_token_invalid": "Token TFA invalide",
"totp": "OTP (One Time Password = Mot de passe à usage unique : Google Authenticator, Authy, etc.)",
"u2f": "Authentification U2F",
"waiting_usb_auth": "<i>En attente dun périphérique USB...</i><br><br>Sil vous plaît appuyez maintenant sur le bouton de votre périphérique USB U2F.",
"waiting_usb_register": "<i>En attente dun périphérique USB...</i><br><br>Veuillez entrer votre mot de passe ci-dessus et confirmer votre inscription U2F en appuyant sur le bouton de votre périphérique USB U2F.",
"webauthn": "Authentification WebAuthn",
"waiting_usb_auth": "<i>En attente dun périphérique USB...</i><br><br>Sil vous plaît appuyez maintenant sur le bouton de votre périphérique USB WebAuthn.",
"waiting_usb_register": "<i>En attente dun périphérique USB...</i><br><br>Veuillez entrer votre mot de passe ci-dessus et confirmer votre inscription WebAuthn en appuyant sur le bouton de votre périphérique USB WebAuthn.",
"yubi_otp": "Authentification OTP Yubico"
},
"fido2": {

View File

@ -455,7 +455,7 @@
"tls_policy_map_parameter_invalid": "Policy parameter is invalid",
"totp_verification_failed": "TOTP verification failed",
"transport_dest_exists": "Transport destination \"%s\" exists",
"u2f_verification_failed": "U2F verification failed: %s",
"webauthn_verification_failed": "WebAuthn verification failed: %s",
"unknown": "An unknown error occurred",
"unknown_tfa_method": "Unknown TFA method",
"unlimited_quota_acl": "Unlimited quota prohibited by ACL",
@ -971,7 +971,7 @@
"upload_success": "File caricato con successo",
"verified_fido2_login": "Verified FIDO2 login",
"verified_totp_login": "Verified TOTP login",
"verified_u2f_login": "Verified U2F login",
"verified_webauthn_login": "Verified WebAuthn login",
"verified_yotp_login": "Verified Yubico OTP login"
},
"tfa": {
@ -982,7 +982,7 @@
"disable_tfa": "Disabilita TFA fino al prossimo accesso",
"enter_qr_code": "Il codice TOTP se il tuo dispositivo non è in grado di acquisire i codici QR",
"error_code": "Codice di errore",
"init_u2f": "Inizializzazione, attendere prego...",
"init_webauthn": "Inizializzazione, attendere prego...",
"key_id": "Identificatore per il tuo YubiKey",
"key_id_totp": "Identificatore per la tua chiave",
"none": "Disattivato",
@ -990,12 +990,12 @@
"scan_qr_code": "Esegui la scansione del seguente codice con l'applicazione di autenticazione o inserisci manualmente il codice.",
"select": "Seleziona",
"set_tfa": "Imposta il metodo di autenticazione a due fattori",
"start_u2f_validation": "Avvia convalida",
"start_webauthn_validation": "Avvia convalida",
"tfa": "Autenticazione a due fattori",
"totp": "Time-based OTP (Google Authenticator etc.)",
"u2f": "Autenticazione U2F",
"waiting_usb_auth": "<i>In attesa del device USB...</i><br /><br />Tocca ora il pulsante sul dispositivo U2F USB.",
"waiting_usb_register": "<i>In attesa del device USB...</i><br /><br />Inserisci la tua password qui sopra e conferma la tua registrazione U2F toccando il pulsante del dispositivo U2F USB.",
"webauthn": "Autenticazione WebAuthn",
"waiting_usb_auth": "<i>In attesa del device USB...</i><br /><br />Tocca ora il pulsante sul dispositivo WebAuthn USB.",
"waiting_usb_register": "<i>In attesa del device USB...</i><br /><br />Inserisci la tua password qui sopra e conferma la tua registrazione WebAuthn toccando il pulsante del dispositivo WebAuthn USB.",
"yubi_otp": "Autenticazione Yubico OTP",
"tfa_token_invalid": "Token TFA non valido"
},

View File

@ -417,7 +417,7 @@
"tls_policy_map_parameter_invalid": "유효하지 않은 정책 매개변수",
"totp_verification_failed": "TOTP 확인 실패",
"transport_dest_exists": "전송 목적지 \"%s\"가 존재합니다.",
"u2f_verification_failed": "U2F 검증 실패: %s",
"webauthn_verification_failed": "WebAuthn 검증 실패: %s",
"unknown": "알 수 없는 오류 발생",
"unknown_tfa_method": "알 수 없는 TFA 방식",
"unlimited_quota_acl": "ACL에 따라 할당량을 무제한으로 둘 수 없습니다.",
@ -852,7 +852,7 @@
"ui_texts": "Saved changes to UI texts",
"upload_success": "File uploaded successfully",
"verified_totp_login": "Verified TOTP login",
"verified_u2f_login": "Verified U2F login",
"verified_webauthn_login": "Verified WebAuthn login",
"verified_yotp_login": "Verified Yubico OTP login"
},
"tfa": {
@ -863,7 +863,7 @@
"disable_tfa": "Disable TFA until next successful login",
"enter_qr_code": "Your TOTP code if your device cannot scan QR codes",
"error_code": "Error code",
"init_u2f": "Initializing, please wait...",
"init_webauthn": "Initializing, please wait...",
"key_id": "An identifier for your YubiKey",
"key_id_totp": "An identifier for your key",
"none": "Deactivate",
@ -871,12 +871,12 @@
"scan_qr_code": "Please scan the following code with your authenticator app or enter the code manually.",
"select": "Please select",
"set_tfa": "Set two-factor authentication method",
"start_u2f_validation": "Start validation",
"start_webauthn_validation": "Start validation",
"tfa": "Two-factor authentication",
"totp": "Time-based OTP (Google Authenticator, Authy, etc.)",
"u2f": "U2F authentication",
"waiting_usb_auth": "<i>Waiting for USB device...</i><br><br>Please tap the button on your U2F USB device now.",
"waiting_usb_register": "<i>Waiting for USB device...</i><br><br>Please enter your password above and confirm your U2F registration by tapping the button on your U2F USB device.",
"webauthn": "WebAuthn authentication",
"waiting_usb_auth": "<i>Waiting for USB device...</i><br><br>Please tap the button on your WebAuthn USB device now.",
"waiting_usb_register": "<i>Waiting for USB device...</i><br><br>Please enter your password above and confirm your WebAuthn registration by tapping the button on your WebAuthn USB device.",
"yubi_otp": "Yubico OTP authentication"
},
"user": {

View File

@ -450,9 +450,9 @@
"set_tfa": "Uzstādīt difi faktoru autentifik;acijas metodi",
"tfa": "Divu faktoru autentifikācija",
"totp": "Uz laiku bāzēta vienreizēja parole (Google Autentifikātors utt.)",
"u2f": "U2F autentifikācija",
"waiting_usb_auth": "<i>Gaida USB ierīci...</i><br><br>Lūdzu, tagad nospiežiet pogu uz Jūsu U2F USB ierīces.",
"waiting_usb_register": "<i>Gaida USB ierīci...</i><br><br>Lūdzu augšā ievadiet Jūsu paroli un apstipriniet U2F reģistrāciju nospiežot pogu uz Jūsu U2F USB ierīces.",
"webauthn": "WebAuthn autentifikācija",
"waiting_usb_auth": "<i>Gaida USB ierīci...</i><br><br>Lūdzu, tagad nospiežiet pogu uz Jūsu WebAuthn USB ierīces.",
"waiting_usb_register": "<i>Gaida USB ierīci...</i><br><br>Lūdzu augšā ievadiet Jūsu paroli un apstipriniet WebAuthn reģistrāciju nospiežot pogu uz Jūsu WebAuthn USB ierīces.",
"yubi_otp": "Yubico OTP autentifikators"
},
"user": {

View File

@ -428,7 +428,7 @@
"tls_policy_map_parameter_invalid": "Beleidsparameter is ongeldig",
"totp_verification_failed": "TOTP-verificatie mislukt",
"transport_dest_exists": "Transportbestemming \"%s\" bestaat reeds",
"u2f_verification_failed": "U2F-verificatie mislukt: %s",
"webauthn_verification_failed": "WebAuthn-verificatie mislukt: %s",
"fido2_verification_failed": "FIDO2-verificatie mislukt: %s",
"unknown": "Er is een onbekende fout opgetreden",
"unknown_tfa_method": "Onbekende tweefactorauthenticatiemethode",
@ -891,7 +891,7 @@
"ui_texts": "Wijzigingen aan labels en teksten zijn opgeslagen",
"upload_success": "Bestand succesvol geupload",
"verified_totp_login": "TOTP succesvol geverifieerd",
"verified_u2f_login": "U2F succesvol geverifieerd",
"verified_webauthn_login": "WebAuthn succesvol geverifieerd",
"verified_fido2_login": "FIDO2 succesvol geverifieerd",
"verified_yotp_login": "Yubico OTP succesvol geverifieerd"
},
@ -903,7 +903,7 @@
"disable_tfa": "Pauzeer tweefactorauthenticatie tot de eerstvolgende succesvolle login",
"enter_qr_code": "Voer deze code in als je apparaat geen QR-codes kan scannen:",
"error_code": "Errorcode",
"init_u2f": "Even geduld aub...",
"init_webauthn": "Even geduld aub...",
"key_id": "Geef deze YubiKey een naam",
"key_id_totp": "Geef deze key een naam",
"none": "Deactiveer",
@ -911,13 +911,13 @@
"scan_qr_code": "Scan de volgende QR-code met je authenticatie-app:",
"select": "Selecteer...",
"set_tfa": "Kies methode voor tweefactorauthenticatie",
"start_u2f_validation": "Start validatie",
"start_webauthn_validation": "Start validatie",
"tfa": "Tweefactorauthenticatie",
"tfa_token_invalid": "Tweefactorauthenticatietoken is ongeldig",
"totp": "TOTP (Step Two, Authy, etc.)",
"u2f": "U2F",
"waiting_usb_auth": "<i>In afwachting van USB-apparaat...</i><br><br>Druk nu op de knop van je U2F-apparaat.",
"waiting_usb_register": "<i>In afwachting van USB-apparaat...</i><br><br>Voer je wachtwoord hierboven in en bevestig de registratie van het U2F-apparaat door op de knop van het apparaat te drukken.",
"webauthn": "WebAuthn",
"waiting_usb_auth": "<i>In afwachting van USB-apparaat...</i><br><br>Druk nu op de knop van je WebAuthn-apparaat.",
"waiting_usb_register": "<i>In afwachting van USB-apparaat...</i><br><br>Voer je wachtwoord hierboven in en bevestig de registratie van het WebAuthn-apparaat door op de knop van het apparaat te drukken.",
"yubi_otp": "Yubico OTP"
},
"fido2": {

View File

@ -329,9 +329,9 @@
"set_tfa": "Ustaw metodę uwierzytelniania dwuetapowego",
"tfa": "Uwierzytelnianie dwuetapowe",
"totp": "Time-based OTP (Google Authenticator itd.)",
"u2f": "Uwierzytelnianie U2F",
"waiting_usb_auth": "<i>Czekam na urządzenie USB...</i><br><br>Wciśnij teraz przycisk na urządzeniu U2F USB.",
"waiting_usb_register": "<i> Czekam na urządzenie USB...</i><br><br>Wprowadź swoje hasło powyżej i potwierdź rejestrację U2F przez naciśnięcie przycisku na urządzeniu U2F USB.",
"webauthn": "Uwierzytelnianie WebAuthn",
"waiting_usb_auth": "<i>Czekam na urządzenie USB...</i><br><br>Wciśnij teraz przycisk na urządzeniu WebAuthn USB.",
"waiting_usb_register": "<i> Czekam na urządzenie USB...</i><br><br>Wprowadź swoje hasło powyżej i potwierdź rejestrację WebAuthn przez naciśnięcie przycisku na urządzeniu WebAuthn USB.",
"yubi_otp": "Uwierzytelnianie Yubico OTP"
},
"user": {

View File

@ -454,7 +454,7 @@
"tls_policy_map_parameter_invalid": "Parametrul politicii este invalid",
"totp_verification_failed": "Verificarea TOTP a eșuat",
"transport_dest_exists": "Destinația transportului \"%s\" există",
"u2f_verification_failed": "Verificarea U2F a eșuat: %s",
"webauthn_verification_failed": "Verificarea WebAuthn a eșuat: %s",
"fido2_verification_failed": "Verificarea FIDO2 a eșuat: %s",
"unknown": "A apărut o eroare necunoscută",
"unknown_tfa_method": "Metodă TFA necunoscută",
@ -977,7 +977,7 @@
"ui_texts": "Modificări salvate în textele UI",
"upload_success": "Fișier încărcat cu succes",
"verified_totp_login": "Autentificarea TOTP verificată",
"verified_u2f_login": "Autentificarea U2F verificată",
"verified_webauthn_login": "Autentificarea WebAuthn verificată",
"verified_fido2_login": "Conectare FIDO2 verificată",
"verified_yotp_login": "Autentificarea Yubico OTP verificată"
},
@ -989,7 +989,7 @@
"disable_tfa": "Dezactivează TFA până la următoarea conectare reușită",
"enter_qr_code": "Codul tău TOTP dacă dispozitivul tău nu poate scana codurile QR",
"error_code": "Cod de eroare",
"init_u2f": "Inițializare, vă rugăm așteptați...",
"init_webauthn": "Inițializare, vă rugăm așteptați...",
"key_id": "Un identificator pentru YubiKey",
"key_id_totp": "Un identificator pentru cheia ta",
"none": "Dezactivează",
@ -997,13 +997,13 @@
"scan_qr_code": "Scanează codul următor cu aplicația ta de autentificare sau introdu manual codul.",
"select": "Te rog selectează",
"set_tfa": "Setează metoda de autentificare cu doi factori",
"start_u2f_validation": "Începi validarea",
"start_webauthn_validation": "Începi validarea",
"tfa": "Autentificare cu doi factori",
"tfa_token_invalid": "Jeton TFA invalid",
"totp": "OTP pe bază de timp (Google Authenticator etc.)",
"u2f": "Autentificare U2F",
"waiting_usb_auth": "<i>În așteptarea dispozitivului USB...</i><br><br>Apasă acum butonul de pe dispozitivul tău USB U2F.",
"waiting_usb_register": "<i>În așteptarea dispozitivului USB...</i><br><br>Introdu parola ta mai sus și confirmă înregistrarea ta U2F atingând butonul de pe dispozitivul tău USB U2F.",
"webauthn": "Autentificare WebAuthn",
"waiting_usb_auth": "<i>În așteptarea dispozitivului USB...</i><br><br>Apasă acum butonul de pe dispozitivul tău USB WebAuthn.",
"waiting_usb_register": "<i>În așteptarea dispozitivului USB...</i><br><br>Introdu parola ta mai sus și confirmă înregistrarea ta WebAuthn atingând butonul de pe dispozitivul tău USB WebAuthn.",
"yubi_otp": "Autentificare Yubico OTP"
},
"user": {

View File

@ -454,7 +454,7 @@
"tls_policy_map_parameter_invalid": "Недопустимое значение параметра политики",
"totp_verification_failed": "Ошибка валидации TOTP",
"transport_dest_exists": "Назначение для отправки \"%s\" уже существует",
"u2f_verification_failed": "Ошибка валидации U2F: %s",
"webauthn_verification_failed": "Ошибка валидации WebAuthn: %s",
"unknown": "Произошла неизвестная ошибка",
"unknown_tfa_method": "Неизвестный метод TFA",
"unlimited_quota_acl": "Неограниченная квота запрещена политикой доступа",
@ -973,7 +973,7 @@
"upload_success": "Файл загружен успешно",
"verified_fido2_login": "Авторизация FIDO2 пройдена",
"verified_totp_login": "Авторизация TOTP пройдена",
"verified_u2f_login": "Авторизация U2F пройдена",
"verified_webauthn_login": "Авторизация WebAuthn пройдена",
"verified_yotp_login": "Авторизация Yubico OTP пройдена"
},
"tfa": {
@ -984,7 +984,7 @@
"disable_tfa": "Отключить TFA до следующего успешного входа",
"enter_qr_code": "Ваш код TOTP, если устройство не может отсканировать QR-код",
"error_code": "Код ошибки",
"init_u2f": "Инициализация, пожалуйста, подождите...",
"init_webauthn": "Инициализация, пожалуйста, подождите...",
"key_id": "Идентификатор YubiKey ключа",
"key_id_totp": "Идентификатор TOTP ключа",
"none": "Отключить",
@ -992,11 +992,11 @@
"scan_qr_code": "Пожалуйста, отсканируйте QR-код с помощью приложения или введите его вручную.",
"select": "Пожалуйста, выберите",
"set_tfa": "Задать метод двухфакторной проверки",
"start_u2f_validation": "Начать проверку",
"start_webauthn_validation": "Начать проверку",
"tfa": "Двухфакторная проверка подлинности",
"tfa_token_invalid": "Неправильный TFA токен",
"totp": "OTP (Authy, Google Authenticator и др.)",
"u2f": "U2F аутентификация",
"webauthn": "WebAuthn аутентификация",
"waiting_usb_auth": "<i>Ожидание устройства USB...</i><br><br>Пожалуйста, нажмите кнопку на USB устройстве сейчас.",
"waiting_usb_register": "<i>Ожидание устройства USB...</i><br><br>Пожалуйста, введите пароль выше и подтвердите регистрацию, нажав кнопку на USB устройстве.",
"yubi_otp": "Yubico OTP аутентификация"

View File

@ -454,7 +454,7 @@
"tls_policy_map_parameter_invalid": "Podmienkový parameter mapy TLS pravidiel je neplatný",
"totp_verification_failed": "TOTP overenie zlyhalo",
"transport_dest_exists": "Transportný cieľ \"%s\" už existuje",
"u2f_verification_failed": "U2F overenie zlyhalo: %s",
"webauthn_verification_failed": "WebAuthn overenie zlyhalo: %s",
"unknown": "Nastala neznáma chyba",
"unknown_tfa_method": "Neznáma TFA metóda",
"unlimited_quota_acl": "Neobmedzené kvóta je zakázaná cez ACL",
@ -973,7 +973,7 @@
"upload_success": "Súbor úspešne nahratý",
"verified_fido2_login": "Overené FIDO2 prihlásenie",
"verified_totp_login": "Overené TOTP prihlásenie",
"verified_u2f_login": "Overené U2F prihlásenie",
"verified_webauthn_login": "Overené WebAuthn prihlásenie",
"verified_yotp_login": "Overené Yubico OTP prihlásenie"
},
"tfa": {
@ -984,7 +984,7 @@
"disable_tfa": "Vypnúť TFA do ďalšieho úspešného prihlásenia",
"enter_qr_code": "Zadajte váš TOTP kód, ak vaše zariadenie nedokáže skenovať QR kódy",
"error_code": "Chyba kódu",
"init_u2f": "Inicializácia, prosím čakajte...",
"init_webauthn": "Inicializácia, prosím čakajte...",
"key_id": "Identifikátor pre váš YubiKey",
"key_id_totp": "Identifikátor pre váš kľúč",
"none": "Deaktivovať",
@ -992,11 +992,11 @@
"scan_qr_code": "Prosím oskenujte nasledovný kód pomocou vašej autentizačnej aplikácie alebo zadajte kód manuálne.",
"select": "Prosím vyberte",
"set_tfa": "Nastaviť dvojúrovňovú autentifikačnú metódu",
"start_u2f_validation": "Spustiť validáciu",
"start_webauthn_validation": "Spustiť validáciu",
"tfa": "Dvojúrovňová autentifikácia (TFA)",
"tfa_token_invalid": "Neplatný TFA token",
"totp": "Časovo-založený OTP (Google Authenticator, Authy, atď.)",
"u2f": "U2F autentifikácia",
"webauthn": "WebAuthn autentifikácia",
"waiting_usb_auth": "<i>Čakanie na USB zariadenie...</i><br><br>Prosím stlačte tlačidlo na vašom USB zariadení.",
"waiting_usb_register": "<i>Čakanie na USB zariadenie...</i><br><br>Prosím zadajte vaše heslo a potvrďte registráciu stlačením tlačidla na vašom USB zariadení.",
"yubi_otp": "Yubico OTP autentifikácia"

View File

@ -441,8 +441,8 @@
"tls_policy_map_parameter_invalid": "Policy parameter är ogiltig",
"totp_verification_failed": "TOTP-verifiering misslyckades",
"transport_dest_exists": "Transportdestinationen \"%s\" existerar redan",
"u2f_verification_failed": "U2F-verifiering misslyckades: %s",
"fido2_verification_failed": "U2F-verifiering misslyckades: %s",
"webauthn_verification_failed": "WebAuthn-verifiering misslyckades: %s",
"fido2_verification_failed": "WebAuthn-verifiering misslyckades: %s",
"unknown": "Ett fel har inträffat",
"unknown_tfa_method": "Okänd TFA method",
"unlimited_quota_acl": "På grund av en åtkomstlista tillåts inte en obegränsad kvot",
@ -911,7 +911,7 @@
"ui_texts": "Ändringarna på texter och rubriker i gränssnittet sparade",
"upload_success": "Filen har laddats upp",
"verified_totp_login": "Verifierad TOTP inloggning",
"verified_u2f_login": "Verifierad U2F inloggning",
"verified_webauthn_login": "Verifierad WebAuthn inloggning",
"verified_fido2_login": "Verifierad FIDO2 inloggning",
"verified_yotp_login": "Verifierad Yubico OTP inloggning"
},
@ -923,7 +923,7 @@
"disable_tfa": "Inaktivera tvåfaktorsautentisering tills nästa lyckade inloggning",
"enter_qr_code": "Om du inte kan skanna den QR-kod som visas, använd säkerhetsnyckeln som visas nedan",
"error_code": "Felkod",
"init_u2f": "Initierar, vänta...",
"init_webauthn": "Initierar, vänta...",
"key_id": "En identifierare för din YubiKey",
"key_id_totp": "En identifierare för din nyckel",
"none": "Avaktivera",
@ -931,11 +931,11 @@
"scan_qr_code": "Skanna nu den QR-kod som visas på skärmen.",
"select": "Välj",
"set_tfa": "Metod för tvåfaktorsautentisering",
"start_u2f_validation": "Startar validering",
"start_webauthn_validation": "Startar validering",
"tfa": "Tvåfaktorsautentisering",
"tfa_token_invalid": "TFA nyckeln är ogiltig!",
"totp": "Tidsbaserad OTP (Google Authenticator, Authy, mm)",
"u2f": "U2F-autentisering",
"webauthn": "WebAuthn-autentisering",
"waiting_usb_auth": "<i>Väntar på USB-enhet...</i><br><br>Tryck på knappen på USB-enheten nu.",
"waiting_usb_register": "<i>Väntar på USB-enhet...</i><br><br>Vänligen fyll i det övre lösenordsfältet först och tryck sedan på knappen på USB-enheten.",
"yubi_otp": "Yubico OTP-autentisering"

View File

@ -424,7 +424,7 @@
"tls_policy_map_parameter_invalid": "策略参数非法",
"totp_verification_failed": "TOTP认证失败",
"transport_dest_exists": "传输目标 \"%s\" 已存在",
"u2f_verification_failed": "U2F认证失败: %s",
"webauthn_verification_failed": "WebAuthn认证失败: %s",
"unknown": "发生未知错误",
"unknown_tfa_method": "未知TFA方法",
"unlimited_quota_acl": "ACL设置禁止了无限配额",
@ -875,7 +875,7 @@
"ui_texts": "已保存UI文本更改",
"upload_success": "成功上传文件",
"verified_totp_login": "TOTP登录验证成功",
"verified_u2f_login": "U2F登录验证成功",
"verified_webauthn_login": "WebAuthn登录验证成功",
"verified_yotp_login": "Yubico OTP登录验证成功"
},
"tfa": {
@ -886,7 +886,7 @@
"disable_tfa": "在下一次成功登录前关闭两步验证",
"enter_qr_code": "如果你的设备不能扫描QR码输入此TOTP码",
"error_code": "错误码",
"init_u2f": "初始化中,请等待...",
"init_webauthn": "初始化中,请等待...",
"key_id": "你的YubiKey的标识",
"key_id_totp": "你的密钥的标识",
"none": "禁用",
@ -894,12 +894,12 @@
"scan_qr_code": "请用你认证应用扫描或手动输入此码。",
"select": "请选择",
"set_tfa": "设置两步验证方法",
"start_u2f_validation": "开始认证",
"start_webauthn_validation": "开始认证",
"tfa": "两步验证(2FA)",
"totp": "TOTP认证 (Google Authenticator、Authy等)",
"u2f": "U2F认证",
"waiting_usb_auth": "<i>等待USB设备...</i><br><br>现在请触碰你的U2F USB设备上的按钮。",
"waiting_usb_register": "<i>等待USB设备...</i><br><br>请在上方输入你的密码并请触碰你的U2F USB设备上的按钮以确认注册U2F设备。",
"webauthn": "WebAuthn认证",
"waiting_usb_auth": "<i>等待USB设备...</i><br><br>现在请触碰你的WebAuthn USB设备上的按钮。",
"waiting_usb_register": "<i>等待USB设备...</i><br><br>请在上方输入你的密码并请触碰你的WebAuthn USB设备上的按钮以确认注册WebAuthn设备。",
"yubi_otp": "Yubico OTP认证"
},
"user": {

View File

@ -40,7 +40,7 @@
<div class="col-sm-9 col-xs-7">
<select data-style="btn btn-sm dropdown-toggle bs-placeholder btn-default" data-width="fit" id="selectTFA" class="selectpicker" title="{{ lang.tfa.select }}">
<option value="yubi_otp">{{ lang.tfa.yubi_otp }}</option>
<option value="u2f">{{ lang.tfa.u2f }}</option>
<option value="webauthn">{{ lang.tfa.webauthn }}</option>
<option value="totp">{{ lang.tfa.totp }}</option>
<option value="none">{{ lang.tfa.none }}</option>
</select>

View File

@ -160,7 +160,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
}
}
}
}
}
$(window).load(function() {
$(".overlay").hide();
});
@ -181,34 +181,52 @@ function recursiveBase64StrToArrayBuffer(obj) {
backdrop: 'static',
keyboard: false
});
$('#u2f_status_auth').html('<p><i class="bi bi-arrow-repeat icon-spin"></i> ' + lang_tfa.init_u2f + '</p>');
$('#ConfirmTFAModal').on('shown.bs.modal', function(){
// validate WebAuthn tfa
$('#start_webauthn_confirmation').click(function(){
$('#webauthn_status_auth').html('<p><i class="bi bi-arrow-repeat icon-spin"></i> ' + lang_tfa.init_webauthn + '</p>');
$(this).find('input[name=token]').focus();
// If U2F
if(document.getElementById("u2f_auth_data") !== null) {
$.ajax({
type: "GET",
cache: false,
dataType: 'script',
url: "/api/v1/get/u2f-authentication/{{ pending_mailcow_cc_username|url_encode(true)|default('null') }}",
complete: function(data){
$('#u2f_status_auth').html(lang_tfa.waiting_usb_auth);
data;
setTimeout(function() {
console.log("Ready to authenticate");
u2f.sign(appId, challenge, registeredKeys, function(data) {
var form = document.getElementById('u2f_auth_form');
var auth = document.getElementById('u2f_auth_data');
console.log("Authenticate callback", data);
auth.value = JSON.stringify(data);
form.submit();
});
}, 1000);
}
if(document.getElementById("webauthn_auth_data") !== null) {
// Check Browser support
if (!window.fetch || !navigator.credentials || !navigator.credentials.create) {
window.alert('Browser not supported for WebAuthn.');
return;
}
// fetch webauthn auth args
window.fetch("/api/v1/get/webauthn-tfa-get-args", {method:'GET',cache:'no-cache'}).then(response => {
return response.json();
}).then(json => {
if (json.success === false) throw new Error();
recursiveBase64StrToArrayBuffer(json);
return json;
}).then(getCredentialArgs => {
// get credentials
return navigator.credentials.get(getCredentialArgs);
}).then(cred => {
return {
id: cred.rawId ? arrayBufferToBase64(cred.rawId) : null,
clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null,
authenticatorData: cred.response.authenticatorData ? arrayBufferToBase64(cred.response.authenticatorData) : null,
signature : cred.response.signature ? arrayBufferToBase64(cred.response.signature) : null
};
}).then(JSON.stringify).then(function(AuthenticatorAttestationResponse) {
// send request by submit
var form = document.getElementById('webauthn_auth_form');
var auth = document.getElementById('webauthn_auth_data');
auth.value = AuthenticatorAttestationResponse;
form.submit();
}).catch(function(err) {
var webauthn_return_code = document.getElementById('webauthn_return_code');
webauthn_return_code.style.display = webauthn_return_code.style.display === 'none' ? '' : null;
webauthn_return_code.innerHTML = lang_tfa.error_code + ': ' + err + ' ' + lang_tfa.reload_retry;
});
}
}
});
$('#ConfirmTFAModal').on('hidden.bs.modal', function(){
// cancel pending login
$.ajax({
type: "GET",
cache: false,
@ -327,46 +345,57 @@ function recursiveBase64StrToArrayBuffer(obj) {
});
$("option:selected").prop("selected", false);
}
if ($(this).val() == "u2f") {
$('#U2FModal').modal('show');
$("option:selected").prop("selected", false);
$("#start_u2f_register").click(function(){
$('#u2f_return_code').html('');
$('#u2f_return_code').hide();
$('#u2f_status_reg').html('<p><i class="bi bi-arrow-repeat icon-spin"></i> ' + lang_tfa.init_u2f + '</p>');
$.ajax({
type: "GET",
cache: false,
dataType: 'script',
url: "/api/v1/get/u2f-registration/{{ mailcow_cc_username|url_encode(true)|default('null') }}",
complete: function(data){
data;
setTimeout(function() {
console.log("Ready to register");
$('#u2f_status_reg').html(lang_tfa.waiting_usb_register);
u2f.register(appId, registerRequests, registeredKeys, function(deviceResponse) {
var form = document.getElementById('u2f_reg_form');
var reg = document.getElementById('u2f_register_data');
console.log("Register callback: ", data);
if (deviceResponse.errorCode && deviceResponse.errorCode != 0) {
var u2f_return_code = document.getElementById('u2f_return_code');
u2f_return_code.style.display = u2f_return_code.style.display === 'none' ? '' : null;
if (deviceResponse.errorCode == "4") {
deviceResponse.errorCode = "4 - The presented device is not eligible for this request. For a registration request this may mean that the token is already registered, and for a sign request it may mean that the token does not know the presented key handle";
}
else if (deviceResponse.errorCode == "5") {
deviceResponse.errorCode = "5 - Timeout reached before request could be satisfied.";
}
u2f_return_code.innerHTML = lang_tfa.error_code + ': ' + deviceResponse.errorCode + ' ' + lang_tfa.reload_retry;
return;
if ($(this).val() == "webauthn") {
// check if Browser is supported
if (!window.fetch || !navigator.credentials || !navigator.credentials.create) {
window.alert('Browser not supported.');
return;
}
// show modal
$('#WebAuthnModal').modal('show');
$("option:selected").prop("selected", false);
$("#start_webauthn_register").click(() => {
var key_id = document.getElementsByName('key_id')[1].value;
// fetch WebAuthn create args
window.fetch("/api/v1/get/webauthn-tfa-registration/{{ mailcow_cc_username|url_encode(true)|default('null') }}", {method:'GET',cache:'no-cache'}).then(response => {
return response.json();
}).then(json => {
if (json.success === false) throw new Error(json.msg);
recursiveBase64StrToArrayBuffer(json);
return json;
}).then(createCredentialArgs => {
// create credentials
return navigator.credentials.create(createCredentialArgs);
}).then(cred => {
return {
clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null,
attestationObject: cred.response.attestationObject ? arrayBufferToBase64(cred.response.attestationObject) : null,
key_id: key_id,
tfa_method: "webauthn"
};
}).then(JSON.stringify).then(AuthenticatorAttestationResponse => {
// send request
return window.fetch("/api/v1/add/webauthn-tfa-registration", {method:'POST', body: AuthenticatorAttestationResponse, cache:'no-cache'});
}).then(response => {
return response.json();
}).then(json => {
if (json.success) {
// reload on success
window.location = window.location.href.split("#")[0];
} else {
throw new Error(json.msg);
}
reg.value = JSON.stringify(deviceResponse);
form.submit();
});
}, 1000);
}
}).catch(function(err) {
console.log(err);
var webauthn_return_code = document.getElementById('webauthn_return_code');
webauthn_return_code.style.display = webauthn_return_code.style.display === 'none' ? '' : null;
webauthn_return_code.innerHTML = lang_tfa.error_code + ': ' + err + ' ' + lang_tfa.reload_retry;
});
});
});
}
if ($(this).val() == "none") {
$('#DisableTFAModal').modal('show');

View File

@ -37,15 +37,15 @@
</div>
</div>
<div class="modal fade" id="U2FModal" tabindex="-1" role="dialog" aria-labelledby="U2FModalLabel">
<div class="modal fade" id="WebAuthnModal" tabindex="-1" role="dialog" aria-labelledby="WebAuthnModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span></button>
<h3 class="modal-title">{{ lang.tfa.u2f }}</h3>
<h3 class="modal-title">{{ lang.tfa.webauthn }}</h3>
</div>
<div class="modal-body">
<form role="form" method="post" id="u2f_reg_form">
<form role="form" method="post" id="webauthn_reg_form">
<div class="form-group">
<input type="text" class="form-control" name="key_id" placeholder="{{ lang.tfa.key_id }}" autocomplete="off" required>
</div>
@ -54,18 +54,18 @@
</div>
<hr>
<center>
<div style="cursor:pointer" id="start_u2f_register">
<div style="cursor:pointer" id="start_webauthn_register">
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24">
<path d="M17.81 4.47c-.08 0-.16-.02-.23-.06C15.66 3.42 14 3 12.01 3c-1.98 0-3.86.47-5.57 1.41-.24.13-.54.04-.68-.2-.13-.24-.04-.55.2-.68C7.82 2.52 9.86 2 12.01 2c2.13 0 3.99.47 6.03 1.52.25.13.34.43.21.67-.09.18-.26.28-.44.28zM3.5 9.72c-.1 0-.2-.03-.29-.09-.23-.16-.28-.47-.12-.7.99-1.4 2.25-2.5 3.75-3.27C9.98 4.04 14 4.03 17.15 5.65c1.5.77 2.76 1.86 3.75 3.25.16.22.11.54-.12.7-.23.16-.54.11-.7-.12-.9-1.26-2.04-2.25-3.39-2.94-2.87-1.47-6.54-1.47-9.4.01-1.36.7-2.5 1.7-3.4 2.96-.08.14-.23.21-.39.21zm6.25 12.07c-.13 0-.26-.05-.35-.15-.87-.87-1.34-1.43-2.01-2.64-.69-1.23-1.05-2.73-1.05-4.34 0-2.97 2.54-5.39 5.66-5.39s5.66 2.42 5.66 5.39c0 .28-.22.5-.5.5s-.5-.22-.5-.5c0-2.42-2.09-4.39-4.66-4.39-2.57 0-4.66 1.97-4.66 4.39 0 1.44.32 2.77.93 3.85.64 1.15 1.08 1.64 1.85 2.42.19.2.19.51 0 .71-.11.1-.24.15-.37.15zm7.17-1.85c-1.19 0-2.24-.3-3.1-.89-1.49-1.01-2.38-2.65-2.38-4.39 0-.28.22-.5.5-.5s.5.22.5.5c0 1.41.72 2.74 1.94 3.56.71.48 1.54.71 2.54.71.24 0 .64-.03 1.04-.1.27-.05.53.13.58.41.05.27-.13.53-.41.58-.57.11-1.07.12-1.21.12zM14.91 22c-.04 0-.09-.01-.13-.02-1.59-.44-2.63-1.03-3.72-2.1-1.4-1.39-2.17-3.24-2.17-5.22 0-1.62 1.38-2.94 3.08-2.94 1.7 0 3.08 1.32 3.08 2.94 0 1.07.93 1.94 2.08 1.94s2.08-.87 2.08-1.94c0-3.77-3.25-6.83-7.25-6.83-2.84 0-5.44 1.58-6.61 4.03-.39.81-.59 1.76-.59 2.8 0 .78.07 2.01.67 3.61.1.26-.03.55-.29.64-.26.1-.55-.04-.64-.29-.49-1.31-.73-2.61-.73-3.96 0-1.2.23-2.29.68-3.24 1.33-2.79 4.28-4.6 7.51-4.6 4.55 0 8.25 3.51 8.25 7.83 0 1.62-1.38 2.94-3.08 2.94s-3.08-1.32-3.08-2.94c0-1.07-.93-1.94-2.08-1.94s-2.08.87-2.08 1.94c0 1.71.66 3.31 1.87 4.51.95.94 1.86 1.46 3.27 1.85.27.07.42.35.35.61-.05.23-.26.38-.47.38z"></path>
</svg>
<p>{{ lang.tfa.start_u2f_validation }}</p>
<p>{{ lang.tfa.start_webauthn_validation }}</p>
<hr>
</div>
</center>
<p id="u2f_status_reg"></p>
<div class="alert alert-danger" style="display:none" id="u2f_return_code"></div>
<input type="hidden" name="token" id="u2f_register_data"/>
<input type="hidden" name="tfa_method" value="u2f">
<p id="webauthn_status_reg"></p>
<div class="alert alert-danger" style="display:none" id="webauthn_return_code"></div>
<input type="hidden" name="token" id="webauthn_register_data"/>
<input type="hidden" name="tfa_method" value="webauthn">
<input type="hidden" name="set_tfa"/><br/>
</form>
</div>
@ -154,24 +154,6 @@
<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>
</form>
{% endif %}
{% if pending_tfa_method == 'u2f' %}
<form role="form" method="post" id="u2f_auth_form">
<center>
<div id="start_u2f_confirmation">
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24">
<path d="M17.81 4.47c-.08 0-.16-.02-.23-.06C15.66 3.42 14 3 12.01 3c-1.98 0-3.86.47-5.57 1.41-.24.13-.54.04-.68-.2-.13-.24-.04-.55.2-.68C7.82 2.52 9.86 2 12.01 2c2.13 0 3.99.47 6.03 1.52.25.13.34.43.21.67-.09.18-.26.28-.44.28zM3.5 9.72c-.1 0-.2-.03-.29-.09-.23-.16-.28-.47-.12-.7.99-1.4 2.25-2.5 3.75-3.27C9.98 4.04 14 4.03 17.15 5.65c1.5.77 2.76 1.86 3.75 3.25.16.22.11.54-.12.7-.23.16-.54.11-.7-.12-.9-1.26-2.04-2.25-3.39-2.94-2.87-1.47-6.54-1.47-9.4.01-1.36.7-2.5 1.7-3.4 2.96-.08.14-.23.21-.39.21zm6.25 12.07c-.13 0-.26-.05-.35-.15-.87-.87-1.34-1.43-2.01-2.64-.69-1.23-1.05-2.73-1.05-4.34 0-2.97 2.54-5.39 5.66-5.39s5.66 2.42 5.66 5.39c0 .28-.22.5-.5.5s-.5-.22-.5-.5c0-2.42-2.09-4.39-4.66-4.39-2.57 0-4.66 1.97-4.66 4.39 0 1.44.32 2.77.93 3.85.64 1.15 1.08 1.64 1.85 2.42.19.2.19.51 0 .71-.11.1-.24.15-.37.15zm7.17-1.85c-1.19 0-2.24-.3-3.1-.89-1.49-1.01-2.38-2.65-2.38-4.39 0-.28.22-.5.5-.5s.5.22.5.5c0 1.41.72 2.74 1.94 3.56.71.48 1.54.71 2.54.71.24 0 .64-.03 1.04-.1.27-.05.53.13.58.41.05.27-.13.53-.41.58-.57.11-1.07.12-1.21.12zM14.91 22c-.04 0-.09-.01-.13-.02-1.59-.44-2.63-1.03-3.72-2.1-1.4-1.39-2.17-3.24-2.17-5.22 0-1.62 1.38-2.94 3.08-2.94 1.7 0 3.08 1.32 3.08 2.94 0 1.07.93 1.94 2.08 1.94s2.08-.87 2.08-1.94c0-3.77-3.25-6.83-7.25-6.83-2.84 0-5.44 1.58-6.61 4.03-.39.81-.59 1.76-.59 2.8 0 .78.07 2.01.67 3.61.1.26-.03.55-.29.64-.26.1-.55-.04-.64-.29-.49-1.31-.73-2.61-.73-3.96 0-1.2.23-2.29.68-3.24 1.33-2.79 4.28-4.6 7.51-4.6 4.55 0 8.25 3.51 8.25 7.83 0 1.62-1.38 2.94-3.08 2.94s-3.08-1.32-3.08-2.94c0-1.07-.93-1.94-2.08-1.94s-2.08.87-2.08 1.94c0 1.71.66 3.31 1.87 4.51.95.94 1.86 1.46 3.27 1.85.27.07.42.35.35.61-.05.23-.26.38-.47.38z"></path>
</svg>
<p>{{ lang.tfa.start_u2f_validation }}</p>
<hr>
</div>
</center>
<p id="u2f_status_auth"></p>
<div class="alert alert-danger" style="display:none" id="u2f_return_code"></div>
<input type="hidden" name="token" id="u2f_auth_data"/>
<input type="hidden" name="tfa_method" value="u2f">
<input type="hidden" name="verify_tfa_login"/><br/>
</form>
{% endif %}
{% if pending_tfa_method == 'totp' %}
<form role="form" method="post">
<div class="form-group">
@ -187,6 +169,36 @@
{% if pending_tfa_method == 'hotp' %}
<div class="empty"></div>
{% endif %}
{% if pending_tfa_method == 'webauthn' %}
<form role="form" method="post" id="webauthn_auth_form">
<center>
<div style="cursor:pointer" id="start_webauthn_confirmation">
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24">
<path d="M17.81 4.47c-.08 0-.16-.02-.23-.06C15.66 3.42 14 3 12.01 3c-1.98 0-3.86.47-5.57 1.41-.24.13-.54.04-.68-.2-.13-.24-.04-.55.2-.68C7.82 2.52 9.86 2 12.01 2c2.13 0 3.99.47 6.03 1.52.25.13.34.43.21.67-.09.18-.26.28-.44.28zM3.5 9.72c-.1 0-.2-.03-.29-.09-.23-.16-.28-.47-.12-.7.99-1.4 2.25-2.5 3.75-3.27C9.98 4.04 14 4.03 17.15 5.65c1.5.77 2.76 1.86 3.75 3.25.16.22.11.54-.12.7-.23.16-.54.11-.7-.12-.9-1.26-2.04-2.25-3.39-2.94-2.87-1.47-6.54-1.47-9.4.01-1.36.7-2.5 1.7-3.4 2.96-.08.14-.23.21-.39.21zm6.25 12.07c-.13 0-.26-.05-.35-.15-.87-.87-1.34-1.43-2.01-2.64-.69-1.23-1.05-2.73-1.05-4.34 0-2.97 2.54-5.39 5.66-5.39s5.66 2.42 5.66 5.39c0 .28-.22.5-.5.5s-.5-.22-.5-.5c0-2.42-2.09-4.39-4.66-4.39-2.57 0-4.66 1.97-4.66 4.39 0 1.44.32 2.77.93 3.85.64 1.15 1.08 1.64 1.85 2.42.19.2.19.51 0 .71-.11.1-.24.15-.37.15zm7.17-1.85c-1.19 0-2.24-.3-3.1-.89-1.49-1.01-2.38-2.65-2.38-4.39 0-.28.22-.5.5-.5s.5.22.5.5c0 1.41.72 2.74 1.94 3.56.71.48 1.54.71 2.54.71.24 0 .64-.03 1.04-.1.27-.05.53.13.58.41.05.27-.13.53-.41.58-.57.11-1.07.12-1.21.12zM14.91 22c-.04 0-.09-.01-.13-.02-1.59-.44-2.63-1.03-3.72-2.1-1.4-1.39-2.17-3.24-2.17-5.22 0-1.62 1.38-2.94 3.08-2.94 1.7 0 3.08 1.32 3.08 2.94 0 1.07.93 1.94 2.08 1.94s2.08-.87 2.08-1.94c0-3.77-3.25-6.83-7.25-6.83-2.84 0-5.44 1.58-6.61 4.03-.39.81-.59 1.76-.59 2.8 0 .78.07 2.01.67 3.61.1.26-.03.55-.29.64-.26.1-.55-.04-.64-.29-.49-1.31-.73-2.61-.73-3.96 0-1.2.23-2.29.68-3.24 1.33-2.79 4.28-4.6 7.51-4.6 4.55 0 8.25 3.51 8.25 7.83 0 1.62-1.38 2.94-3.08 2.94s-3.08-1.32-3.08-2.94c0-1.07-.93-1.94-2.08-1.94s-2.08.87-2.08 1.94c0 1.71.66 3.31 1.87 4.51.95.94 1.86 1.46 3.27 1.85.27.07.42.35.35.61-.05.23-.26.38-.47.38z"></path>
</svg>
<p>{{ lang.tfa.start_webauthn_validation }}</p>
<hr>
</div>
</center>
<p id="webauthn_status_auth"></p>
<div class="alert alert-danger" style="display:none" id="webauthn_return_code"></div>
<input type="hidden" name="token" id="webauthn_auth_data"/>
<input type="hidden" name="tfa_method" value="webauthn">
<input type="hidden" name="verify_tfa_login"/><br/>
</form>
{% endif %}
{# leave this here to inform users that u2f is deprecated #}
{% if pending_tfa_method == 'u2f' %}
<form role="form" method="post" id="u2f_auth_form">
<p>{{ lang.tfa.u2f_deprecated }}</p>
<p><b>{{ lang.tfa.u2f_deprecated_important }}</b></p>
<input type="hidden" name="token" value="destroy" />
<input type="hidden" name="tfa_method" value="u2f">
<input type="hidden" name="verify_tfa_login"/><br/>
<button type="submit" class="btn btn-xs-lg btn-success" value="Login">{{ lang.login.login }}</button>
</form>
{% endif %}
</div>
</div>
</div>

View File

@ -157,6 +157,7 @@ services:
- ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n}
- MASTER=${MASTER:-y}
- DEV_MODE=${DEV_MODE:-n}
- WEBAUTHN_ONLY_TRUSTED_VENDORS=${WEBAUTHN_ONLY_TRUSTED_VENDORS:-n}
restart: always
networks:
mailcow-network:

View File

@ -344,6 +344,11 @@ DOVECOT_MASTER_PASS=
# https://mailcow.github.io/mailcow-dockerized-docs/debug-reset_tls/
ACME_CONTACT=
# WebAuthn device manufacturer verification
# After setting WEBAUTHN_ONLY_TRUSTED_VENDORS=y only devices from trusted manufacturers are allowed
# root certificates can be placed for validation under mailcow-dockerized/data/web/inc/lib/WebAuthn/rootCertificates
WEBAUTHN_ONLY_TRUSTED_VENDORS=n
EOF
mkdir -p data/assets/ssl

View File

@ -307,6 +307,7 @@ CONFIG_ARRAY=(
"ADDITIONAL_SERVER_NAMES"
"ACME_CONTACT"
"WATCHDOG_VERBOSE"
"WEBAUTHN_ONLY_TRUSTED_VENDORS"
)
sed -i --follow-symlinks '$a\' mailcow.conf
@ -514,6 +515,13 @@ for option in ${CONFIG_ARRAY[@]}; do
echo '# https://mailcow.github.io/mailcow-dockerized-docs/debug-reset-tls/' >> mailcow.conf
echo 'ACME_CONTACT=' >> mailcow.conf
fi
elif [[ ${option} == "WEBAUTHN_ONLY_TRUSTED_VENDORS" ]]; then
if ! grep -q ${option} mailcow.conf; then
echo "# WebAuthn device manufacturer verification" >> mailcow.conf
echo '# After setting WEBAUTHN_ONLY_TRUSTED_VENDORS=y only devices from trusted manufacturers are allowed' >> mailcow.conf
echo '# root certificates can be placed for validation under mailcow-dockerized/data/web/inc/lib/WebAuthn/rootCertificates' >> mailcow.conf
echo 'WEBAUTHN_ONLY_TRUSTED_VENDORS=n' >> mailcow.conf
fi
elif [[ ${option} == "WATCHDOG_VERBOSE" ]]; then
if ! grep -q ${option} mailcow.conf; then
echo '# Enable watchdog verbose logging' >> mailcow.conf