/* * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except * in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.wso2.carbon.identity.application.authenticator.fido; import com.yubico.u2f.data.messages.AuthenticateRequestData; import com.yubico.u2f.data.messages.AuthenticateResponse; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.identity.application.authentication.framework.AbstractApplicationAuthenticator; import org.wso2.carbon.identity.application.authentication.framework.AuthenticatorFlowStatus; import org.wso2.carbon.identity.application.authentication.framework.LocalApplicationAuthenticator; import org.wso2.carbon.identity.application.authentication.framework.config.ConfigurationFacade; import org.wso2.carbon.identity.application.authentication.framework.config.model.StepConfig; import org.wso2.carbon.identity.application.authentication.framework.context.AuthenticationContext; import org.wso2.carbon.identity.application.authentication.framework.exception.AuthenticationFailedException; import org.wso2.carbon.identity.application.authentication.framework.exception.InvalidCredentialsException; import org.wso2.carbon.identity.application.authentication.framework.exception.LogoutFailedException; import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; import org.wso2.carbon.identity.application.authenticator.fido.dto.FIDOUser; import org.wso2.carbon.identity.application.authenticator.fido.u2f.U2FService; import org.wso2.carbon.identity.application.authenticator.fido.util.FIDOAuthenticatorConstants; import org.wso2.carbon.identity.application.authenticator.fido.util.FIDOUtil; import org.wso2.carbon.identity.application.common.util.IdentityApplicationConstants; import org.wso2.carbon.identity.core.util.IdentityCoreConstants; import org.wso2.carbon.user.core.UserCoreConstants; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URLEncoder; /** * FIDO U2F Specification based authenticator. */ public class FIDOAuthenticator extends AbstractApplicationAuthenticator implements LocalApplicationAuthenticator { private static Log log = LogFactory.getLog(FIDOAuthenticator.class); private static FIDOAuthenticator instance = new FIDOAuthenticator(); @Override public AuthenticatorFlowStatus process(HttpServletRequest request, HttpServletResponse response, AuthenticationContext context) throws AuthenticationFailedException, LogoutFailedException { return super.process(request, response, context); } @Override protected void processAuthenticationResponse(HttpServletRequest request, HttpServletResponse response, AuthenticationContext context) throws AuthenticationFailedException { String tokenResponse = request.getParameter("tokenResponse"); if (tokenResponse != null && !tokenResponse.contains("errorCode")) { String appID = FIDOUtil.getOrigin(request); AuthenticatedUser user = getUsername(context); U2FService u2FService = U2FService.getInstance(); FIDOUser fidoUser = new FIDOUser(user.getUserName(), user.getTenantDomain(), user.getUserStoreDomain(), AuthenticateResponse.fromJson(tokenResponse)); fidoUser.setAppID(appID); u2FService.finishAuthentication(fidoUser); context.setSubject(user); } else { if (log.isDebugEnabled()) { log.debug("FIDO authentication filed : " + tokenResponse); } throw new InvalidCredentialsException("FIDO device authentication failed "); } } @Override public boolean canHandle(javax.servlet.http.HttpServletRequest httpServletRequest) { String tokenResponse = httpServletRequest.getParameter("tokenResponse"); return null != tokenResponse; } @Override public String getContextIdentifier( javax.servlet.http.HttpServletRequest httpServletRequest) { return httpServletRequest.getParameter("sessionDataKey"); } @Override public String getName() { return FIDOAuthenticatorConstants.AUTHENTICATOR_NAME; } @Override public String getFriendlyName() { return FIDOAuthenticatorConstants.AUTHENTICATOR_FRIENDLY_NAME; } @Override protected void initiateAuthenticationRequest(HttpServletRequest request, HttpServletResponse response, AuthenticationContext context) throws AuthenticationFailedException { //FIDO BE service component U2FService u2FService = U2FService.getInstance(); try { //authentication page's URL. String loginPage; loginPage = context.getAuthenticatorProperties().get(IdentityApplicationConstants.Authenticator.FIDO .FIDO_AUTH); if (StringUtils.isBlank(loginPage)){ loginPage = ConfigurationFacade.getInstance().getAuthenticationEndpointURL().replace("login.do", "fido-auth.jsp"); } //username from basic authenticator. AuthenticatedUser user = getUsername(context); //origin as appID eg.: http://example.com:8080 String appID = FIDOUtil.getOrigin(request); //calls BE service method to generate challenge. FIDOUser fidoUser = new FIDOUser(user.getUserName(), user.getTenantDomain(), user.getUserStoreDomain(), appID); AuthenticateRequestData data = u2FService.startAuthentication(fidoUser); //redirect to FIDO login page if (data != null) { response.sendRedirect(response.encodeRedirectURL(loginPage + ("?")) + "&authenticators=" + getName() + ":" + "LOCAL" + "&type=fido&sessionDataKey=" + request.getParameter("sessionDataKey") + "&data=" + data.toJson()); } else { String redirectURL = ConfigurationFacade.getInstance().getAuthenticationEndpointRetryURL(); redirectURL = response.encodeRedirectURL(redirectURL + ("?")) + "&failedUsername=" + URLEncoder.encode(user.getUserName(), IdentityCoreConstants.UTF_8) + "&statusMsg=" + URLEncoder.encode(FIDOAuthenticatorConstants.AUTHENTICATION_ERROR_MESSAGE, IdentityCoreConstants.UTF_8) + "&status=" + URLEncoder.encode(FIDOAuthenticatorConstants.AUTHENTICATION_STATUS, IdentityCoreConstants.UTF_8); response.sendRedirect(redirectURL); } } catch (IOException e) { throw new AuthenticationFailedException( "Could not initiate FIDO authentication request", e); } } @Override protected boolean retryAuthenticationEnabled() { //retry disabled return false; } private AuthenticatedUser getUsername(AuthenticationContext context) throws AuthenticationFailedException { //username from authentication context. AuthenticatedUser authenticatedUser = null; for (int i = 1; i <= context.getSequenceConfig().getStepMap().size(); i++) { StepConfig stepConfig = context.getSequenceConfig().getStepMap().get(i); if (stepConfig.getAuthenticatedUser() != null && stepConfig.getAuthenticatedAutenticator() .getApplicationAuthenticator() instanceof LocalApplicationAuthenticator) { authenticatedUser = stepConfig.getAuthenticatedUser(); if (authenticatedUser.getUserStoreDomain() == null) { authenticatedUser.setUserStoreDomain(UserCoreConstants.PRIMARY_DEFAULT_DOMAIN_NAME); } if (log.isDebugEnabled()) { log.debug("username :" + authenticatedUser.toString()); } break; } } if(authenticatedUser == null){ throw new AuthenticationFailedException("Could not locate an authenticated username from previous steps " + "of the sequence. Hence cannot continue with FIDO authentication."); } return authenticatedUser; } /** * Gets a FIDOAuthenticator instance. * * @return a FIDOAuthenticator. */ public static FIDOAuthenticator getInstance() { return instance; } }