diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/WebAuthnAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/WebAuthnAuthenticator.java index 006eaa61c2..deb9bf73dc 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/WebAuthnAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/WebAuthnAuthenticator.java @@ -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, diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/WebAuthnRegister.java b/services/src/main/java/org/keycloak/authentication/requiredactions/WebAuthnRegister.java index ebeaeff604..4ccda79713 100644 --- a/services/src/main/java/org/keycloak/authentication/requiredactions/WebAuthnRegister.java +++ b/services/src/main/java/org/keycloak/authentication/requiredactions/WebAuthnRegister.java @@ -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)