KEYCLOAK-12826 WebAuthn fails to login user when their security key supports "user handle"

This commit is contained in:
Peter Skopek 2020-02-19 15:16:51 +01:00 committed by Marek Posolda
parent 9e47022116
commit 5db98a58d3
2 changed files with 19 additions and 10 deletions

View file

@ -49,6 +49,7 @@ import org.keycloak.models.credential.WebAuthnCredentialModel;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.nio.charset.StandardCharsets;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -136,25 +137,24 @@ public class WebAuthnAuthenticator implements Authenticator, CredentialValidator
byte[] authenticatorData = Base64Url.decode(params.getFirst(WebAuthnConstants.AUTHENTICATOR_DATA)); byte[] authenticatorData = Base64Url.decode(params.getFirst(WebAuthnConstants.AUTHENTICATOR_DATA));
byte[] signature = Base64Url.decode(params.getFirst(WebAuthnConstants.SIGNATURE)); byte[] signature = Base64Url.decode(params.getFirst(WebAuthnConstants.SIGNATURE));
String userId = params.getFirst(WebAuthnConstants.USER_HANDLE); final String userHandle = params.getFirst(WebAuthnConstants.USER_HANDLE);
boolean isUVFlagChecked = false; final String userId;
String userVerificationRequirement = getWebAuthnPolicy(context).getUserVerificationRequirement();
if (WebAuthnConstants.OPTION_REQUIRED.equals(userVerificationRequirement)) isUVFlagChecked = true;
// existing User Handle means that the authenticator used Resident Key supported public key credential // existing User Handle means that the authenticator used Resident Key supported public key credential
if (userId == null || userId.isEmpty()) { if (userHandle == null || userHandle.isEmpty()) {
// Resident Key not supported public key credential was used // Resident Key not supported public key credential was used
// so rely on the user that has already been authenticated // so rely on the user that has already been authenticated
userId = context.getUser().getId(); userId = context.getUser().getId();
} else { } else {
// decode using the same charset as it has been encoded (see: WebAuthnRegister.java)
userId = new String(Base64Url.decode(userHandle), StandardCharsets.UTF_8);
if (context.getUser() != null) { if (context.getUser() != null) {
// Resident Key supported public key credential was used, // Resident Key supported public key credential was used,
// so need to confirm whether the already authenticated user is equals to one authenticated by the webauthn authenticator // so need to confirm whether the already authenticated user is equals to one authenticated by the webauthn authenticator
String firstAuthenticatedUserId = context.getUser().getId(); String firstAuthenticatedUserId = context.getUser().getId();
if (firstAuthenticatedUserId != null && !firstAuthenticatedUserId.equals(userId)) { if (firstAuthenticatedUserId != null && !firstAuthenticatedUserId.equals(userId)) {
context.getEvent() context.getEvent()
.detail("first_authenticated_user_id", firstAuthenticatedUserId) .detail("first_authenticated_user_id", firstAuthenticatedUserId)
.detail("web_authn_authenticator_authenticated_user_id", userId); .detail("web_authn_authenticator_authenticated_user_id", userId);
setErrorResponse(context, WEBAUTHN_ERROR_DIFFERENT_USER, null); setErrorResponse(context, WEBAUTHN_ERROR_DIFFERENT_USER, null);
return; return;
} }
@ -165,6 +165,11 @@ public class WebAuthnAuthenticator implements Authenticator, CredentialValidator
// NOP // NOP
} }
} }
boolean isUVFlagChecked = false;
String userVerificationRequirement = getWebAuthnPolicy(context).getUserVerificationRequirement();
if (WebAuthnConstants.OPTION_REQUIRED.equals(userVerificationRequirement)) isUVFlagChecked = true;
UserModel user = session.users().getUserById(userId, context.getRealm()); UserModel user = session.users().getUserById(userId, context.getRealm());
WebAuthnAuthenticationContext authenticationContext = new WebAuthnAuthenticationContext( WebAuthnAuthenticationContext authenticationContext = new WebAuthnAuthenticationContext(
credentialId, credentialId,

View file

@ -16,6 +16,7 @@
package org.keycloak.authentication.requiredactions; package org.keycloak.authentication.requiredactions;
import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
import java.util.List; import java.util.List;
@ -94,7 +95,10 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis
@Override @Override
public void requiredActionChallenge(RequiredActionContext context) { public void requiredActionChallenge(RequiredActionContext context) {
UserModel userModel = context.getUser(); UserModel userModel = context.getUser();
String userid = Base64Url.encode(userModel.getId().getBytes()); // Use standard UTF-8 charset to get bytes from string.
// Otherwise the platform's default charset is used and it might cause problems later when
// decoded on different system.
String userId = Base64Url.encode(userModel.getId().getBytes(StandardCharsets.UTF_8));
String username = userModel.getUsername(); String username = userModel.getUsername();
Challenge challenge = new DefaultChallenge(); Challenge challenge = new DefaultChallenge();
String challengeValue = Base64Url.encode(challenge.getValue()); String challengeValue = Base64Url.encode(challenge.getValue());
@ -133,7 +137,7 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis
Response form = context.form() Response form = context.form()
.setAttribute(WebAuthnConstants.CHALLENGE, challengeValue) .setAttribute(WebAuthnConstants.CHALLENGE, challengeValue)
.setAttribute(WebAuthnConstants.USER_ID, userid) .setAttribute(WebAuthnConstants.USER_ID, userId)
.setAttribute(WebAuthnConstants.USER_NAME, username) .setAttribute(WebAuthnConstants.USER_NAME, username)
.setAttribute(WebAuthnConstants.RP_ENTITY_NAME, rpEntityName) .setAttribute(WebAuthnConstants.RP_ENTITY_NAME, rpEntityName)
.setAttribute(WebAuthnConstants.SIGNATURE_ALGORITHMS, signatureAlgorithms) .setAttribute(WebAuthnConstants.SIGNATURE_ALGORITHMS, signatureAlgorithms)