diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php
index 524172b98..b57222e3a 100644
--- a/data/web/inc/functions.inc.php
+++ b/data/web/inc/functions.inc.php
@@ -1394,6 +1394,18 @@ function set_tfa($_data) {
case "webauthn":
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
+ // Refuse incomplete WebAuthn registrations. Without this guard a request
+ // that does not contain the parsed credential data (e.g. a plain form
+ // POST) would silently insert an empty keyHandle while reporting success.
+ if (empty($_data['registration']) || empty($_data['registration']->credentialId)) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_data_log),
+ 'msg' => 'webauthn_registration_failed'
+ );
+ return false;
+ }
+
$stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`)
VALUES (?, ?, 'webauthn', ?, ?, ?, ?, '1')");
$stmt->execute(array(
diff --git a/data/web/lang/lang.en-gb.json b/data/web/lang/lang.en-gb.json
index 6409821a2..53136230e 100644
--- a/data/web/lang/lang.en-gb.json
+++ b/data/web/lang/lang.en-gb.json
@@ -1429,6 +1429,7 @@
"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",
+ "webauthn_registration_failed": "WebAuthn registration failed. The credential could not be verified, please try again.",
"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/templates/base.twig b/data/web/templates/base.twig
index 0006845a5..9c3cf67a5 100644
--- a/data/web/templates/base.twig
+++ b/data/web/templates/base.twig
@@ -416,18 +416,24 @@ function recursiveBase64StrToArrayBuffer(obj) {
return navigator.credentials.create(json);
}).then(function(cred) {
return {
- id: cred.id,
- rawId: arrayBufferToBase64(cred.rawId),
- response: {
- attestationObject: arrayBufferToBase64(cred.response.attestationObject),
- clientDataJSON: arrayBufferToBase64(cred.response.clientDataJSON)
- },
- type: cred.type
+ clientDataJSON: arrayBufferToBase64(cred.response.clientDataJSON),
+ attestationObject: arrayBufferToBase64(cred.response.attestationObject),
+ key_id: keyId,
+ tfa_method: 'webauthn',
+ confirm_password: ''
};
- }).then(function(credData) {
- $('#setup_webauthn_register_data').val(JSON.stringify(credData));
- $('#setup_webauthn_reg_form input[name=set_tfa]').val('1');
- $('#setup_webauthn_reg_form').submit();
+ }).then(JSON.stringify).then(function(credentialData) {
+ // send the attestation to the JSON API, like the regular WebAuthn modal does,
+ // so that the credential is parsed and stored correctly
+ return window.fetch('/api/v1/add/webauthn-tfa-registration', {method: 'POST', body: credentialData, cache: 'no-cache'});
+ }).then(function(response) {
+ return response.json();
+ }).then(function(json) {
+ if (json.success) {
+ window.location = window.location.href.split('#')[0];
+ } else {
+ throw new Error(json.msg);
+ }
}).catch(function(err) {
$('#setup_webauthn_return_code').show().text(err.message || 'Registration failed');
});