Search in sources :

Example 1 with AuthenticationParameters

use of com.webauthn4j.data.AuthenticationParameters in project keycloak by keycloak.

the class WebAuthnCredentialProvider method isValid.

@Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
    if (!WebAuthnCredentialModelInput.class.isInstance(input))
        return false;
    WebAuthnCredentialModelInput context = WebAuthnCredentialModelInput.class.cast(input);
    List<WebAuthnCredentialModelInput> auths = getWebAuthnCredentialModelList(realm, user);
    WebAuthnAuthenticationManager webAuthnAuthenticationManager = new WebAuthnAuthenticationManager();
    AuthenticationData authenticationData = null;
    try {
        for (WebAuthnCredentialModelInput auth : auths) {
            byte[] credentialId = auth.getAttestedCredentialData().getCredentialId();
            if (Arrays.equals(credentialId, context.getAuthenticationRequest().getCredentialId())) {
                Authenticator authenticator = new AuthenticatorImpl(auth.getAttestedCredentialData(), auth.getAttestationStatement(), auth.getCount());
                // parse
                authenticationData = webAuthnAuthenticationManager.parse(context.getAuthenticationRequest());
                // validate
                AuthenticationParameters authenticationParameters = new AuthenticationParameters(context.getAuthenticationParameters().getServerProperty(), authenticator, context.getAuthenticationParameters().isUserVerificationRequired());
                webAuthnAuthenticationManager.validate(authenticationData, authenticationParameters);
                logger.debugv("response.getAuthenticatorData().getFlags() = {0}", authenticationData.getAuthenticatorData().getFlags());
                CredentialModel credModel = getCredentialStore().getStoredCredentialById(realm, user, auth.getCredentialDBId());
                WebAuthnCredentialModel webAuthnCredModel = getCredentialFromModel(credModel);
                // update authenticator counter
                // counters are an optional feature of the spec - if an authenticator does not support them, it
                // will always send zero. MacOS/iOS does this for keys stored in the secure enclave (TouchID/FaceID)
                long count = auth.getCount();
                if (count > 0) {
                    webAuthnCredModel.updateCounter(count + 1);
                    getCredentialStore().updateCredential(realm, user, webAuthnCredModel);
                }
                logger.debugf("Successfully validated WebAuthn credential for user %s", user.getUsername());
                dumpCredentialModel(webAuthnCredModel, auth);
                return true;
            }
        }
    } catch (WebAuthnException wae) {
        wae.printStackTrace();
        throw (wae);
    }
    // no authenticator matched
    return false;
}
Also used : AuthenticationParameters(com.webauthn4j.data.AuthenticationParameters) WebAuthnCredentialModel(org.keycloak.models.credential.WebAuthnCredentialModel) WebAuthnException(com.webauthn4j.util.exception.WebAuthnException) AuthenticationData(com.webauthn4j.data.AuthenticationData) AuthenticatorImpl(com.webauthn4j.authenticator.AuthenticatorImpl) WebAuthnCredentialModel(org.keycloak.models.credential.WebAuthnCredentialModel) WebAuthnAuthenticationManager(com.webauthn4j.WebAuthnAuthenticationManager) Authenticator(com.webauthn4j.authenticator.Authenticator)

Example 2 with AuthenticationParameters

use of com.webauthn4j.data.AuthenticationParameters in project keycloak by keycloak.

the class WebAuthnAuthenticator method action.

public void action(AuthenticationFlowContext context) {
    MultivaluedMap<String, String> params = context.getHttpRequest().getDecodedFormParameters();
    context.getEvent().detail(Details.CREDENTIAL_TYPE, getCredentialType());
    // receive error from navigator.credentials.get()
    String errorMsgFromWebAuthnApi = params.getFirst(WebAuthnConstants.ERROR);
    if (errorMsgFromWebAuthnApi != null && !errorMsgFromWebAuthnApi.isEmpty()) {
        setErrorResponse(context, WEBAUTHN_ERROR_API_GET, errorMsgFromWebAuthnApi);
        return;
    }
    String baseUrl = UriUtils.getOrigin(context.getUriInfo().getBaseUri());
    String rpId = getRpID(context);
    Origin origin = new Origin(baseUrl);
    Challenge challenge = new DefaultChallenge(context.getAuthenticationSession().getAuthNote(WebAuthnConstants.AUTH_CHALLENGE_NOTE));
    ServerProperty server = new ServerProperty(origin, rpId, challenge, null);
    byte[] credentialId = Base64Url.decode(params.getFirst(WebAuthnConstants.CREDENTIAL_ID));
    byte[] clientDataJSON = Base64Url.decode(params.getFirst(WebAuthnConstants.CLIENT_DATA_JSON));
    byte[] authenticatorData = Base64Url.decode(params.getFirst(WebAuthnConstants.AUTHENTICATOR_DATA));
    byte[] signature = Base64Url.decode(params.getFirst(WebAuthnConstants.SIGNATURE));
    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 (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(WebAuthnConstants.FIRST_AUTHENTICATED_USER_ID, firstAuthenticatedUserId).detail(WebAuthnConstants.AUTHENTICATED_USER_ID, userId);
                setErrorResponse(context, WEBAUTHN_ERROR_DIFFERENT_USER, null);
                return;
            }
        } else {
        // Resident Key supported public key credential was used,
        // and the user has not yet been identified
        // so rely on the user authenticated by the webauthn authenticator
        // NOP
        }
    }
    boolean isUVFlagChecked = false;
    String userVerificationRequirement = getWebAuthnPolicy(context).getUserVerificationRequirement();
    if (WebAuthnConstants.OPTION_REQUIRED.equals(userVerificationRequirement))
        isUVFlagChecked = true;
    UserModel user = session.users().getUserById(context.getRealm(), userId);
    AuthenticationRequest authenticationRequest = new AuthenticationRequest(credentialId, authenticatorData, clientDataJSON, signature);
    AuthenticationParameters authenticationParameters = new AuthenticationParameters(server, // here authenticator cannot be fetched, set it afterwards in WebAuthnCredentialProvider.isValid()
    null, isUVFlagChecked);
    WebAuthnCredentialModelInput cred = new WebAuthnCredentialModelInput(getCredentialType());
    cred.setAuthenticationRequest(authenticationRequest);
    cred.setAuthenticationParameters(authenticationParameters);
    boolean result = false;
    try {
        result = session.userCredentialManager().isValid(context.getRealm(), user, cred);
    } catch (WebAuthnException wae) {
        setErrorResponse(context, WEBAUTHN_ERROR_AUTH_VERIFICATION, wae.getMessage());
        return;
    }
    String encodedCredentialID = Base64Url.encode(credentialId);
    if (result) {
        String isUVChecked = Boolean.toString(isUVFlagChecked);
        logger.debugv("WebAuthn Authentication successed. isUserVerificationChecked = {0}, PublicKeyCredentialID = {1}", isUVChecked, encodedCredentialID);
        context.setUser(user);
        context.getEvent().detail(WebAuthnConstants.USER_VERIFICATION_CHECKED, isUVChecked).detail(WebAuthnConstants.PUBKEY_CRED_ID_ATTR, encodedCredentialID);
        context.success();
    } else {
        context.getEvent().detail(WebAuthnConstants.AUTHENTICATED_USER_ID, userId).detail(WebAuthnConstants.PUBKEY_CRED_ID_ATTR, encodedCredentialID);
        setErrorResponse(context, WEBAUTHN_ERROR_USER_NOT_FOUND, null);
        context.cancelLogin();
    }
}
Also used : Origin(com.webauthn4j.data.client.Origin) AuthenticationParameters(com.webauthn4j.data.AuthenticationParameters) ServerProperty(com.webauthn4j.server.ServerProperty) WebAuthnCredentialModelInput(org.keycloak.credential.WebAuthnCredentialModelInput) Challenge(com.webauthn4j.data.client.challenge.Challenge) DefaultChallenge(com.webauthn4j.data.client.challenge.DefaultChallenge) UserModel(org.keycloak.models.UserModel) DefaultChallenge(com.webauthn4j.data.client.challenge.DefaultChallenge) WebAuthnException(com.webauthn4j.util.exception.WebAuthnException) AuthenticationRequest(com.webauthn4j.data.AuthenticationRequest)

Aggregations

AuthenticationParameters (com.webauthn4j.data.AuthenticationParameters)2 WebAuthnException (com.webauthn4j.util.exception.WebAuthnException)2 WebAuthnAuthenticationManager (com.webauthn4j.WebAuthnAuthenticationManager)1 Authenticator (com.webauthn4j.authenticator.Authenticator)1 AuthenticatorImpl (com.webauthn4j.authenticator.AuthenticatorImpl)1 AuthenticationData (com.webauthn4j.data.AuthenticationData)1 AuthenticationRequest (com.webauthn4j.data.AuthenticationRequest)1 Origin (com.webauthn4j.data.client.Origin)1 Challenge (com.webauthn4j.data.client.challenge.Challenge)1 DefaultChallenge (com.webauthn4j.data.client.challenge.DefaultChallenge)1 ServerProperty (com.webauthn4j.server.ServerProperty)1 WebAuthnCredentialModelInput (org.keycloak.credential.WebAuthnCredentialModelInput)1 UserModel (org.keycloak.models.UserModel)1 WebAuthnCredentialModel (org.keycloak.models.credential.WebAuthnCredentialModel)1