diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 7ac0af58..b222e13d 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -1142,7 +1142,7 @@ function set_tfa($_data) { global $yubi; global $u2f; global $tfa; - $_data_log = $_data; + $_data_log = $_data["tfa_method"]; !isset($_data_log['confirm_password']) ?: $_data_log['confirm_password'] = '*'; $username = $_SESSION['mailcow_cc_username']; if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { @@ -1183,6 +1183,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,14 +1242,18 @@ function set_tfa($_data) { 'msg' => array('object_modified', htmlspecialchars($username)) ); break; + // u2f - deprecated, should be removed 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), @@ -1286,6 +1292,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)); @@ -1516,6 +1545,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 +1564,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 +1576,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 +1603,261 @@ 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": + $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; + 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,6 +2119,7 @@ function rspamd_ui($action, $data = null) { break; } } +// u2f - deprecated, should be removed function get_u2f_registrations($username) { global $pdo; $sel = $pdo->prepare("SELECT * FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = ? AND `active` = '1'"); diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 60a8ead2..a79c6237 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -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", diff --git a/data/web/inc/lib/WebAuthn/Attestation/AttestationObject.php b/data/web/inc/lib/WebAuthn/Attestation/AttestationObject.php index aeb4e201..115d7878 100644 --- a/data/web/inc/lib/WebAuthn/Attestation/AttestationObject.php +++ b/data/web/inc/lib/WebAuthn/Attestation/AttestationObject.php @@ -1,9 +1,9 @@ _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); } } } diff --git a/data/web/inc/lib/WebAuthn/Attestation/AuthenticatorData.php b/data/web/inc/lib/WebAuthn/Attestation/AuthenticatorData.php index 374d9ab4..1e1212ea 100644 --- a/data/web/inc/lib/WebAuthn/Attestation/AuthenticatorData.php +++ b/data/web/inc/lib/WebAuthn/Attestation/AuthenticatorData.php @@ -1,9 +1,9 @@ _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; diff --git a/data/web/inc/lib/WebAuthn/Attestation/Format/None.php b/data/web/inc/lib/WebAuthn/Attestation/Format/None.php index 1664c559..ba95e40f 100644 --- a/data/web/inc/lib/WebAuthn/Attestation/Format/None.php +++ b/data/web/inc/lib/WebAuthn/Attestation/Format/None.php @@ -1,13 +1,14 @@ 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 // ----------------------- diff --git a/data/web/inc/lib/WebAuthn/CBOR/CborDecoder.php b/data/web/inc/lib/WebAuthn/CBOR/CborDecoder.php index 45626eb1..e6b5427d 100644 --- a/data/web/inc/lib/WebAuthn/CBOR/CborDecoder.php +++ b/data/web/inc/lib/WebAuthn/CBOR/CborDecoder.php @@ -1,9 +1,9 @@ _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 // ----------------------------------------------- diff --git a/data/web/inc/lib/WebAuthn/WebAuthnException.php b/data/web/inc/lib/WebAuthn/WebAuthnException.php index 823f7d80..e399bd3b 100644 --- a/data/web/inc/lib/WebAuthn/WebAuthnException.php +++ b/data/web/inc/lib/WebAuthn/WebAuthnException.php @@ -1,5 +1,5 @@ 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 dev mode is false, to support testing with chromiums virutal authenticator +if (!$DEV_MODE){ + $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'); +} // Redis $redis = new Redis(); diff --git a/data/web/inc/triggers.inc.php b/data/web/inc/triggers.inc.php index a2342dfc..cb3a3771 100644 --- a/data/web/inc/triggers.inc.php +++ b/data/web/inc/triggers.inc.php @@ -1,15 +1,28 @@ array( diff --git a/data/web/json_api.php b/data/web/json_api.php index 595bd8f9..e04fe549 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -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,6 +392,7 @@ if (isset($_GET['query'])) { exit(); } switch ($category) { + // u2f - deprecated, should be removed case "u2f-registration": header('Content-Type: application/javascript'); if (isset($_SESSION["mailcow_cc_role"]) && $_SESSION["mailcow_cc_username"] == $object) { @@ -366,21 +409,6 @@ if (isset($_GET['query'])) { return; } break; - // fido2-registration via GET - 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); - print(json_encode($createArgs)); - $_SESSION['challenge'] = $WebAuthn->getChallenge(); - return; - } - else { - return; - } - break; case "u2f-authentication": header('Content-Type: application/javascript'); if (isset($_SESSION['pending_mailcow_cc_username']) && $_SESSION['pending_mailcow_cc_username'] == $object) { @@ -403,6 +431,21 @@ if (isset($_GET['query'])) { return; } break; + // 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); + 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! @@ -416,6 +459,36 @@ if (isset($_GET['query'])) { $_SESSION['challenge'] = $WebAuthn->getChallenge(); return; break; + // webauthn two factor authentication + case "webauthn-tfa-registration": + if (isset($_SESSION["mailcow_cc_role"])) { + $excludeCredentialIds = null; + + $createArgs = $WebAuthn->getCreateArgs($_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], 30, false, $GLOBALS['WEBAUTHN_UV_FLAG_REGISTER'], true); + + print(json_encode($createArgs)); + $_SESSION['challenge'] = $WebAuthn->getChallenge(); + return; + + } + else { + return; + } + break; + 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']); + } + + $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; + break; } if (isset($_SESSION['mailcow_cc_role'])) { switch ($category) { diff --git a/data/web/lang/lang.ca.json b/data/web/lang/lang.ca.json index d75c83b1..88bbcef1 100644 --- a/data/web/lang/lang.ca.json +++ b/data/web/lang/lang.ca.json @@ -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": "Esperant el dispositiu USB...

Apreta el botó del teu dispositiu USB U2F ara.", - "waiting_usb_register": "Esperant el dispositiu USB...

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": "Esperant el dispositiu USB...

Apreta el botó del teu dispositiu USB WebAuthn ara.", + "waiting_usb_register": "Esperant el dispositiu USB...

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": { diff --git a/data/web/lang/lang.cs.json b/data/web/lang/lang.cs.json index 5d82a007..5021dec4 100644 --- a/data/web/lang/lang.cs.json +++ b/data/web/lang/lang.cs.json @@ -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": "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", + "webauthn": "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": "Čeká se na USB zařízení...

Prosím stiskněte tlačítko na svém U2F USB zařízení.", - "waiting_usb_register": "Čeká se na USB zařízení...

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": "Čeká se na USB zařízení...

Prosím stiskněte tlačítko na svém WebAuthn USB zařízení.", + "waiting_usb_register": "Čeká se na USB zařízení...

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": { diff --git a/data/web/lang/lang.da.json b/data/web/lang/lang.da.json index 7c23e323..fc03ec5c 100644 --- a/data/web/lang/lang.da.json +++ b/data/web/lang/lang.da.json @@ -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": "Venter på USB-enhed...

Tryk let på knappen på din USB-enhed nu.", "waiting_usb_register": "Venter på USB-enhed...

Indtast din adgangskode ovenfor, og bekræft din registrering ved at trykke på knappen på din USB-enhed.", "yubi_otp": "Yubico OTP godkendelse" diff --git a/data/web/lang/lang.de.json b/data/web/lang/lang.de.json index c94eb31b..6f928c0b 100644 --- a/data/web/lang/lang.de.json +++ b/data/web/lang/lang.de.json @@ -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,7 +982,7 @@ "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...", + "init_webauthn": "Initialisiere, bitte warten...", "key_id": "Ein Namen für diesen YubiKey", "key_id_totp": "Ein eindeutiger Name", "none": "Deaktiviert", @@ -990,11 +990,11 @@ "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", + "webauthn": "WebAuthn-Authentifizierung", "waiting_usb_auth": "Warte auf USB-Gerät...

Bitte jetzt den vorgesehenen Taster des USB-Gerätes berühren.", "waiting_usb_register": "Warte auf USB-Gerät...

Bitte zuerst das obere Passwortfeld ausfüllen und erst dann den vorgesehenen Taster des USB-Gerätes berühren.", "yubi_otp": "Yubico OTP-Authentifizierung" diff --git a/data/web/lang/lang.en.json b/data/web/lang/lang.en.json index aae79c70..4c26ee0c 100644 --- a/data/web/lang/lang.en.json +++ b/data/web/lang/lang.en.json @@ -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,7 +989,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", @@ -997,11 +997,11 @@ "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", + "webauthn": "WebAuthn authentication", "waiting_usb_auth": "Waiting for USB device...

Please tap the button on your USB device now.", "waiting_usb_register": "Waiting for USB device...

Please enter your password above and confirm your registration by tapping the button on your USB device.", "yubi_otp": "Yubico OTP authentication" diff --git a/data/web/lang/lang.es.json b/data/web/lang/lang.es.json index 1e269650..87be8b4d 100644 --- a/data/web/lang/lang.es.json +++ b/data/web/lang/lang.es.json @@ -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": "Esperando al dispositivo USB...

Toque el botón en su dispositivo USB U2F ahora.", - "waiting_usb_register": "Esperando al dispositivo USB....

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": "Esperando al dispositivo USB...

Toque el botón en su dispositivo USB WebAuthn ahora.", + "waiting_usb_register": "Esperando al dispositivo USB....

Ingrese su contraseña arriba y confirme su registro WebAuthn tocando el botón en su dispositivo USB WebAuthn.", "yubi_otp": "Yubico OTP" }, "user": { diff --git a/data/web/lang/lang.fi.json b/data/web/lang/lang.fi.json index 950d3a62..66e279e2 100644 --- a/data/web/lang/lang.fi.json +++ b/data/web/lang/lang.fi.json @@ -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": "Odotetaan USB-laitetta...

Napauta painiketta U2F USB-laitteessa nyt", - "waiting_usb_register": "Odotetaan USB-laitetta...

Anna salasanasi yltä ja vahvista U2F-rekisteröinti napauttamalla painiketta U2F USB-laitteessa.", + "webauthn": "WebAuthn todennus", + "waiting_usb_auth": "Odotetaan USB-laitetta...

Napauta painiketta WebAuthn USB-laitteessa nyt", + "waiting_usb_register": "Odotetaan USB-laitetta...

Anna salasanasi yltä ja vahvista WebAuthn-rekisteröinti napauttamalla painiketta WebAuthn USB-laitteessa.", "yubi_otp": "Yubico OTP-todennus" }, "user": { diff --git a/data/web/lang/lang.fr.json b/data/web/lang/lang.fr.json index e0750c5e..1b5c4233 100644 --- a/data/web/lang/lang.fr.json +++ b/data/web/lang/lang.fr.json @@ -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 l’interface 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 d’authentification ou entrer le code manuellement.", "select": "Veuillez sélectionner", "set_tfa": "Définir une méthode d’authentification à 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": "En attente d’un périphérique USB...

S’il vous plaît appuyez maintenant sur le bouton de votre périphérique USB U2F.", - "waiting_usb_register": "En attente d’un périphérique USB...

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": "En attente d’un périphérique USB...

S’il vous plaît appuyez maintenant sur le bouton de votre périphérique USB WebAuthn.", + "waiting_usb_register": "En attente d’un périphérique USB...

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": { diff --git a/data/web/lang/lang.it.json b/data/web/lang/lang.it.json index 496c0d71..70786eea 100644 --- a/data/web/lang/lang.it.json +++ b/data/web/lang/lang.it.json @@ -449,7 +449,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", @@ -947,7 +947,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": { @@ -958,7 +958,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", @@ -966,12 +966,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": "In attesa del device USB...

Tocca ora il pulsante sul dispositivo U2F USB.", - "waiting_usb_register": "In attesa del device USB...

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": "In attesa del device USB...

Tocca ora il pulsante sul dispositivo WebAuthn USB.", + "waiting_usb_register": "In attesa del device USB...

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" }, diff --git a/data/web/lang/lang.ko.json b/data/web/lang/lang.ko.json index e4ceb1c7..3dd1f446 100644 --- a/data/web/lang/lang.ko.json +++ b/data/web/lang/lang.ko.json @@ -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": "Waiting for USB device...

Please tap the button on your U2F USB device now.", - "waiting_usb_register": "Waiting for USB device...

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": "Waiting for USB device...

Please tap the button on your WebAuthn USB device now.", + "waiting_usb_register": "Waiting for USB device...

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": { diff --git a/data/web/lang/lang.lv.json b/data/web/lang/lang.lv.json index d4af59c6..ef6f85fb 100644 --- a/data/web/lang/lang.lv.json +++ b/data/web/lang/lang.lv.json @@ -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": "Gaida USB ierīci...

Lūdzu, tagad nospiežiet pogu uz Jūsu U2F USB ierīces.", - "waiting_usb_register": "Gaida USB ierīci...

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": "Gaida USB ierīci...

Lūdzu, tagad nospiežiet pogu uz Jūsu WebAuthn USB ierīces.", + "waiting_usb_register": "Gaida USB ierīci...

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": { diff --git a/data/web/lang/lang.nl.json b/data/web/lang/lang.nl.json index 0c548d00..4628aacf 100644 --- a/data/web/lang/lang.nl.json +++ b/data/web/lang/lang.nl.json @@ -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": "In afwachting van USB-apparaat...

Druk nu op de knop van je U2F-apparaat.", - "waiting_usb_register": "In afwachting van USB-apparaat...

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": "In afwachting van USB-apparaat...

Druk nu op de knop van je WebAuthn-apparaat.", + "waiting_usb_register": "In afwachting van USB-apparaat...

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": { diff --git a/data/web/lang/lang.pl.json b/data/web/lang/lang.pl.json index ea8fb6f2..4bfde1d7 100644 --- a/data/web/lang/lang.pl.json +++ b/data/web/lang/lang.pl.json @@ -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": "Czekam na urządzenie USB...

Wciśnij teraz przycisk na urządzeniu U2F USB.", - "waiting_usb_register": " Czekam na urządzenie USB...

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": "Czekam na urządzenie USB...

Wciśnij teraz przycisk na urządzeniu WebAuthn USB.", + "waiting_usb_register": " Czekam na urządzenie USB...

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": { diff --git a/data/web/lang/lang.ro.json b/data/web/lang/lang.ro.json index 20ba0e7e..07d615cf 100644 --- a/data/web/lang/lang.ro.json +++ b/data/web/lang/lang.ro.json @@ -448,7 +448,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ă", @@ -928,7 +928,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ă" }, @@ -940,7 +940,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ă", @@ -948,13 +948,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": "În așteptarea dispozitivului USB...

Apasă acum butonul de pe dispozitivul tău USB U2F.", - "waiting_usb_register": "În așteptarea dispozitivului USB...

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": "În așteptarea dispozitivului USB...

Apasă acum butonul de pe dispozitivul tău USB WebAuthn.", + "waiting_usb_register": "În așteptarea dispozitivului USB...

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" }, "fido2": { diff --git a/data/web/lang/lang.ru.json b/data/web/lang/lang.ru.json index 9310e276..0628fe2d 100644 --- a/data/web/lang/lang.ru.json +++ b/data/web/lang/lang.ru.json @@ -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": "Ожидание устройства USB...

Пожалуйста, нажмите кнопку на USB устройстве сейчас.", "waiting_usb_register": "Ожидание устройства USB...

Пожалуйста, введите пароль выше и подтвердите регистрацию, нажав кнопку на USB устройстве.", "yubi_otp": "Yubico OTP аутентификация" diff --git a/data/web/lang/lang.sk.json b/data/web/lang/lang.sk.json index b3518908..49002c56 100644 --- a/data/web/lang/lang.sk.json +++ b/data/web/lang/lang.sk.json @@ -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": "Čakanie na USB zariadenie...

Prosím stlačte tlačidlo na vašom USB zariadení.", "waiting_usb_register": "Čakanie na USB zariadenie...

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" diff --git a/data/web/lang/lang.sv.json b/data/web/lang/lang.sv.json index f2b9e8fc..12cf2586 100644 --- a/data/web/lang/lang.sv.json +++ b/data/web/lang/lang.sv.json @@ -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": "Väntar på USB-enhet...

Tryck på knappen på USB-enheten nu.", "waiting_usb_register": "Väntar på USB-enhet...

Vänligen fyll i det övre lösenordsfältet först och tryck sedan på knappen på USB-enheten.", "yubi_otp": "Yubico OTP-autentisering" diff --git a/data/web/lang/lang.zh.json b/data/web/lang/lang.zh.json index 4d587385..485a4636 100644 --- a/data/web/lang/lang.zh.json +++ b/data/web/lang/lang.zh.json @@ -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": "等待USB设备...

现在请触碰你的U2F USB设备上的按钮。", - "waiting_usb_register": "等待USB设备...

请在上方输入你的密码并请触碰你的U2F USB设备上的按钮以确认注册U2F设备。", + "webauthn": "WebAuthn认证", + "waiting_usb_auth": "等待USB设备...

现在请触碰你的WebAuthn USB设备上的按钮。", + "waiting_usb_register": "等待USB设备...

请在上方输入你的密码并请触碰你的WebAuthn USB设备上的按钮以确认注册WebAuthn设备。", "yubi_otp": "Yubico OTP认证" }, "user": { diff --git a/data/web/templates/admin/tab-config-admins.twig b/data/web/templates/admin/tab-config-admins.twig index d7c17c8b..cfb69d4d 100644 --- a/data/web/templates/admin/tab-config-admins.twig +++ b/data/web/templates/admin/tab-config-admins.twig @@ -40,7 +40,7 @@
diff --git a/data/web/templates/base.twig b/data/web/templates/base.twig index 79042d59..96728878 100644 --- a/data/web/templates/base.twig +++ b/data/web/templates/base.twig @@ -160,7 +160,7 @@ function recursiveBase64StrToArrayBuffer(obj) { } } } - } +} $(window).load(function() { $(".overlay").hide(); }); @@ -181,32 +181,52 @@ function recursiveBase64StrToArrayBuffer(obj) { backdrop: 'static', keyboard: false }); - $('#u2f_status_auth').html('

' + lang_tfa.init_u2f + '

'); + $('#webauthn_status_auth').html('

' + lang_tfa.init_u2f + '

'); $('#ConfirmTFAModal').on('shown.bs.modal', function(){ $(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 WebAuthn + 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; + } + + window.fetch("/api/v1/get/webauthn-tfa-get-args", {method:'GET',cache:'no-cache'}).then(response => { + return response.json(); + }).then(json => { + console.log(json); + if (json.success === false) throw new Error(); + + recursiveBase64StrToArrayBuffer(json); + return json; + }).then(getCredentialArgs => { + console.log(getCredentialArgs); + return navigator.credentials.get(getCredentialArgs); + }).then(cred => { + console.log(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) { + console.log(AuthenticatorAttestationResponse); + + var form = document.getElementById('webauthn_auth_form'); + var auth = document.getElementById('webauthn_auth_data'); + console.log("Authenticate callback", AuthenticatorAttestationResponse); + auth.value = AuthenticatorAttestationResponse; + form.submit(); + }).catch(function(err) { + if (typeof err.message === 'undefined') { + mailcow_alert_box(lang_fido2.fido2_validation_failed, "danger"); + } else { + mailcow_alert_box(lang_fido2.fido2_validation_failed + ":
" + err.message + "", "danger"); + } }); - } + } }); $('#ConfirmTFAModal').on('hidden.bs.modal', function(){ $.ajax({ @@ -327,46 +347,62 @@ 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('

' + lang_tfa.init_u2f + '

'); - $.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 CreateArgs + 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 => { + console.log(json); + + if (json.success === false) throw new Error(json.msg); + + recursiveBase64StrToArrayBuffer(json); + + return json; + }).then(createCredentialArgs => { + 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 => { + console.log(AuthenticatorAttestationResponse); + + return window.fetch("/api/v1/add/webauthn-tfa-registration", {method:'POST', body: AuthenticatorAttestationResponse, cache:'no-cache'}); + }).then(response => { + return response.json(); + }).then(json => { + console.log(json); + + if (json.success) { + console.log("success"); + window.location.reload(); + } 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'); diff --git a/data/web/templates/modals/footer.twig b/data/web/templates/modals/footer.twig index 8e5cb175..306005a1 100644 --- a/data/web/templates/modals/footer.twig +++ b/data/web/templates/modals/footer.twig @@ -37,15 +37,15 @@
-