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.Response;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
@ -136,25 +137,24 @@ public class WebAuthnAuthenticator implements Authenticator, CredentialValidator
byte[] authenticatorData = Base64Url.decode(params.getFirst(WebAuthnConstants.AUTHENTICATOR_DATA));
byte[] signature = Base64Url.decode(params.getFirst(WebAuthnConstants.SIGNATURE));
String userId = params.getFirst(WebAuthnConstants.USER_HANDLE);
boolean isUVFlagChecked = false;
String userVerificationRequirement = getWebAuthnPolicy(context).getUserVerificationRequirement();
if (WebAuthnConstants.OPTION_REQUIRED.equals(userVerificationRequirement)) isUVFlagChecked = true;
final String userHandle = params.getFirst(WebAuthnConstants.USER_HANDLE);
final String userId;
// 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
// so rely on the user that has already been authenticated
userId = context.getUser().getId();
} 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) {
// 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
String firstAuthenticatedUserId = context.getUser().getId();
if (firstAuthenticatedUserId != null && !firstAuthenticatedUserId.equals(userId)) {
context.getEvent()
.detail("first_authenticated_user_id", firstAuthenticatedUserId)
.detail("web_authn_authenticator_authenticated_user_id", userId);
.detail("first_authenticated_user_id", firstAuthenticatedUserId)
.detail("web_authn_authenticator_authenticated_user_id", userId);
setErrorResponse(context, WEBAUTHN_ERROR_DIFFERENT_USER, null);
return;
}
@ -165,6 +165,11 @@ public class WebAuthnAuthenticator implements Authenticator, CredentialValidator
// 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());
WebAuthnAuthenticationContext authenticationContext = new WebAuthnAuthenticationContext(
credentialId,

View file

@ -16,6 +16,7 @@
package org.keycloak.authentication.requiredactions;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
@ -94,7 +95,10 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis
@Override
public void requiredActionChallenge(RequiredActionContext context) {
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();
Challenge challenge = new DefaultChallenge();
String challengeValue = Base64Url.encode(challenge.getValue());
@ -133,7 +137,7 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis
Response form = context.form()
.setAttribute(WebAuthnConstants.CHALLENGE, challengeValue)
.setAttribute(WebAuthnConstants.USER_ID, userid)
.setAttribute(WebAuthnConstants.USER_ID, userId)
.setAttribute(WebAuthnConstants.USER_NAME, username)
.setAttribute(WebAuthnConstants.RP_ENTITY_NAME, rpEntityName)
.setAttribute(WebAuthnConstants.SIGNATURE_ALGORITHMS, signatureAlgorithms)